diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb0072afd..1e033c828 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,12 +12,12 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1'] - sdk: [Android11Java8, Android11Java11, Android12Java8, Android12Java11, CLINode16, CLINode18, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node12, Node14, Node16, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift55, Swift55, WebChromium, WebNode] + php-version: ['8.2'] + sdk: [Android5Java17, Android14Java17, CLINode16, CLINode18, DartBeta, DartStable, Deno1193, Deno1303, DotNet60, DotNet70, FlutterStable, FlutterBeta, Go112, Go118, KotlinJava8, KotlinJava11, KotlinJava17, Node16, Node18, Node20, PHP74, PHP80, Python38, Python39, Python310, Ruby27, Ruby30, Ruby31, AppleSwift56, Swift56, WebChromium, WebNode] steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP with PECL extension uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 137854346..f59602c73 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,8 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "type": "library", "license": "MIT", + "minimum-stability": "dev", + "prefer-stable": true, "authors": [ { "name": "Eldad Fux", @@ -28,14 +30,15 @@ "ext-curl": "*", "ext-mbstring": "*", "ext-json": "*", - "twig/twig": "^3.4.1", - "matthiasmullie/minify": "^1.3.68" + "twig/twig": "v3.8.*", + "matthiasmullie/minify": "1.3.*" }, "require-dev": { - "phpunit/phpunit": "^9.5.21", - "brianium/paratest": "^6.4", - "squizlabs/php_codesniffer": "^3.6" + "phpunit/phpunit": "10.5.*", + "brianium/paratest": "v7.4.*", + "squizlabs/php_codesniffer": "3.9.*" }, - "minimum-stability": "dev", - "prefer-stable": true + "platform": { + "php": "8.2" + } } diff --git a/composer.lock b/composer.lock index 8f0e09370..aa7c305a4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "533996af87f893e7dfaa0508e3ee720d", + "content-hash": "05fda1f077af4fd406e116aaa2c06314", "packages": [ { "name": "matthiasmullie/minify", @@ -132,16 +132,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -155,9 +155,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -194,7 +191,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -210,20 +207,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -237,9 +234,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -277,7 +271,87 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -293,30 +367,31 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -352,7 +427,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -364,22 +439,22 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" } ], "packages-dev": [ { "name": "brianium/paratest", - "version": "v6.10.1", + "version": "v7.4.3", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "d6f32a91302b74458e8ef5d132bb2215a5edb34b" + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/d6f32a91302b74458e8ef5d132bb2215a5edb34b", - "reference": "d6f32a91302b74458e8ef5d132bb2215a5edb34b", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", "shasum": "" }, "require": { @@ -387,25 +462,27 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "fidry/cpu-core-counter": "^1.1.0", "jean85/pretty-package-versions": "^2.0.5", - "php": "^7.3 || ^8.0", - "phpunit/php-code-coverage": "^9.2.25", - "phpunit/php-file-iterator": "^3.0.6", - "phpunit/php-timer": "^5.0.3", - "phpunit/phpunit": "^9.6.4", - "sebastian/environment": "^5.1.5", - "symfony/console": "^5.4.21 || ^6.2.7", - "symfony/process": "^5.4.21 || ^6.2.7" + "php": "~8.2.0 || ~8.3.0", + "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", + "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", + "phpunit/php-timer": "^6.0.0 || ^7.0.0", + "phpunit/phpunit": "^10.5.9 || ^11.0.3", + "sebastian/environment": "^6.0.1 || ^7.0.0", + "symfony/console": "^6.4.3 || ^7.0.3", + "symfony/process": "^6.4.3 || ^7.0.3" }, "require-dev": { - "doctrine/coding-standard": "^10.0.0", + "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.26.19", - "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^5.4.21 || ^6.2.7", - "vimeo/psalm": "^5.7.7" + "phpstan/phpstan": "^1.10.58", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", + "squizlabs/php_codesniffer": "^3.9.0", + "symfony/filesystem": "^6.4.3 || ^7.0.3" }, "bin": [ "bin/paratest", @@ -446,7 +523,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v6.10.1" + "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" }, "funding": [ { @@ -458,90 +535,20 @@ "type": "paypal" } ], - "time": "2023-10-04T13:33:07+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2024-02-20T07:24:02+00:00" }, { "name": "fidry/cpu-core-counter", - "version": "0.5.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", "shasum": "" }, "require": { @@ -549,13 +556,13 @@ }, "require-dev": { "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", "phpstan/phpstan": "^1.9.2", "phpstan/phpstan-deprecation-rules": "^1.0.0", "phpstan/phpstan-phpunit": "^1.2.2", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -581,7 +588,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" }, "funding": [ { @@ -589,7 +596,7 @@ "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2024-02-07T09:43:46+00:00" }, { "name": "jean85/pretty-package-versions", @@ -711,25 +718,27 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -737,7 +746,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -761,9 +770,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2024-02-21T19:24:10+00:00" }, { "name": "phar-io/manifest", @@ -878,35 +887,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "10.1.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "78c3b7625965c2513ee96569a4dbb62601784145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145", + "reference": "78c3b7625965c2513ee96569a4dbb62601784145", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -915,7 +924,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.1-dev" } }, "autoload": { @@ -944,7 +953,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11" }, "funding": [ { @@ -952,32 +961,32 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-21T15:38:30+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1004,7 +1013,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { @@ -1012,28 +1022,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" @@ -1041,7 +1051,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1067,7 +1077,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { @@ -1075,32 +1085,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1126,7 +1136,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, "funding": [ { @@ -1134,32 +1145,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1185,7 +1196,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { @@ -1193,24 +1204,23 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.13", + "version": "10.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", - "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c", + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -1220,27 +1230,26 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.28", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.5", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.1", + "sebastian/global-state": "^6.0.1", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -1248,7 +1257,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "10.5-dev" } }, "autoload": { @@ -1280,7 +1289,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.10" }, "funding": [ { @@ -1296,7 +1305,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:39:22+00:00" + "time": "2024-02-04T09:07:51+00:00" }, { "name": "psr/container", @@ -1353,28 +1362,28 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1397,7 +1406,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" }, "funding": [ { @@ -1405,32 +1414,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2023-02-03T06:58:15+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1453,7 +1462,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -1461,32 +1470,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1508,7 +1517,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -1516,34 +1525,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "2db5010a484d53ebf536087a70b4a5423c102372" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", + "reference": "2db5010a484d53ebf536087a70b4a5423c102372", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1582,7 +1593,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" }, "funding": [ { @@ -1590,33 +1602,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2023-08-14T13:18:12+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "68ff824baeae169ec9f2137158ee529584553799" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.2-dev" } }, "autoload": { @@ -1639,7 +1651,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" }, "funding": [ { @@ -1647,33 +1660,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-21T08:37:17+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1705,7 +1718,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" }, "funding": [ { @@ -1713,27 +1727,27 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2023-12-22T10:55:06+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951", + "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -1741,7 +1755,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1760,7 +1774,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -1768,7 +1782,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1" }, "funding": [ { @@ -1776,34 +1791,34 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2023-04-11T05:39:26+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc", + "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1845,7 +1860,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1" }, "funding": [ { @@ -1853,38 +1869,35 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2023-09-24T13:22:09+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.6", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bde739e7565280bda77be70044ac1047bc007e34" + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", - "reference": "bde739e7565280bda77be70044ac1047bc007e34", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1909,7 +1922,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" }, "funding": [ { @@ -1917,33 +1931,33 @@ "type": "github" } ], - "time": "2023-08-02T09:26:13+00:00" + "time": "2023-07-19T07:19:23+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -1966,7 +1980,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" }, "funding": [ { @@ -1974,34 +1989,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-21T08:38:20+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2023,7 +2038,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -2031,32 +2046,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -2078,7 +2093,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -2086,32 +2101,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -2141,62 +2156,7 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { @@ -2204,32 +2164,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2252,7 +2212,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -2260,29 +2220,29 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -2305,7 +2265,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -2313,20 +2273,20 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.9.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", "shasum": "" }, "require": { @@ -2336,11 +2296,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -2355,62 +2315,88 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-02-16T15:06:51+00:00" }, { "name": "symfony/console", - "version": "v6.3.4", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6" + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/eca495f2ee845130855ddf1cf18460c38966c8b6", - "reference": "eca495f2ee845130855ddf1cf18460c38966c8b6", + "url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456", + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0" + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -2444,74 +2430,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.4" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-08-16T10:10:12+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/console/tree/v7.0.3" }, "funding": [ { @@ -2527,20 +2446,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -2551,9 +2470,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2592,7 +2508,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -2608,20 +2524,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -2632,9 +2548,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2676,7 +2589,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -2692,24 +2605,24 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v6.3.4", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54" + "reference": "937a195147e0c27b2759ade834169ed006d0bc74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/0b5c29118f2e980d455d2e34a5659f4579847c54", - "reference": "0b5c29118f2e980d455d2e34a5659f4579847c54", + "url": "https://api.github.com/repos/symfony/process/zipball/937a195147e0c27b2759ade834169ed006d0bc74", + "reference": "937a195147e0c27b2759ade834169ed006d0bc74", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -2737,7 +2650,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.3.4" + "source": "https://github.com/symfony/process/tree/v7.0.3" }, "funding": [ { @@ -2753,25 +2666,25 @@ "type": "tidelift" } ], - "time": "2023-08-07T10:39:22+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.4.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", + "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -2819,7 +2732,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" }, "funding": [ { @@ -2835,24 +2748,24 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-12-26T14:02:43+00:00" }, { "name": "symfony/string", - "version": "v6.3.5", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", - "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", + "url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac", + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -2862,11 +2775,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -2905,7 +2818,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.5" + "source": "https://github.com/symfony/string/tree/v7.0.3" }, "funding": [ { @@ -2921,20 +2834,20 @@ "type": "tidelift" } ], - "time": "2023-09-18T10:38:32+00:00" + "time": "2024-01-29T15:41:16+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2963,7 +2876,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2971,7 +2884,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], diff --git a/example.php b/example.php index 6af298baf..bc13d64bc 100644 --- a/example.php +++ b/example.php @@ -37,11 +37,11 @@ function getSSLPage($url) { } // Leave the platform you want uncommented - $platform = 'client'; - // $platform = 'console'; +// $platform = 'client'; + $platform = 'console'; // $platform = 'server'; - $spec = getSSLPage("https://raw.githubusercontent.com/appwrite/appwrite/master/app/config/specs/swagger2-latest-{$platform}.json"); + $spec = getSSLPage("https://raw.githubusercontent.com/appwrite/appwrite/feat-rc-sdks/app/config/specs/swagger2-latest-{$platform}.json"); if(empty($spec)) { throw new Exception('Failed to fetch spec from Appwrite server'); @@ -68,7 +68,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -93,7 +93,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -117,7 +117,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -140,7 +140,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -208,7 +208,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -231,7 +231,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -259,7 +259,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -286,7 +286,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -311,7 +311,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -336,11 +336,11 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; - $sdk->generate(__DIR__ . '/examples/swift-server'); + $sdk->generate(__DIR__ . '/examples/swift'); // Swift (Client) $sdk = new SDK(new Apple(), new Swagger2($spec)); @@ -360,7 +360,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; @@ -384,7 +384,7 @@ function getSSLPage($url) { ->setTwitter('appwrite_io') ->setDiscord('564160730845151244', 'https://appwrite.io/discord') ->setDefaultHeaders([ - 'X-Appwrite-Response-Format' => '1.2.0', + 'X-Appwrite-Response-Format' => '1.5.0', ]) ; diff --git a/mock-server/Dockerfile b/mock-server/Dockerfile index 456aa530e..a1c3ed8aa 100644 --- a/mock-server/Dockerfile +++ b/mock-server/Dockerfile @@ -14,8 +14,9 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \ --no-plugins --no-scripts --prefer-dist \ `if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi` -FROM phpswoole/swoole:4.8.7-php8.1-alpine as final -RUN ["apk", "add", "docker"] +FROM phpswoole/swoole:5.1.2-php8.3-alpine as final + +RUN apk add docker ENV _APP_REDIS_HOST=redis \ _APP_REDIS_PORT=6379 diff --git a/mock-server/app/http.php b/mock-server/app/http.php index 243c92335..467cb48b8 100644 --- a/mock-server/app/http.php +++ b/mock-server/app/http.php @@ -6,6 +6,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; } +use Swoole\Constant; use Utopia\App; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -37,21 +38,22 @@ const APP_PLATFORM_CONSOLE = 'console'; const APP_STORAGE_CACHE = '/storage/cache'; -$http = new Server("0.0.0.0", App::getEnv('PORT', 80)); +$http = new Server( + host: '0.0.0.0', + port: App::getEnv('PORT', 80), + mode: SWOOLE_PROCESS +); $payloadSize = 6 * (1024 * 1024); // 6MB $workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6)); -$http - ->set([ - 'worker_num' => $workerNumber, - 'open_http2_protocol' => true, - // 'document_root' => __DIR__.'/../public', - // 'enable_static_handler' => true, - 'http_compression' => true, - 'http_compression_level' => 6, - 'package_max_length' => $payloadSize, - 'buffer_output_size' => $payloadSize, - ]); +$http->set([ + 'worker_num' => $workerNumber, + 'open_http2_protocol' => true, + 'http_compression' => true, + 'http_compression_level' => 6, + 'package_max_length' => $payloadSize, + 'buffer_output_size' => $payloadSize, +]); // Version Route for CLI App::get('/v1/health/version') @@ -401,7 +403,6 @@ ->label('sdk.mock', true) ->inject('response') ->action(function (UtopiaSwooleResponse $response) { - $response->redirect('/v1/mock/tests/general/redirect/done'); }); @@ -435,7 +436,6 @@ ->inject('response') ->inject('request') ->action(function (UtopiaSwooleResponse $response, Request $request) { - $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', $request->getHostname(), true, true); }); @@ -453,7 +453,6 @@ ->label('sdk.mock', true) ->inject('request') ->action(function (Request $request) { - if ($request->getCookie('cookieName', '') !== 'cookieValue') { throw new Exception(Exception::GENERAL_MOCK, 'Missing cookie value'); } @@ -472,7 +471,6 @@ ->label('sdk.mock', true) ->inject('response') ->action(function (UtopiaSwooleResponse $response) { - $response->noContent(); }); @@ -562,14 +560,17 @@ ->label('scope', 'public') ->label('docs', false) ->label('sdk.mock', true) - ->param('client_id', '', new Text(100), 'OAuth2 Client ID.') - ->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack - ->param('scope', '', new Text(100), 'OAuth2 scope list.') + ->label('sdk.methodType', 'webAuth') + ->label('sdk.namespace', 'general') + ->label('sdk.method', 'oauth2') + ->param('clientId', '', new Text(100), 'OAuth2 Client ID.') + ->param('scopes', [], new ArrayList(new Text(100)), 'OAuth2 scope list.') ->param('state', '', new Text(1024), 'OAuth2 state.') + ->param('success', '', new Text(1024), 'OAuth2 success redirect URI.') + ->param('failure', '', new Text(1024), 'OAuth2 failure redirect URI.') ->inject('response') - ->action(function (string $client_id, string $redirectURI, string $scope, string $state, UtopiaSwooleResponse $response) { - - $response->redirect($redirectURI . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state])); + ->action(function (string $clientId, array $scopes, string $state, string $success, string $failure, UtopiaSwooleResponse $response) { + $response->redirect($success . '?' . \http_build_query(['code' => 'abcdef', 'state' => $state])); }); App::get('/v1/mock/tests/general/oauth2/token') @@ -578,6 +579,7 @@ ->label('scope', 'public') ->label('docs', false) ->label('sdk.mock', true) + ->label('sdk.methodType', 'webAuth') ->param('client_id', '', new Text(100), 'OAuth2 Client ID.') ->param('client_secret', '', new Text(100), 'OAuth2 scope list.') ->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true) @@ -586,7 +588,6 @@ ->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true) ->inject('response') ->action(function (string $client_id, string $client_secret, string $grantType, string $redirectURI, string $code, string $refreshToken, UtopiaSwooleResponse $response) { - if ($client_id != '1') { throw new Exception(Exception::GENERAL_MOCK, 'Invalid client ID'); } @@ -626,7 +627,6 @@ ->param('token', '', new Text(100), 'OAuth2 Access Token.') ->inject('response') ->action(function (string $token, UtopiaSwooleResponse $response) { - if ($token != '123456') { throw new Exception(Exception::GENERAL_MOCK, 'Invalid token'); } @@ -763,32 +763,27 @@ function ($utopia, $error, $request, $response) { ['utopia', 'error', 'request', 'response'] ); -$http->on( - 'start', - function (Server $http) use ($payloadSize) { - Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); - Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); - // listen ctrl + c - Process::signal( - 2, - function () use ($http) { - Console::log('Stop by Ctrl+C'); - $http->shutdown(); - } - ); - } -); +$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize) { + Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)'); + Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); -$http->on( - 'request', - function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { - $request = new Request($swooleRequest); - $response = new UtopiaSwooleResponse($swooleResponse); + // Listen for ctrl + c + Process::signal( + 2, + function () use ($http) { + Console::log('Stop by Ctrl+C'); + $http->shutdown(); + } + ); +}); - $app = new App('UTC'); +$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { + $request = new Request($swooleRequest); + $response = new UtopiaSwooleResponse($swooleResponse); - $app->run($request, $response); - } -); + $app = new App('UTC'); + + $app->run($request, $response); +}); $http->start(); diff --git a/mock-server/composer.json b/mock-server/composer.json index faf40c69f..66ee3931c 100644 --- a/mock-server/composer.json +++ b/mock-server/composer.json @@ -6,19 +6,16 @@ "Utopia\\MockServer\\": "src/" } }, - "authors": [ - { - "name": "Bradley Schofield", - "email": "bradley@appwrite.io" - } - ], "require": { - "utopia-php/framework": "^0.31.0", - "utopia-php/database": "^0.44.2", - "utopia-php/cli": "^0.16.0", - "utopia-php/swoole": "^0.5.0" + "utopia-php/framework": "0.33.*", + "utopia-php/database": "0.48.*", + "utopia-php/cli": "0.16.*", + "utopia-php/swoole": "0.8.*" }, "require-dev": { - "swoole/ide-helper": "^5.1" + "swoole/ide-helper": "5.1.2" + }, + "platform": { + "php": "8.2" } } diff --git a/mock-server/composer.lock b/mock-server/composer.lock index 45311434f..9acf97cf3 100644 --- a/mock-server/composer.lock +++ b/mock-server/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "64978d2ed8d7602b1a2430086a54fbbd", + "content-hash": "e8e3df78a113bec48bb61da0227ea50f", "packages": [ { "name": "jean85/pretty-package-versions", @@ -136,16 +136,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -153,9 +153,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -199,7 +196,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -215,20 +212,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "utopia-php/cache", - "version": "0.8.0", + "version": "0.9.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "212e66100a1f32e674fca5d9bc317cc998303089" + "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/212e66100a1f32e674fca5d9bc317cc998303089", - "reference": "212e66100a1f32e674fca5d9bc317cc998303089", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", + "reference": "4fc7b4789b5f0ce74835c1ecfec4f3afe6f0e34e", "shasum": "" }, "require": { @@ -239,6 +236,7 @@ }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "1.9.x-dev", "phpunit/phpunit": "^9.3", "vimeo/psalm": "4.13.1" }, @@ -262,9 +260,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.8.0" + "source": "https://github.com/utopia-php/cache/tree/0.9.0" }, - "time": "2022-10-16T16:48:09+00:00" + "time": "2024-01-07T18:11:23+00:00" }, { "name": "utopia-php/cli", @@ -317,29 +315,29 @@ }, { "name": "utopia-php/database", - "version": "0.44.2", + "version": "0.48.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06" + "reference": "0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/591cadbc2c622a3304aae9a16ebfdbe75ef33a06", - "reference": "591cadbc2c622a3304aae9a16ebfdbe75ef33a06", + "url": "https://api.github.com/repos/utopia-php/database/zipball/0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4", + "reference": "0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/cache": "0.8.*", + "utopia-php/cache": "0.9.*", "utopia-php/framework": "0.*.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { "fakerphp/faker": "^1.14", - "laravel/pint": "1.4.*", + "laravel/pint": "1.13.*", "pcov/clobber": "^2.0", "phpstan/phpstan": "1.10.*", "phpunit/phpunit": "^9.4", @@ -367,22 +365,22 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.44.2" + "source": "https://github.com/utopia-php/database/tree/0.48.2" }, - "time": "2023-10-19T07:39:00+00:00" + "time": "2024-02-02T14:10:14+00:00" }, { "name": "utopia-php/framework", - "version": "0.31.0", + "version": "0.33.2", "source": { "type": "git", - "url": "https://github.com/utopia-php/framework.git", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0" + "url": "https://github.com/utopia-php/http.git", + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/207f77378965fca9a9bc3783ea379d3549f86bc0", - "reference": "207f77378965fca9a9bc3783ea379d3549f86bc0", + "url": "https://api.github.com/repos/utopia-php/http/zipball/b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", + "reference": "b1423ca3e3b61c6c4c2e619d2cb80672809a19f3", "shasum": "" }, "require": { @@ -411,10 +409,10 @@ "upf" ], "support": { - "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.31.0" + "issues": "https://github.com/utopia-php/http/issues", + "source": "https://github.com/utopia-php/http/tree/0.33.2" }, - "time": "2023-08-30T16:10:04+00:00" + "time": "2024-01-31T10:35:59+00:00" }, { "name": "utopia-php/mongo", @@ -478,28 +476,28 @@ }, { "name": "utopia-php/swoole", - "version": "0.5.0", + "version": "0.8.2", "source": { "type": "git", "url": "https://github.com/utopia-php/swoole.git", - "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1" + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/swoole/zipball/c2a3a4f944a2f22945af3cbcb95b13f0769628b1", - "reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", "shasum": "" }, "require": { "ext-swoole": "*", "php": ">=8.0", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.33.*" }, "require-dev": { "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", - "swoole/ide-helper": "4.8.3", - "vimeo/psalm": "4.15.0" + "swoole/ide-helper": "5.0.2" }, "type": "library", "autoload": { @@ -523,24 +521,24 @@ ], "support": { "issues": "https://github.com/utopia-php/swoole/issues", - "source": "https://github.com/utopia-php/swoole/tree/0.5.0" + "source": "https://github.com/utopia-php/swoole/tree/0.8.2" }, - "time": "2022-10-19T22:19:07+00:00" + "time": "2024-02-01T14:54:12+00:00" } ], "packages-dev": [ { "name": "swoole/ide-helper", - "version": "5.1.0", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/swoole/ide-helper.git", - "reference": "07692fa8f1bb8eac828410acd613ea5877237b09" + "reference": "33ec7af9111b76d06a70dd31191cc74793551112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swoole/ide-helper/zipball/07692fa8f1bb8eac828410acd613ea5877237b09", - "reference": "07692fa8f1bb8eac828410acd613ea5877237b09", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/33ec7af9111b76d06a70dd31191cc74793551112", + "reference": "33ec7af9111b76d06a70dd31191cc74793551112", "shasum": "" }, "type": "library", @@ -557,9 +555,9 @@ "description": "IDE help files for Swoole.", "support": { "issues": "https://github.com/swoole/ide-helper/issues", - "source": "https://github.com/swoole/ide-helper/tree/5.1.0" + "source": "https://github.com/swoole/ide-helper/tree/5.1.2" }, - "time": "2023-10-05T04:52:59+00:00" + "time": "2024-02-01T22:28:11+00:00" } ], "aliases": [], diff --git a/phpunit.xml b/phpunit.xml index ec39a7ede..ed8cc9670 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,16 +1,12 @@ - + + colors="true"> ./tests/ - \ No newline at end of file + diff --git a/src/SDK/Language.php b/src/SDK/Language.php index 0a7a259f7..68a90cfbe 100644 --- a/src/SDK/Language.php +++ b/src/SDK/Language.php @@ -41,7 +41,7 @@ abstract public function getFiles(): array; * @param array $parameter * @return string */ - abstract public function getTypeName(array $parameter): string; + abstract public function getTypeName(array $parameter, array $spec = []): string; /** * @param array $param @@ -84,7 +84,7 @@ public function getFilters(): array return []; } - protected function toUpperCaseWords(string $value): string + protected function toPascalCase(string $value): string { return ucfirst($this->toCamelCase($value)); } @@ -97,4 +97,17 @@ protected function toCamelCase($str): string $str = str_replace(" ", "", $str); return lcfirst($str); } + + protected function toSnakeCase($str): string + { + $str = \preg_replace('/([a-z])([A-Z])/', '$1 $2', $str); + $str = \explode(' ', $str); + $str = \implode('_', $str); + return \strtolower($str); + } + + protected function toUpperSnakeCase($str): string + { + return \strtoupper($this->toSnakeCase($str)); + } } diff --git a/src/SDK/Language/Android.php b/src/SDK/Language/Android.php index 5d3e90bb0..8f1fdb1f9 100644 --- a/src/SDK/Language/Android.php +++ b/src/SDK/Language/Android.php @@ -103,122 +103,122 @@ public function getFiles(): array [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Client.kt', - 'template' => '/android/library/src/main/java/io/appwrite/Client.kt.twig', + 'template' => '/android/library/src/main/java/io/package/Client.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Permission.kt', - 'template' => '/android/library/src/main/java/io/appwrite/Permission.kt.twig', + 'template' => '/android/library/src/main/java/io/package/Permission.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Role.kt', - 'template' => '/android/library/src/main/java/io/appwrite/Role.kt.twig', + 'template' => '/android/library/src/main/java/io/package/Role.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/ID.kt', - 'template' => '/android/library/src/main/java/io/appwrite/ID.kt.twig', + 'template' => '/android/library/src/main/java/io/package/ID.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Query.kt', - 'template' => '/android/library/src/main/java/io/appwrite/Query.kt.twig', + 'template' => '/android/library/src/main/java/io/package/Query.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/exceptions/{{spec.title | caseUcfirst}}Exception.kt', - 'template' => '/android/library/src/main/java/io/appwrite/exceptions/Exception.kt.twig', + 'template' => '/android/library/src/main/java/io/package/exceptions/Exception.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/extensions/JsonExtensions.kt', - 'template' => '/android/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt.twig', + 'template' => '/android/library/src/main/java/io/package/extensions/JsonExtensions.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/extensions/TypeExtensions.kt', - 'template' => '/android/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt.twig', + 'template' => '/android/library/src/main/java/io/package/extensions/TypeExtensions.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/extensions/CollectionExtensions.kt', - 'template' => '/android/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt.twig', - ], - [ - 'scope' => 'default', - 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/json/PreciseNumberAdapter.kt', - 'template' => '/android/library/src/main/java/io/appwrite/json/PreciseNumberAdapter.kt.twig', + 'template' => '/android/library/src/main/java/io/package/extensions/CollectionExtensions.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/models/InputFile.kt', - 'template' => '/android/library/src/main/java/io/appwrite/models/InputFile.kt.twig', + 'template' => '/android/library/src/main/java/io/package/models/InputFile.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/models/RealtimeModels.kt', - 'template' => '/android/library/src/main/java/io/appwrite/models/RealtimeModels.kt.twig', + 'template' => '/android/library/src/main/java/io/package/models/RealtimeModels.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/models/UploadProgress.kt', - 'template' => '/android/library/src/main/java/io/appwrite/models/UploadProgress.kt.twig', + 'template' => '/android/library/src/main/java/io/package/models/UploadProgress.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/WebAuthComponent.kt', - 'template' => '/android/library/src/main/java/io/appwrite/WebAuthComponent.kt.twig', + 'template' => '/android/library/src/main/java/io/package/WebAuthComponent.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/KeepAliveService.kt', - 'template' => '/android/library/src/main/java/io/appwrite/KeepAliveService.kt.twig', + 'template' => '/android/library/src/main/java/io/package/KeepAliveService.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/views/CallbackActivity.kt', - 'template' => '/android/library/src/main/java/io/appwrite/views/CallbackActivity.kt.twig', + 'template' => '/android/library/src/main/java/io/package/views/CallbackActivity.kt.twig', ], [ 'scope' => 'default', - 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/services/Service.kt', - 'template' => '/android/library/src/main/java/io/appwrite/services/Service.kt.twig', + 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/Service.kt', + 'template' => '/android/library/src/main/java/io/package/Service.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/services/Realtime.kt', - 'template' => '/android/library/src/main/java/io/appwrite/services/Realtime.kt.twig', + 'template' => '/android/library/src/main/java/io/package/services/Realtime.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/cookies/Extensions.kt', - 'template' => '/android/library/src/main/java/io/appwrite/cookies/Extensions.kt.twig', + 'template' => '/android/library/src/main/java/io/package/cookies/Extensions.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/coroutines/Callback.kt', - 'template' => '/android/library/src/main/java/io/appwrite/coroutines/Callback.kt.twig', + 'template' => '/android/library/src/main/java/io/package/coroutines/Callback.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/cookies/stores/InMemoryCookieStore.kt', - 'template' => '/android/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt.twig', + 'template' => '/android/library/src/main/java/io/package/cookies/stores/InMemoryCookieStore.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/cookies/stores/SharedPreferencesCookieStore.kt', - 'template' => '/android/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt.twig', + 'template' => '/android/library/src/main/java/io/package/cookies/stores/SharedPreferencesCookieStore.kt.twig', ], [ 'scope' => 'default', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/cookies/InternalCookie.kt', - 'template' => '/android/library/src/main/java/io/appwrite/cookies/InternalCookie.kt.twig', + 'template' => '/android/library/src/main/java/io/package/cookies/InternalCookie.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/cookies/ListenableCookieJar.kt', + 'template' => '/android/library/src/main/java/io/package/cookies/ListenableCookieJar.kt.twig', ], [ 'scope' => 'service', 'destination' => '/library/src/main/java/{{ sdk.namespace | caseSlash }}/services/{{service.name | caseUcfirst}}.kt', - 'template' => '/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig', + 'template' => '/android/library/src/main/java/io/package/services/Service.kt.twig', ], [ 'scope' => 'default', @@ -235,31 +235,46 @@ public function getFiles(): array 'destination' => '/library/.gitignore', 'template' => '/android/library/.gitignore', ], + [ + 'scope' => 'definition', + 'destination' => 'library/src/main/java/{{ sdk.namespace | caseSlash }}/models/{{ definition.name | caseUcfirst }}.kt', + 'template' => '/android/library/src/main/java/io/package/models/Model.kt.twig', + ], + [ + 'scope' => 'enum', + 'destination' => 'library/src/main/java/{{ sdk.namespace | caseSlash }}/enums/{{ enum.name | caseUcfirst }}.kt', + 'template' => '/android/library/src/main/java/io/package/enums/Enum.kt.twig', + ], // Config for project :example [ 'scope' => 'default', 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/MainActivity.kt', - 'template' => '/android/example/src/main/java/io/appwrite/android/MainActivity.kt.twig', + 'template' => '/android/example/src/main/java/io/package/android/MainActivity.kt.twig', ], [ 'scope' => 'default', 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/ui/accounts/AccountsFragment.kt', - 'template' => '/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt.twig', + 'template' => '/android/example/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig', ], [ 'scope' => 'default', 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/ui/accounts/AccountsViewModel.kt', - 'template' => '/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt.twig', + 'template' => '/android/example/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig', ], [ 'scope' => 'default', 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/utils/Client.kt', - 'template' => '/android/example/src/main/java/io/appwrite/android/utils/Client.kt.twig', + 'template' => '/android/example/src/main/java/io/package/android/utils/Client.kt.twig', ], [ 'scope' => 'default', 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/utils/Event.kt', - 'template' => '/android/example/src/main/java/io/appwrite/android/utils/Event.kt.twig', + 'template' => '/android/example/src/main/java/io/package/android/utils/Event.kt.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/example/src/main/java/{{ sdk.namespace | caseSlash }}/android/services/MessagingService.kt', + 'template' => '/android/example/src/main/java/io/package/android/services/MessagingService.kt.twig', ], [ 'scope' => 'copy', @@ -321,72 +336,14 @@ public function getFiles(): array 'destination' => '/example/.gitignore', 'template' => '/android/example/.gitignore', ], - // Config for project :example-java - [ - 'scope' => 'default', - 'destination' => '/example-java/src/main/java/{{ sdk.namespace | caseSlash }}/example_java/MainActivity.java', - 'template' => '/android/example-java/src/main/java/io/appwrite/example_java/MainActivity.java.twig', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/drawable/ic_launcher_background.xml', - 'template' => '/android/example-java/src/main/res/drawable/ic_launcher_background.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/drawable/ic_launcher_foreground.xml', - 'template' => '/android/example-java/src/main/res/drawable/ic_launcher_foreground.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/layout/activity_main.xml', - 'template' => '/android/example-java/src/main/res/layout/activity_main.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml', - 'template' => '/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml', - 'template' => '/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml' - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/values/colors.xml', - 'template' => '/android/example-java/src/main/res/values/colors.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/values/strings.xml', - 'template' => '/android/example-java/src/main/res/values/strings.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/res/values/themes.xml', - 'template' => '/android/example-java/src/main/res/values/themes.xml', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/src/main/AndroidManifest.xml', - 'template' => '/android/example-java/src/main/AndroidManifest.xml', - ], - [ - 'scope' => 'default', - 'destination' => '/example-java/build.gradle', - 'template' => '/android/example-java/build.gradle.twig', - ], - [ - 'scope' => 'copy', - 'destination' => '/example-java/.gitignore', - 'template' => '/android/example-java/.gitignore', - ], - [ - 'scope' => 'definition', - 'destination' => 'library/src/main/java/io/appwrite/models/{{ definition.name | caseUcfirst }}.kt', - 'template' => '/android/library/src/main/java/io/appwrite/models/Model.kt.twig', - ], ]; } + + protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string + { + if ($method['type'] === 'webAuth') { + return 'Bool'; + } + return parent::getReturnType($method, $spec, $namespace, $generic); + } } diff --git a/src/SDK/Language/Apple.php b/src/SDK/Language/Apple.php index e3515de48..69e3498c4 100644 --- a/src/SDK/Language/Apple.php +++ b/src/SDK/Language/Apple.php @@ -14,7 +14,193 @@ public function getName(): string public function getFiles(): array { - return \array_merge(parent::getFiles(), [ + return [ + [ + 'scope' => 'default', + 'destination' => 'README.md', + 'template' => 'swift/README.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'CHANGELOG.md', + 'template' => 'swift/CHANGELOG.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'LICENSE', + 'template' => 'swift/LICENSE.twig', + ], + [ + 'scope' => 'default', + 'destination' => 'Package.swift', + 'template' => 'apple/Package.swift.twig', + ], + [ + 'scope' => 'method', + 'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md', + 'template' => 'swift/docs/example.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Tests/{{ spec.title | caseUcfirst}}Tests/Tests.swift', + 'template' => 'swift/Tests/Tests.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Models/{{ spec.title | caseUcfirst}}Error.swift', + 'template' => '/swift/Sources/Models/Error.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Models/InputFile.swift', + 'template' => 'swift/Sources/Models/InputFile.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Permission.swift', + 'template' => 'swift/Sources/Permission.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Role.swift', + 'template' => 'swift/Sources/Role.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/ID.swift', + 'template' => 'swift/Sources/ID.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Query.swift', + 'template' => 'swift/Sources/Query.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Models/UploadProgress.swift', + 'template' => 'swift/Sources/Models/UploadProgress.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/JSONCodable/Codable+JSON.swift', + 'template' => 'swift/Sources/JSONCodable/Codable+JSON.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Extensions/Cookie+Codable.swift', + 'template' => 'swift/Sources/Extensions/Cookie+Codable.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Extensions/HTTPClientRequest+Cookies.swift', + 'template' => 'swift/Sources/Extensions/HTTPClientRequest+Cookies.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Extensions/String+MimeTypes.swift', + 'template' => 'swift/Sources/Extensions/String+MimeTypes.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/StreamingDelegate.swift', + 'template' => 'swift/Sources/StreamingDelegate.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Services/Service.swift', + 'template' => 'swift/Sources/Service.swift.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/iOS/IOSDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/iOS/IOSDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/iOS/UIDevice+ModelName.swift', + 'template' => 'swift/Sources/DeviceInfo/iOS/UIDevice+ModelName.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/Linux/LinuxDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/Linux/LinuxDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/macOS/MacOSDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/macOS/MacOSDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/watchOS/WatchOSDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/watchOS/WatchOSDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/watchOS/WKInterfaceDevice+ModelName.swift', + 'template' => 'swift/Sources/DeviceInfo/watchOS/WKInterfaceDevice+ModelName.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/macOS/CwlSysCtl.swift', + 'template' => 'swift/Sources/DeviceInfo/macOS/CwlSysCtl.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/Windows/WindowsDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/Windows/WindowsDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/DeviceInfo/OSDeviceInfo.swift', + 'template' => 'swift/Sources/DeviceInfo/OSDeviceInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/PackageInfo/Apple/PackageInfo+Apple.swift', + 'template' => 'swift/Sources/PackageInfo/Apple/PackageInfo+Apple.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/PackageInfo/Linux/PackageInfo+Linux.swift', + 'template' => 'swift/Sources/PackageInfo/Linux/PackageInfo+Linux.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/PackageInfo/Windows/PackageInfo+Windows.swift', + 'template' => 'swift/Sources/PackageInfo/Windows/PackageInfo+Windows.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/PackageInfo/OSPackageInfo.swift', + 'template' => 'swift/Sources/PackageInfo/OSPackageInfo.swift', + ], + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/PackageInfo/PackageInfo.swift', + 'template' => 'swift/Sources/PackageInfo/PackageInfo.swift', + ], + [ + 'scope' => 'service', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}.swift', + 'template' => 'apple/Sources/Services/Service.swift.twig', + ], + [ + 'scope' => 'definition', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}Models/{{ definition.name | caseUcfirst }}.swift', + 'template' => '/swift/Sources/Models/Model.swift.twig', + ], + [ + 'scope' => 'enum', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}Enums/{{ enum.name | caseUcfirst }}.swift', + 'template' => '/swift/Sources/Enums/Enum.swift.twig', + ], + // Apple specific + [ + 'scope' => 'default', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/Client.swift', + 'template' => '/apple/Sources/Client.swift.twig', + ], [ 'scope' => 'default', 'destination' => '/Sources/{{ spec.title | caseUcfirst}}/OAuth/WebAuthComponent.swift', @@ -191,6 +377,21 @@ public function getFiles(): array 'destination' => '/example-swiftui/Tests macOS/Tests_macOS.swift', 'template' => '/swift/example-swiftui/Tests macOS/Tests_macOS.swift', ], + [ + 'scope' => 'default', + 'destination' => '/example-swiftui/test (iOS).entitlements', + 'template' => '/swift/example-swiftui/test (iOS).entitlements', + ], + [ + 'scope' => 'default', + 'destination' => '/example-swiftui/test (tvOS).entitlements', + 'template' => '/swift/example-swiftui/test (tvOS).entitlements', + ], + [ + 'scope' => 'default', + 'destination' => '/example-swiftui/test (watchOS).entitlements', + 'template' => '/swift/example-swiftui/test (watchOS).entitlements', + ], // Config for project example-uikit [ 'scope' => 'default', @@ -282,6 +483,14 @@ public function getFiles(): array 'destination' => '/example-uikit/UIKitExampleUITests/UIKitExampleUITests.swift', 'template' => '/swift/example-uikit/UIKitExampleUITests/UIKitExampleUITests.swift', ], - ]); + ]; + } + + protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string + { + if ($method['type'] === 'webAuth') { + return 'Bool'; + } + return parent::getReturnType($method, $spec, $namespace, $generic); } } diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index d225b3768..b61380a01 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -112,16 +112,28 @@ public function getKeywords(): array */ public function getIdentifierOverrides(): array { - return ['Function' => 'Func', 'default' => 'xdefault', 'required' => 'xrequired', 'async' => 'xasync']; + return [ + 'Function' => 'Func', + 'default' => 'xdefault', + 'required' => 'xrequired', + 'async' => 'xasync', + 'enum' => 'xenum', + ]; } /** * @param array $parameter * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { + if (isset($parameter['enumName'])) { + return 'enums.' . \ucfirst($parameter['enumName']); + } + if (!empty($parameter['enumValues'])) { + return 'enums.' . \ucfirst($parameter['name']); + } + switch ($parameter['type'] ?? '') { case self::TYPE_INTEGER: return 'int'; case self::TYPE_STRING: @@ -131,7 +143,7 @@ public function getTypeName(array $parameter): string case self::TYPE_BOOLEAN: return 'bool'; case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { + if (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) { return 'List<' . $this->getTypeName($parameter['array']) . '>'; } return 'List'; @@ -139,9 +151,9 @@ public function getTypeName(array $parameter): string return 'Map'; case self::TYPE_NUMBER: return 'double'; + default: + return $parameter['type']; } - - return $parameter['type']; } /** @@ -386,6 +398,11 @@ public function getFiles(): array 'destination' => '/lib/models.dart', 'template' => 'dart/lib/models.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/lib/enums.dart', + 'template' => 'dart/lib/enums.dart.twig', + ], [ 'scope' => 'service', 'destination' => '/lib/services/{{service.name | caseDash}}.dart', @@ -466,6 +483,11 @@ public function getFiles(): array 'destination' => 'lib/src/input_file.dart', 'template' => 'dart/lib/src/input_file.dart.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'lib/src/enums/{{ enum.name | caseSnake }}.dart', + 'template' => 'dart/lib/src/enums/enum.dart.twig', + ], ]; } @@ -479,6 +501,9 @@ public function getFilters(): array } return implode("\n", $value); }, ['is_safe' => ['html']]), + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toCamelCase($value); + }), ]; } } diff --git a/src/SDK/Language/Deno.php b/src/SDK/Language/Deno.php index 3027ed503..787cd7a54 100644 --- a/src/SDK/Language/Deno.php +++ b/src/SDK/Language/Deno.php @@ -2,6 +2,8 @@ namespace Appwrite\SDK\Language; +use Twig\TwigFilter; + class Deno extends JS { /** @@ -118,6 +120,11 @@ public function getFiles(): array 'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md', 'template' => 'deno/docs/example.md.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'src/enums/{{ enum.name | caseDash }}.ts', + 'template' => 'deno/src/enums/enum.ts.twig', + ], ]; } @@ -125,27 +132,25 @@ public function getFiles(): array * @param array $parameter * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'number'; - case self::TYPE_STRING: - return 'string'; - case self::TYPE_FILE: - return 'InputFile'; - case self::TYPE_BOOLEAN: - return 'boolean'; - case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { - return $this->getTypeName($parameter['array']) . '[]'; - } - return 'string[]'; - case self::TYPE_OBJECT: - return 'object'; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); } - - return $parameter['type']; + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_INTEGER => 'number', + self::TYPE_STRING => 'string', + self::TYPE_FILE => 'InputFile', + self::TYPE_BOOLEAN => 'boolean', + self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) + ? $this->getTypeName($parameter['array']) . '[]' + : 'string[]', + self::TYPE_OBJECT => 'object', + default => $parameter['type'] + }; } /** diff --git a/src/SDK/Language/DotNet.php b/src/SDK/Language/DotNet.php index f569225f2..a3da83dc9 100644 --- a/src/SDK/Language/DotNet.php +++ b/src/SDK/Language/DotNet.php @@ -149,29 +149,26 @@ public function getIdentifierOverrides(): array * @param array $parameter * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'long'; - case self::TYPE_NUMBER: - return 'double'; - case self::TYPE_STRING: - return 'string'; - case self::TYPE_FILE: - return 'InputFile'; - case self::TYPE_BOOLEAN: - return 'bool'; - case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { - return 'List<' . $this->getTypeName($parameter['array']) . '>'; - } - return 'List'; - case self::TYPE_OBJECT: - return 'object'; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); } - - return $parameter['type']; + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_INTEGER => 'long', + self::TYPE_NUMBER => 'double', + self::TYPE_STRING => 'string', + self::TYPE_BOOLEAN => 'bool', + self::TYPE_FILE => 'InputFile', + self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) + ? 'List<' . $this->getTypeName($parameter['array']) . '>' + : 'List', + self::TYPE_OBJECT => 'object', + default => $parameter['type'] + }; } /** @@ -259,7 +256,11 @@ public function getParamExample(array $param): string if (\str_ends_with($example, ']')) { $example = \substr($example, 0, -1); } - $output .= 'new List<' . $this->getTypeName($param['array']) . '> {' . $example . '}'; + if (!empty($example)) { + $output .= 'new List<' . $this->getTypeName($param['array']) . '>() {' . $example . '}'; + } else { + $output .= 'new List<' . $this->getTypeName($param['array']) . '>()'; + } break; } } else { @@ -361,6 +362,11 @@ public function getFiles(): array 'destination' => '/src/{{ spec.title | caseUcfirst }}/Role.cs', 'template' => 'dotnet/src/Appwrite/Role.cs.twig', ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Converters/ValueClassConverter.cs', + 'template' => 'dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig', + ], [ 'scope' => 'default', 'destination' => '/src/{{ spec.title | caseUcfirst }}/Extensions/Extensions.cs', @@ -395,6 +401,16 @@ public function getFiles(): array 'scope' => 'definition', 'destination' => '/src/{{ spec.title | caseUcfirst }}/Models/{{ definition.name | caseUcfirst | overrideIdentifier }}.cs', 'template' => 'dotnet/src/Appwrite/Models/Model.cs.twig', + ], + [ + 'scope' => 'enum', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Enums/{{ enum.name | caseUcfirst | overrideIdentifier }}.cs', + 'template' => 'dotnet/src/Appwrite/Enums/Enum.cs.twig', + ], + [ + 'scope' => 'default', + 'destination' => '/src/{{ spec.title | caseUcfirst }}/Enums/IEnum.cs', + 'template' => 'dotnet/src/Appwrite/Enums/IEnum.cs.twig', ] ]; } @@ -408,7 +424,10 @@ public function getFilters(): array $value[$key] = " /// " . wordwrap($line, 75, "\n /// "); } return implode("\n", $value); - }, ['is_safe' => ['html']]) + }, ['is_safe' => ['html']]), + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toPascalCase($value); + }), ]; } } diff --git a/src/SDK/Language/Flutter.php b/src/SDK/Language/Flutter.php index 806e19054..77f5c66bf 100644 --- a/src/SDK/Language/Flutter.php +++ b/src/SDK/Language/Flutter.php @@ -60,6 +60,11 @@ public function getFiles(): array 'destination' => '/lib/models.dart', 'template' => 'dart/lib/models.dart.twig', ], + [ + 'scope' => 'default', + 'destination' => '/lib/enums.dart', + 'template' => 'dart/lib/enums.dart.twig', + ], [ 'scope' => 'default', 'destination' => '/lib/permission.dart', @@ -330,6 +335,11 @@ public function getFiles(): array 'destination' => '.travis.yml', 'template' => 'flutter/.travis.yml.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'lib/src/enums/{{ enum.name | caseSnake }}.dart', + 'template' => 'dart/lib/src/enums/enum.dart.twig', + ], ]; } } diff --git a/src/SDK/Language/Go.php b/src/SDK/Language/Go.php index ebb4347e6..07c1f3e2b 100644 --- a/src/SDK/Language/Go.php +++ b/src/SDK/Language/Go.php @@ -106,25 +106,17 @@ public function getFiles(): array * @param array $nestedTypes * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'int'; - case self::TYPE_NUMBER: - return 'float64'; - case self::TYPE_FILE: - case self::TYPE_STRING: - return 'string'; - case self::TYPE_BOOLEAN: - return 'bool'; - case self::TYPE_OBJECT: - return 'interface{}'; - case self::TYPE_ARRAY: - return '[]interface{}'; - } - - return $parameter['type']; + return match ($parameter['type']) { + self::TYPE_INTEGER => 'int', + self::TYPE_NUMBER => 'float64', + self::TYPE_FILE, self::TYPE_STRING => 'string', + self::TYPE_BOOLEAN => 'bool', + self::TYPE_OBJECT => 'interface{}', + self::TYPE_ARRAY => '[]interface{}', + default => $parameter['type'], + }; } /** @@ -247,7 +239,10 @@ public function getFilters(): array $value[$key] = "// " . wordwrap($line, 75, "\n// "); } return implode("\n", $value); - }, ['is_safe' => ['html']]) + }, ['is_safe' => ['html']]), + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toUpperSnakeCase($value); + }), ]; } } diff --git a/src/SDK/Language/GraphQL.php b/src/SDK/Language/GraphQL.php index 628f7598f..ca4f6735e 100644 --- a/src/SDK/Language/GraphQL.php +++ b/src/SDK/Language/GraphQL.php @@ -16,7 +16,7 @@ public function getName(): string * @param $type * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { $type = ''; @@ -34,7 +34,7 @@ public function getTypeName(array $parameter): string $type = 'Bool'; break; case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { + if (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) { $type = '[' . $this->getTypeName($parameter['array']) . ']'; break; } diff --git a/src/SDK/Language/HTTP.php b/src/SDK/Language/HTTP.php index 1e674c945..f1abcc4a9 100644 --- a/src/SDK/Language/HTTP.php +++ b/src/SDK/Language/HTTP.php @@ -30,7 +30,7 @@ public function getIdentifierOverrides(): array * @return string * @throws Exception */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { throw new Exception('Method not supported for HTTP APIs'); } diff --git a/src/SDK/Language/JS.php b/src/SDK/Language/JS.php index c41727e36..14d50b8ad 100644 --- a/src/SDK/Language/JS.php +++ b/src/SDK/Language/JS.php @@ -3,6 +3,7 @@ namespace Appwrite\SDK\Language; use Appwrite\SDK\Language; +use Twig\TwigFilter; abstract class JS extends Language { @@ -122,14 +123,20 @@ public function getIdentifierOverrides(): array * @param array $nestedTypes * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); + } + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } switch ($parameter['type']) { case self::TYPE_INTEGER: case self::TYPE_NUMBER: return 'number'; case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { + if (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) { return $this->getTypeName($parameter['array']) . '[]'; } return 'string[]'; @@ -192,4 +199,13 @@ public function getParamDefault(array $param): string return $output; } + + public function getFilters(): array + { + return [ + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toPascalCase($value); + }), + ]; + } } diff --git a/src/SDK/Language/Kotlin.php b/src/SDK/Language/Kotlin.php index a7a4c5aea..a0b33436c 100644 --- a/src/SDK/Language/Kotlin.php +++ b/src/SDK/Language/Kotlin.php @@ -103,29 +103,26 @@ public function getIdentifierOverrides(): array * @param $type * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'Long'; - case self::TYPE_NUMBER: - return 'Double'; - case self::TYPE_STRING: - return 'String'; - case self::TYPE_FILE: - return 'InputFile'; - case self::TYPE_BOOLEAN: - return 'Boolean'; - case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { - return 'List<' . $this->getTypeName($parameter['array']) . '>'; - } - return 'List'; - case self::TYPE_OBJECT: - return 'Any'; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); } - - return $parameter['type']; + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_INTEGER => 'Long', + self::TYPE_NUMBER => 'Double', + self::TYPE_STRING => 'String', + self::TYPE_FILE => 'InputFile', + self::TYPE_BOOLEAN => 'Boolean', + self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) + ? 'List<' . $this->getTypeName($parameter['array']) . '>' + : 'List', + self::TYPE_OBJECT => 'Any', + default => $parameter['type'], + }; } /** @@ -371,7 +368,7 @@ public function getFiles(): array [ 'scope' => 'default', 'destination' => '/src/main/kotlin/{{ sdk.namespace | caseSlash }}/coroutines/Callback.kt', - 'template' => '/android/library/src/main/java/io/appwrite/coroutines/Callback.kt.twig', + 'template' => '/kotlin/src/main/kotlin/io/appwrite/coroutines/Callback.kt.twig', ], [ 'scope' => 'default', @@ -389,11 +386,6 @@ public function getFiles(): array 'template' => '/kotlin/src/main/kotlin/io/appwrite/extensions/TypeExtensions.kt.twig', 'minify' => false, ], - [ - 'scope' => 'default', - 'destination' => '/src/main/kotlin/{{ sdk.namespace | caseSlash }}/json/PreciseNumberAdapter.kt', - 'template' => '/kotlin/src/main/kotlin/io/appwrite/json/PreciseNumberAdapter.kt.twig', - ], [ 'scope' => 'default', 'destination' => '/src/main/kotlin/{{ sdk.namespace | caseSlash }}/services/Service.kt', @@ -419,6 +411,11 @@ public function getFiles(): array 'destination' => '/src/main/kotlin/{{ sdk.namespace | caseSlash }}/models/{{ definition.name | caseUcfirst }}.kt', 'template' => '/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig', ], + [ + 'scope' => 'enum', + 'destination' => '/src/main/kotlin/{{ sdk.namespace | caseSlash }}/enums/{{ enum.name | caseUcfirst }}.kt', + 'template' => '/kotlin/src/main/kotlin/io/appwrite/enums/Enum.kt.twig', + ], ]; } @@ -437,13 +434,20 @@ public function getFilters(): array new TwigFilter('hasGenericType', function (string $model, array $spec) { return $this->hasGenericType($model, $spec); }), + new TwigFilter('caseEnumKey', function (string $value) { + if (isset($this->getIdentifierOverrides()[$value])) { + $value = $this->getIdentifierOverrides()[$value]; + } + $value = \preg_replace('/[^a-zA-Z0-9]/', '', $value); + return $this->toUpperSnakeCase($value); + }), ]; } protected function getReturnType(array $method, array $spec, string $namespace, string $generic = 'T'): string { if ($method['type'] === 'webAuth') { - return 'Bool'; + return 'String'; } if ($method['type'] === 'location') { return 'ByteArray'; @@ -457,7 +461,7 @@ protected function getReturnType(array $method, array $spec, string $namespace, return 'Any'; } - $ret = $this->toUpperCaseWords($method['responseModel']); + $ret = $this->toPascalCase($method['responseModel']); if ($this->hasGenericType($method['responseModel'], $spec)) { $ret .= '<' . $generic . '>'; @@ -469,15 +473,15 @@ protected function getReturnType(array $method, array $spec, string $namespace, protected function getModelType(array $definition, array $spec, string $generic = 'T'): string { if ($this->hasGenericType($definition['name'], $spec)) { - return $this->toUpperCaseWords($definition['name']) . '<' . $generic . '>'; + return $this->toPascalCase($definition['name']) . '<' . $generic . '>'; } - return $this->toUpperCaseWords($definition['name']); + return $this->toPascalCase($definition['name']); } protected function getPropertyType(array $property, array $spec, string $generic = 'T'): string { if (\array_key_exists('sub_schema', $property)) { - $type = $this->toUpperCaseWords($property['sub_schema']); + $type = $this->toPascalCase($property['sub_schema']); if ($this->hasGenericType($property['sub_schema'], $spec)) { $type .= '<' . $generic . '>'; diff --git a/src/SDK/Language/Node.php b/src/SDK/Language/Node.php index 435e1cce0..42999bcae 100644 --- a/src/SDK/Language/Node.php +++ b/src/SDK/Language/Node.php @@ -14,25 +14,29 @@ public function getName(): string /** * @param array $parameter - * @param array $nestedTypes + * @param array $spec * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - case self::TYPE_NUMBER: - return 'number'; - case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { - return $this->getTypeName($parameter['array']) . '[]'; - } - return 'string[]'; - case self::TYPE_FILE: - return 'InputFile'; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); } - - return $parameter['type']; + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_INTEGER, + self::TYPE_NUMBER => 'number', + self::TYPE_STRING => 'string', + self::TYPE_FILE => 'InputFile', + self::TYPE_BOOLEAN => 'boolean', + self::TYPE_OBJECT => 'object', + self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) + ? $this->getTypeName($parameter['array']) . '[]' + : 'string[]', + default => $parameter['type'], + }; } /** @@ -126,6 +130,11 @@ public function getFiles(): array 'destination' => '.travis.yml', 'template' => 'node/.travis.yml.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'lib/enums/{{ enum.name | caseDash }}.js', + 'template' => 'node/lib/enums/enum.js.twig', + ], ]; } diff --git a/src/SDK/Language/PHP.php b/src/SDK/Language/PHP.php index 035621534..bdb548c9f 100644 --- a/src/SDK/Language/PHP.php +++ b/src/SDK/Language/PHP.php @@ -237,6 +237,11 @@ public function getFiles(): array 'destination' => '/tests/{{ spec.title | caseUcfirst}}/Services/{{service.name | caseUcfirst}}Test.php', 'template' => 'php/tests/Services/ServiceTest.php.twig', ], + [ + 'scope' => 'enum', + 'destination' => '/src/{{ spec.title | caseUcfirst}}/Enums/{{ enum.name | caseUcfirst }}.php', + 'template' => 'php/src/Enums/Enum.php.twig', + ], ]; } @@ -245,29 +250,24 @@ public function getFiles(): array * @param array $nestedTypes * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_STRING: - $type = 'string'; - break; - case self::TYPE_BOOLEAN: - $type = 'bool'; - break; - case self::TYPE_NUMBER: - case self::TYPE_INTEGER: - $type = 'int'; - break; - case self::TYPE_ARRAY: - case self::TYPE_OBJECT: - $type = 'array'; - break; - case self::TYPE_FILE: - $type = 'InputFile'; - break; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); } - - return $type . ' '; + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_STRING => 'string', + self::TYPE_BOOLEAN => 'bool', + self::TYPE_NUMBER, + self::TYPE_INTEGER => 'int', + self::TYPE_ARRAY, + self::TYPE_OBJECT => 'array', + self::TYPE_FILE => 'InputFile', + default => $parameter['type'], + }; } /** @@ -398,7 +398,7 @@ protected function jsonToAssoc(array $data): string protected function getReturn(array $method): string { - if (($method['emptyResponse'] ?? true) || $method['type'] === 'location') { + if (($method['emptyResponse'] ?? true) || $method['type'] === 'location' || $method['type'] === 'webAuth') { return 'string'; } @@ -414,6 +414,13 @@ public function getFilters(): array new TwigFilter('deviceInfo', function ($value) { return php_uname('s') . '; ' . php_uname('v') . '; ' . php_uname('m'); }), + new TwigFilter('caseEnumKey', function (string $value) { + if (isset($this->getIdentifierOverrides()[$value])) { + $value = $this->getIdentifierOverrides()[$value]; + } + $value = \preg_replace('/[^a-zA-Z0-9]/', '', $value); + return $this->toUpperSnakeCase($value); + }), ]; } } diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index c8d19e639..891d0d67d 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -4,6 +4,7 @@ use Appwrite\SDK\Language; use Exception; +use Twig\TwigFilter; class Python extends Language { @@ -90,99 +91,119 @@ public function getFiles(): array { return [ [ - 'scope' => 'default', - 'destination' => 'README.md', - 'template' => 'python/README.md.twig', + 'scope' => 'default', + 'destination' => 'README.md', + 'template' => 'python/README.md.twig', ], [ - 'scope' => 'default', - 'destination' => 'CHANGELOG.md', - 'template' => 'python/CHANGELOG.md.twig', + 'scope' => 'default', + 'destination' => 'CHANGELOG.md', + 'template' => 'python/CHANGELOG.md.twig', ], [ - 'scope' => 'default', - 'destination' => 'LICENSE', - 'template' => 'python/LICENSE.twig', + 'scope' => 'default', + 'destination' => 'LICENSE', + 'template' => 'python/LICENSE.twig', ], [ - 'scope' => 'default', - 'destination' => 'setup.py', - 'template' => 'python/setup.py.twig', + 'scope' => 'default', + 'destination' => 'setup.py', + 'template' => 'python/setup.py.twig', ], [ - 'scope' => 'default', - 'destination' => 'setup.cfg', - 'template' => 'python/setup.cfg.twig', + 'scope' => 'default', + 'destination' => 'setup.cfg', + 'template' => 'python/setup.cfg.twig', ], [ - 'scope' => 'default', - 'destination' => 'requirements.txt', - 'template' => 'python/requirements.txt.twig', + 'scope' => 'default', + 'destination' => 'requirements.txt', + 'template' => 'python/requirements.txt.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/__init__.py', - 'template' => 'python/package/__init__.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/__init__.py', + 'template' => 'python/package/__init__.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/client.py', - 'template' => 'python/package/client.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/client.py', + 'template' => 'python/package/client.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/permission.py', - 'template' => 'python/package/permission.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/permission.py', + 'template' => 'python/package/permission.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/role.py', - 'template' => 'python/package/role.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/role.py', + 'template' => 'python/package/role.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/id.py', - 'template' => 'python/package/id.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/id.py', + 'template' => 'python/package/id.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/query.py', - 'template' => 'python/package/query.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/query.py', + 'template' => 'python/package/query.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/exception.py', - 'template' => 'python/package/exception.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/exception.py', + 'template' => 'python/package/exception.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/input_file.py', - 'template' => 'python/package/input_file.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/input_file.py', + 'template' => 'python/package/input_file.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/service.py', - 'template' => 'python/package/service.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/service.py', + 'template' => 'python/package/service.py.twig', ], [ - 'scope' => 'default', - 'destination' => '{{ spec.title | caseSnake}}/services/__init__.py', - 'template' => 'python/package/services/__init__.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/services/__init__.py', + 'template' => 'python/package/services/__init__.py.twig', ], [ - 'scope' => 'service', - 'destination' => '{{ spec.title | caseSnake}}/services/{{service.name | caseSnake}}.py', - 'template' => 'python/package/services/service.py.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/encoders/__init__.py', + 'template' => 'python/package/services/__init__.py.twig', ], [ - 'scope' => 'method', - 'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md', - 'template' => 'python/docs/example.md.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/enums/__init__.py', + 'template' => 'python/package/services/__init__.py.twig', ], [ - 'scope' => 'default', - 'destination' => '.travis.yml', - 'template' => 'python/.travis.yml.twig', + 'scope' => 'default', + 'destination' => '{{ spec.title | caseSnake}}/encoders/value_class_encoder.py', + 'template' => 'python/package/encoders/value_class_encoder.py.twig', + ], + [ + 'scope' => 'service', + 'destination' => '{{ spec.title | caseSnake}}/services/{{service.name | caseSnake}}.py', + 'template' => 'python/package/services/service.py.twig', + ], + [ + 'scope' => 'method', + 'destination' => 'docs/examples/{{service.name | caseLower}}/{{method.name | caseDash}}.md', + 'template' => 'python/docs/example.md.twig', + ], + [ + 'scope' => 'default', + 'destination' => '.travis.yml', + 'template' => 'python/.travis.yml.twig', + ], + [ + 'scope' => 'enum', + 'destination' => '{{ spec.title | caseSnake}}/enums/{{ enum.name | caseSnake }}.py', + 'template' => 'python/package/enums/enum.py.twig', ], ]; } @@ -192,9 +213,24 @@ public function getFiles(): array * @return string * @throws Exception */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - throw new Exception('Method not supported for Python SDKs'); + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); + } + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } + return match ($parameter['type'] ?? '') { + self::TYPE_FILE => 'InputFile', + self::TYPE_NUMBER, + self::TYPE_INTEGER => 'float', + self::TYPE_BOOLEAN => 'bool', + self::TYPE_STRING => 'str', + self::TYPE_ARRAY => 'list', + self::TYPE_OBJECT => 'dict', + default => $parameter['type'], + }; } /** @@ -203,9 +239,9 @@ public function getTypeName(array $parameter): string */ public function getParamDefault(array $param): string { - $type = $param['type'] ?? ''; - $default = $param['default'] ?? ''; - $required = $param['required'] ?? ''; + $type = $param['type'] ?? ''; + $default = $param['default'] ?? ''; + $required = $param['required'] ?? ''; if ($required) { return ''; @@ -260,8 +296,8 @@ public function getParamDefault(array $param): string */ public function getParamExample(array $param): string { - $type = $param['type'] ?? ''; - $example = $param['example'] ?? ''; + $type = $param['type'] ?? ''; + $example = $param['example'] ?? ''; $output = ''; @@ -307,4 +343,13 @@ public function getParamExample(array $param): string return $output; } + + public function getFilters(): array + { + return [ + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toUpperSnakeCase($value); + }), + ]; + } } diff --git a/src/SDK/Language/Ruby.php b/src/SDK/Language/Ruby.php index eb50551b0..04f4869a7 100644 --- a/src/SDK/Language/Ruby.php +++ b/src/SDK/Language/Ruby.php @@ -182,6 +182,11 @@ public function getFiles(): array 'destination' => '/lib/{{ spec.title | caseDash }}/models/{{ definition.name | caseSnake }}.rb', 'template' => 'ruby/lib/container/models/model.rb.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'lib/{{ spec.title | caseSnake}}/enums/{{ enum.name | caseSnake }}.rb', + 'template' => 'ruby/lib/container/enums/enum.rb.twig', + ], ]; } @@ -190,24 +195,23 @@ public function getFiles(): array * @param array $nestedTypes * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'Integer'; - case self::TYPE_NUMBER: - return 'Float'; - case self::TYPE_STRING: - return 'String'; - case self::TYPE_ARRAY: - return 'Array'; - case self::TYPE_OBJECT: - return 'Hash'; - case self::TYPE_BOOLEAN: - return ''; - default: - return $parameter['type']; + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); + } + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); } + return match ($parameter['type']) { + self::TYPE_INTEGER => 'Integer', + self::TYPE_NUMBER => 'Float', + self::TYPE_STRING => 'String', + self::TYPE_ARRAY => 'Array', + self::TYPE_OBJECT => 'Hash', + self::TYPE_BOOLEAN => '', + default => $parameter['type'], + }; } /** @@ -348,7 +352,10 @@ public function getFilters(): array $value[$key] = " # " . wordwrap($line, 75, "\n # "); } return implode("\n", $value); - }, ['is_safe' => ['html']]) + }, ['is_safe' => ['html']]), + new TwigFilter('caseEnumKey', function (string $value) { + return $this->toUpperSnakeCase($value); + }), ]; } } diff --git a/src/SDK/Language/Swift.php b/src/SDK/Language/Swift.php index 9b2e08798..c4bfed878 100644 --- a/src/SDK/Language/Swift.php +++ b/src/SDK/Language/Swift.php @@ -87,6 +87,7 @@ public function getKeywords(): array "unowned", "weak", "willSet", + "Type" ]; } @@ -95,7 +96,9 @@ public function getKeywords(): array */ public function getIdentifierOverrides(): array { - return []; + return [ + 'enum' => 'xenum' + ]; } /** @@ -284,6 +287,11 @@ public function getFiles(): array 'destination' => '/Sources/{{ spec.title | caseUcfirst}}Models/{{ definition.name | caseUcfirst }}.swift', 'template' => '/swift/Sources/Models/Model.swift.twig', ], + [ + 'scope' => 'enum', + 'destination' => '/Sources/{{ spec.title | caseUcfirst}}Enums/{{ enum.name | caseUcfirst }}.swift', + 'template' => '/swift/Sources/Enums/Enum.swift.twig', + ] ]; } @@ -291,29 +299,26 @@ public function getFiles(): array * @param array $parameter * @return string */ - public function getTypeName(array $parameter): string + public function getTypeName(array $parameter, array $spec = []): string { - switch ($parameter['type']) { - case self::TYPE_INTEGER: - return 'Int'; - case self::TYPE_NUMBER: - return 'Double'; - case self::TYPE_STRING: - return 'String'; - case self::TYPE_FILE: - return 'InputFile'; - case self::TYPE_BOOLEAN: - return 'Bool'; - case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { - return '[' . $this->getTypeName($parameter['array']) . ']'; - } - return '[Any]'; - case self::TYPE_OBJECT: - return 'Any'; + if (isset($parameter['enumName'])) { + return ($spec['title'] ?? '') . 'Enums.' . \ucfirst($parameter['enumName']); } - - return $parameter['type']; + if (!empty($parameter['enumValues'])) { + return ($spec['title'] ?? '') . 'Enums.' . \ucfirst($parameter['name']); + } + return match ($parameter['type']) { + self::TYPE_INTEGER => 'Int', + self::TYPE_NUMBER => 'Double', + self::TYPE_STRING => 'String', + self::TYPE_FILE => 'InputFile', + self::TYPE_BOOLEAN => 'Bool', + self::TYPE_ARRAY => (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) + ? '[' . $this->getTypeName($parameter['array']) . ']' + : '[Any]', + self::TYPE_OBJECT => 'Any', + default => $parameter['type'], + }; } /** @@ -456,13 +461,26 @@ public function getFilters(): array new TwigFilter('hasGenericType', function (string $model, array $spec) { return $this->hasGenericType($model, $spec); }), + new TwigFilter('escapeSwiftKeyword', function ($value) { + if (\in_array($value, $this->getKeywords())) { + return "`{$value}`"; + } + return $value; + }), + new TwigFilter('caseEnumKey', function (string $value) { + if (isset($this->getIdentifierOverrides()[$value])) { + $value = $this->getIdentifierOverrides()[$value]; + } + $value = \preg_replace('/[^a-zA-Z0-9]/', '', $value); + return $this->toCamelCase($value); + }), ]; } protected function getReturnType(array $method, array $spec, string $generic): string { if ($method['type'] === 'webAuth') { - return 'Bool'; + return 'String?'; } if ($method['type'] === 'location') { return 'ByteBuffer'; @@ -476,7 +494,7 @@ protected function getReturnType(array $method, array $spec, string $generic): s return 'Any'; } - $ret = $this->toUpperCaseWords($method['responseModel']); + $ret = $this->toPascalCase($method['responseModel']); if ($this->hasGenericType($method['responseModel'], $spec)) { $ret .= '<' . $generic . '>'; @@ -488,15 +506,15 @@ protected function getReturnType(array $method, array $spec, string $generic): s protected function getModelType(array $definition, array $spec, string $generic): string { if ($this->hasGenericType($definition['name'], $spec)) { - return $this->toUpperCaseWords($definition['name']) . '<' . $generic . '>'; + return $this->toPascalCase($definition['name']) . '<' . $generic . '>'; } - return $this->toUpperCaseWords($definition['name']); + return $this->toPascalCase($definition['name']); } protected function getPropertyType(array $property, array $spec, string $generic): string { if (\array_key_exists('sub_schema', $property)) { - $type = $this->toUpperCaseWords($property['sub_schema']); + $type = $this->toPascalCase($property['sub_schema']); if ($this->hasGenericType($property['sub_schema'], $spec)) { $type .= '<' . $generic . '>'; diff --git a/src/SDK/Language/Web.php b/src/SDK/Language/Web.php index 26ec51136..e29d0d0f1 100644 --- a/src/SDK/Language/Web.php +++ b/src/SDK/Language/Web.php @@ -115,6 +115,11 @@ public function getFiles(): array 'destination' => '.travis.yml', 'template' => 'web/.travis.yml.twig', ], + [ + 'scope' => 'enum', + 'destination' => 'src/enums/{{ enum.name | caseDash }}.ts', + 'template' => 'web/src/enums/enum.ts.twig', + ], ]; } @@ -174,12 +179,18 @@ public function getParamExample(array $param): string public function getTypeName(array $parameter, array $method = []): string { + if (isset($parameter['enumName'])) { + return \ucfirst($parameter['enumName']); + } + if (!empty($parameter['enumValues'])) { + return \ucfirst($parameter['name']); + } switch ($parameter['type']) { case self::TYPE_INTEGER: case self::TYPE_NUMBER: return 'number'; case self::TYPE_ARRAY: - if (!empty($parameter['array']['type'])) { + if (!empty(($parameter['array'] ?? [])['type']) && !\is_array($parameter['array']['type'])) { return $this->getTypeName($parameter['array']) . '[]'; } return 'string[]'; @@ -200,15 +211,15 @@ public function getTypeName(array $parameter, array $method = []): string return "Partial>"; } } + break; } - return $parameter['type']; } protected function populateGenerics(string $model, array $spec, array &$generics, bool $skipFirst = false) { if (!$skipFirst && $spec['definitions'][$model]['additionalProperties']) { - $generics[] = $this->toUpperCaseWords($model); + $generics[] = $this->toPascalCase($model); } $properties = $spec['definitions'][$model]['properties']; @@ -257,14 +268,14 @@ public function getReturn(array $method, array $spec): string $ret .= 'Models.'; } - $ret .= $this->toUpperCaseWords($method['responseModel']); + $ret .= $this->toPascalCase($method['responseModel']); $models = []; $this->populateGenerics($method['responseModel'], $spec, $models); $models = array_unique($models); - $models = array_filter($models, fn ($model) => $model != $this->toUpperCaseWords($method['responseModel'])); + $models = array_filter($models, fn ($model) => $model != $this->toPascalCase($method['responseModel'])); if (!empty($models)) { $ret .= '<' . implode(', ', $models) . '>'; @@ -284,9 +295,9 @@ public function getSubSchema(array $property, array $spec): string $generics = []; $this->populateGenerics($property['sub_schema'], $spec, $generics); - $generics = array_filter($generics, fn ($model) => $model != $this->toUpperCaseWords($property['sub_schema'])); + $generics = array_filter($generics, fn ($model) => $model != $this->toPascalCase($property['sub_schema'])); - $ret .= $this->toUpperCaseWords($property['sub_schema']); + $ret .= $this->toPascalCase($property['sub_schema']); if (!empty($generics)) { $ret .= '<' . implode(', ', $generics) . '>'; } @@ -329,6 +340,9 @@ public function getFilters(): array } return implode("\n", $value); }, ['is_safe' => ['html']]), + new TwigFilter('caseEnumKey', function ($value) { + return $this->toPascalCase($value); + }), ]; } } diff --git a/src/SDK/SDK.php b/src/SDK/SDK.php index 73a75ce39..39d4f4251 100644 --- a/src/SDK/SDK.php +++ b/src/SDK/SDK.php @@ -137,8 +137,8 @@ public function __construct(Language $language, Spec $spec) $this->twig->addFilter(new TwigFilter('caseArray', function ($value) { return (is_array($value)) ? json_encode($value) : '[]'; }, ['is_safe' => ['html']])); - $this->twig->addFilter(new TwigFilter('typeName', function ($value) { - return $this->language->getTypeName($value); + $this->twig->addFilter(new TwigFilter('typeName', function ($value, $spec = []) { + return $this->language->getTypeName($value, $spec); }, ['is_safe' => ['html']])); $this->twig->addFilter(new TwigFilter('paramDefault', function ($value) { return $this->language->getParamDefault($value); @@ -545,6 +545,7 @@ public function generate(string $target): void 'contactURL' => $this->spec->getContactURL(), 'contactEmail' => $this->spec->getContactEmail(), 'services' => $this->spec->getServices(), + 'enums' => $this->spec->getEnums(), 'definitions' => $this->spec->getDefinitions(), 'global' => [ 'headers' => $this->spec->getGlobalHeaders(), @@ -634,6 +635,13 @@ public function generate(string $target): void } } break; + case 'enum': + foreach ($this->spec->getEnums() as $key => $enum) { + $params['enum'] = $enum; + + $this->render($template, $destination, $block, $params, $minify); + } + break; } } } diff --git a/src/Spec/Spec.php b/src/Spec/Spec.php index 6ffbbba53..cc6754731 100644 --- a/src/Spec/Spec.php +++ b/src/Spec/Spec.php @@ -164,4 +164,11 @@ public function setAttribute($key, $value, $type = self::SET_TYPE_ASSIGN) return $this; } + + /** + * Get Enums + * + * @return array + */ + abstract public function getEnums(); } diff --git a/src/Spec/Swagger2.php b/src/Spec/Swagger2.php index bf343ea83..56a9c0ab3 100644 --- a/src/Spec/Swagger2.php +++ b/src/Spec/Swagger2.php @@ -221,6 +221,11 @@ public function getMethods($service) } $param['default'] = (is_array($param['default'])) ? json_encode($param['default']) : $param['default']; + if (isset($parameter['enum'])) { + $param['enumValues'] = $parameter['enum']; + $param['enumName'] = $parameter['x-enum-name']; + $param['enumKeys'] = $parameter['x-enum-keys']; + } switch ($parameter['in']) { case 'header': @@ -240,24 +245,31 @@ public function getMethods($service) $bodyRequired = $parameter['schema']['required'] ?? []; foreach ($bodyProperties as $key => $value) { - $param['name'] = $key; - $param['type'] = $value['type'] ?? null; - $param['description'] = $value['description'] ?? ''; - $param['required'] = (in_array($key, $bodyRequired)); - $param['example'] = $value['x-example'] ?? null; - $param['isUploadID'] = $value['x-upload-id'] ?? false; - $param['nullable'] = $value['x-nullable'] ?? false; - $param['array'] = [ + $temp = $param; + $temp['name'] = $key; + $temp['type'] = $value['type'] ?? null; + $temp['description'] = $value['description'] ?? ''; + $temp['required'] = (in_array($key, $bodyRequired)); + $temp['example'] = $value['x-example'] ?? null; + $temp['isUploadID'] = $value['x-upload-id'] ?? false; + $temp['nullable'] = $value['x-nullable'] ?? false; + $temp['array'] = [ 'type' => $value['items']['type'] ?? '', ]; if ($value['type'] === 'object' && is_array($value['default'])) { $value['default'] = (empty($value['default'])) ? new stdClass() : $value['default']; } - $param['default'] = (is_array($value['default']) || $value['default'] instanceof stdClass) ? json_encode($value['default']) : $value['default']; + if (isset($value['enum'])) { + $temp['enumValues'] = $value['enum']; + $temp['enumName'] = $value['x-enum-name']; + $temp['enumKeys'] = $value['x-enum-keys']; + } + + $temp['default'] = (is_array($value['default']) || $value['default'] instanceof stdClass) ? json_encode($value['default']) : $value['default']; - $output['parameters']['body'][] = $param; - $output['parameters']['all'][] = $param; + $output['parameters']['body'][] = $temp; + $output['parameters']['all'][] = $temp; } continue 2; @@ -341,4 +353,32 @@ public function getDefinitions() } return $list; } + + /** + * @return array + */ + public function getEnums(): array + { + $list = []; + + foreach ($this->getServices() as $key => $service) { + foreach ($this->getMethods($key) as $method) { + if (isset($method['parameters']) && is_array($method['parameters'])) { + foreach ($method['parameters']['all'] as $parameter) { + $enumName = $parameter['enumName'] ?? $parameter['name']; + + if (isset($parameter['enumValues']) && !\in_array($enumName, $list)) { + $list[$enumName] = [ + 'name' => $enumName, + 'enum' => $parameter['enumValues'], + 'keys' => $parameter['enumKeys'], + ]; + } + } + } + } + } + + return \array_values($list); + } } diff --git a/templates/android/.github/workflows/publish.yml b/templates/android/.github/workflows/publish.yml index ae9090b5c..7145ba674 100644 --- a/templates/android/.github/workflows/publish.yml +++ b/templates/android/.github/workflows/publish.yml @@ -12,11 +12,10 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 11 - # Base64 decodes and pipes the GPG key content into the secret file + java-version: 17 - name: Prepare environment env: GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} @@ -37,7 +36,7 @@ jobs: # Runs upload, and then closes & releases the repository - name: Publish Release Version to MavenCentral run: | - if ${{ endswith(github.event.release.tag_name, '-SNAPSHOT') }}; then + if ${{ contains(github.event.release.tag_name, '-rc') }}; then echo "Publising Snapshot Version ${{ github.event.release.tag_name}} to Snapshot repository" ./gradlew publishToSonatype else diff --git a/templates/android/build.gradle.twig b/templates/android/build.gradle.twig index f2080e685..0b0c86cd5 100644 --- a/templates/android/build.gradle.twig +++ b/templates/android/build.gradle.twig @@ -2,20 +2,19 @@ apply plugin: 'io.github.gradle-nexus.publish-plugin' // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.8.0" + ext.kotlin_version = "1.9.10" + version System.getenv("SDK_VERSION") + repositories { maven { url "https://plugins.gradle.org/m2/" } google() mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:4.2.2" + classpath "com.android.tools.build:gradle:8.2.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'io.github.gradle-nexus:publish-plugin:1.1.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "io.github.gradle-nexus:publish-plugin:1.3.0" } } diff --git a/templates/android/docs/java/example.md.twig b/templates/android/docs/java/example.md.twig index 862a85670..aa6a922e0 100644 --- a/templates/android/docs/java/example.md.twig +++ b/templates/android/docs/java/example.md.twig @@ -4,42 +4,66 @@ import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback; import {{ sdk.namespace | caseDot }}.models.InputFile; {% endif %} import {{ sdk.namespace | caseDot }}.services.{{ service.name | caseUcfirst }}; +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import {{ sdk.namespace | caseDot }}.enums.{{ name | caseUcfirst }}; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} Client client = new Client(context) -{% if method.auth|length > 0 %} + {%~ if method.auth|length > 0 %} .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header | caseUcfirst}}("{{node[header]['x-appwrite']['demo']}}"){% if loop.last %};{% endif %} // {{node[header].description}} -{% endfor %}{% endfor %}{% endif %} + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header | caseUcfirst}}("{{node[header]['x-appwrite']['demo']}}"){% if loop.last %};{% endif %} // {{ node[header].description }} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %}new CoroutineCallback<>((result, error) -> { - if (error != null) + if (error != null) { error.printStackTrace(); return; } - Log.d("Appwrite", result.toString()); + Log.d("{{ spec.title | caseUcfirst }}", result.toString()); }));{% endif %} {% for parameter in method.parameters.all %} -{% if parameter.required %} - {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // {{parameter.name}} + {%~ if parameter.enumValues is not empty -%} + {%~ if parameter.enumName is not empty -%} + {%~ set name = parameter.enumName -%} + {%~ else -%} + {%~ set name = parameter.name -%} + {%~ endif %} + {{ name }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}, // {{ parameter.name }} {% if not parameter.required %}(optional){% endif %} + {%~ else %} + {{ parameter | paramExample }}, // {{ parameter.name }} {% if not parameter.required %}(optional){% endif %} + {%~ endif %} + {%~ if loop.last %} -{% else %} - {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // {{parameter.name}} (optional) -{% endif %} -{% if loop.last %} new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); return; } - Log.d("Appwrite", result.toString()); + Log.d("{{ spec.title | caseUcfirst }}", result.toString()); }) ); {% endif %} + {% endfor %} \ No newline at end of file diff --git a/templates/android/docs/kotlin/example.md.twig b/templates/android/docs/kotlin/example.md.twig index 49d97d49a..cd9be2ca9 100644 --- a/templates/android/docs/kotlin/example.md.twig +++ b/templates/android/docs/kotlin/example.md.twig @@ -1,29 +1,53 @@ import {{ sdk.namespace | caseDot }}.Client +import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback {% if method.parameters.all | filter((param) => param.type == 'file') | length > 0 %} import {{ sdk.namespace | caseDot }}.models.InputFile {% endif %} import {{ sdk.namespace | caseDot }}.services.{{ service.name | caseUcfirst }} +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import {{ sdk.namespace | caseDot }}.enums.{{ name | caseUcfirst }} +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} val client = Client(context) -{% if method.auth|length > 0 %} + {%~ if method.auth|length > 0 %} .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} .set{{header | caseUcfirst}}("{{node[header]['x-appwrite']['demo']}}") // {{node[header].description}} -{% endfor %}{% endfor %}{% endif %} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} val {{ service.name | caseCamel }} = {{ service.name | caseUcfirst }}(client) -{% if method.type == 'webAuth' %}{% elseif method.type == 'location' %}val result = {% else %}val response = {% endif %}{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %}){% endif %} +{% if method.type == 'webAuth' %} +{% else %} +val result = {% endif %}{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %}){% endif %} -{% for parameter in method.parameters.all %} -{% if parameter.required %} - {{parameter.name}} = {{ parameter | paramExample }}{% if not loop.last %},{% endif %} + {%~ for parameter in method.parameters.all %} + {%~ if parameter.enumValues is not empty -%} + {%~ if parameter.enumName is not empty -%} + {%~ set name = parameter.enumName -%} + {%~ else -%} + {%~ set name = parameter.name -%} + {%~ endif %} + {{ parameter.name }} = {{ name }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }},{% if not parameter.required %} // (optional){% endif %} + {%~ else %} + {{ parameter.name }} = {{ parameter | paramExample }}, {% if not parameter.required %}// (optional){% endif %} + {%~ endif %} -{% else %} - {{parameter.name}} = {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // optional -{% endif %} -{% if loop.last %} -) -{% endif %} -{% endfor %} \ No newline at end of file + {%~ endfor %} +{% if method.parameters.all | length > 0 %}){% endif %} diff --git a/templates/android/example-java/.gitignore b/templates/android/example-java/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/templates/android/example-java/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/templates/android/example-java/build.gradle.twig b/templates/android/example-java/build.gradle.twig deleted file mode 100644 index 85dfab207..000000000 --- a/templates/android/example-java/build.gradle.twig +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id 'com.android.application' -} - -android { - compileSdkVersion 33 - - defaultConfig { - applicationId "{{ sdk.namespace | caseDot }}.example_java" - minSdkVersion 23 - targetSdkVersion 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation project(path: ':library') - - implementation 'androidx.appcompat:appcompat:1.6.0' - implementation 'com.google.android.material:material:1.8.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} \ No newline at end of file diff --git a/templates/android/example-java/src/main/AndroidManifest.xml b/templates/android/example-java/src/main/AndroidManifest.xml deleted file mode 100644 index 0276c9c74..000000000 --- a/templates/android/example-java/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/templates/android/example-java/src/main/java/io/appwrite/example_java/MainActivity.java.twig b/templates/android/example-java/src/main/java/io/appwrite/example_java/MainActivity.java.twig deleted file mode 100644 index dfc0e7fb0..000000000 --- a/templates/android/example-java/src/main/java/io/appwrite/example_java/MainActivity.java.twig +++ /dev/null @@ -1,34 +0,0 @@ -package io.appwrite.example_java; - -import android.os.Bundle; -import android.util.Log; - -import androidx.appcompat.app.AppCompatActivity; - -import {{ sdk.namespace | caseDot }}.Client; -import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback; -import {{ sdk.namespace | caseDot }}.services.Account; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - Client client = new Client(getApplicationContext()) - .setEndpoint("https://demo.appwrite.io/v1") - .setProject("6070749e6acd4"); - - Account account = new Account(client); - - account.createEmailSession("test7@test.com", "password", new CoroutineCallback<>((session, error) -> { - if (error != null) { - Log.e("Appwrite", error.getMessage()); - return; - } - - Log.d("Appwrite", session.toMap().toString()); - })); - } -} \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/drawable/ic_launcher_background.xml b/templates/android/example-java/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cb..000000000 --- a/templates/android/example-java/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/android/example-java/src/main/res/drawable/ic_launcher_foreground.xml b/templates/android/example-java/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d114..000000000 --- a/templates/android/example-java/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/layout/activity_main.xml b/templates/android/example-java/src/main/res/layout/activity_main.xml deleted file mode 100644 index 4fc244418..000000000 --- a/templates/android/example-java/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cfe5..000000000 --- a/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cfe5..000000000 --- a/templates/android/example-java/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/values/colors.xml b/templates/android/example-java/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127d3..000000000 --- a/templates/android/example-java/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/values/strings.xml b/templates/android/example-java/src/main/res/values/strings.xml deleted file mode 100644 index 71e50b35a..000000000 --- a/templates/android/example-java/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Example Java - \ No newline at end of file diff --git a/templates/android/example-java/src/main/res/values/themes.xml b/templates/android/example-java/src/main/res/values/themes.xml deleted file mode 100644 index dde245d7a..000000000 --- a/templates/android/example-java/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/templates/android/example/build.gradle.twig b/templates/android/example/build.gradle.twig index bdef1a830..4b720c078 100644 --- a/templates/android/example/build.gradle.twig +++ b/templates/android/example/build.gradle.twig @@ -1,15 +1,18 @@ plugins { id 'com.android.application' id 'kotlin-android' + id 'kotlin-kapt' } android { - compileSdkVersion 33 + namespace "{{ sdk.namespace | caseDot }}.android" + + compileSdkVersion 34 defaultConfig { applicationId "{{ sdk.namespace | caseDot }}.android" minSdkVersion 21 - targetSdkVersion 33 + targetSdkVersion 34 versionCode 1 versionName "1.0" @@ -36,23 +39,27 @@ android { } dependencies { - implementation project(path: ':library') - - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.6.0' - implementation 'com.google.android.material:material:1.8.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' - implementation "androidx.fragment:fragment-ktx:1.5.5" - implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' - implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' - implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation(project(path: ':library')) + + implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") + implementation("androidx.fragment:fragment-ktx:1.6.2") + implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") + implementation("androidx.navigation:navigation-ui-ktx:2.7.7") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") + + implementation(platform("com.google.firebase:firebase-bom:32.7.0")) + implementation("com.google.firebase:firebase-messaging") + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") } \ No newline at end of file diff --git a/templates/android/example/src/main/AndroidManifest.xml b/templates/android/example/src/main/AndroidManifest.xml index e3c242180..3564397e7 100644 --- a/templates/android/example/src/main/AndroidManifest.xml +++ b/templates/android/example/src/main/AndroidManifest.xml @@ -3,12 +3,14 @@ package="io.appwrite.android"> + android:theme="@style/Theme.AppwriteAndroidSDK" + android:usesCleartextTraffic="true"> + @@ -24,6 +26,13 @@ + + + + + + + \ No newline at end of file diff --git a/templates/android/example/src/main/java/io/appwrite/android/utils/Client.kt.twig b/templates/android/example/src/main/java/io/appwrite/android/utils/Client.kt.twig deleted file mode 100644 index 0472380b1..000000000 --- a/templates/android/example/src/main/java/io/appwrite/android/utils/Client.kt.twig +++ /dev/null @@ -1,20 +0,0 @@ -package {{ sdk.namespace | caseDot }}.android.utils - -import android.content.Context -import io.appwrite.Client - -object Client { - lateinit var client : Client - - fun create(context: Context) { - client = Client(context) - .setEndpoint("https://demo.appwrite.io/v1") - .setProject("6070749e6acd4") - - /* Useful when testing locally */ -// client = Client(context) -// .setEndpoint("https://192.168.1.35/v1") -// .setProject("60bdbc911784e") -// .setSelfSigned(true) - } -} \ No newline at end of file diff --git a/templates/android/example/src/main/java/io/appwrite/android/MainActivity.kt.twig b/templates/android/example/src/main/java/io/package/android/MainActivity.kt.twig similarity index 100% rename from templates/android/example/src/main/java/io/appwrite/android/MainActivity.kt.twig rename to templates/android/example/src/main/java/io/package/android/MainActivity.kt.twig diff --git a/templates/android/example/src/main/java/io/package/android/services/MessagingService.kt.twig b/templates/android/example/src/main/java/io/package/android/services/MessagingService.kt.twig new file mode 100644 index 000000000..bf2a2fdd5 --- /dev/null +++ b/templates/android/example/src/main/java/io/package/android/services/MessagingService.kt.twig @@ -0,0 +1,37 @@ +package {{ sdk.namespace | caseDot }}.android.services + +import com.google.firebase.messaging.FirebaseMessagingService +import {{ sdk.namespace | caseDot }}.ID +import {{ sdk.namespace | caseDot }}.services.Account +import kotlinx.coroutines.runBlocking + +class MessagingService : FirebaseMessagingService() { + + companion object { + var account: Account? = null + } + + override fun onNewToken(token: String) { + super.onNewToken(token) + + val prefs = getSharedPreferences("example", MODE_PRIVATE) + + prefs.edit().putString("fcmToken", token).apply() + + if (account == null) { + return + } + + val targetId = prefs.getString("targetId", null) + + runBlocking { + if (targetId == null) { + val target = account!!.createPushTarget(ID.unique(), token) + + prefs.edit().putString("targetId", target.id).apply() + } else { + account!!.updatePushTarget(targetId, token) + } + } + } +} \ No newline at end of file diff --git a/templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt.twig b/templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig similarity index 57% rename from templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt.twig rename to templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig index a11e5a560..94905a61f 100644 --- a/templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsFragment.kt.twig +++ b/templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsFragment.kt.twig @@ -1,5 +1,6 @@ package {{ sdk.namespace | caseDot }}.android.ui.accounts +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,7 +10,6 @@ import androidx.activity.ComponentActivity import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer import {{ sdk.namespace | caseDot }}.android.R import {{ sdk.namespace | caseDot }}.android.databinding.FragmentAccountBinding @@ -31,37 +31,54 @@ class AccountsFragment : Fragment() { false ) binding.lifecycleOwner = viewLifecycleOwner + binding.login.setOnClickListener{ - viewModel.onLogin(binding.email.text, binding.password.text) + viewModel.onLogin( + binding.email.text.toString(), + binding.password.text.toString(), + context + ?.getSharedPreferences("example", Context.MODE_PRIVATE) + ?.getString("fcmToken", null) ?: "" + ) } - binding.signup.setOnClickListener{ - viewModel.onSignup(binding.email.text, binding.password.text, binding.name.text) + viewModel.onSignup( + binding.email.text.toString(), + binding.password.text.toString(), + binding.name.text.toString() + ) } - binding.getUser.setOnClickListener{ viewModel.getUser() } - binding.oAuth.setOnClickListener{ viewModel.oAuthLogin(activity as ComponentActivity) } - binding.logout.setOnClickListener{ viewModel.logout() } - viewModel.error.observe(viewLifecycleOwner, Observer { event -> - event?.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled - Toast.makeText(requireContext(), it.message , Toast.LENGTH_SHORT).show() + viewModel.error.observe(viewLifecycleOwner) { event -> + event?.getContentIfNotHandled()?.let { + Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show() } - }) + } - viewModel.response.observe(viewLifecycleOwner, Observer { event -> + viewModel.response.observe(viewLifecycleOwner) { event -> event?.getContentIfNotHandled()?.let { binding.responseTV.setText(it) } - }) + } + + viewModel.target.observe(viewLifecycleOwner) { event -> + event?.getContentIfNotHandled()?.let { + context + ?.getSharedPreferences("example", Context.MODE_PRIVATE) + ?.edit() + ?.putString("targetId", it.id) + ?.apply() + } + } return binding.root } diff --git a/templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt.twig b/templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig similarity index 54% rename from templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt.twig rename to templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig index 530f7e7bb..917e84456 100644 --- a/templates/android/example/src/main/java/io/appwrite/android/ui/accounts/AccountsViewModel.kt.twig +++ b/templates/android/example/src/main/java/io/package/android/ui/accounts/AccountsViewModel.kt.twig @@ -1,50 +1,74 @@ package {{ sdk.namespace | caseDot }}.android.ui.accounts -import android.text.Editable import androidx.activity.ComponentActivity import androidx.lifecycle.* +import {{ sdk.namespace | caseDot }}.ID +import {{ sdk.namespace | caseDot }}.android.services.MessagingService import {{ sdk.namespace | caseDot }}.android.utils.Client.client import {{ sdk.namespace | caseDot }}.android.utils.Event +import {{ sdk.namespace | caseDot }}.enums.OAuthProvider import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.toJson +import {{ sdk.namespace | caseDot }}.models.Target import {{ sdk.namespace | caseDot }}.services.Account import kotlinx.coroutines.launch class AccountsViewModel : ViewModel() { - private val _error = MutableLiveData>().apply { - value = null - } + private val _error = MutableLiveData>().apply { value = null } val error: LiveData> = _error - private val _response = MutableLiveData>().apply { - value = null - } + private val _response = MutableLiveData>().apply { value = null } val response: LiveData> = _response - private val accountService by lazy { - Account(client) + private val _target = MutableLiveData>().apply { value = null } + val target: LiveData> = _target + + private val account by lazy { + val account = Account(client) + + MessagingService.account = account + + account } - fun onLogin(email: Editable, password: Editable) { + fun onLogin( + email: String, + password: String, + token: String?, + ) { viewModelScope.launch { try { - val session = accountService.createEmailSession(email.toString(), password.toString()) + val session = account.createEmailPasswordSession( + email, + password + ) + + if (token != null) { + val target = account.createPushTarget(ID.unique(), token) + + _target.postValue(Event(target)) + } + _response.postValue(Event(session.toJson())) - } catch (e: {{ spec.title | caseUcfirst }}Exception) { + } catch (e: AppwriteException) { _error.postValue(Event(e)) } } } - fun onSignup(email: Editable, password: Editable, name: Editable) { + fun onSignup(email: String, password: String, name: String) { viewModelScope.launch { try { - val user = - accountService.create(email.toString(), password.toString(), name.toString()) + val user = account.create( + ID.unique(), + email, + password, + name + ) _response.postValue(Event(user.toJson())) - } catch (e: {{ spec.title | caseUcfirst }}Exception) { + } catch (e: AppwriteException) { _error.postValue(Event(e)) } } @@ -54,15 +78,15 @@ class AccountsViewModel : ViewModel() { fun oAuthLogin(activity: ComponentActivity) { viewModelScope.launch { try { - accountService.createOAuth2Session( + account.createOAuth2Session( activity, - "facebook", + OAuthProvider.FACEBOOK, "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/success", "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/failure" ) } catch (e: Exception) { _error.postValue(Event(e)) - } catch (e: {{ spec.title | caseUcfirst }}Exception) { + } catch (e: AppwriteException) { _error.postValue(Event(e)) } } @@ -71,9 +95,9 @@ class AccountsViewModel : ViewModel() { fun getUser() { viewModelScope.launch { try { - val account = accountService.get() - _response.postValue(Event(account.toJson())) - } catch (e: {{ spec.title | caseUcfirst }}Exception) { + val user = account.get() + _response.postValue(Event(user.toJson())) + } catch (e: AppwriteException) { _error.postValue(Event(e)) } } @@ -82,9 +106,9 @@ class AccountsViewModel : ViewModel() { fun logout() { viewModelScope.launch { try { - val result = accountService.deleteSession("current") + val result = account.deleteSession("current") _response.postValue(Event(result.toJson())) - } catch (e: {{ spec.title | caseUcfirst }}Exception) { + } catch (e: AppwriteException) { _error.postValue(Event(e)) } } diff --git a/templates/android/example/src/main/java/io/package/android/utils/Client.kt.twig b/templates/android/example/src/main/java/io/package/android/utils/Client.kt.twig new file mode 100644 index 000000000..f66e24507 --- /dev/null +++ b/templates/android/example/src/main/java/io/package/android/utils/Client.kt.twig @@ -0,0 +1,15 @@ +package {{ sdk.namespace | caseDot }}.android.utils + +import android.content.Context +import io.appwrite.Client + +object Client { + lateinit var client : Client + + fun create(context: Context) { + client = Client(context) + .setEndpoint("http://192.168.4.24/v1") + .setProject("65a8e2b4632c04b1f5da") + .setSelfSigned(true) + } +} \ No newline at end of file diff --git a/templates/android/example/src/main/java/io/appwrite/android/utils/Event.kt.twig b/templates/android/example/src/main/java/io/package/android/utils/Event.kt.twig similarity index 100% rename from templates/android/example/src/main/java/io/appwrite/android/utils/Event.kt.twig rename to templates/android/example/src/main/java/io/package/android/utils/Event.kt.twig diff --git a/templates/android/example/src/main/res/layout/fragment_account.xml b/templates/android/example/src/main/res/layout/fragment_account.xml index 2fb34c957..4173be134 100644 --- a/templates/android/example/src/main/res/layout/fragment_account.xml +++ b/templates/android/example/src/main/res/layout/fragment_account.xml @@ -57,6 +57,7 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:hint="name" + android:text="Tester" android:inputType="text" app:layout_constraintStart_toStartOf="@id/password" app:layout_constraintTop_toBottomOf="@id/password" /> diff --git a/templates/android/gradle/wrapper/gradle-wrapper.properties b/templates/android/gradle/wrapper/gradle-wrapper.properties index 85e684fc9..ebd754f09 100644 --- a/templates/android/gradle/wrapper/gradle-wrapper.properties +++ b/templates/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Jun 01 15:55:54 IST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/templates/android/library/build.gradle.twig b/templates/android/library/build.gradle.twig index c4d21d673..e17fb10d0 100644 --- a/templates/android/library/build.gradle.twig +++ b/templates/android/library/build.gradle.twig @@ -22,8 +22,14 @@ ext { version PUBLISH_VERSION android { + namespace PUBLISH_GROUP_ID + compileSdkVersion(34) + buildFeatures { + buildConfig true + } + defaultConfig { minSdkVersion(21) targetSdkVersion(34) @@ -43,10 +49,6 @@ android { ) } } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } kotlinOptions { jvmTarget = "1.8" } @@ -54,27 +56,28 @@ android { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") - api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1") + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") + api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") api(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) api("com.squareup.okhttp3:okhttp") implementation("com.squareup.okhttp3:okhttp-urlconnection") implementation("com.squareup.okhttp3:logging-interceptor") - implementation("com.google.code.gson:gson:2.9.0") + implementation("com.google.code.gson:gson:2.10.1") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") - implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") - implementation("androidx.appcompat:appcompat:1.6.0") - implementation("androidx.fragment:fragment-ktx:1.5.5") - implementation("androidx.activity:activity-ktx:1.6.1") - implementation("androidx.browser:browser:1.4.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.lifecycle:lifecycle-common-java8:2.7.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.fragment:fragment-ktx:1.6.2") + implementation("androidx.activity:activity-ktx:1.8.2") + implementation("androidx.browser:browser:1.7.0") + implementation("androidx.core:core-ktx:1.12.0") - testImplementation 'junit:junit:4.13.2' - testImplementation "androidx.test.ext:junit-ktx:1.1.5" - testImplementation "androidx.test:core-ktx:1.5.0" - testImplementation "org.robolectric:robolectric:4.5.1" - testApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1") + testImplementation("junit:junit:4.13.2") + testImplementation("androidx.test.ext:junit-ktx:1.1.5") + testImplementation("androidx.test:core-ktx:1.5.0") + testImplementation("org.robolectric:robolectric:4.11.1") + testApi("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1") } apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/templates/android/library/src/main/AndroidManifest.xml.twig b/templates/android/library/src/main/AndroidManifest.xml.twig index 55b98cbdb..899321de4 100644 --- a/templates/android/library/src/main/AndroidManifest.xml.twig +++ b/templates/android/library/src/main/AndroidManifest.xml.twig @@ -1,8 +1,7 @@ - + - + + \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/Query.kt.twig b/templates/android/library/src/main/java/io/appwrite/Query.kt.twig deleted file mode 100644 index 7f4fbcd81..000000000 --- a/templates/android/library/src/main/java/io/appwrite/Query.kt.twig +++ /dev/null @@ -1,60 +0,0 @@ -package {{ sdk.namespace | caseDot }} - -class Query { - companion object { - fun equal(attribute: String, value: Any) = addQuery(attribute, "equal", value) - - fun notEqual(attribute: String, value: Any) = Query.addQuery(attribute, "notEqual", value) - - fun lessThan(attribute: String, value: Any) = Query.addQuery(attribute, "lessThan", value) - - fun lessThanEqual(attribute: String, value: Any) = Query.addQuery(attribute, "lessThanEqual", value) - - fun greaterThan(attribute: String, value: Any) = Query.addQuery(attribute, "greaterThan", value) - - fun greaterThanEqual(attribute: String, value: Any) = Query.addQuery(attribute, "greaterThanEqual", value) - - fun search(attribute: String, value: String) = Query.addQuery(attribute, "search", value) - - fun isNull(attribute: String) = "isNull(\"${attribute}\")" - - fun isNotNull(attribute: String) = "isNotNull(\"${attribute}\")" - - fun between(attribute: String, start: Int, end: Int) = "between(\"${attribute}\", ${start}, ${end})" - - fun between(attribute: String, start: Double, end: Double) = "between(\"${attribute}\", ${start}, ${end})" - - fun between(attribute: String, start: String, end: String) = "between(\"${attribute}\", \"${start}\", \"${end}\")" - - fun startsWith(attribute: String, value: String) = Query.addQuery(attribute, "startsWith", value) - - fun endsWith(attribute: String, value: String) = Query.addQuery(attribute, "endsWith", value) - - fun select(attributes: List) = "select([${attributes.joinToString(",") { "\"$it\"" }}])" - - fun orderAsc(attribute: String) = "orderAsc(\"${attribute}\")" - - fun orderDesc(attribute: String) = "orderDesc(\"${attribute}\")" - - fun cursorBefore(documentId: String) = "cursorBefore(\"${documentId}\")" - - fun cursorAfter(documentId: String) = "cursorAfter(\"${documentId}\")" - - fun limit(limit: Int) = "limit(${limit})" - - fun offset(offset: Int) = "offset(${offset})" - - private fun addQuery(attribute: String, method: String, value: Any): String { - return when (value) { - is List<*> -> "${method}(\"${attribute}\", [${value.map{it -> parseValues(it!!)}.joinToString(",")}])" - else -> "${method}(\"${attribute}\", [${Query.parseValues(value)}])" - } - } - private fun parseValues(value: Any): String { - return when (value) { - is String -> "\"${value}\"" - else -> "${value}" - } - } - } -} diff --git a/templates/android/library/src/main/java/io/appwrite/json/PreciseNumberAdapter.kt.twig b/templates/android/library/src/main/java/io/appwrite/json/PreciseNumberAdapter.kt.twig deleted file mode 100644 index 09cb786cf..000000000 --- a/templates/android/library/src/main/java/io/appwrite/json/PreciseNumberAdapter.kt.twig +++ /dev/null @@ -1,64 +0,0 @@ -package {{ sdk.namespace | caseDot }}.json - -import com.google.gson.Gson -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken.* -import com.google.gson.stream.JsonWriter -import java.io.IOException - -internal class PreciseNumberAdapter : TypeAdapter() { - - private val delegate = Gson() - .getAdapter(Any::class.java) - - @Throws(IOException::class) - override fun write(out: JsonWriter?, value: Any?) { - delegate.write(out, value) - } - - @Throws(IOException::class) - override fun read(input: JsonReader): Any? { - return when (input.peek()) { - BEGIN_ARRAY -> { - val list = mutableListOf() - input.beginArray() - while (input.hasNext()) { - list.add(read(input)) - } - input.endArray() - list - } - BEGIN_OBJECT -> { - val map = mutableMapOf() - input.beginObject() - while (input.hasNext()) { - map[input.nextName()] = read(input) - } - input.endObject() - map - } - STRING -> { - input.nextString() - } - NUMBER -> { - val numberString = input.nextString() - if (numberString.indexOf('.') != -1) { - numberString.toDouble() - } else { - numberString.toLong() - } - } - BOOLEAN -> { - input.nextBoolean() - } - NULL -> { - input.nextNull() - null - } - else -> { - throw IllegalStateException() - } - } - } -} \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/Client.kt.twig b/templates/android/library/src/main/java/io/package/Client.kt.twig similarity index 89% rename from templates/android/library/src/main/java/io/appwrite/Client.kt.twig rename to templates/android/library/src/main/java/io/package/Client.kt.twig index 210ef4fbe..a49bf083a 100644 --- a/templates/android/library/src/main/java/io/appwrite/Client.kt.twig +++ b/templates/android/library/src/main/java/io/package/Client.kt.twig @@ -1,14 +1,13 @@ package {{ sdk.namespace | caseDot }} import android.content.Context +import android.content.Intent import android.content.pm.PackageManager -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken -import {{ sdk.namespace | caseDot }}.{{ spec.title | caseLower }}.BuildConfig +import {{ sdk.namespace | caseDot }}.cookies.ListenableCookieJar import {{ sdk.namespace | caseDot }}.cookies.stores.SharedPreferencesCookieStore import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.fromJson -import {{ sdk.namespace | caseDot }}.json.PreciseNumberAdapter +import {{ sdk.namespace | caseDot }}.extensions.toJson import {{ sdk.namespace | caseDot }}.models.InputFile import {{ sdk.namespace | caseDot }}.models.UploadProgress import kotlinx.coroutines.CoroutineScope @@ -30,7 +29,6 @@ import java.net.CookieManager import java.net.CookiePolicy import java.security.SecureRandom import java.security.cert.X509Certificate -import javax.net.ssl.HostnameVerifier import javax.net.ssl.SSLContext import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager @@ -40,13 +38,15 @@ import kotlin.coroutines.resume class Client @JvmOverloads constructor( context: Context, - var endPoint: String = "{{spec.endpoint}}", - var endPointRealtime: String? = null, + var endpoint: String = "{{spec.endpoint}}", + var endpointRealtime: String? = null, private var selfSigned: Boolean = false ) : CoroutineScope { companion object { - const val CHUNK_SIZE = 5*1024*1024; // 5MB + internal const val CHUNK_SIZE = 5*1024*1024; // 5MB + internal const val GLOBAL_PREFS = "{{ sdk.namespace | caseDot }}" + internal const val COOKIE_PREFS = "myCookie" } override val coroutineContext: CoroutineContext @@ -54,21 +54,16 @@ class Client @JvmOverloads constructor( private val job = Job() - private val gson = GsonBuilder().registerTypeAdapter( - object : TypeToken>(){}.type, - PreciseNumberAdapter() - ).create() + internal lateinit var http: OkHttpClient - lateinit var http: OkHttpClient - - private val headers: MutableMap + internal val headers: MutableMap val config: MutableMap - private val cookieJar = CookieManager( - SharedPreferencesCookieStore(context, "myCookie"), + internal val cookieJar = ListenableCookieJar(CookieManager( + SharedPreferencesCookieStore(context.getSharedPreferences(COOKIE_PREFS, Context.MODE_PRIVATE)), CookiePolicy.ACCEPT_ALL - ) + )) private val appVersion by lazy { try { @@ -131,7 +126,7 @@ class Client @JvmOverloads constructor( val builder = OkHttpClient() .newBuilder() - .cookieJar(JavaNetCookieJar(cookieJar)) + .cookieJar(cookieJar) if (!selfSigned) { http = builder.build() @@ -161,7 +156,7 @@ class Client @JvmOverloads constructor( // Create an ssl socket factory with our all-trusting manager val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) - builder.hostnameVerifier(HostnameVerifier { _, _ -> true }) + builder.hostnameVerifier { _, _ -> true } http = builder.build() } catch (e: Exception) { @@ -178,11 +173,11 @@ class Client @JvmOverloads constructor( * * @return this */ - fun setEndpoint(endPoint: String): Client { - this.endPoint = endPoint + fun setEndpoint(endpoint: String): Client { + this.endpoint = endpoint - if (this.endPointRealtime == null && endPoint.startsWith("http")) { - this.endPointRealtime = endPoint.replaceFirst("http", "ws") + if (this.endpointRealtime == null && endpoint.startsWith("http")) { + this.endpointRealtime = endpoint.replaceFirst("http", "ws") } return this @@ -195,8 +190,8 @@ class Client @JvmOverloads constructor( * * @return this */ - fun setEndpointRealtime(endPoint: String): Client { - this.endPointRealtime = endPoint + fun setEndpointRealtime(endpoint: String): Client { + this.endpointRealtime = endpoint return this } @@ -238,7 +233,7 @@ class Client @JvmOverloads constructor( .addAll(headers.toHeaders()) .build() - val httpBuilder = (endPoint + path).toHttpUrl().newBuilder() + val httpBuilder = (endpoint + path).toHttpUrl().newBuilder() if ("GET" == method) { filteredParams.forEach { @@ -293,7 +288,8 @@ class Client @JvmOverloads constructor( } builder.build() } else { - gson.toJson(filteredParams) + filteredParams + .toJson() .toRequestBody("application/json".toMediaType()) } @@ -416,14 +412,14 @@ class Client @JvmOverloads constructor( ) offset += CHUNK_SIZE - headers["x-{{ spec.title | caseLower }}-id"] = result!!["\$id"].toString() + headers["x-{{ spec.title | caseLower }}-id"] = result["\$id"].toString() onProgress?.invoke( UploadProgress( - id = result!!["\$id"].toString(), + id = result["\$id"].toString(), progress = offset.coerceAtMost(size).toDouble() / size * 100, sizeUploaded = offset.coerceAtMost(size), - chunksTotal = result!!["chunksTotal"].toString().toInt(), - chunksUploaded = result!!["chunksUploaded"].toString().toInt(), + chunksTotal = result["chunksTotal"].toString().toInt(), + chunksUploaded = result["chunksUploaded"].toString().toInt(), ) ) } @@ -463,10 +459,8 @@ class Client @JvmOverloads constructor( .use(BufferedReader::readText) val error = if (response.headers["content-type"]?.contains("application/json") == true) { - val map = gson.fromJson>( - body, - object : TypeToken>(){}.type - ) + val map = body.fromJson>() + {{ spec.title | caseUcfirst }}Exception( map["message"] as? String ?: "", (map["code"] as Number).toInt(), @@ -505,10 +499,9 @@ class Client @JvmOverloads constructor( it.resume(true as T) return } - val map = gson.fromJson( - body, - object : TypeToken(){}.type - ) + + val map = body.fromJson() + it.resume( converter?.invoke(map) ?: map as T ) diff --git a/templates/android/library/src/main/java/io/appwrite/ID.kt.twig b/templates/android/library/src/main/java/io/package/ID.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/ID.kt.twig rename to templates/android/library/src/main/java/io/package/ID.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/KeepAliveService.kt.twig b/templates/android/library/src/main/java/io/package/KeepAliveService.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/KeepAliveService.kt.twig rename to templates/android/library/src/main/java/io/package/KeepAliveService.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/Permission.kt.twig b/templates/android/library/src/main/java/io/package/Permission.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/Permission.kt.twig rename to templates/android/library/src/main/java/io/package/Permission.kt.twig diff --git a/templates/android/library/src/main/java/io/package/Query.kt.twig b/templates/android/library/src/main/java/io/package/Query.kt.twig new file mode 100644 index 000000000..d83ff3c7d --- /dev/null +++ b/templates/android/library/src/main/java/io/package/Query.kt.twig @@ -0,0 +1,65 @@ +package {{ sdk.namespace | caseDot }} + +import {{ sdk.namespace | caseDot }}.extensions.toJson +import {{ sdk.namespace | caseDot }}.extensions.fromJson + +class Query( + val method: String, + val attribute: String? = null, + val values: List? = null, +) { + override fun toString() = this.toJson() + + companion object { + fun equal(attribute: String, value: Any) = Query("equal", attribute, parseValue(value)).toJson() + + fun notEqual(attribute: String, value: Any) = Query("notEqual", attribute, parseValue(value)).toJson() + + fun lessThan(attribute: String, value: Any) = Query("lessThan", attribute, parseValue(value)).toJson() + + fun lessThanEqual(attribute: String, value: Any) = Query("lessThanEqual", attribute, parseValue(value)).toJson() + + fun greaterThan(attribute: String, value: Any) = Query("greaterThan", attribute, parseValue(value)).toJson() + + fun greaterThanEqual(attribute: String, value: Any) = Query("greaterThanEqual", attribute, parseValue(value)).toJson() + + fun search(attribute: String, value: String) = Query("search", attribute, listOf(value)).toJson() + + fun isNull(attribute: String) = Query("isNull", attribute).toJson() + + fun isNotNull(attribute: String) = Query("isNotNull", attribute).toJson() + + fun between(attribute: String, start: Any, end: Any) = Query("between", attribute, listOf(start, end)).toJson() + + fun startsWith(attribute: String, value: String) = Query("startsWith", attribute, listOf(value)).toJson() + + fun endsWith(attribute: String, value: String) = Query("endsWith", attribute, listOf(value)).toJson() + + fun select(attributes: List) = Query("select", null, attributes).toJson() + + fun orderAsc(attribute: String) = Query("orderAsc", attribute).toJson() + + fun orderDesc(attribute: String) = Query("orderDesc", attribute).toJson() + + fun cursorBefore(documentId: String) = Query("cursorBefore", null, listOf(documentId)).toJson() + + fun cursorAfter(documentId: String) = Query("cursorAfter", null, listOf(documentId)).toJson() + + fun limit(limit: Int) = Query("limit", null, listOf(limit)).toJson() + + fun offset(offset: Int) = Query("offset", null, listOf(offset)).toJson() + + fun contains(attribute: String, value: Any) = Query("contains", attribute, parseValue(value)).toJson() + + fun or(queries: List) = Query("or", null, queries.map { it.fromJson() }).toJson() + + fun and(queries: List) = Query("and", null, queries.map { it.fromJson() }).toJson() + + private fun parseValue(value: Any): List { + return when (value) { + is List<*> -> value as List + else -> listOf(value) + } + } + } +} \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/Role.kt.twig b/templates/android/library/src/main/java/io/package/Role.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/Role.kt.twig rename to templates/android/library/src/main/java/io/package/Role.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/services/Service.kt.twig b/templates/android/library/src/main/java/io/package/Service.kt.twig similarity index 65% rename from templates/android/library/src/main/java/io/appwrite/services/Service.kt.twig rename to templates/android/library/src/main/java/io/package/Service.kt.twig index a6b5623fc..8170d21ca 100644 --- a/templates/android/library/src/main/java/io/appwrite/services/Service.kt.twig +++ b/templates/android/library/src/main/java/io/package/Service.kt.twig @@ -1,4 +1,4 @@ -package {{ sdk.namespace | caseDot }}.services +package {{ sdk.namespace | caseDot }} import {{ sdk.namespace | caseDot }}.Client diff --git a/templates/android/library/src/main/java/io/appwrite/WebAuthComponent.kt.twig b/templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/WebAuthComponent.kt.twig rename to templates/android/library/src/main/java/io/package/WebAuthComponent.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/cookies/Extensions.kt.twig b/templates/android/library/src/main/java/io/package/cookies/Extensions.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/cookies/Extensions.kt.twig rename to templates/android/library/src/main/java/io/package/cookies/Extensions.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/cookies/InternalCookie.kt.twig b/templates/android/library/src/main/java/io/package/cookies/InternalCookie.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/cookies/InternalCookie.kt.twig rename to templates/android/library/src/main/java/io/package/cookies/InternalCookie.kt.twig diff --git a/templates/android/library/src/main/java/io/package/cookies/ListenableCookieJar.kt.twig b/templates/android/library/src/main/java/io/package/cookies/ListenableCookieJar.kt.twig new file mode 100644 index 000000000..863eb42e1 --- /dev/null +++ b/templates/android/library/src/main/java/io/package/cookies/ListenableCookieJar.kt.twig @@ -0,0 +1,119 @@ +package io.appwrite.cookies + +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.internal.cookieToString +import okhttp3.internal.delimiterOffset +import okhttp3.internal.platform.Platform +import okhttp3.internal.trimSubstring +import java.io.IOException +import java.net.CookieHandler +import java.net.HttpCookie +import java.util.Collections + +typealias CookieListener = (existing: List, new: List) -> Unit + +class ListenableCookieJar(private val cookieHandler: CookieHandler) : CookieJar { + + private val listeners: MutableMap = mutableMapOf() + + fun onSave(key: String, listener: CookieListener) { + listeners[key.hashCode()] = listener + } + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val existingCookies = loadForRequest(url) + + listeners.values.forEach { it(existingCookies, cookies) } + + val cookieStrings = mutableListOf() + for (cookie in cookies) { + cookieStrings.add(cookieToString(cookie, true)) + } + val multimap = mapOf("Set-Cookie" to cookieStrings) + try { + cookieHandler.put(url.toUri(), multimap) + } catch (e: IOException) { + Platform.get().log( + "Saving cookies failed for " + url.resolve("/...")!!, + Platform.WARN, e + ) + } + } + + override fun loadForRequest(url: HttpUrl): List { + val cookieHeaders = try { + cookieHandler.get(url.toUri(), emptyMap>()) + } catch (e: IOException) { + Platform.get().log( + "Loading cookies failed for " + url.resolve("/...")!!, + Platform.WARN, e + ) + return emptyList() + } + + var cookies: MutableList? = null + for ((key, value) in cookieHeaders) { + if (("Cookie".equals(key, ignoreCase = true) || "Cookie2".equals( + key, + ignoreCase = true + )) && + value.isNotEmpty() + ) { + for (header in value) { + if (cookies == null) cookies = mutableListOf() + cookies.addAll(decodeHeaderAsJavaNetCookies(url, header)) + } + } + } + + return if (cookies != null) { + Collections.unmodifiableList(cookies) + } else { + emptyList() + } + } + + /** + * Convert a request header to OkHttp's cookies via [HttpCookie]. That extra step handles + * multiple cookies in a single request header, which [Cookie.parse] doesn't support. + */ + private fun decodeHeaderAsJavaNetCookies(url: HttpUrl, header: String): List { + val result = mutableListOf() + var pos = 0 + val limit = header.length + var pairEnd: Int + while (pos < limit) { + pairEnd = header.delimiterOffset(";,", pos, limit) + val equalsSign = header.delimiterOffset('=', pos, pairEnd) + val name = header.trimSubstring(pos, equalsSign) + if (name.startsWith("$")) { + pos = pairEnd + 1 + continue + } + + // We have either name=value or just a name. + var value = if (equalsSign < pairEnd) { + header.trimSubstring(equalsSign + 1, pairEnd) + } else { + "" + } + + // If the value is "quoted", drop the quotes. + if (value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length - 1) + } + + result.add( + Cookie.Builder() + .name(name) + .value(value) + .domain(url.host) + .build() + ) + pos = pairEnd + 1 + } + return result + } +} \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt.twig b/templates/android/library/src/main/java/io/package/cookies/stores/InMemoryCookieStore.kt.twig similarity index 93% rename from templates/android/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt.twig rename to templates/android/library/src/main/java/io/package/cookies/stores/InMemoryCookieStore.kt.twig index 2a83052d5..984e0bb7d 100644 --- a/templates/android/library/src/main/java/io/appwrite/cookies/stores/InMemoryCookieStore.kt.twig +++ b/templates/android/library/src/main/java/io/package/cookies/stores/InMemoryCookieStore.kt.twig @@ -6,10 +6,9 @@ import java.net.CookieStore import java.net.HttpCookie import java.net.URI import java.net.URISyntaxException -import java.util.Collections import java.util.concurrent.locks.ReentrantLock -open class InMemoryCookieStore(private val name: String) : CookieStore { +open class InMemoryCookieStore : CookieStore { internal val uriIndex = mutableMapOf>() private val lock = ReentrantLock(false) @@ -29,7 +28,7 @@ open class InMemoryCookieStore(private val name: String) : CookieStore { if (cookie == null) { Log.i( javaClass.simpleName, - "tried to add null cookie in cookie store named $name. Doing nothing." + "Tried to add null cookie in cookie store. Doing nothing." ) return } @@ -37,7 +36,7 @@ open class InMemoryCookieStore(private val name: String) : CookieStore { if (uri == null) { Log.i( javaClass.simpleName, - "tried to add null URI in cookie store named $name. Doing nothing." + "Tried to add null URI in cookie store. Doing nothing." ) return } @@ -89,7 +88,7 @@ open class InMemoryCookieStore(private val name: String) : CookieStore { if (cookie == null) { Log.i( javaClass.simpleName, - "tried to remove null cookie from cookie store named $name. Doing nothing." + "Tried to remove null cookie from cookie store. Doing nothing." ) return true } @@ -97,7 +96,7 @@ open class InMemoryCookieStore(private val name: String) : CookieStore { if (uri == null) { Log.i( javaClass.simpleName, - "tried to remove null URI from cookie store named $name. Doing nothing." + "Tried to remove null URI from cookie store. Doing nothing." ) return true } @@ -122,7 +121,7 @@ open class InMemoryCookieStore(private val name: String) : CookieStore { if (uri == null) { Log.i( javaClass.simpleName, - "getting cookies from cookie store named $name for null URI results in empty list" + "Getting cookies from cookie store for null URI results in empty list" ) return emptyList() } diff --git a/templates/android/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt.twig b/templates/android/library/src/main/java/io/package/cookies/stores/SharedPreferencesCookieStore.kt.twig similarity index 93% rename from templates/android/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt.twig rename to templates/android/library/src/main/java/io/package/cookies/stores/SharedPreferencesCookieStore.kt.twig index a5a041d97..c7349d0d4 100644 --- a/templates/android/library/src/main/java/io/appwrite/cookies/stores/SharedPreferencesCookieStore.kt.twig +++ b/templates/android/library/src/main/java/io/package/cookies/stores/SharedPreferencesCookieStore.kt.twig @@ -1,7 +1,7 @@ package {{ sdk.namespace | caseDot }}.cookies.stores import {{ sdk.namespace | caseDot }}.cookies.InternalCookie -import android.content.Context +import android.content.SharedPreferences import android.os.Build import android.util.Log import com.google.gson.Gson @@ -10,11 +10,9 @@ import java.net.HttpCookie import java.net.URI open class SharedPreferencesCookieStore( - context: Context, - private val name: String -) : InMemoryCookieStore(name) { + private val preferences: SharedPreferences, +) : InMemoryCookieStore() { - private val preferences = context.getSharedPreferences(name, Context.MODE_PRIVATE) private val gson = Gson() init { @@ -30,7 +28,7 @@ open class SharedPreferencesCookieStore( } catch (exception: Throwable) { Log.e( javaClass.simpleName, - "Error while loading key = $key, value = $value from cookie store named $name", + "Error while loading key = $key, value = $value from cookie store", exception ) } diff --git a/templates/android/library/src/main/java/io/appwrite/coroutines/Callback.kt.twig b/templates/android/library/src/main/java/io/package/coroutines/Callback.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/coroutines/Callback.kt.twig rename to templates/android/library/src/main/java/io/package/coroutines/Callback.kt.twig diff --git a/templates/android/library/src/main/java/io/package/enums/Enum.kt.twig b/templates/android/library/src/main/java/io/package/enums/Enum.kt.twig new file mode 100644 index 000000000..562aa5d3c --- /dev/null +++ b/templates/android/library/src/main/java/io/package/enums/Enum.kt.twig @@ -0,0 +1,14 @@ +package {{ sdk.namespace | caseDot }}.enums + +import com.google.gson.annotations.SerializedName + +enum class {{ enum.name | caseUcfirst | overrideIdentifier }}(val value: String) { +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + @SerializedName("{{ value }}") + {{ key | caseEnumKey }}("{{ value }}"){% if not loop.last %},{% else %};{% endif %} + +{% endfor %} + + override fun toString() = value +} \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/exceptions/Exception.kt.twig b/templates/android/library/src/main/java/io/package/exceptions/Exception.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/exceptions/Exception.kt.twig rename to templates/android/library/src/main/java/io/package/exceptions/Exception.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt.twig b/templates/android/library/src/main/java/io/package/extensions/CollectionExtensions.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/extensions/CollectionExtensions.kt.twig rename to templates/android/library/src/main/java/io/package/extensions/CollectionExtensions.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt.twig b/templates/android/library/src/main/java/io/package/extensions/JsonExtensions.kt.twig similarity index 72% rename from templates/android/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt.twig rename to templates/android/library/src/main/java/io/package/extensions/JsonExtensions.kt.twig index 48e536b3a..14ad26726 100644 --- a/templates/android/library/src/main/java/io/appwrite/extensions/JsonExtensions.kt.twig +++ b/templates/android/library/src/main/java/io/package/extensions/JsonExtensions.kt.twig @@ -1,8 +1,14 @@ package {{ sdk.namespace | caseDot }}.extensions import com.google.gson.Gson - -val gson = Gson() +import com.google.gson.GsonBuilder +import com.google.gson.ToNumberPolicy +import com.google.gson.reflect.TypeToken + +val gson: Gson = GsonBuilder() + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create() fun Any.toJson(): String = gson.toJson(this) diff --git a/templates/android/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt.twig b/templates/android/library/src/main/java/io/package/extensions/TypeExtensions.kt.twig similarity index 87% rename from templates/android/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt.twig rename to templates/android/library/src/main/java/io/package/extensions/TypeExtensions.kt.twig index 60ae41788..ee0a6a14d 100644 --- a/templates/android/library/src/main/java/io/appwrite/extensions/TypeExtensions.kt.twig +++ b/templates/android/library/src/main/java/io/package/extensions/TypeExtensions.kt.twig @@ -4,5 +4,6 @@ import kotlin.reflect.KClass import kotlin.reflect.typeOf inline fun classOf(): Class { + @Suppress("UNCHECKED_CAST") return (typeOf().classifier!! as KClass).java } \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/models/InputFile.kt.twig b/templates/android/library/src/main/java/io/package/models/InputFile.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/models/InputFile.kt.twig rename to templates/android/library/src/main/java/io/package/models/InputFile.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/models/Model.kt.twig b/templates/android/library/src/main/java/io/package/models/Model.kt.twig similarity index 78% rename from templates/android/library/src/main/java/io/appwrite/models/Model.kt.twig rename to templates/android/library/src/main/java/io/package/models/Model.kt.twig index 6b049555c..4a5cd7014 100644 --- a/templates/android/library/src/main/java/io/appwrite/models/Model.kt.twig +++ b/templates/android/library/src/main/java/io/package/models/Model.kt.twig @@ -61,7 +61,7 @@ import io.appwrite.extensions.jsonCast {%~ endif %} ) = {{ definition | modelType(spec) | raw }}( {%~ for property in definition.properties %} - {{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}, + {{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}, {%~ endfor %} {%~ if definition.additionalProperties %} data = map.jsonCast(to = nestedType) diff --git a/templates/android/library/src/main/java/io/package/models/Notification.kt.twig b/templates/android/library/src/main/java/io/package/models/Notification.kt.twig new file mode 100644 index 000000000..37cfd92f1 --- /dev/null +++ b/templates/android/library/src/main/java/io/package/models/Notification.kt.twig @@ -0,0 +1,12 @@ +package {{ sdk.namespace | caseDot }}.models + +data class Notification( + val title: String = "", + val body: String = "", + val clickAction: String = "", + val color: String = "", + val icon: String = "", + val imageURL: String = "", + val sound: String = "", + val data: Map = mapOf(), +) \ No newline at end of file diff --git a/templates/android/library/src/main/java/io/appwrite/models/RealtimeModels.kt.twig b/templates/android/library/src/main/java/io/package/models/RealtimeModels.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/models/RealtimeModels.kt.twig rename to templates/android/library/src/main/java/io/package/models/RealtimeModels.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/models/UploadProgress.kt.twig b/templates/android/library/src/main/java/io/package/models/UploadProgress.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/models/UploadProgress.kt.twig rename to templates/android/library/src/main/java/io/package/models/UploadProgress.kt.twig diff --git a/templates/android/library/src/main/java/io/appwrite/services/Realtime.kt.twig b/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig similarity index 98% rename from templates/android/library/src/main/java/io/appwrite/services/Realtime.kt.twig rename to templates/android/library/src/main/java/io/package/services/Realtime.kt.twig index cbd909c35..8724af4fb 100644 --- a/templates/android/library/src/main/java/io/appwrite/services/Realtime.kt.twig +++ b/templates/android/library/src/main/java/io/package/services/Realtime.kt.twig @@ -1,5 +1,6 @@ package {{ sdk.namespace | caseDot }}.services +import {{ sdk.namespace | caseDot }}.Service import {{ sdk.namespace | caseDot }}.Client import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.forEachAsync @@ -55,7 +56,7 @@ class Realtime(client: Client) : Service(client), CoroutineScope { } val request = Request.Builder() - .url("${client.endPointRealtime}/realtime?$queryParamBuilder") + .url("${client.endpointRealtime}/realtime?$queryParamBuilder") .build() if (socket != null) { diff --git a/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig b/templates/android/library/src/main/java/io/package/services/Service.kt.twig similarity index 93% rename from templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig rename to templates/android/library/src/main/java/io/package/services/Service.kt.twig index a461b5f91..05de594d6 100644 --- a/templates/android/library/src/main/java/io/appwrite/services/ServiceTemplate.kt.twig +++ b/templates/android/library/src/main/java/io/package/services/Service.kt.twig @@ -2,7 +2,13 @@ package {{ sdk.namespace | caseDot }}.services import android.net.Uri import {{ sdk.namespace | caseDot }}.Client +import {{ sdk.namespace | caseDot }}.Service +{% if spec.definitions is not empty %} import {{ sdk.namespace | caseDot }}.models.* +{% endif %} +{% if spec.enums is not empty %} +import {{ sdk.namespace | caseDot }}.enums.* +{% endif %} import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.classOf {% if service.features.webAuth %} @@ -19,9 +25,7 @@ import java.io.File /** * {{ service.description | raw | replace({"\n": "", "\r": ""}) }} **/ -class {{ service.name | caseUcfirst }} : Service { - - public constructor (client: Client) : super(client) { } +class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { {% for method in service.methods %} /** @@ -55,7 +59,7 @@ class {{ service.name | caseUcfirst }} : Service { ){% if method.type != "webAuth" %}: {{ method | returnType(spec, sdk.namespace | caseDot) | raw }}{% endif %} { val apiPath = "{{ method.path }}" {%~ for parameter in method.parameters.path %} - .replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }}) + .replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }}{% if parameter.enumValues is not empty %}.value{% endif %}) {%~ endfor %} val apiParams = mutableMapOf( @@ -88,7 +92,7 @@ class {{ service.name | caseUcfirst }} : Service { } } - val apiUrl = Uri.parse("${client.endPoint}${apiPath}?${apiQuery.joinToString("&")}") + val apiUrl = Uri.parse("${client.endpoint}${apiPath}?${apiQuery.joinToString("&")}") val callbackUrlScheme = "{{ spec.title | caseLower }}-callback-${client.config["project"]}" WebAuthComponent.authenticate(activity, apiUrl, callbackUrlScheme) { @@ -106,12 +110,12 @@ class {{ service.name | caseUcfirst }} : Service { val cookie = Cookie.Builder() .name(key) .value(secret) - .domain(Uri.parse(client.endPoint).host!!) + .domain(Uri.parse(client.endpoint).host!!) .httpOnly() .build() client.http.cookieJar.saveFromResponse( - client.endPoint.toHttpUrl(), + client.endpoint.toHttpUrl(), listOf(cookie) ) } @@ -133,6 +137,7 @@ class {{ service.name | caseUcfirst }} : Service { {%~ if method.responseModel == 'any' %} it {%~ else %} + @Suppress("UNCHECKED_CAST") {{sdk.namespace | caseDot}}.models.{{ method.responseModel | caseUcfirst }}.from(map = it as Map{% if method.responseModel | hasGenericType(spec) %}, nestedType{% endif %}) {%~ endif %} } diff --git a/templates/android/library/src/main/java/io/appwrite/views/CallbackActivity.kt.twig b/templates/android/library/src/main/java/io/package/views/CallbackActivity.kt.twig similarity index 100% rename from templates/android/library/src/main/java/io/appwrite/views/CallbackActivity.kt.twig rename to templates/android/library/src/main/java/io/package/views/CallbackActivity.kt.twig diff --git a/templates/android/scripts/publish-module.gradle b/templates/android/scripts/publish-module.gradle index e32ff846e..96b31b246 100644 --- a/templates/android/scripts/publish-module.gradle +++ b/templates/android/scripts/publish-module.gradle @@ -63,7 +63,8 @@ publishing { configurations .getByName("releaseCompileClasspath") .resolvedConfiguration - .firstLevelModuleDependencies.forEach { + .firstLevelModuleDependencies + .forEach { def dependency = dependencies.appendNode("dependency") dependency.appendNode("groupId", it.moduleGroup) dependency.appendNode("artifactId", it.moduleName) diff --git a/templates/android/settings.gradle b/templates/android/settings.gradle index 73c359ba7..423c64efe 100644 --- a/templates/android/settings.gradle +++ b/templates/android/settings.gradle @@ -1,4 +1,3 @@ rootProject.name = "Appwrite Android SDK" include ':example' -include ':library' -include ':example-java' \ No newline at end of file +include ':library' \ No newline at end of file diff --git a/templates/apple/Package.swift.twig b/templates/apple/Package.swift.twig new file mode 100644 index 000000000..132ae387f --- /dev/null +++ b/templates/apple/Package.swift.twig @@ -0,0 +1,67 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "{{spec.title | caseUcfirst}}", + platforms: [ + .iOS("15.0"), + .macOS("11.0"), + .watchOS("7.0"), + .tvOS("13.0"), + ], + products: [ + .library( + name: "{{spec.title | caseUcfirst}}", + targets: [ + "{{spec.title | caseUcfirst}}", + "{{spec.title | caseUcfirst}}Enums", + "{{spec.title | caseUcfirst}}Models", + "JSONCodable" + ] + ), + ], + dependencies: [ + .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), + ], + targets: [ + .target( + name: "{{spec.title | caseUcfirst}}", + dependencies: [ + .product(name: "AsyncHTTPClient", package: "async-http-client"), + .product(name: "NIOWebSocket", package: "swift-nio"), + {%~ if spec.definitions is not empty %} + "{{spec.title | caseUcfirst}}Models", + {%~ endif %} + {%~ if spec.enums is not empty %} + "{{spec.title | caseUcfirst}}Enums", + {%~ endif %} + "JSONCodable" + ] + ), + {%~ if spec.definitions is not empty %} + .target( + name: "{{spec.title | caseUcfirst}}Models", + dependencies: [ + "JSONCodable" + ] + ), + {%~ endif %} + {%~ if spec.enums is not empty %} + .target( + name: "{{spec.title | caseUcfirst}}Enums" + ), + {%~ endif %} + .target( + name: "JSONCodable" + ), + .testTarget( + name: "{{spec.title | caseUcfirst}}Tests", + dependencies: [ + "{{ spec.title | caseUcfirst }}" + ] + ) + ], + swiftLanguageVersions: [.v5] +) \ No newline at end of file diff --git a/templates/apple/Sources/Client.swift.twig b/templates/apple/Sources/Client.swift.twig new file mode 100644 index 000000000..83646442c --- /dev/null +++ b/templates/apple/Sources/Client.swift.twig @@ -0,0 +1,578 @@ +import NIO +import NIOCore +import NIOFoundationCompat +import NIOSSL +import Foundation +import AsyncHTTPClient +@_exported import {{spec.title | caseUcfirst}}Models + +let DASHDASH = "--" +let CRLF = "\r\n" + +open class Client { + + // MARK: Properties + public static var chunkSize = 5 * 1024 * 1024 // 5MB + + open var endPoint = "{{spec.endpoint}}" + + open var endPointRealtime: String? = nil + + open var headers: [String: String] = [ + "content-type": "application/json", + "x-sdk-name": "{{ sdk.name }}", + "x-sdk-platform": "{{ sdk.platform }}", + "x-sdk-language": "{{ language.name | caseLower }}", + "x-sdk-version": "{{ sdk.version }}"{% if spec.global.defaultHeaders | length > 0 %},{% endif %} + + {%~ for key,header in spec.global.defaultHeaders %} + "{{key | caseLower }}": "{{header}}"{% if not loop.last %},{% endif %} + + {%~ endfor %} + ] + + internal var config: [String: String] = [:] + + internal var selfSigned: Bool = false + + internal var http: HTTPClient + + + private static let boundaryChars = "abcdefghijklmnopqrstuvwxyz1234567890" + + private static let boundary = randomBoundary() + + private static var eventLoopGroupProvider = HTTPClient.EventLoopGroupProvider.singleton + + // MARK: Methods + + public init() { + http = Client.createHTTP() + addUserAgentHeader() + addOriginHeader() + } + + private static func createHTTP( + selfSigned: Bool = false, + maxRedirects: Int = 5, + alloweRedirectCycles: Bool = false, + connectTimeout: TimeAmount = .seconds(30), + readTimeout: TimeAmount = .seconds(30) + ) -> HTTPClient { + let timeout = HTTPClient.Configuration.Timeout( + connect: connectTimeout, + read: readTimeout + ) + let redirect = HTTPClient.Configuration.RedirectConfiguration.follow( + max: 5, + allowCycles: false + ) + var tls = TLSConfiguration + .makeClientConfiguration() + + if selfSigned { + tls.certificateVerification = .none + } + + return HTTPClient( + eventLoopGroupProvider: eventLoopGroupProvider, + configuration: HTTPClient.Configuration( + tlsConfiguration: tls, + redirectConfiguration: redirect, + timeout: timeout, + decompression: .enabled(limit: .none) + ) + ) + } + + deinit { + do { + try http.syncShutdown() + } catch { + print(error) + } + } + + {%~ for header in spec.global.headers %} + /// + /// Set {{header.key | caseUcfirst}} + /// + {%~ if header.description %} + /// {{header.description}} + /// + {%~ endif %} + /// @param String value + /// + /// @return Client + /// + open func set{{ header.key | caseUcfirst }}(_ value: String) -> Client { + config["{{ header.key | caseLower }}"] = value + _ = addHeader(key: "{{header.name}}", value: value) + return self + } + + {%~ endfor %} + + /// + /// Set self signed + /// + /// @param Bool status + /// + /// @return Client + /// + open func setSelfSigned(_ status: Bool = true) -> Client { + self.selfSigned = status + try! http.syncShutdown() + http = Client.createHTTP(selfSigned: status) + return self + } + + /// + /// Set endpoint + /// + /// @param String endPoint + /// + /// @return Client + /// + open func setEndpoint(_ endPoint: String) -> Client { + self.endPoint = endPoint + + if (self.endPointRealtime == nil && endPoint.starts(with: "http")) { + self.endPointRealtime = endPoint + .replacingOccurrences(of: "http://", with: "ws://") + .replacingOccurrences(of: "https://", with: "wss://") + } + + return self + } + + /// + /// Set realtime endpoint. + /// + /// @param String endPoint + /// + /// @return Client + /// + open func setEndpointRealtime(_ endPoint: String) -> Client { + self.endPointRealtime = endPoint + + return self + } + + /// + /// Add header + /// + /// @param String key + /// @param String value + /// + /// @return Client + /// + open func addHeader(key: String, value: String) -> Client { + self.headers[key] = value + return self + } + + /// + /// Builds a query string from parameters + /// + /// @param Dictionary params + /// @param String prefix + /// + /// @return String + /// + open func parametersToQueryString(params: [String: Any?]) -> String { + var output: String = "" + + func appendWhenNotLast(_ index: Int, ofTotal count: Int, outerIndex: Int? = nil, outerCount: Int? = nil) { + if (index != count - 1 || (outerIndex != nil + && outerCount != nil + && index == count - 1 + && outerIndex! != outerCount! - 1)) { + output += "&" + } + } + + for (parameterIndex, element) in params.enumerated() { + switch element.value { + case nil: + break + case is Array: + let list = element.value as! Array + for (nestedIndex, item) in list.enumerated() { + output += "\(element.key)[]=\(item!)" + appendWhenNotLast(nestedIndex, ofTotal: list.count, outerIndex: parameterIndex, outerCount: params.count) + } + appendWhenNotLast(parameterIndex, ofTotal: params.count) + default: + output += "\(element.key)=\(element.value!)" + appendWhenNotLast(parameterIndex, ofTotal: params.count) + } + } + + return output.addingPercentEncoding( + withAllowedCharacters: .urlHostAllowed + ) ?? "" + } + + /// + /// Make an API call + /// + /// @param String method + /// @param String path + /// @param Dictionary params + /// @param Dictionary headers + /// @return Response + /// @throws Exception + /// + open func call( + method: String, + path: String = "", + headers: [String: String] = [:], + params: [String: Any?] = [:], + sink: ((ByteBuffer) -> Void)? = nil, + converter: ((Any) -> T)? = nil + ) async throws -> T { + let validParams = params.filter { $0.value != nil } + + let queryParameters = method == "GET" && !validParams.isEmpty + ? "?" + parametersToQueryString(params: validParams) + : "" + + var request = HTTPClientRequest(url: endPoint + path + queryParameters) + request.method = .RAW(value: method) + + + for (key, value) in self.headers.merging(headers, uniquingKeysWith: { $1 }) { + request.headers.add(name: key, value: value) + } + + request.addDomainCookies() + + if "GET" == method { + return try await execute(request, converter: converter) + } + + try buildBody(for: &request, with: validParams) + + return try await execute(request, withSink: sink, converter: converter) + } + + private func buildBody( + for request: inout HTTPClientRequest, + with params: [String: Any?] + ) throws { + if request.headers["content-type"][0] == "multipart/form-data" { + buildMultipart(&request, with: params, chunked: !request.headers["content-range"].isEmpty) + } else { + try buildJSON(&request, with: params) + } + } + + private func execute( + _ request: HTTPClientRequest, + withSink bufferSink: ((ByteBuffer) -> Void)? = nil, + converter: ((Any) -> T)? = nil + ) async throws -> T { + let response = try await http.execute( + request, + timeout: .seconds(30) + ) + + switch response.status.code { + case 0..<400: + if response.headers["Set-Cookie"].count > 0 { + let domain = URL(string: request.url)!.host! + let existing = UserDefaults.standard.stringArray(forKey: domain) + let new = response.headers["Set-Cookie"] + + UserDefaults.standard.set(new, forKey: domain) + } + switch T.self { + case is Bool.Type: + return true as! T + case is ByteBuffer.Type: + return try await response.body.collect(upTo: Int.max) as! T + default: + let data = try await response.body.collect(upTo: Int.max) + if data.readableBytes == 0 { + return true as! T + } + let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] + + return converter?(dict!) ?? dict! as! T + } + default: + var message = "" + var data = try await response.body.collect(upTo: Int.max) + var type = "" + + do { + let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] + + message = dict?["message"] as? String ?? response.status.reasonPhrase + type = dict?["type"] as? String ?? "" + } catch { + message = data.readString(length: data.readableBytes)! + } + + throw {{ spec.title | caseUcfirst }}Error( + message: message, + code: Int(response.status.code), + type: type + ) + } + } + + func chunkedUpload( + path: String, + headers: inout [String: String], + params: inout [String: Any?], + paramName: String, + idParamName: String? = nil, + converter: ((Any) -> T)? = nil, + onProgress: ((UploadProgress) -> Void)? = nil + ) async throws -> T { + let input = params[paramName] as! InputFile + + switch(input.sourceType) { + case "path": + input.data = ByteBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: input.path))) + case "data": + input.data = ByteBuffer(data: input.data as! Data) + default: + break + } + + let size = (input.data as! ByteBuffer).readableBytes + + if size < Client.chunkSize { + params[paramName] = input + return try await call( + method: "POST", + path: path, + headers: headers, + params: params, + converter: converter + ) + } + + var offset = 0 + var result = [String:Any]() + + if idParamName != nil && params[idParamName!] as! String != "unique()" { + // Make a request to check if a file already exists + do { + let map = try await call( + method: "GET", + path: path + "/" + (params[idParamName!] as! String), + headers: headers, + params: [:], + converter: { return $0 as! [String: Any] } + ) + let chunksUploaded = map["chunksUploaded"] as! Int + offset = chunksUploaded * Client.chunkSize + } catch { + // File does not exist yet, swallow exception + } + } + + while offset < size { + let slice = (input.data as! ByteBuffer).getSlice(at: offset, length: Client.chunkSize) + ?? (input.data as! ByteBuffer).getSlice(at: offset, length: Int(size - offset)) + + params[paramName] = InputFile.fromBuffer(slice!, filename: input.filename, mimeType: input.mimeType) + headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size - 1))/\(size)" + + result = try await call( + method: "POST", + path: path, + headers: headers, + params: params, + converter: { return $0 as! [String: Any] } + ) + + offset += Client.chunkSize + headers["x-{{ spec.title | caseLower }}-id"] = result["$id"] as? String + onProgress?(UploadProgress( + id: result["$id"] as? String ?? "", + progress: Double(min(offset, size))/Double(size) * 100.0, + sizeUploaded: min(offset, size), + chunksTotal: result["chunksTotal"] as? Int ?? -1, + chunksUploaded: result["chunksUploaded"] as? Int ?? -1 + )) + } + + return converter!(result) + } + + private static func randomBoundary() -> String { + var string = "" + for _ in 0..<16 { + string.append(Client.boundaryChars.randomElement()!) + } + return string + } + + private func buildJSON( + _ request: inout HTTPClientRequest, + with params: [String: Any?] = [:] + ) throws { + var encodedParams = [String:Any]() + + for (key, param) in params { + if param is String + || param is Int + || param is Float + || param is Bool + || param is [String] + || param is [Int] + || param is [Float] + || param is [Bool] + || param is [String: Any] + || param is [Int: Any] + || param is [Float: Any] + || param is [Bool: Any] { + encodedParams[key] = param + } else { + let value = try! (param as! Encodable).toJson() + + let range = value.index(value.startIndex, offsetBy: 1).. String { + #if os(iOS) + return "ios" + #elseif os(watchOS) + return "watchos" + #elseif os(tvOS) + return "tvos" + #elseif os(macOS) + return "macos" + #elseif os(Linux) + return "linux" + #elseif os(Windows) + return "windows" + #endif + } + + private static func getDevice() -> String { + let deviceInfo = OSDeviceInfo() + var device = "" + + #if os(iOS) + let info = deviceInfo.iOSInfo + device = "\(info!.modelIdentifier) iOS/\(info!.systemVersion)" + #elseif os(watchOS) + let info = deviceInfo.watchOSInfo + device = "\(info!.modelIdentifier) watchOS/\(info!.systemVersion)" + #elseif os(tvOS) + let info = deviceInfo.iOSInfo + device = "\(info!.modelIdentifier) tvOS/\(info!.systemVersion)" + #elseif os(macOS) + let info = deviceInfo.macOSInfo + device = "(Macintosh; \(info!.model))" + #elseif os(Linux) + let info = deviceInfo.linuxInfo + device = "(Linux; U; \(info!.id) \(info!.version))" + #elseif os(Windows) + let info = deviceInfo.windowsInfo + device = "(Windows NT; \(info!.computerName))" + #endif + + return device + } +} diff --git a/templates/apple/Sources/Services/Service.swift.twig b/templates/apple/Sources/Services/Service.swift.twig new file mode 100644 index 000000000..234919b48 --- /dev/null +++ b/templates/apple/Sources/Services/Service.swift.twig @@ -0,0 +1,117 @@ +import AsyncHTTPClient +import Foundation +import NIO +import JSONCodable +import {{spec.title | caseUcfirst}}Enums +import {{spec.title | caseUcfirst}}Models + +/// {{ service.description }} +open class {{ service.name | caseUcfirst | overrideIdentifier }}: Service { + + {%~ for method in service.methods %} + /// + /// {{ method.title }} + /// + {%~ if method.description %} + {{~ method.description | swiftComment }} + /// + {%~ endif %} + {%~ for parameter in method.parameters.all %} + /// @param {{ parameter | typeName(spec) | raw}} {{ parameter.name | caseCamel }} + {%~ endfor %} + /// @throws Exception + /// @return array + /// + {%~ if method.type == "webAuth" %} + @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + {%~ endif %} + open func {{ method.name | caseCamel | overrideIdentifier }}{% if method.responseModel | hasGenericType(spec) %}{% endif %}( + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter | typeName(spec) | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes or method.responseModel | hasGenericType(spec) %},{% endif %} + + {%~ endfor %} + {%~ if method.responseModel | hasGenericType(spec) %} + nestedType: T.Type + {%~ endif %} + {%~ if 'multipart/form-data' in method.consumes %} + onProgress: ((UploadProgress) -> Void)? = nil + {%~ endif %} + ) async throws -> {{ method | returnType(spec) | raw }} { + {{~ include('swift/base/params.twig') }} + {%~ if method.type == 'webAuth' %} + {{~ include('apple/base/requests/oauth.twig') }} + {%~ elseif method.type == 'location' %} + {{~ include('swift/base/requests/location.twig')}} + {%~ else %} + {%~ if method.headers | length <= 0 %} + let apiHeaders: [String: String] = [:] + {%~ else %} + {% if 'multipart/form-data' in method.consumes -%} var + {%- else -%} let + {%- endif %} apiHeaders: [String: String] = [ + {%~ for key, header in method.headers %} + "{{ key }}": "{{ header }}"{% if not loop.last %},{% endif %} + + {%~ endfor %} + ] + {%~ endif %} + + {%~ if method.responseModel %} + let converter: (Any) -> {{ method | returnType(spec) | raw }} = { response in + {%~ if method.responseModel == 'any' %} + return response + {%~ else %} + return {{ spec.title | caseUcfirst}}Models.{{method.responseModel | caseUcfirst}}.from(map: response as! [String: Any]) + {%~ endif %} + } + + {%~ endif %} + {%~ if 'multipart/form-data' in method.consumes %} + {{~ include('swift/base/requests/file.twig') }} + {%~ else %} + {{~ include('swift/base/requests/api.twig') }} + {%~ endif %} + {%~ endif %} + } + {%~ if method.responseModel | hasGenericType(spec) %} + + /// + /// {{ method.title }} + /// + {%~ if method.description %} + {{~ method.description | swiftComment }} + /// + {%~ endif %} + {%~ for parameter in method.parameters.all %} + /// @param {{ parameter | typeName(spec) | raw}} {{ parameter.name | caseCamel }} + {%~ endfor %} + /// @throws Exception + /// @return array + /// + {%~ if method.type == "webAuth" %} + @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) + {%~ endif %} + open func {{ method.name | caseCamel }}( + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter | typeName(spec) | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes %},{% endif %} + + {%~ endfor %} + {%~ if 'multipart/form-data' in method.consumes %} + onProgress: ((UploadProgress) -> Void)? = nil + {%~ endif %} + ) async throws -> {{ method | returnType(spec, '[String: AnyCodable]') | raw }} { + return try await {{ method.name | caseCamel }}( + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter.name | caseCamel | escapeSwiftKeyword }}, + {%~ endfor %} + nestedType: [String: AnyCodable].self + {%~ if 'multipart/form-data' in method.consumes %} + onProgress: onProgress + {%~ endif %} + ) + } + {%~ endif %} + +{% endfor %} + +} \ No newline at end of file diff --git a/templates/apple/base/requests/oauth.twig b/templates/apple/base/requests/oauth.twig new file mode 100644 index 000000000..ede718d7e --- /dev/null +++ b/templates/apple/base/requests/oauth.twig @@ -0,0 +1,11 @@ + let query = "?\(client.parametersToQueryString(params: apiParams))" + let url = URL(string: client.endPoint + apiPath + query)! + let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")" + + _ = try await withCheckedThrowingContinuation { continuation in + WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in + continuation.resume(with: result) + } + } + + return true \ No newline at end of file diff --git a/templates/cli/.github/workflows/npm-publish.yml b/templates/cli/.github/workflows/npm-publish.yml index d4184aadc..22d670eae 100644 --- a/templates/cli/.github/workflows/npm-publish.yml +++ b/templates/cli/.github/workflows/npm-publish.yml @@ -32,7 +32,7 @@ jobs: npm run mac-arm64 - name: Publish NPM library run: | - if ${{ contains(github.event.release.tag_name, '-RC') }}; then + if ${{ contains(github.event.release.tag_name, '-rc') }}; then echo "Publishing Release Candidate ${{ github.event.release.tag_name}} to NPM" npm publish --tag next else diff --git a/templates/cli/lib/commands/deploy.js.twig b/templates/cli/lib/commands/deploy.js.twig index 420bd3f85..bd6c46ecf 100644 --- a/templates/cli/lib/commands/deploy.js.twig +++ b/templates/cli/lib/commands/deploy.js.twig @@ -52,7 +52,7 @@ const awaitPools = { const { total } = await databasesListAttributes({ databaseId, collectionId, - queries: ['limit(1)'], + queries: [JSON.stringify({ method: 'limit', values: [1] })], parseOutput: false }); @@ -83,7 +83,7 @@ const awaitPools = { const { total } = await databasesListIndexes({ databaseId, collectionId, - queries: ['limit(100)'], + queries: [JSON.stringify({ method: 'limit', values: [1] })], parseOutput: false }); @@ -302,7 +302,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { const { total } = await functionsListVariables({ functionId: func['$id'], - queries: ['limit(1)'], + queries: [JSON.stringify({ method: 'limit', values: [1] })], parseOutput: false }); diff --git a/templates/cli/lib/paginate.js.twig b/templates/cli/lib/paginate.js.twig index 309e2ef0b..8c57fa731 100644 --- a/templates/cli/lib/paginate.js.twig +++ b/templates/cli/lib/paginate.js.twig @@ -10,8 +10,8 @@ const paginate = async (action, args = {}, limit = 100, wrapper = '') => { const response = await action({ ...args, queries: [ - `limit(${limit})`, - `offset(${offset})` + JSON.stringify({ method: 'limit', values: [limit] }), + JSON.stringify({ method: 'offset', values: [offset] }) ] }); diff --git a/templates/cli/lib/parser.js.twig b/templates/cli/lib/parser.js.twig index ed6ba8d8c..60b3e9c4d 100644 --- a/templates/cli/lib/parser.js.twig +++ b/templates/cli/lib/parser.js.twig @@ -170,6 +170,7 @@ const commandDescriptions = { "logout": `The logout command allows you to logout of your {{ spec.title|caseUcfirst }} account.`, "console" : `The console command allows gives you access to the APIs used by the Appwrite console.`, "assistant": `The assistant command allows you to interact with the Appwrite Assistant AI`, + "messaging": `The messaging command allows you to send messages.`, "migrations": `The migrations command allows you to migrate data between services.`, "project": `The project command is for overall project administration.`, "proxy": `The proxy command allows you to configure behavior for your attached domains.`, diff --git a/templates/dart/docs/example.md.twig b/templates/dart/docs/example.md.twig index fd2b0fded..dd8006caf 100644 --- a/templates/dart/docs/example.md.twig +++ b/templates/dart/docs/example.md.twig @@ -3,35 +3,23 @@ import 'dart:io'; {% endif %} import 'package:{{ language.params.packageName }}/{{ language.params.packageName }}.dart'; -void main() { // Init SDK - Client client = Client(); - {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = {{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample }}{% endfor %}{% endif %}); - -{% if method.auth|length > 0 %} - client +Client client = Client() + {%~ if method.auth|length > 0 %} .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %} ; + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last %};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} -{% endif %} - Future result = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} +{{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = {{service.name | caseUcfirst}}(client); -{% endif %} -{% if parameter.required %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, -{% else %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, // optional -{% endif %} -{% endfor %}{% if method.parameters.all | length > 0 %} {% endif %}); +{% if method.method != 'delete' and method.type != 'webAuth' %}{% if method.type == 'location' %}UInt8List{% else %}{{ method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} + + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | overrideIdentifier }}: {% if parameter.enumValues | length > 0 %}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseCamel | replace({'-': ''}) }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}{% endif %},{% if not parameter.required %} // (optional){% endif %} - result - .then((response) { - print(response); - }).catchError((error) { - print(error.response); - }); -} \ No newline at end of file + {%~ endfor %} +{% if method.parameters.all | length > 0 %}); +{% endif %} \ No newline at end of file diff --git a/templates/dart/lib/enums.dart.twig b/templates/dart/lib/enums.dart.twig new file mode 100644 index 000000000..76589d06d --- /dev/null +++ b/templates/dart/lib/enums.dart.twig @@ -0,0 +1,6 @@ +/// {{spec.title | caseUcfirst}} Enums +library {{ language.params.packageName }}.enums; + +{% for enum in spec.enums %} +part 'src/enums/{{enum.name | caseSnake}}.dart'; +{% endfor %} \ No newline at end of file diff --git a/templates/dart/lib/package.dart.twig b/templates/dart/lib/package.dart.twig index 2ff4077c0..9f57a7511 100644 --- a/templates/dart/lib/package.dart.twig +++ b/templates/dart/lib/package.dart.twig @@ -7,12 +7,14 @@ library {{ language.params.packageName }}; import 'dart:async'; import 'dart:typed_data'; +import 'dart:convert'; import 'src/enums.dart'; import 'src/service.dart'; import 'src/input_file.dart'; import 'src/upload_progress.dart'; import 'models.dart' as models; +import 'enums.dart' as enums; export 'src/response.dart'; export 'src/client.dart'; diff --git a/templates/dart/lib/query.dart.twig b/templates/dart/lib/query.dart.twig index a5763d79d..4fe939e8c 100644 --- a/templates/dart/lib/query.dart.twig +++ b/templates/dart/lib/query.dart.twig @@ -1,8 +1,32 @@ part of {{ language.params.packageName }}; + /// Helper class to generate query strings. class Query { - Query._(); + final String method; + final String? attribute; + final dynamic values; + + Query._(this.method, [this.attribute = null, this.values = null]); + + Map toJson() { + final map = { + 'method': method, + }; + + if(attribute != null) { + map['attribute'] = attribute; + } + + if(values != null) { + map['values'] = values is List ? values : [values]; + } + + return map; + } + + @override + String toString() => jsonEncode(toJson()); /// Filter resources where [attribute] is equal to [value]. /// @@ -10,90 +34,90 @@ class Query { /// the query will return resources where [attribute] is equal /// to any of the values in the list. static String equal(String attribute, dynamic value) => - _addQuery(attribute, 'equal', value); + Query._('equal', attribute, value).toString(); /// Filter resources where [attribute] is not equal to [value]. - /// - /// [value] can be a single value or a list. If a list is used - /// the query will return resources where [attribute] is equal - /// to any of the values in the list. static String notEqual(String attribute, dynamic value) => - _addQuery(attribute, 'notEqual', value); + Query._('notEqual', attribute, [value]).toString(); /// Filter resources where [attribute] is less than [value]. static String lessThan(String attribute, dynamic value) => - _addQuery(attribute, 'lessThan', value); + Query._('lessThan', attribute, value).toString(); /// Filter resources where [attribute] is less than or equal to [value]. static String lessThanEqual(String attribute, dynamic value) => - _addQuery(attribute, 'lessThanEqual', value); + Query._('lessThanEqual', attribute, value).toString(); /// Filter resources where [attribute] is greater than [value]. static String greaterThan(String attribute, dynamic value) => - _addQuery(attribute, 'greaterThan', value); + Query._('greaterThan', attribute, value).toString(); /// Filter resources where [attribute] is greater than or equal to [value]. static String greaterThanEqual(String attribute, dynamic value) => - _addQuery(attribute, 'greaterThanEqual', value); + Query._('greaterThanEqual', attribute, value).toString(); /// Filter resources where by searching [attribute] for [value]. static String search(String attribute, String value) => - _addQuery(attribute, 'search', value); + Query._('search', attribute, value).toString(); /// Filter resources where [attribute] is null. - static String isNull(String attribute) => 'isNull("$attribute")'; + static String isNull(String attribute) => Query._('isNull', attribute).toString(); /// Filter resources where [attribute] is not null. - static String isNotNull(String attribute) => 'isNotNull("$attribute")'; + static String isNotNull(String attribute) => Query._('isNotNull', attribute).toString(); /// Filter resources where [attribute] is between [start] and [end] (inclusive). static String between(String attribute, dynamic start, dynamic end) => - 'between("$attribute", ${_parseValues(start)}, ${_parseValues(end)})'; + Query._('between', attribute, [start, end]).toString(); /// Filter resources where [attribute] starts with [value]. static String startsWith(String attribute, String value) => - _addQuery(attribute, 'startsWith', value); + Query._('startsWith', attribute, value).toString(); /// Filter resources where [attribute] ends with [value]. static String endsWith(String attribute, String value) => - _addQuery(attribute, 'endsWith', value); + Query._('endsWith', attribute, value).toString(); + + /// Filter resources where [attribute] contains [value] + /// [value] can be a single value or a list. + static String contains(String attribute, dynamic value) => + Query._('contains', attribute, value).toString(); + + static String or(List queries) => + Query._('or', null, queries.map((query) => jsonDecode(query)).toList()).toString(); + + static String and(List queries) => + Query._('and', null, queries.map((query) => jsonDecode(query)).toList()).toString(); /// Specify which attributes should be returned by the API call. static String select(List attributes) => - 'select([${attributes.map((attr) => "\"$attr\"").join(",")}])'; + Query._('select', null, attributes).toString(); /// Sort results by [attribute] ascending. - static String orderAsc(String attribute) => 'orderAsc("$attribute")'; + static String orderAsc(String attribute) => Query._('orderAsc', attribute).toString(); /// Sort results by [attribute] descending. - static String orderDesc(String attribute) => 'orderDesc("$attribute")'; + static String orderDesc(String attribute) => Query._('orderDesc', attribute).toString(); /// Return results before [id]. /// /// Refer to the [Cursor Based Pagination]({{sdk.url}}/docs/pagination#cursor-pagination) /// docs for more information. - static String cursorBefore(String id) => 'cursorBefore("$id")'; + static String cursorBefore(String id) => Query._('cursorBefore', null, id).toString(); /// Return results after [id]. /// /// Refer to the [Cursor Based Pagination]({{sdk.url}}/docs/pagination#cursor-pagination) /// docs for more information. - static String cursorAfter(String id) => 'cursorAfter("$id")'; + static String cursorAfter(String id) => Query._('cursorAfter', null, id).toString(); /// Return only [limit] results. - static String limit(int limit) => 'limit($limit)'; + static String limit(int limit) => Query._('limit', null, limit).toString(); /// Return results from [offset]. /// /// Refer to the [Offset Pagination]({{sdk.url}}/docs/pagination#offset-pagination) /// docs for more information. - static String offset(int offset) => 'offset($offset)'; - - static String _addQuery(String attribute, String method, dynamic value) => (value - is List) - ? '$method("$attribute", [${value.map((item) => _parseValues(item)).join(",")}])' - : '$method("$attribute", [${_parseValues(value)}])'; + static String offset(int offset) => Query._('offset', null, offset).toString(); - static String _parseValues(dynamic value) => - (value is String) ? '"$value"' : '$value'; } \ No newline at end of file diff --git a/templates/dart/lib/services/service.dart.twig b/templates/dart/lib/services/service.dart.twig index 71cba9ccc..0b522738a 100644 --- a/templates/dart/lib/services/service.dart.twig +++ b/templates/dart/lib/services/service.dart.twig @@ -19,13 +19,15 @@ class {{ service.name | caseUcfirst }} extends Service { {%~ if method.description %} {{ method.description | dartComment }} {% endif %} - {% if method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async { - final String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %}; + {% if method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel | overrideIdentifier }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async { + final String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}{% if parameter.enumValues | length > 0 %}.value{% endif %}){% endfor %}; {% if 'multipart/form-data' in method.consumes %} {{ include('dart/base/requests/file.twig') }} {% elseif method.type == 'location' %} {{ include('dart/base/requests/location.twig') }} +{% elseif method.type == 'webAuth' %} +{{ include('dart/base/requests/oauth.twig') }} {% else %} {{ include('dart/base/requests/api.twig') }} {% endif %} diff --git a/templates/dart/lib/src/client.dart.twig b/templates/dart/lib/src/client.dart.twig index 6376d9922..5e386223f 100644 --- a/templates/dart/lib/src/client.dart.twig +++ b/templates/dart/lib/src/client.dart.twig @@ -23,6 +23,9 @@ abstract class Client { bool selfSigned = false}) => createClient(endPoint: endPoint, selfSigned: selfSigned); + /// Handle OAuth2 session creation. + Future webAuth(Uri url); + /// Set self signed to [status]. /// /// If self signed is true, [Client] will ignore invalid certificates. diff --git a/templates/dart/lib/src/client_browser.dart.twig b/templates/dart/lib/src/client_browser.dart.twig index c386af787..494e9978b 100644 --- a/templates/dart/lib/src/client_browser.dart.twig +++ b/templates/dart/lib/src/client_browser.dart.twig @@ -77,6 +77,14 @@ class ClientBrowser extends ClientBase with ClientMixin { return this; } + @override + Future webAuth(Uri url) async { + final request = http.Request('GET', url); + request.followRedirects = false; + final response = await _httpClient.send(request); + return response.headers['location']; + } + @override Future chunkedUpload({ required String path, diff --git a/templates/dart/lib/src/client_io.dart.twig b/templates/dart/lib/src/client_io.dart.twig index bcacd2895..8a51e5979 100644 --- a/templates/dart/lib/src/client_io.dart.twig +++ b/templates/dart/lib/src/client_io.dart.twig @@ -185,6 +185,14 @@ class ClientIO extends ClientBase with ClientMixin { return res; } + @override + Future webAuth(Uri url) async { + final request = http.Request('GET', url); + request.followRedirects = false; + final response = await _httpClient.send(request); + return response.headers['location']; + } + @override Future call( HttpMethod method, { diff --git a/templates/dart/lib/src/enums/enum.dart.twig b/templates/dart/lib/src/enums/enum.dart.twig new file mode 100644 index 000000000..10ab94833 --- /dev/null +++ b/templates/dart/lib/src/enums/enum.dart.twig @@ -0,0 +1,17 @@ +part of {{ language.params.packageName }}.enums; + +enum {{ enum.name | caseUcfirst | overrideIdentifier }} { + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseEnumKey }}(value: '{{ value }}'){% if not loop.last %},{% else %};{% endif %} + + {%~ endfor %} + + const {{ enum.name | caseUcfirst | overrideIdentifier }}({ + required this.value + }); + + final String value; + + String toJson() => value; +} \ No newline at end of file diff --git a/templates/dart/test/query_test.dart.twig b/templates/dart/test/query_test.dart.twig index 9ed2f12e4..cbe838079 100644 --- a/templates/dart/test/query_test.dart.twig +++ b/templates/dart/test/query_test.dart.twig @@ -23,42 +23,42 @@ void main() { BasicFilterQueryTest( description: 'with a string', value: 's', - expectedValues: '["s"]', + expectedValues: ["s"], ), BasicFilterQueryTest( description: 'with an integer', value: 1, - expectedValues: '[1]', + expectedValues: [1], ), BasicFilterQueryTest( description: 'with a double', value: 1.2, - expectedValues: '[1.2]', + expectedValues: [1.2], ), BasicFilterQueryTest( description: 'with a whole number double', value: 1.0, - expectedValues: '[1.0]', + expectedValues: [1.0], ), BasicFilterQueryTest( description: 'with a bool', value: false, - expectedValues: '[false]', + expectedValues: [false], ), BasicFilterQueryTest( description: 'with a list', value: ['a', 'b', 'c'], - expectedValues: '["a","b","c"]', + expectedValues: ["a","b","c"], ), ]; group('equal()', () { for (var t in tests) { test(t.description, () { - expect( - Query.equal('attr', t.value), - 'equal("attr", ${t.expectedValues})', - ); + final query = Query.equal('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'equal'); }); } }); @@ -66,10 +66,10 @@ void main() { group('notEqual()', () { for (var t in tests) { test(t.description, () { - expect( - Query.notEqual('attr', t.value), - 'notEqual("attr", ${t.expectedValues})', - ); + final query = Query.notEqual('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'notEqual'); }); } }); @@ -77,10 +77,10 @@ void main() { group('lessThan()', () { for (var t in tests) { test(t.description, () { - expect( - Query.lessThan('attr', t.value), - 'lessThan("attr", ${t.expectedValues})', - ); + final query = Query.lessThan('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'lessThan'); }); } }); @@ -88,10 +88,10 @@ void main() { group('lessThanEqual()', () { for (var t in tests) { test(t.description, () { - expect( - Query.lessThanEqual('attr', t.value), - 'lessThanEqual("attr", ${t.expectedValues})', - ); + final query = Query.lessThanEqual('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'lessThanEqual'); }); } }); @@ -99,10 +99,10 @@ void main() { group('greaterThan()', () { for (var t in tests) { test(t.description, () { - expect( - Query.greaterThan('attr', t.value), - 'greaterThan("attr", ${t.expectedValues})', - ); + final query = Query.greaterThan('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'greaterThan'); }); } }); @@ -110,87 +110,106 @@ void main() { group('greaterThanEqual()', () { for (var t in tests) { test(t.description, () { - expect( - Query.greaterThanEqual('attr', t.value), - 'greaterThanEqual("attr", ${t.expectedValues})', - ); + final query = Query.greaterThanEqual('attr', t.value).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], t.expectedValues); + expect(query['method'], 'greaterThanEqual'); }); } }); }); - group('search()', () { - test('returns search', () { - expect(Query.search('attr', 'keyword1 keyword2'), 'search("attr", ["keyword1 keyword2"])'); - }); + test('returns search', () { + final query = Query.search('attr', 'keyword1 keyword2').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], ['keyword1 keyword2']); + expect(query['method'], 'search'); }); - group('isNull()', () { - test('returns isNull', () { - expect(Query.isNull('attr'), 'isNull("attr")'); - }); + test('returns isNull', () { + final query = Query.isNull('attr').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], null); + expect(query['method'], 'isNull'); }); - group('isNotNull()', () { - test('returns isNotNull', () { - expect(Query.isNotNull('attr'), 'isNotNull("attr")'); - }); + test('returns isNotNull', () { + final query = Query.isNotNull('attr', 'keyword1 keyword2').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], null); + expect(query['method'], 'isNotNull'); }); group('between()', () { test('with integers', () { - expect(Query.between('attr', 1, 2), 'between("attr", [1,2])'); + final query = Query.between('attr', 1, 2).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], [1, 2]); + expect(query['method'], 'between'); }); test('with doubles', () { - expect(Query.between('attr', 1.0, 2.0), 'between("attr", [1.0,2.0])'); + final query = Query.between('attr', 1.0, 2.0).toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], [1.0, 2.0]); + expect(query['method'], 'between'); }); test('with strings', () { - expect(Query.between('attr', "a", "z"), 'between("attr", ["a","z"])'); + final query = Query.between('attr', 'a', 'z').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], ['a', 'z']); + expect(query['method'], 'between'); }); }); - group('select()', () { - test('returns select', () { - expect(Query.select(['attr1', 'attr2']), 'select(["attr1","attr2"])'); - }); + test('returns select', () { + final query = Query.select(['attr1', 'attr2']).toJson(); + expect(query['attribute'], null); + expect(query['values'], ['attr1', 'attr2']); + expect(query['method'], 'select'); }); - group('orderAsc()', () { - test('returns orderAsc', () { - expect(Query.orderAsc('attr'), 'orderAsc("attr")'); - }); + test('returns orderAsc', () { + final query = Query.orderAsc('attr').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], null); + expect(query['method'], 'orderAsc'); }); - group('orderDesc()', () { - test('returns orderDesc', () { - expect(Query.orderDesc('attr'), 'orderDesc("attr")'); - }); + test('returns orderDesc', () { + final query = Query.orderDesc('attr').toJson(); + expect(query['attribute'], 'attr'); + expect(query['values'], null); + expect(query['method'], 'orderDesc'); }); - group('cursorBefore()', () { - test('returns cursorBefore', () { - expect(Query.cursorBefore(ID.custom('custom')), 'cursorBefore("custom")'); - }); + test('returns cursorBefore', () { + final query = Query.cursorBefore('custom').toJson(); + expect(query['attribute'], null); + expect(query['values'], 'custom'); + expect(query['method'], 'cursorBefore'); }); - group('cursorAfter()', () { - test('returns cursorAfter', () { - expect(Query.cursorAfter(ID.custom('custom')), 'cursorAfter("custom")'); - }); + test('returns cursorAfter', () { + final query = Query.cursorAfter('custom').toJson(); + expect(query['attribute'], null); + expect(query['values'], 'custom'); + expect(query['method'], 'cursorAfter'); }); - group('limit()', () { - test('returns limit', () { - expect(Query.limit(1), 'limit(1)'); - }); + test('returns limit', () { + final query = Query.limit(1).toJson(); + expect(query['attribute'], null); + expect(query['values'], 1); + expect(query['method'], 'limit'); }); - group('offset()', () { - test('returns offset', () { - expect(Query.offset(1), 'offset(1)'); - }); + test('returns offset', () { + final query = Query.offset(1).toJson(); + expect(query['attribute'], null); + expect(query['values'], 1); + expect(query['method'], 'offset'); }); } diff --git a/templates/deno/docs/example.md.twig b/templates/deno/docs/example.md.twig index 868d9f81e..1250f883d 100644 --- a/templates/deno/docs/example.md.twig +++ b/templates/deno/docs/example.md.twig @@ -1,35 +1,23 @@ -import * as sdk from "https://deno.land/x/{{ spec.title | caseDash }}/mod.ts"; -{# {% if method.consumes[0] == 'multipart/form-data' %} -const fs = require('fs'); -{% endif %} #} +import { Client, {{ service.name | caseUcfirst }}{% for parameter in method.parameters.all %}{% if parameter.enumValues | length > 0%}, {{ parameter.enumName | caseUcfirst}}{% endif %}{% endfor %} } from "https://deno.land/x/{{ spec.title | caseDash }}/mod.ts"; -// Init SDK -let client = new sdk.Client(); - -let {{ service.name | caseCamel }} = new sdk.{{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); - -{% if method.auth|length > 0 %} -client +const client = new Client() + {%~ if method.auth|length > 0 %} .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %}; -{% endif %} + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last %};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} + +const {{ service.name | caseCamel }} = new {{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); -const promise = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} +{% if method.type == 'location' %}const result = {% elseif method.type != 'webAuth' %}const response = await {% endif -%} +{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} - {% endif %}{% if parameter.required %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} - - {% else %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} (optional) - {% endif %}{% endfor %}); +{%~ for parameter in method.parameters.all %} + {% if parameter.enumValues | length > 0 %}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else%}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %} // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} -promise.then(function (response) { - console.log(response); -}, function (error) { - console.log(error); -}); \ No newline at end of file +{%~ endfor %} +{% if method.parameters.all | length > 0 %}); +{% endif %} \ No newline at end of file diff --git a/templates/deno/mod.ts.twig b/templates/deno/mod.ts.twig index 2878b6360..ecebed34d 100644 --- a/templates/deno/mod.ts.twig +++ b/templates/deno/mod.ts.twig @@ -8,6 +8,9 @@ import { {{spec.title | caseUcfirst}}Exception } from "./src/exception.ts"; {% for service in spec.services %} import { {{service.name | caseUcfirst}} } from "./src/services/{{service.name | caseDash}}.ts"; {% endfor %} +{% for enum in spec.enums %} +import { {{enum.name | caseUcfirst}} } from "./src/enums/{{enum.name | caseDash}}.ts"; +{% endfor %} export { Client, @@ -20,6 +23,9 @@ export { {% for service in spec.services %} {{service.name | caseUcfirst}}, {% endfor %} +{% for enum in spec.enums %} + {{enum.name | caseUcfirst}}, +{% endfor %} }; export type { Models } from "./src/models.d.ts"; diff --git a/templates/deno/src/client.ts.twig b/templates/deno/src/client.ts.twig index dc899958a..bfa2940cc 100644 --- a/templates/deno/src/client.ts.twig +++ b/templates/deno/src/client.ts.twig @@ -61,80 +61,83 @@ export class Client { return this; } - withoutHeader(key: string, headers: Payload): Payload { - return Object.keys(headers).reduce((acc: Payload, cv: string) => { - if (cv === 'content-type') return acc; - acc[cv] = headers[cv]; - return acc; - }, {}) - } + async call(method: string, path: string = "", headers: Payload = {}, params: Payload = {}, responseType: string = "json") { + headers = {...this.headers, ...headers}; + const url = new URL(this.endpoint + path); - async call(method: string, path: string = '', headers: Payload = {}, params: Payload = {}) { - headers = Object.assign({}, this.headers, headers); + let body: string | FormData | undefined = undefined; - let body; - const url = new URL(this.endpoint + path); - if (method.toUpperCase() === 'GET') { - url.search = new URLSearchParams(this.flatten(params)).toString(); - body = null; - } else if (headers['content-type'].toLowerCase().startsWith('multipart/form-data')) { - headers = this.withoutHeader('content-type', headers); + if (method.toUpperCase() === "GET") { + url.search = new URLSearchParams(Client.flatten(params)).toString(); + } else if (headers["content-type"]?.toLowerCase().startsWith("multipart/form-data")) { + delete headers["content-type"]; const formData = new FormData(); - const flatParams = this.flatten(params); - for (const key in flatParams) { - const value = flatParams[key]; - if(value && value.type && value.type === 'file') { + const flatParams = Client.flatten(params); + + for (const [key, value] of Object.entries(flatParams)) { + if (value && value.type && value.type === "file") { formData.append(key, value.file, value.filename); } else { - formData.append(key, flatParams[key]); + formData.append(key, value); } } + body = formData; } else { body = JSON.stringify(params); } - const options = { - method: method.toUpperCase(), - headers: headers, - body: body, - }; - + let response = undefined; try { - let response = await fetch(url.toString(), options); - const contentType = response.headers.get('content-type'); - - if (contentType && contentType.includes('application/json')) { - if (response.status >= 400) { - let res = await response.json(); - throw new {{ spec.title | caseUcfirst}}Exception(res.message, res.status, res.type ?? "", res); - } + response = await fetch(url.toString(), { + redirect: responseType === "location" ? "manual" : "follow", + method: method.toUpperCase(), + headers, + body + }); + } catch (error) { + throw new {{spec.title | caseUcfirst}}Exception(error.message); + } - return response.json(); - } else { - if (response.status >= 400) { - let res = await response.text(); - throw new {{ spec.title | caseUcfirst}}Exception(res, response.status, "", null); - } - return response; + if (response.status >= 400) { + const text = await response.text(); + let json = undefined; + try { + json = JSON.parse(text); + } catch (error) { + throw new {{spec.title | caseUcfirst}}Exception(text, response.status, "", text); } - } catch(error) { - throw new {{ spec.title | caseUcfirst}}Exception(error?.response?.message || error.message, error?.response?.code, error?.response?.type, error.response); + throw new {{spec.title | caseUcfirst}}Exception(json.message, json.code, json.type, json); } + + if (responseType === "arraybuffer") { + const data = await response.arrayBuffer(); + return data; + } + + if (responseType === "location") { + return response.headers.get("location"); + } + + const text = await response.text(); + let json = undefined; + try { + json = JSON.parse(text); + } catch (error) { + return text; + } + return json; } - flatten(data: Payload, prefix = '') { + static flatten(data: Payload, prefix = ''): Payload { let output: Payload = {}; - for (const key in data) { - let value = data[key]; + for (const [key, value] of Object.entries(data)) { let finalKey = prefix ? prefix + '[' + key +']' : key; - if (Array.isArray(value)) { - output = { ...output, ...this.flatten(value, finalKey) }; // @todo: handle name collision here if needed - } - else { + output = { ...output, ...Client.flatten(value, finalKey) }; + } else { output[finalKey] = value; } } diff --git a/templates/deno/src/enums/enum.ts.twig b/templates/deno/src/enums/enum.ts.twig new file mode 100644 index 000000000..34724aba0 --- /dev/null +++ b/templates/deno/src/enums/enum.ts.twig @@ -0,0 +1,6 @@ +export enum {{ enum.name | caseUcfirst | overrideIdentifier }} { + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseUcfirst | replace({'-': ''}) }} = '{{ value }}', + {%~ endfor %} +} \ No newline at end of file diff --git a/templates/deno/src/inputFile.ts.twig b/templates/deno/src/inputFile.ts.twig index fdda97d4c..b12230284 100644 --- a/templates/deno/src/inputFile.ts.twig +++ b/templates/deno/src/inputFile.ts.twig @@ -19,6 +19,12 @@ export class InputFile { return new InputFile(stream, filename, size); }; + static fromBlob = async (blob: Blob, filename: string) => { + const arrayBuffer = await blob.arrayBuffer(); + const buffer = new Uint8Array(arrayBuffer); + return InputFile.fromBuffer(buffer, filename); + }; + static fromBuffer = (buffer: Uint8Array, filename: string): InputFile => { const stream = _bufferToString(buffer); const size = buffer.byteLength; diff --git a/templates/deno/src/query.ts.twig b/templates/deno/src/query.ts.twig index ce87185fb..98930a205 100644 --- a/templates/deno/src/query.ts.twig +++ b/templates/deno/src/query.ts.twig @@ -1,74 +1,104 @@ type QueryTypesSingle = string | number | boolean; -export type QueryTypesList = string[] | number[] | boolean[]; +export type QueryTypesList = string[] | number[] | boolean[] | Query[]; export type QueryTypes = QueryTypesSingle | QueryTypesList; +type AttributesTypes = string | string[]; export class Query { + method: string; + attribute: AttributesTypes | undefined; + values: QueryTypesList | undefined; + + constructor( + method: string, + attribute?: AttributesTypes, + values?: QueryTypes + ) { + this.method = method; + this.attribute = attribute; + + if (values !== undefined) { + if (Array.isArray(values)) { + this.values = values; + } else { + this.values = [values] as QueryTypesList; + } + } + } + + toString(): string { + return JSON.stringify({ + method: this.method, + attribute: this.attribute, + values: this.values, + }); + } + static equal = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "equal", value); + new Query("equal", attribute, value).toString(); static notEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "notEqual", value); + new Query("notEqual", attribute, value).toString(); static lessThan = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "lessThan", value); + new Query("lessThan", attribute, value).toString(); static lessThanEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "lessThanEqual", value); + new Query("lessThanEqual", attribute, value).toString(); static greaterThan = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "greaterThan", value); + new Query("greaterThan", attribute, value).toString(); static greaterThanEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "greaterThanEqual", value); - - static search = (attribute: string, value: string): string => - Query.addQuery(attribute, "search", value); + new Query("greaterThanEqual", attribute, value).toString(); static isNull = (attribute: string): string => - `isNull("${attribute}")`; + new Query("isNull", attribute).toString(); static isNotNull = (attribute: string): string => - `isNotNull("${attribute}")`; + new Query("isNotNull", attribute).toString(); - static between = (attribute: string, start: string|number, end: string|number): string => - `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})`; + static between = ( + attribute: string, + start: string | number, + end: string | number + ) => new Query("between", attribute, [start, end] as QueryTypesList).toString(); static startsWith = (attribute: string, value: string): string => - Query.addQuery(attribute, "startsWith", value); + new Query("startsWith", attribute, value).toString(); static endsWith = (attribute: string, value: string): string => - Query.addQuery(attribute, "endsWith", value); + new Query("endsWith", attribute, value).toString(); static select = (attributes: string[]): string => - `select([${attributes.map((attr: string) => `"${attr}"`).join(",")}])`; + new Query("select", undefined, attributes).toString(); + + static search = (attribute: string, value: string): string => + new Query("search", attribute, value).toString(); static orderDesc = (attribute: string): string => - `orderDesc("${attribute}")`; + new Query("orderDesc", attribute).toString(); static orderAsc = (attribute: string): string => - `orderAsc("${attribute}")`; + new Query("orderAsc", attribute).toString(); static cursorAfter = (documentId: string): string => - `cursorAfter("${documentId}")`; + new Query("cursorAfter", undefined, documentId).toString(); static cursorBefore = (documentId: string): string => - `cursorBefore("${documentId}")`; + new Query("cursorBefore", undefined, documentId).toString(); static limit = (limit: number): string => - `limit(${limit})`; + new Query("limit", undefined, limit).toString(); static offset = (offset: number): string => - `offset(${offset})`; - - private static addQuery = (attribute: string, method: string, value: QueryTypes): string => - value instanceof Array - ? `${method}("${attribute}", [${value - .map((v: QueryTypesSingle) => Query.parseValues(v)) - .join(",")}])` - : `${method}("${attribute}", [${Query.parseValues(value)}])`; - - private static parseValues = (value: QueryTypes): string => - typeof value === "string" || value instanceof String - ? `"${value}"` - : `${value}`; -} \ No newline at end of file + new Query("offset", undefined, offset).toString(); + + static contains = (attribute: string, value: string | string[]): string => + new Query("contains", attribute, value).toString(); + + static or = (queries: string[]) => + new Query("or", undefined, queries.map((query) => JSON.parse(query))).toString(); + + static and = (queries: string[]) => + new Query("and", undefined, queries.map((query) => JSON.parse(query))).toString(); +} diff --git a/templates/deno/src/services/service.ts.twig b/templates/deno/src/services/service.ts.twig index dbaecfca6..6bb5fe5a5 100644 --- a/templates/deno/src/services/service.ts.twig +++ b/templates/deno/src/services/service.ts.twig @@ -31,6 +31,23 @@ import { Payload, Client } from '../client.ts'; import { InputFile } from '../inputFile.ts'; import { AppwriteException } from '../exception.ts'; import type { Models } from '../models.d.ts'; +import { Query } from '../query.ts'; +{% set added = [] %} +{% for method in service.methods %} +{% for parameter in method.parameters.all %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import { {{ name | caseUcfirst }} } from '../enums/{{ name | caseDash }}.ts'; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} export type UploadProgress = { $id: string; @@ -63,7 +80,7 @@ export class {{ service.name | caseUcfirst }} extends Service { * @throws {AppwriteException} * @returns {Promise} */ - async {{ method.name | caseCamel }}{% if generics %}<{{generics}}>{% endif %}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress = (progress: UploadProgress) => {}{% endif %}): Promise<{% if method.type == 'webAuth' %}Response{% elseif method.type == 'location' %}Response{% else %}{% if method.responseModel and method.responseModel != 'any' %}{% if not spec.definitions[method.responseModel].additionalProperties %}Models.{% endif %}{{method.responseModel | caseUcfirst}}{% if generics_return %}<{{generics_return}}>{% endif %}{% else %}Response{% endif %}{% endif %}> { + async {{ method.name | caseCamel }}{% if generics %}<{{generics}}>{% endif %}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, onProgress = (progress: UploadProgress) => {}{% endif %}): Promise<{% if method.type == 'webAuth' %}string{% elseif method.type == 'location' %}ArrayBuffer{% else %}{% if method.responseModel and method.responseModel != 'any' %}{% if not spec.definitions[method.responseModel].additionalProperties %}Models.{% endif %}{{method.responseModel | caseUcfirst}}{% if generics_return %}<{{generics_return}}>{% endif %}{% else %}Response{% endif %}{% endif %}> { {% for parameter in method.parameters.all %} {% if parameter.required %} if (typeof {{ parameter.name | caseCamel | escapeKeyword }} === 'undefined') { @@ -110,7 +127,11 @@ export class {{ service.name | caseUcfirst }} extends Service { {% if parameter.isUploadID %} if({{ parameter.name | caseCamel | escapeKeyword }} != 'unique()') { try { - response = await this.client.call('get', apiPath + '/' + {{ parameter.name }}, apiHeaders); + response = await this.client.call( + 'get', + apiPath + '/' + {{ parameter.name }}, + apiHeaders + ); chunksUploaded = response.chunksUploaded; } catch(e) { } @@ -128,7 +149,11 @@ export class {{ service.name | caseUcfirst }} extends Service { } const start = ((currentChunk - 1) * Client.CHUNK_SIZE); - const end = start + currentPosition; + let end = start + currentPosition; + + if (end === size) { + end -= 1; + } if(!lastUpload || currentChunk !== 1) { apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size; @@ -151,7 +176,7 @@ export class {{ service.name | caseUcfirst }} extends Service { payload['{{ parameter.name }}'] = { type: 'file', file: new File([uploadableChunkTrimmed], {{ parameter.name | caseCamel | escapeKeyword }}.filename), filename: {{ parameter.name | caseCamel | escapeKeyword }}.filename }; - response = await this.client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); + response = await this.client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% elseif method.type == 'webAuth' %}, 'location'{% endif %}); if (!id) { id = response['$id']; @@ -193,14 +218,20 @@ export class {{ service.name | caseUcfirst }} extends Service { {% endif %} {% endfor %} {% else %} - return await this.client.call('{{ method.method | caseLower }}', apiPath, { -{% for parameter in method.parameters.header %} - '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, -{% endfor %} -{% for key, header in method.headers %} - '{{ key }}': '{{ header }}', -{% endfor %} - }, payload); + return await this.client.call( + '{{ method.method | caseLower }}', + apiPath, + { + {%~ for parameter in method.parameters.header %} + '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, + {%~ endfor %} + {% for key, header in method.headers %} + '{{ key }}': '{{ header }}', + {%~ endfor %} + }, + payload, + {% if method.type == 'location' %}'arraybuffer'{% elseif method.type == 'webAuth' %}'location'{% else %}'json'{% endif %} + ); {% endif %} } {% endfor %} diff --git a/templates/deno/test/query.test.ts.twig b/templates/deno/test/query.test.ts.twig index a307e55d2..d1fac7f7c 100644 --- a/templates/deno/test/query.test.ts.twig +++ b/templates/deno/test/query.test.ts.twig @@ -46,8 +46,8 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.equal("attr", t.value), - `equal("attr", ${t.expectedValues})`, + Query.equal("attr", t.value).toString(), + `{"method":"equal","attribute":"attr","values":${t.expectedValues}}`, ) ) } @@ -57,8 +57,8 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.notEqual("attr", t.value), - `notEqual("attr", ${t.expectedValues})`, + Query.notEqual("attr", t.value).toString(), + `{"method":"notEqual","attribute":"attr","values":${t.expectedValues}}`, ) ) } @@ -68,8 +68,8 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.lessThan("attr", t.value), - `lessThan("attr", ${t.expectedValues})`, + Query.lessThan("attr", t.value).toString(), + `{"method":"lessThan","attribute":"attr","values":${t.expectedValues}}`, ) ) } @@ -79,8 +79,8 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.lessThanEqual("attr", t.value), - `lessThanEqual("attr", ${t.expectedValues})`, + Query.lessThanEqual("attr", t.value).toString(), + `{"method":"lessThanEqual","attribute":"attr","values":${t.expectedValues}}`, ) ) } @@ -90,8 +90,8 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.greaterThan("attr", t.value), - `greaterThan("attr", ${t.expectedValues})`, + Query.greaterThan("attr", t.value).toString(), + `{"method":"greaterThan","attribute":"attr","values":${t.expectedValues}}`, ) ) } @@ -101,75 +101,75 @@ describe('Query', () => { for (const t of tests) { test(t.description, () => assertEquals( - Query.greaterThanEqual("attr", t.value), - `greaterThanEqual("attr", ${t.expectedValues})`, + Query.greaterThanEqual("attr", t.value).toString(), + `{"method":"greaterThanEqual","attribute":"attr","values":${t.expectedValues}}`, ) ) } }); test('search', () => assertEquals( - Query.search('attr', 'keyword1 keyword2'), - 'search("attr", ["keyword1 keyword2"])', + Query.search('attr', 'keyword1 keyword2').toString(), + '{"method":"search","attribute":"attr","values":["keyword1 keyword2"]}', )); test('isNull', () => assertEquals( - Query.isNull('attr'), - 'isNull("attr")', + Query.isNull('attr').toString(), + `{"method":"isNull","attribute":"attr","values":[]}`, )); test('isNotNull', () => assertEquals( - Query.isNotNull('attr'), - 'isNotNull("attr")', + Query.isNotNull('attr').toString(), + `{"method":"isNotNull","attribute":"attr","values":[]}`, )); describe('between', () => { test('with integers', () => assertEquals( - Query.between('attr', 1, 2), - 'between("attr", 1, 2)' + Query.between('attr', 1, 2).toString(), + `{"method":"between","attribute":"attr","values":[1,2]}`, )); test('with doubles', () => assertEquals( - Query.between('attr', 1.2, 2.2), - 'between("attr", 1.2, 2.2)' + Query.between('attr', 1.2, 2.2).toString(), + `{"method":"between","attribute":"attr","values":[1.2,2.2]}`, )); test('with strings', () => assertEquals( - Query.between('attr', "a", "z"), - 'between("attr", "a", "z")' + Query.between('attr', "a", "z").toString(), + `{"method":"between","attribute":"attr","values":["a","z"]}`, )); }); test('select', () => assertEquals( - Query.select(['attr1', 'attr2']), - 'select(["attr1","attr2"])', + Query.select(['attr1', 'attr2']).toString(), + `{"method":"select","attribute":["attr1","attr2"]}`, )); test('orderAsc', () => assertEquals( - Query.orderAsc('attr'), - 'orderAsc("attr")', + Query.orderAsc('attr').toString(), + `{"method":"orderAsc","attribute":"attr"}`, )); test('orderDesc', () => assertEquals( - Query.orderDesc('attr'), - 'orderDesc("attr")', + Query.orderDesc('attr').toString(), + `{"method":"orderDesc","attribute":"attr"}`, )); test('cursorBefore', () => assertEquals( - Query.cursorBefore('attr'), - 'cursorBefore("attr")', + Query.cursorBefore('attr').toString(), + `{"method":"cursorBefore","attribute":"attr"}`, )); test('cursorAfter', () => assertEquals( - Query.cursorAfter('attr'), - 'cursorAfter("attr")', + Query.cursorAfter('attr').toString(), + `{"method":"cursorAfter","attribute":"attr"}`, )); test('limit', () => assertEquals( - Query.limit(1), - 'limit(1)' + Query.limit(1).toString(), + `{"method":"limit","values":[1]}`, )); test('offset', () => assertEquals( - Query.offset(1), - 'offset(1)' + Query.offset(1).toString(), + `{"method":"offset","values":[1]}`, )); }) diff --git a/templates/dotnet/.travis.yml.twig b/templates/dotnet/.travis.yml.twig index 2b0acfcb1..c74d0a72e 100644 --- a/templates/dotnet/.travis.yml.twig +++ b/templates/dotnet/.travis.yml.twig @@ -19,6 +19,6 @@ before_deploy: deploy: skip_cleanup: true provider: script - script: dotnet nuget push ./src/Appwrite/bin/Release/Appwrite.*.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json + script: dotnet nuget push ./src/Appwrite/bin/Release/Appwrite.*.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json on: tags: true diff --git a/templates/dotnet/base/params.twig b/templates/dotnet/base/params.twig index 0918c9d65..482ae36ed 100644 --- a/templates/dotnet/base/params.twig +++ b/templates/dotnet/base/params.twig @@ -1,6 +1,6 @@ {% import 'dotnet/base/utils.twig' as utils %} {%~ for parameter in method.parameters.path %} - .Replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel | escapeKeyword }}){% if loop.last %};{% endif %} + .Replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel | escapeKeyword }}{% if parameter.enumValues is not empty %}.Value{% endif %}){% if loop.last %};{% endif %} {%~ endfor %} diff --git a/templates/dotnet/base/requests/api.twig b/templates/dotnet/base/requests/api.twig index 0b10b627d..3c43c6bee 100644 --- a/templates/dotnet/base/requests/api.twig +++ b/templates/dotnet/base/requests/api.twig @@ -1,5 +1,5 @@ {% import 'dotnet/base/utils.twig' as utils %} - return _client.Call{% if method.type != 'webAuth' %}<{{ utils.resultType(spec.title, method) }}>{% endif %}( + return _client.Call<{{ utils.resultType(spec.title, method) }}>( method: "{{ method.method | caseUpper }}", path: apiPath, headers: apiHeaders, diff --git a/templates/dotnet/base/requests/oauth.twig b/templates/dotnet/base/requests/oauth.twig new file mode 100644 index 000000000..a257ef6a2 --- /dev/null +++ b/templates/dotnet/base/requests/oauth.twig @@ -0,0 +1,5 @@ + return _client.Redirect( + method: "{{ method.method | caseUpper }}", + path: apiPath, + headers: apiHeaders, + parameters: apiParameters.Where(it => it.Value != null).ToDictionary(it => it.Key, it => it.Value)!); diff --git a/templates/dotnet/docs/example.md.twig b/templates/dotnet/docs/example.md.twig index 8eff8963d..90b9b01d1 100644 --- a/templates/dotnet/docs/example.md.twig +++ b/templates/dotnet/docs/example.md.twig @@ -1,8 +1,15 @@ using {{ spec.title | caseUcfirst }}; -using {{ spec.title | caseUcfirst }}.Services; +{% set addedEnum = false %} +{% for parameter in method.parameters.all %} +{% if parameter.enumValues | length > 0 and not addedEnum %} +using {{ spec.title | caseUcfirst }}.Enums; +{% set addedEnum = true %} +{% endif %} +{% endfor %} using {{ spec.title | caseUcfirst }}.Models; +using {{ spec.title | caseUcfirst }}.Services; -var client = new Client() +Client client = new Client() {% if method.auth|length > 0 %} .SetEndPoint("https://cloud.appwrite.io/v1") // Your API Endpoint {% for node in method.auth %} @@ -10,11 +17,12 @@ var client = new Client() .Set{{header | caseUcfirst}}("{{node[header]['x-appwrite']['demo']}}"){% if loop.last %};{% endif %} // {{node[header].description}} {% endfor %}{% endfor %}{% endif %} -var {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); +{{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); + +{% if method.method != 'delete' and method.type != 'webAuth' %}{% if method.type == 'location' %}byte[]{% else %}{{ method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}({% if method.parameters.all | length == 0 %});{% endif %} + {%~ for parameter in method.parameters.all %} -{% if method.method != 'delete' %}{% if method.type == 'location' %}byte[]{% else %}{{ method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ method.name | caseUcfirst }}({% if method.parameters.all | length == 0 %});{% endif %} -{% for parameter in method.parameters.all %}{% if parameter.required %}{% if not loop.first %},{% endif %} + {{ parameter.name }}: {% if parameter.enumValues | length > 0%}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %}{% if not parameter.required %} // optional{% endif %} + {%~ endfor %} - {{ parameter.name }}: {{ parameter | paramExample }}{% if loop.last | length > 0 %});{% endif %}{% else %} - - {{ parameter.name }}: {{ parameter | paramExample }}{% if loop.last | length > 0 %});{% endif %} // optional{% endif %}{% endfor %} +{% if method.parameters.all | length > 0 %});{% endif %} \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig index 74673a1eb..9d5539f32 100644 --- a/templates/dotnet/src/Appwrite/Appwrite.csproj.twig +++ b/templates/dotnet/src/Appwrite/Appwrite.csproj.twig @@ -20,7 +20,7 @@ - + diff --git a/templates/dotnet/src/Appwrite/Client.cs.twig b/templates/dotnet/src/Appwrite/Client.cs.twig index 108ca492e..e8860babd 100644 --- a/templates/dotnet/src/Appwrite/Client.cs.twig +++ b/templates/dotnet/src/Appwrite/Client.cs.twig @@ -10,6 +10,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using {{ spec.title | caseUcfirst }}.Converters; using {{ spec.title | caseUcfirst }}.Extensions; using {{ spec.title | caseUcfirst }}.Models; @@ -21,6 +22,7 @@ namespace {{ spec.title | caseUcfirst }} public Dictionary Config => _config; private HttpClient _http; + private HttpClient _httpForRedirect; private readonly Dictionary _headers; private readonly Dictionary _config; private string _endpoint; @@ -34,7 +36,8 @@ namespace {{ spec.title | caseUcfirst }} ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List { - new StringEnumConverter() + new StringEnumConverter(new CamelCaseNamingStrategy()), + new ValueClassConverter() } }; @@ -44,17 +47,25 @@ namespace {{ spec.title | caseUcfirst }} ContractResolver = new CamelCasePropertyNamesContractResolver(), Converters = new List { - new StringEnumConverter() + new StringEnumConverter(new CamelCaseNamingStrategy()), + new ValueClassConverter() } }; public Client( string endpoint = "{{spec.endpoint}}", bool selfSigned = false, - HttpClient? http = null) + HttpClient? http = null, + HttpClient? httpForRedirect = null) { _endpoint = endpoint; _http = http ?? new HttpClient(); + + _httpForRedirect = httpForRedirect ?? new HttpClient( + new HttpClientHandler(){ + AllowAutoRedirect = false + }); + _headers = new Dictionary() { { "content-type", "application/json" }, @@ -119,21 +130,11 @@ namespace {{ spec.title | caseUcfirst }} return this; } - public Task> Call( + private HttpRequestMessage PrepareRequest( string method, string path, Dictionary headers, Dictionary parameters) - { - return Call>(method, path, headers, parameters); - } - - public async Task Call( - string method, - string path, - Dictionary headers, - Dictionary parameters, - Func, T>? convert = null) where T : class { var methodGet = "GET".Equals(method, StringComparison.OrdinalIgnoreCase); @@ -214,8 +215,58 @@ namespace {{ spec.title | caseUcfirst }} } } + return request; + } + + public async Task Redirect( + string method, + string path, + Dictionary headers, + Dictionary parameters) + { + var request = this.PrepareRequest(method, path, headers, parameters); + + var response = await _httpForRedirect.SendAsync(request); + var code = (int)response.StatusCode; + + if (code >= 400) { + var message = await response.Content.ReadAsStringAsync(); + + var contentType = response.Content.Headers + .GetValues("Content-Type") + .FirstOrDefault() ?? string.Empty; + + if (contentType.Contains("application/json")) { + message = JObject.Parse(message)["message"]!.ToString(); + } + + throw new {{spec.title | caseUcfirst}}Exception(message, code); + } + + return response.Headers.Location.OriginalString; + } + + public Task> Call( + string method, + string path, + Dictionary headers, + Dictionary parameters) + { + return Call>(method, path, headers, parameters); + } + + public async Task Call( + string method, + string path, + Dictionary headers, + Dictionary parameters, + Func, T>? convert = null) where T : class + { + var request = this.PrepareRequest(method, path, headers, parameters); + var response = await _http.SendAsync(request); var code = (int)response.StatusCode; + var contentType = response.Content.Headers .GetValues("Content-Type") .FirstOrDefault() ?? string.Empty; @@ -321,7 +372,7 @@ namespace {{ spec.title | caseUcfirst }} parameters = new Dictionary() ); var chunksUploaded = (long)current["chunksUploaded"]; - offset = Math.Min(chunksUploaded * ChunkSize, size); + offset = chunksUploaded * ChunkSize; } while (offset < size) @@ -337,7 +388,7 @@ namespace {{ spec.title | caseUcfirst }} case "bytes": buffer = ((byte[])input.Data) .Skip((int)offset) - .Take((int)Math.Min(size - offset, ChunkSize)) + .Take((int)Math.Min(size - offset, ChunkSize - 1)) .ToArray(); break; } diff --git a/templates/dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig b/templates/dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig new file mode 100644 index 000000000..8c6a46714 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Converters/ValueClassConverter.cs.twig @@ -0,0 +1,38 @@ +using System; +using {{ spec.title | caseUcfirst }}.Enums; +using Newtonsoft.Json; + +namespace {{ spec.title | caseUcfirst }}.Converters +{ + public class ValueClassConverter : JsonConverter { + + public override bool CanConvert(Type objectType) + { + return typeof(IEnum).IsAssignableFrom(objectType); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var value = (string)reader.Value; + var constructor = objectType.GetConstructor(new[] { typeof(string) }); + var obj = constructor.Invoke(new object[] { value }); + + return Convert.ChangeType(obj, objectType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var type = value.GetType(); + var property = type.GetProperty(nameof(IEnum.Value)); + var propertyValue = property.GetValue(value); + + if (propertyValue == null) + { + writer.WriteNull(); + return; + } + + writer.WriteValue(propertyValue); + } + } +} diff --git a/templates/dotnet/src/Appwrite/Enums/Enum.cs.twig b/templates/dotnet/src/Appwrite/Enums/Enum.cs.twig new file mode 100644 index 000000000..6720ce59b --- /dev/null +++ b/templates/dotnet/src/Appwrite/Enums/Enum.cs.twig @@ -0,0 +1,19 @@ +using System; + +namespace {{ spec.title | caseUcfirst }}.Enums +{ + public class {{ enum.name | caseUcfirst | overrideIdentifier }} : IEnum + { + public string Value { get; private set; } + + public {{ enum.name | caseUcfirst | overrideIdentifier }}(string value) + { + Value = value; + } + + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + public static {{ enum.name | caseUcfirst | overrideIdentifier }} {{ key | caseEnumKey }} => new {{ enum.name | caseUcfirst | overrideIdentifier }}("{{ value }}"); + {%~ endfor %} + } +} diff --git a/templates/dotnet/src/Appwrite/Enums/IEnum.cs.twig b/templates/dotnet/src/Appwrite/Enums/IEnum.cs.twig new file mode 100644 index 000000000..5d7744d12 --- /dev/null +++ b/templates/dotnet/src/Appwrite/Enums/IEnum.cs.twig @@ -0,0 +1,9 @@ +using System; + +namespace {{ spec.title | caseUcfirst }}.Enums +{ + public interface IEnum + { + public string Value { get; } + } +} diff --git a/templates/dotnet/src/Appwrite/Query.cs.twig b/templates/dotnet/src/Appwrite/Query.cs.twig index f10bac28c..3b26a02a1 100644 --- a/templates/dotnet/src/Appwrite/Query.cs.twig +++ b/templates/dotnet/src/Appwrite/Query.cs.twig @@ -1,139 +1,151 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; + namespace Appwrite { - public static class Query + public class Query { + public string method; + public string? attribute; + public List? values; + + public Query(string method, string? attribute, object? values) + { + this.method = method; + this.attribute = attribute; + + if (values is IList valuesList) + { + this.values = new List(); + foreach (var value in valuesList) + { + this.values.Add(value); // Automatically boxes if value is a value type + } + } + else if (values != null) + { + this.values = new List { values }; + } + } + + override public string ToString() + { + return JsonConvert.SerializeObject(this); + } + public static string Equal(string attribute, object value) { - return AddQuery(attribute, "equal", value); + return new Query("equal", attribute, value).ToString(); } public static string NotEqual(string attribute, object value) { - return AddQuery(attribute, "notEqual", value); + return new Query("notEqual", attribute, value).ToString(); } public static string LessThan(string attribute, object value) { - return AddQuery(attribute, "lessThan", value); + return new Query("lessThan", attribute, value).ToString(); } public static string LessThanEqual(string attribute, object value) { - return AddQuery(attribute, "lessThanEqual", value); + return new Query("lessThanEqual", attribute, value).ToString(); } public static string GreaterThan(string attribute, object value) { - return AddQuery(attribute, "greaterThan", value); + return new Query("greaterThan", attribute, value).ToString(); } public static string GreaterThanEqual(string attribute, object value) { - return AddQuery(attribute, "greaterThanEqual", value); + return new Query("greaterThanEqual", attribute, value).ToString(); } public static string Search(string attribute, string value) { - return AddQuery(attribute, "search", value); + return new Query("search", attribute, value).ToString(); } public static string IsNull(string attribute) { - return $"isNull(\"{attribute}\")"; + return new Query("isNull", attribute, null).ToString(); } public static string IsNotNull(string attribute) { - return $"isNotNull(\"{attribute}\")"; + return new Query("isNotNull", attribute, null).ToString(); } public static string StartsWith(string attribute, string value) { - return AddQuery(attribute, "startsWith", value); + return new Query("startsWith", attribute, value).ToString(); } public static string EndsWith(string attribute, string value) { - return AddQuery(attribute, "endsWith", value); + return new Query("endsWith", attribute, value).ToString(); } public static string Between(string attribute, string start, string end) { - return $"between(\"{attribute}\", \"{start}\", \"{end}\")"; + return new Query("between", attribute, new List { start, end }).ToString(); } public static string Between(string attribute, int start, int end) { - return $"between(\"{attribute}\", {start}, {end})"; + return new Query("between", attribute, new List { start, end }).ToString(); } public static string Between(string attribute, double start, double end) { - return $"between(\"{attribute}\", {start}, {end})"; + return new Query("between", attribute, new List { start, end }).ToString(); } public static string Select(List attributes) { - return $"select([{string.Join(",", attributes.Select(attribute => $"\"{attribute}\""))}])"; + return new Query("select", null, attributes).ToString(); } public static string CursorAfter(string documentId) { - return $"cursorAfter(\"{documentId}\")"; + return new Query("cursorAfter", null, documentId).ToString(); } public static string CursorBefore(string documentId) { - return $"cursorBefore(\"{documentId}\")"; + return new Query("cursorBefore", null, documentId).ToString(); } public static string OrderAsc(string attribute) { - return $"orderAsc(\"{attribute}\")"; + return new Query("orderAsc", attribute, null).ToString(); } public static string OrderDesc(string attribute) { - return $"orderDesc(\"{attribute}\")"; + return new Query("orderDesc", attribute, null).ToString(); } public static string Limit(int limit) { - return $"limit({limit})"; + return new Query("limit", null, limit).ToString(); } public static string Offset(int offset) { - return $"offset({offset})"; + return new Query("offset", null, offset).ToString(); } - private static string AddQuery(string attribute, string method, object value) - { - if (value is IList list) - { - var parsed = new List(); - foreach (var item in list) - { - parsed.Add(ParseValues(item)); - } - return $"{method}(\"{attribute}\", [{string.Join(",", parsed)}])"; - } - else - { - return $"{method}(\"{attribute}\", [{ParseValues(value)}])"; - } + public static string Contains(string attribute, object value) { + return new Query("contains", attribute, value).ToString(); } - private static string ParseValues(object value) - { - switch (value) - { - case string str: - return $"\"{str}\""; - case bool boolean: - return boolean.ToString().ToLower(); - default: - return value.ToString(); - } + public static string Or(List queries) { + return new Query("or", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); + } + + public static string And(List queries) { + return new Query("and", null, queries.Select(q => JsonConvert.DeserializeObject(q)).ToList()).ToString(); } } } \ No newline at end of file diff --git a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig index 732d36ac7..c20801c06 100644 --- a/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig +++ b/templates/dotnet/src/Appwrite/Services/ServiceTemplate.cs.twig @@ -4,7 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +{% if spec.definitions is not empty %} using {{ spec.title | caseUcfirst }}.Models; +{% endif %} +{% if spec.enums is not empty %} +using {{ spec.title | caseUcfirst }}.Enums; +{% endif %} namespace {{ spec.title | caseUcfirst }}.Services { @@ -25,32 +30,30 @@ namespace {{ spec.title | caseUcfirst }}.Services /// {%~ endif %} /// - public Task{% if method.type != "webAuth" %}<{{ utils.resultType(spec.title, method) }}>{% endif %} {{ method.name | caseUcfirst }}({{ utils.method_parameters(method.parameters, method.consumes) }}) + public Task{% if method.type == "webAuth" %}{% else %}<{{ utils.resultType(spec.title, method) }}>{% endif %} {{ method.name | caseUcfirst }}({{ utils.method_parameters(method.parameters, method.consumes) }}) { var apiPath = "{{ method.path }}"{% if method.parameters.path | length == 0 %};{% endif %} - {{~ include('dotnet/base/params.twig') }} + {{~ include('dotnet/base/params.twig') }} - {%~ if method.type == 'location' %} - {{~ include('dotnet/base/requests/location.twig') }} - {%~ else %} - - {%~ if method.responseModel %} + {%~ if method.responseModel %} static {{ utils.resultType(spec.title, method) }} Convert(Dictionary it) => - {%~ if method.responseModel == 'any' %} + {%~ if method.responseModel == 'any' %} it; - {%~ else %} + {%~ else %} {{ utils.resultType(spec.title, method) }}.From(map: it); - {%~ endif %} - {%~ endif %} - - {%~ if 'multipart/form-data' in method.consumes %} - {{~ include('dotnet/base/requests/file.twig') }} - {%~ else %} - - {{~ include('dotnet/base/requests/api.twig')}} - {%~ endif %} - {%~ endif %} + {%~ endif %} + {%~ endif %} + + {%~ if method.type == 'location' %} + {{~ include('dotnet/base/requests/location.twig') }} + {%~ elseif method.type == 'webAuth' %} + {{~ include('dotnet/base/requests/oauth.twig') }} + {%~ elseif 'multipart/form-data' in method.consumes %} + {{~ include('dotnet/base/requests/file.twig') }} + {%~ else %} + {{~ include('dotnet/base/requests/api.twig')}} + {%~ endif %} } {%~ endfor %} diff --git a/templates/flutter/base/requests/oauth.twig b/templates/flutter/base/requests/oauth.twig index 4c5617bd9..f7c2f8c04 100644 --- a/templates/flutter/base/requests/oauth.twig +++ b/templates/flutter/base/requests/oauth.twig @@ -31,4 +31,4 @@ query: query.join('&') ); - return client.webAuth(url, callbackUrlScheme: success); \ No newline at end of file + return client.webAuth(url, callbackUrlScheme: success); \ No newline at end of file diff --git a/templates/flutter/docs/example.md.twig b/templates/flutter/docs/example.md.twig index d50f47497..07eeb3677 100644 --- a/templates/flutter/docs/example.md.twig +++ b/templates/flutter/docs/example.md.twig @@ -3,76 +3,50 @@ import 'dart:io'; {% endif %} import 'package:{{ language.params.packageName }}/{{ language.params.packageName }}.dart'; -void main() { // Init SDK - Client client = Client(); - {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = {{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample }}{% endfor %}{% endif %}); - -{% if method.auth|length > 0 %} - client +Client client = Client() + {%~ if method.auth|length > 0 %} .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %} ; -{% endif %} + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last %};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} + +{{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = {{service.name | caseUcfirst}}(client); + {% if method.type == 'location' %} - // downloading file - Future result = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} +// Downloading file +UInt8List bytes = await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}( + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | overrideIdentifier}}: {% if parameter.enumValues | length > 0%}{{parameter.enumName}}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}{% endif %},{% if not parameter.required %} // optional{% endif %} -{% endif %} -{% if parameter.required %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, -{% else %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, // optional -{% endif %} -{% endfor %}{% if method.parameters.all | length > 0 %} {% endif %}).then((bytes) { - final file = File('path_to_file/filename.ext'); - file.writeAsBytesSync(bytes) - }).catchError((error) { - print(error.response); - }) -} + {%~ endfor %}{% if method.parameters.all | length > 0 %}{% endif %}) + +final file = File('path_to_file/filename.ext'); +file.writeAsBytesSync(bytes); -//displaying image preview +// Displaying image preview FutureBuilder( - future: {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} + future: {{ service.name | caseCamel }}.{{ method.name | caseCamel }}( + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | overrideIdentifier}}:{% if parameter.enumValues | length > 0%} {{parameter.enumName}}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }} {% endif %},{% if not parameter.required %} // optional{% endif %} -{% endif %} -{% if parameter.required %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, -{% else %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, // optional -{% endif %} -{% endfor %}{% if method.parameters.all | length > 0 %} {% endif %} -), //works for both public file and private file, for private files you need to be logged in - builder: (context, snapshot) { - return snapshot.hasData && snapshot.data != null - ? Image.memory( - snapshot.data, - ) - : CircularProgressIndicator(); - }, + {%~ endfor %} +), // Works for both public file and private file, for private files you need to be logged in + builder: (context, snapshot) { + return snapshot.hasData && snapshot.data != null + ? Image.memory(snapshot.data) + : CircularProgressIndicator(); + } ); {% else %} - Future result = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} +{% if method.method != 'delete' and method.type != 'webAuth' %}{% if method.type == 'location' %}UInt8List{% else %}{{ method.responseModel | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} -{% endif %} -{% if parameter.required %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, -{% else %} - {{ parameter.name | caseCamel | overrideIdentifier }}: {{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}, // optional -{% endif %} -{% endfor %}{% if method.parameters.all | length > 0 %} {% endif %}); + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel | overrideIdentifier}}: {% if parameter.enumValues | length > 0%}{{parameter.enumName}}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}{% endif %},{% if not parameter.required %} // optional{% endif %} - result - .then((response) { - print(response); - }).catchError((error) { - print(error.response); - }); -} + {%~ endfor %} +{% if method.parameters.all | length > 0 %}); +{% endif %} {% endif %} \ No newline at end of file diff --git a/templates/flutter/lib/package.dart.twig b/templates/flutter/lib/package.dart.twig index 9d92197b1..e777f5b5e 100644 --- a/templates/flutter/lib/package.dart.twig +++ b/templates/flutter/lib/package.dart.twig @@ -7,10 +7,13 @@ library {{ language.params.packageName }}; import 'dart:async'; import 'dart:typed_data'; +import 'dart:convert'; + import 'src/enums.dart'; import 'src/service.dart'; import 'src/input_file.dart'; import 'models.dart' as models; +import 'enums.dart' as enums; import 'src/upload_progress.dart'; export 'src/response.dart'; diff --git a/templates/flutter/lib/services/service.dart.twig b/templates/flutter/lib/services/service.dart.twig index 03be8528e..e03257834 100644 --- a/templates/flutter/lib/services/service.dart.twig +++ b/templates/flutter/lib/services/service.dart.twig @@ -20,8 +20,8 @@ class {{ service.name | caseUcfirst }} extends Service { {%~ if method.description %} {{ method.description|dartComment }} {% endif %} - {% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async { - {% if method.parameters.path | length > 0 %}final{% else %}const{% endif %} String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}){% endfor %}; + {% if method.type == 'webAuth' %}Future{% elseif method.type == 'location' %}Future{% else %}{% if method.responseModel and method.responseModel != 'any' %}Future{% else %}Future{% endif %}{% endif %} {{ method.name | caseCamel | overrideIdentifier }}({{ _self.method_parameters(method.parameters.all, method.consumes) }}) async { + {% if method.parameters.path | length > 0 %}final{% else %}const{% endif %} String apiPath = '{{ method.path }}'{% for parameter in method.parameters.path %}.replaceAll('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | caseCamel | overrideIdentifier }}{% if parameter.enumValues | length > 0 %}.value{% endif %}){% endfor %}; {% if 'multipart/form-data' in method.consumes %} {{ include('flutter/base/requests/file.twig') }} diff --git a/templates/kotlin/.github/workflows/publish.yml b/templates/kotlin/.github/workflows/publish.yml index e9721ddfe..d7674593d 100644 --- a/templates/kotlin/.github/workflows/publish.yml +++ b/templates/kotlin/.github/workflows/publish.yml @@ -12,10 +12,10 @@ jobs: steps: - name: Check out code uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 11 + java-version: 17 # Base64 decodes and pipes the GPG key content into the secret file - name: Prepare environment env: @@ -35,7 +35,7 @@ jobs: # Runs upload, and then closes & releases the repository - name: Publish Release Version to MavenCentral run: | - if ${{ endswith(github.event.release.tag_name, '-SNAPSHOT') }}; then + if ${{ contains(github.event.release.tag_name, '-rc') }}; then echo "Publising Snapshot Version ${{ github.event.release.tag_name}} to Snapshot repository" ./gradlew publishToSonatype else diff --git a/templates/kotlin/base/requests/oauth.twig b/templates/kotlin/base/requests/oauth.twig new file mode 100644 index 000000000..6e4ad3ede --- /dev/null +++ b/templates/kotlin/base/requests/oauth.twig @@ -0,0 +1,6 @@ + return client.redirect( + "{{ method.method | caseUpper }}", + apiPath, + apiHeaders, + apiParams + ) \ No newline at end of file diff --git a/templates/kotlin/build.gradle.twig b/templates/kotlin/build.gradle.twig index 9ae0c32e8..b5feaed62 100644 --- a/templates/kotlin/build.gradle.twig +++ b/templates/kotlin/build.gradle.twig @@ -1,7 +1,7 @@ plugins { - id "org.jetbrains.kotlin.jvm" version '1.8.0' + id "org.jetbrains.kotlin.jvm" version '1.9.10' id "java-library" - id "io.github.gradle-nexus.publish-plugin" version "1.1.0" + id "io.github.gradle-nexus.publish-plugin" version "1.3.0" } apply from: "${rootDir}/scripts/configure.gradle" @@ -29,14 +29,16 @@ repositories { } dependencies { - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - api(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") + + api(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) api("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:okhttp-urlconnection") implementation("com.squareup.okhttp3:logging-interceptor") implementation("com.google.code.gson:gson:2.9.0") - testImplementation 'org.jetbrains.kotlin:kotlin-test-junit' + testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } test { diff --git a/templates/kotlin/docs/java/example.md.twig b/templates/kotlin/docs/java/example.md.twig index a8288aae8..4d6c5e6a2 100644 --- a/templates/kotlin/docs/java/example.md.twig +++ b/templates/kotlin/docs/java/example.md.twig @@ -4,6 +4,22 @@ import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback; import {{ sdk.namespace | caseDot }}.models.InputFile; {% endif %} import {{ sdk.namespace | caseDot }}.services.{{ service.name | caseUcfirst }}; +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import {{ sdk.namespace | caseDot }}.enums.{{ name | caseUcfirst }}; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} Client client = new Client() {% if method.auth|length > 0 %} @@ -24,14 +40,10 @@ Client client = new Client() System.out.println(result); }));{% endif %} -{% for parameter in method.parameters.all %} -{% if parameter.required %} - {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // {{parameter.name}} + {%~ for parameter in method.parameters.all %} + {% if parameter.enumValues | length > 0%}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %}, // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} + {%~ if loop.last %} -{% else %} - {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // {{parameter.name}} (optional) -{% endif %} -{% if loop.last %} new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); @@ -42,4 +54,5 @@ Client client = new Client() }) ); {% endif %} + {% endfor %} \ No newline at end of file diff --git a/templates/kotlin/docs/kotlin/example.md.twig b/templates/kotlin/docs/kotlin/example.md.twig index 49d97d49a..3aa386ea2 100644 --- a/templates/kotlin/docs/kotlin/example.md.twig +++ b/templates/kotlin/docs/kotlin/example.md.twig @@ -1,10 +1,27 @@ import {{ sdk.namespace | caseDot }}.Client +import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback {% if method.parameters.all | filter((param) => param.type == 'file') | length > 0 %} import {{ sdk.namespace | caseDot }}.models.InputFile {% endif %} import {{ sdk.namespace | caseDot }}.services.{{ service.name | caseUcfirst }} +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import {{ sdk.namespace | caseDot }}.enums.{{ name | caseUcfirst }} +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} -val client = Client(context) +val client = Client() {% if method.auth|length > 0 %} .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint {% for node in method.auth %} @@ -18,7 +35,7 @@ val {{ service.name | caseCamel }} = {{ service.name | caseUcfirst }}(client) {% for parameter in method.parameters.all %} {% if parameter.required %} - {{parameter.name}} = {{ parameter | paramExample }}{% if not loop.last %},{% endif %} + {{parameter.name}} = {% if parameter.enumValues | length > 0 %} {{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else%}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %} {% else %} {{parameter.name}} = {{ parameter | paramExample }}{% if not loop.last %},{% endif %} // optional diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig index 7181175ca..9e6d2a45d 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Client.kt.twig @@ -1,10 +1,8 @@ package {{ sdk.namespace | caseDot }} -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.fromJson -import {{ sdk.namespace | caseDot }}.json.PreciseNumberAdapter +import {{ sdk.namespace | caseDot }}.extensions.toJson import {{ sdk.namespace | caseDot }}.models.InputFile import {{ sdk.namespace | caseDot }}.models.UploadProgress import kotlinx.coroutines.CoroutineScope @@ -47,13 +45,10 @@ class Client @JvmOverloads constructor( private val job = Job() - private val gson = GsonBuilder().registerTypeAdapter( - object : TypeToken>(){}.type, - PreciseNumberAdapter() - ).create() - lateinit var http: OkHttpClient + lateinit var httpForRedirect: OkHttpClient + private val headers: MutableMap val config: MutableMap @@ -111,6 +106,7 @@ class Client @JvmOverloads constructor( if (!selfSigned) { http = builder.build() + httpForRedirect = builder.followRedirects(false).build() return this } @@ -173,24 +169,22 @@ class Client @JvmOverloads constructor( } /** - * Send the HTTP request - * + * Prepare the HTTP request + * * @param method * @param path * @param headers * @param params * - * @return [T] + * @return [Request] */ @Throws({{ spec.title | caseUcfirst }}Exception::class) - suspend fun call( + suspend fun prepareRequest( method: String, path: String, headers: Map = mapOf(), params: Map = mapOf(), - responseType: Class, - converter: ((Any) -> T)? = null - ): T { + ): Request { val filteredParams = params.filterValues { it != null } val requestHeaders = this.headers.toHeaders().newBuilder() @@ -219,13 +213,12 @@ class Client @JvmOverloads constructor( } } } - val request = Request.Builder() + + return Request.Builder() .url(httpBuilder.build()) .headers(requestHeaders) .get() .build() - - return awaitResponse(request, responseType, converter) } val body = if (MultipartBody.FORM.toString() == headers["content-type"]) { @@ -252,19 +245,63 @@ class Client @JvmOverloads constructor( } builder.build() } else { - gson.toJson(filteredParams) + filteredParams + .toJson() .toRequestBody("application/json".toMediaType()) } - val request = Request.Builder() + return Request.Builder() .url(httpBuilder.build()) .headers(requestHeaders) .method(method, body) .build() + } + /** + * Send the HTTP request + * + * @param method + * @param path + * @param headers + * @param params + * + * @return [T] + */ + @Throws({{ spec.title | caseUcfirst }}Exception::class) + suspend fun call( + method: String, + path: String, + headers: Map = mapOf(), + params: Map = mapOf(), + responseType: Class, + converter: ((Any) -> T)? = null + ): T { + val request = prepareRequest(method, path, headers, params) return awaitResponse(request, responseType, converter) } + /** + * Send the HTTP request + * + * @param method + * @param path + * @param headers + * @param params + * + * @return [T] + */ + @Throws({{ spec.title | caseUcfirst }}Exception::class) + suspend fun redirect( + method: String, + path: String, + headers: Map = mapOf(), + params: Map = mapOf(), + ): String { + val request = prepareRequest(method, path, headers, params) + val response = awaitRedirect(request) + return response.header("Location") ?: "" + } + /** * Upload a file in chunks * @@ -390,6 +427,54 @@ class Client @JvmOverloads constructor( return converter(result as Map) } + /** + * Await Redirect + * + * @param request + * @param responseType + + * @return [Response] + */ + @Throws({{ spec.title | caseUcfirst }}Exception::class) + private suspend fun awaitRedirect( + request: Request + ) = suspendCancellableCoroutine { + httpForRedirect.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + if (it.isCancelled) { + return + } + it.cancel(e) + } + + @Suppress("UNCHECKED_CAST") + override fun onResponse(call: Call, response: Response) { + if (response.code < 300 || response.code >= 400) { + val body = response.body!! + .charStream() + .buffered() + .use(BufferedReader::readText) + + val error = if (response.headers["content-type"]?.contains("application/json") == true) { + val map = body.fromJson>() + + {{ spec.title | caseUcfirst }}Exception( + map["message"] as? String ?: "", + (map["code"] as Number).toInt(), + map["type"] as? String ?: "", + body + ) + } else { + {{ spec.title | caseUcfirst }}Exception(body, response.code) + } + it.cancel(error) + return + } + it.resume(response) + } + }) + } + /** * Await Response * @@ -422,10 +507,8 @@ class Client @JvmOverloads constructor( .use(BufferedReader::readText) val error = if (response.headers["content-type"]?.contains("application/json") == true) { - val map = gson.fromJson>( - body, - object : TypeToken>(){}.type - ) + val map = body.fromJson>() + {{ spec.title | caseUcfirst }}Exception( map["message"] as? String ?: "", (map["code"] as Number).toInt(), @@ -464,10 +547,7 @@ class Client @JvmOverloads constructor( it.resume(true as T) return } - val map = gson.fromJson>( - body, - object : TypeToken>(){}.type - ) + val map = body.fromJson>() it.resume( converter?.invoke(map) ?: map as T ) diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig index 7f4fbcd81..d83ff3c7d 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig @@ -1,60 +1,65 @@ package {{ sdk.namespace | caseDot }} -class Query { - companion object { - fun equal(attribute: String, value: Any) = addQuery(attribute, "equal", value) +import {{ sdk.namespace | caseDot }}.extensions.toJson +import {{ sdk.namespace | caseDot }}.extensions.fromJson - fun notEqual(attribute: String, value: Any) = Query.addQuery(attribute, "notEqual", value) +class Query( + val method: String, + val attribute: String? = null, + val values: List? = null, +) { + override fun toString() = this.toJson() - fun lessThan(attribute: String, value: Any) = Query.addQuery(attribute, "lessThan", value) + companion object { + fun equal(attribute: String, value: Any) = Query("equal", attribute, parseValue(value)).toJson() - fun lessThanEqual(attribute: String, value: Any) = Query.addQuery(attribute, "lessThanEqual", value) + fun notEqual(attribute: String, value: Any) = Query("notEqual", attribute, parseValue(value)).toJson() - fun greaterThan(attribute: String, value: Any) = Query.addQuery(attribute, "greaterThan", value) + fun lessThan(attribute: String, value: Any) = Query("lessThan", attribute, parseValue(value)).toJson() - fun greaterThanEqual(attribute: String, value: Any) = Query.addQuery(attribute, "greaterThanEqual", value) - - fun search(attribute: String, value: String) = Query.addQuery(attribute, "search", value) + fun lessThanEqual(attribute: String, value: Any) = Query("lessThanEqual", attribute, parseValue(value)).toJson() - fun isNull(attribute: String) = "isNull(\"${attribute}\")" + fun greaterThan(attribute: String, value: Any) = Query("greaterThan", attribute, parseValue(value)).toJson() - fun isNotNull(attribute: String) = "isNotNull(\"${attribute}\")" + fun greaterThanEqual(attribute: String, value: Any) = Query("greaterThanEqual", attribute, parseValue(value)).toJson() - fun between(attribute: String, start: Int, end: Int) = "between(\"${attribute}\", ${start}, ${end})" + fun search(attribute: String, value: String) = Query("search", attribute, listOf(value)).toJson() - fun between(attribute: String, start: Double, end: Double) = "between(\"${attribute}\", ${start}, ${end})" + fun isNull(attribute: String) = Query("isNull", attribute).toJson() - fun between(attribute: String, start: String, end: String) = "between(\"${attribute}\", \"${start}\", \"${end}\")" + fun isNotNull(attribute: String) = Query("isNotNull", attribute).toJson() - fun startsWith(attribute: String, value: String) = Query.addQuery(attribute, "startsWith", value) + fun between(attribute: String, start: Any, end: Any) = Query("between", attribute, listOf(start, end)).toJson() - fun endsWith(attribute: String, value: String) = Query.addQuery(attribute, "endsWith", value) + fun startsWith(attribute: String, value: String) = Query("startsWith", attribute, listOf(value)).toJson() - fun select(attributes: List) = "select([${attributes.joinToString(",") { "\"$it\"" }}])" + fun endsWith(attribute: String, value: String) = Query("endsWith", attribute, listOf(value)).toJson() - fun orderAsc(attribute: String) = "orderAsc(\"${attribute}\")" + fun select(attributes: List) = Query("select", null, attributes).toJson() - fun orderDesc(attribute: String) = "orderDesc(\"${attribute}\")" + fun orderAsc(attribute: String) = Query("orderAsc", attribute).toJson() - fun cursorBefore(documentId: String) = "cursorBefore(\"${documentId}\")" + fun orderDesc(attribute: String) = Query("orderDesc", attribute).toJson() - fun cursorAfter(documentId: String) = "cursorAfter(\"${documentId}\")" + fun cursorBefore(documentId: String) = Query("cursorBefore", null, listOf(documentId)).toJson() - fun limit(limit: Int) = "limit(${limit})" + fun cursorAfter(documentId: String) = Query("cursorAfter", null, listOf(documentId)).toJson() - fun offset(offset: Int) = "offset(${offset})" + fun limit(limit: Int) = Query("limit", null, listOf(limit)).toJson() - private fun addQuery(attribute: String, method: String, value: Any): String { - return when (value) { - is List<*> -> "${method}(\"${attribute}\", [${value.map{it -> parseValues(it!!)}.joinToString(",")}])" - else -> "${method}(\"${attribute}\", [${Query.parseValues(value)}])" - } - } - private fun parseValues(value: Any): String { - return when (value) { - is String -> "\"${value}\"" - else -> "${value}" - } + fun offset(offset: Int) = Query("offset", null, listOf(offset)).toJson() + + fun contains(attribute: String, value: Any) = Query("contains", attribute, parseValue(value)).toJson() + + fun or(queries: List) = Query("or", null, queries.map { it.fromJson() }).toJson() + + fun and(queries: List) = Query("and", null, queries.map { it.fromJson() }).toJson() + + private fun parseValue(value: Any): List { + return when (value) { + is List<*> -> value as List + else -> listOf(value) + } + } } - } -} +} \ No newline at end of file diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/enums/Enum.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/enums/Enum.kt.twig new file mode 100644 index 000000000..7abe7fe6c --- /dev/null +++ b/templates/kotlin/src/main/kotlin/io/appwrite/enums/Enum.kt.twig @@ -0,0 +1,14 @@ +package {{ sdk.namespace | caseDot }}.enums + +import com.google.gson.annotations.SerializedName + +enum class {{ enum.name | caseUcfirst | overrideIdentifier }}(val value: String) { +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + @SerializedName("{{ value }}") + {{ key | caseEnumKey }}("{{value}}"){% if not loop.last %},{% else %};{% endif %} + +{% endfor %} + + override fun toString() = value +} \ No newline at end of file diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/extensions/JsonExtensions.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/extensions/JsonExtensions.kt.twig index 48e536b3a..14ad26726 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/extensions/JsonExtensions.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/extensions/JsonExtensions.kt.twig @@ -1,8 +1,14 @@ package {{ sdk.namespace | caseDot }}.extensions import com.google.gson.Gson - -val gson = Gson() +import com.google.gson.GsonBuilder +import com.google.gson.ToNumberPolicy +import com.google.gson.reflect.TypeToken + +val gson: Gson = GsonBuilder() + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .create() fun Any.toJson(): String = gson.toJson(this) diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/json/PreciseNumberAdapter.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/json/PreciseNumberAdapter.kt.twig deleted file mode 100644 index 09cb786cf..000000000 --- a/templates/kotlin/src/main/kotlin/io/appwrite/json/PreciseNumberAdapter.kt.twig +++ /dev/null @@ -1,64 +0,0 @@ -package {{ sdk.namespace | caseDot }}.json - -import com.google.gson.Gson -import com.google.gson.TypeAdapter -import com.google.gson.stream.JsonReader -import com.google.gson.stream.JsonToken.* -import com.google.gson.stream.JsonWriter -import java.io.IOException - -internal class PreciseNumberAdapter : TypeAdapter() { - - private val delegate = Gson() - .getAdapter(Any::class.java) - - @Throws(IOException::class) - override fun write(out: JsonWriter?, value: Any?) { - delegate.write(out, value) - } - - @Throws(IOException::class) - override fun read(input: JsonReader): Any? { - return when (input.peek()) { - BEGIN_ARRAY -> { - val list = mutableListOf() - input.beginArray() - while (input.hasNext()) { - list.add(read(input)) - } - input.endArray() - list - } - BEGIN_OBJECT -> { - val map = mutableMapOf() - input.beginObject() - while (input.hasNext()) { - map[input.nextName()] = read(input) - } - input.endObject() - map - } - STRING -> { - input.nextString() - } - NUMBER -> { - val numberString = input.nextString() - if (numberString.indexOf('.') != -1) { - numberString.toDouble() - } else { - numberString.toLong() - } - } - BOOLEAN -> { - input.nextBoolean() - } - NULL -> { - input.nextNull() - null - } - else -> { - throw IllegalStateException() - } - } - } -} \ No newline at end of file diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig index 6b049555c..4a5cd7014 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/models/Model.kt.twig @@ -61,7 +61,7 @@ import io.appwrite.extensions.jsonCast {%~ endif %} ) = {{ definition | modelType(spec) | raw }}( {%~ for property in definition.properties %} - {{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map{% if definition.name | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}, + {{ property.name | escapeKeyword | removeDollarSign }} = {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{ property.name | escapeDollarSign }}"] as List>).map { {{ property.sub_schema | caseUcfirst }}.from(map = it{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}) }{% else %}{{ property.sub_schema | caseUcfirst }}.from(map = map["{{property.name | escapeDollarSign }}"] as Map{% if property.sub_schema | hasGenericType(spec) %}, nestedType{% endif %}){% endif %}{% else %}{% if property.type == "integer" or property.type == "number" %}({% endif %}map["{{ property.name | escapeDollarSign }}"]{% if property.type == "integer" or property.type == "number" %} as{% if not property.required %}?{% endif %} Number){% endif %}{% if property.type == "integer" %}{% if not property.required %}?{% endif %}.toLong(){% elseif property.type == "number" %}{% if not property.required %}?{% endif %}.toDouble(){% else %} as{% if not property.required %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% endif %}, {%~ endfor %} {%~ if definition.additionalProperties %} data = map.jsonCast(to = nestedType) diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig index 5fb0f8069..c5add5d0f 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig @@ -1,7 +1,12 @@ package {{ sdk.namespace | caseDot }}.services import {{ sdk.namespace | caseDot }}.Client +{% if spec.definitions is not empty %} import {{ sdk.namespace | caseDot }}.models.* +{% endif %} +{% if spec.enums is not empty %} +import {{ sdk.namespace | caseDot }}.enums.* +{% endif %} import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.title | caseUcfirst }}Exception import {{ sdk.namespace | caseDot }}.extensions.classOf import okhttp3.Cookie @@ -14,9 +19,7 @@ import java.io.File /** * {{ service.description | raw | replace({"\n": "", "\r": ""}) }} **/ -class {{ service.name | caseUcfirst }} : Service { - - public constructor (client: Client) : super(client) { } +class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { {% for method in service.methods %} /** @@ -27,18 +30,13 @@ class {{ service.name | caseUcfirst }} : Service { {%~ for parameter in method.parameters.all %} * @param {{ parameter.name | caseCamel }} {{ parameter.description | raw }} {%~ endfor %} - {%~ if method.type != "webAuth" %} * @return [{{ method | returnType(spec, sdk.namespace | caseDot) | raw }}] - {%~ endif %} */ {%~ if method.parameters.all | reduce((carry, param) => carry or not param.required) %} @JvmOverloads {%~ endif %} @Throws({{ spec.title | caseUcfirst }}Exception::class) suspend fun {% if method.responseModel | hasGenericType(spec) %}{{ '' | raw }} {% endif %}{{ method.name | caseCamel }}( - {%~ if method.type == "webAuth" %} - activity: ComponentActivity, - {%~ endif %} {%~ for parameter in method.parameters.all %} {{ parameter.name | caseCamel }}: {{ parameter | typeName }}{%~ if not parameter.required or parameter.nullable %}? = null{% endif %}, {%~ endfor %} @@ -51,7 +49,7 @@ class {{ service.name | caseUcfirst }} : Service { ): {{ method | returnType(spec, sdk.namespace | caseDot) | raw }} { val apiPath = "{{ method.path }}" {%~ for parameter in method.parameters.path %} - .replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }}) + .replace("{{ '{' ~ parameter.name | caseCamel ~ '}' }}", {{ parameter.name | caseCamel }}{% if parameter.enumValues is not empty %}.value{% endif %}) {%~ endfor %} val apiParams = mutableMapOf( @@ -59,14 +57,16 @@ class {{ service.name | caseUcfirst }} : Service { "{{ parameter.name }}" to {{ parameter.name | caseCamel }}, {%~ endfor %} ) - {%~ if method.type == 'location' %} - {{~ include('kotlin/base/requests/location.twig') }} - {%~ else %} val apiHeaders = mutableMapOf( {%~ for key, header in method.headers %} "{{ key }}" to "{{ header }}", {%~ endfor %} ) + {%~ if method.type == 'location' %} + {{~ include('kotlin/base/requests/location.twig') }} + {%~ elseif method.type == 'webAuth' %} + {{~ include('kotlin/base/requests/oauth.twig') }} + {%~ else %} {%~ if method.responseModel %} val converter: (Any) -> {{ method | returnType(spec, sdk.namespace | caseDot) | raw }} = { {%~ if method.responseModel == 'any' %} diff --git a/templates/node/.travis.yml.twig b/templates/node/.travis.yml.twig index b8f9cf81c..9bbe69b99 100644 --- a/templates/node/.travis.yml.twig +++ b/templates/node/.travis.yml.twig @@ -5,7 +5,7 @@ node_js: jobs: include: - stage: NPM RC Release - if: tag == *-RC* + if: tag == *-rc* node_js: "14.16" script: echo "Deploying RC to NPM..." deploy: @@ -14,7 +14,7 @@ jobs: api_key: $NPM_API_KEY tag: next - stage: NPM Release - if: tag != *-RC* + if: tag != *-rc* node_js: "14.16" script: echo "Deploying to NPM..." deploy: diff --git a/templates/node/base/requests/api.twig b/templates/node/base/requests/api.twig index 4c578a889..316db438c 100644 --- a/templates/node/base/requests/api.twig +++ b/templates/node/base/requests/api.twig @@ -5,4 +5,4 @@ {% for key, header in method.headers %} '{{ key }}': '{{ header }}', {% endfor %} - }, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); \ No newline at end of file + }, payload{% if method.type == 'location' %}, 'arraybuffer'{% elseif method.type == 'webAuth' %}, 'location'{% endif %}); \ No newline at end of file diff --git a/templates/node/base/requests/file.twig b/templates/node/base/requests/file.twig index 1f6f4a85b..76a20f0d7 100644 --- a/templates/node/base/requests/file.twig +++ b/templates/node/base/requests/file.twig @@ -1,7 +1,8 @@ {% for parameter in method.parameters.all %} {% if parameter.type == 'file' %} - const size = {{ parameter.name | caseCamel | escapeKeyword }}.size; + const size = {{ parameter.name | caseCamel | escapeKeyword }}.size; + const apiHeaders = { {% for parameter in method.parameters.header %} '{{ parameter.name }}': ${{ parameter.name | caseCamel | escapeKeyword }}, @@ -28,117 +29,77 @@ {% endif %} {% endfor %} - let currentChunk = Buffer.from(''); - let currentChunkSize = 0; - let currentChunkStart = 0; + let currentChunk = 1; + let currentPosition = 0; + let uploadableChunk = new Uint8Array(client.CHUNK_SIZE); + - const selfClient = this.client; - - async function uploadChunk(lastUpload = false) { - if(chunksUploaded - 1 >= currentChunkStart / client.CHUNK_SIZE) { + const uploadChunk = async (lastUpload = false) => { + if(currentChunk <= chunksUploaded) { return; } - - const start = currentChunkStart; - const end = currentChunkStart + currentChunkSize - 1; - if(!lastUpload || currentChunkStart !== 0) { + const start = ((currentChunk - 1) * client.CHUNK_SIZE); + let end = start + currentPosition - 1; + + if(!lastUpload || currentChunk !== 1) { apiHeaders['content-range'] = 'bytes ' + start + '-' + end + '/' + size; } + let uploadableChunkTrimmed; + + if(currentPosition + 1 >= client.CHUNK_SIZE) { + uploadableChunkTrimmed = uploadableChunk; + } else { + uploadableChunkTrimmed = new Uint8Array(currentPosition); + for(let i = 0; i <= currentPosition; i++) { + uploadableChunkTrimmed[i] = uploadableChunk[i]; + } + } + if (id) { apiHeaders['x-{{spec.title | caseLower }}-id'] = id; } - payload['{{ parameter.name }}'] = { - type: 'file', - file: currentChunk, - filename: {{ parameter.name }}.filename, - size: currentChunkSize - }; + payload['{{ parameter.name }}'] = { type: 'file', file: new File([uploadableChunkTrimmed], {{ parameter.name | caseCamel | escapeKeyword }}.filename), filename: {{ parameter.name | caseCamel | escapeKeyword }}.filename }; - response = await selfClient.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); + response = await this.client.call('{{ method.method | caseLower }}', apiPath, apiHeaders, payload{% if method.type == 'location' %}, 'arraybuffer'{% endif %}); if (!id) { id = response['$id']; } - + if (onProgress !== null) { onProgress({ $id: response['$id'], - progress: Math.min((start+client.CHUNK_SIZE) * client.CHUNK_SIZE, size) / size * 100, + progress: Math.min((currentChunk) * client.CHUNK_SIZE, size) / size * 100, sizeUploaded: end+1, chunksTotal: response['chunksTotal'], chunksUploaded: response['chunksUploaded'] }); } - currentChunkStart += client.CHUNK_SIZE; + uploadableChunk = new Uint8Array(client.CHUNK_SIZE); + currentChunk++; + currentPosition = 0; } - return await new Promise((resolve, reject) => { - const writeStream = new Stream.Writable(); - writeStream._write = async (mainChunk, encoding, callback) => { - try { - // Segment incoming chunk into up to 5MB chunks - const mainChunkSize = Buffer.byteLength(mainChunk); - const chunksCount = Math.ceil(mainChunkSize / client.CHUNK_SIZE); - const chunks = []; - - for(let i = 0; i < chunksCount; i++) { - const chunk = mainChunk.slice(i * client.CHUNK_SIZE, (i + 1) * client.CHUNK_SIZE); - chunks.push(chunk); - } - - for (const chunk of chunks) { - const chunkSize = Buffer.byteLength(chunk); - - if(chunkSize + currentChunkSize == client.CHUNK_SIZE) { - // Upload chunk - currentChunk = Buffer.concat([currentChunk, chunk]); - currentChunkSize = Buffer.byteLength(currentChunk); - await uploadChunk(); - currentChunk = Buffer.from(''); - currentChunkSize = 0; - } else if(chunkSize + currentChunkSize > client.CHUNK_SIZE) { - // Upload chunk, put rest into next chunk - const bytesToUpload = client.CHUNK_SIZE - currentChunkSize; - const newChunkSection = chunk.slice(0, bytesToUpload); - currentChunk = Buffer.concat([currentChunk, newChunkSection]); - currentChunkSize = Buffer.byteLength(currentChunk); - await uploadChunk(); - currentChunk = chunk.slice(bytesToUpload, undefined); - currentChunkSize = chunkSize - bytesToUpload; - } else { - // Append into current chunk - currentChunk = Buffer.concat([currentChunk, chunk]); - currentChunkSize = chunkSize + currentChunkSize; - } - } - - callback(); - } catch (e) { - callback(e); + for await (const chunk of {{ parameter.name | caseCamel | escapeKeyword }}.stream) { + for(const b of chunk) { + uploadableChunk[currentPosition] = b; + + currentPosition++; + if(currentPosition >= client.CHUNK_SIZE) { + await uploadChunk(); + currentPosition = 0; } } + } - writeStream.on("finish", async () => { - if(currentChunkSize > 0) { - try { - await uploadChunk(true); - } catch (e) { - reject(e); - } - } - - resolve(response); - }); + if (currentPosition > 0) { // Check if there's any remaining data for the last chunk + await uploadChunk(true); + } - writeStream.on("error", (err) => { - reject(err); - }); - - {{ parameter.name | caseCamel | escapeKeyword }}.stream.pipe(writeStream); - }); + return response; {% endif %} {% endfor %} diff --git a/templates/node/docs/example.md.twig b/templates/node/docs/example.md.twig index 2853299f1..7531bb690 100644 --- a/templates/node/docs/example.md.twig +++ b/templates/node/docs/example.md.twig @@ -3,33 +3,23 @@ const sdk = require('node-{{ spec.title | caseDash }}'); const fs = require('fs'); {% endif %} -// Init SDK -const client = new sdk.Client(); - -const {{ service.name | caseCamel }} = new sdk.{{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); - -{% if method.auth|length > 0 %} -client +const client = new sdk.Client() + {%~ if method.auth|length > 0 %} .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %}; + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last %};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} -{% endif %} -const promise = {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} +const {{ service.name | caseCamel }} = new sdk.{{service.name | caseUcfirst}}(client); + +const result = await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} - {% endif %}{% if parameter.required %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} - - {% else %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} (optional) - {% endif %}{% endfor %}); + {%~ for parameter in method.parameters.all %} + {% if parameter.enumValues | length > 0%}sdk.{{ parameter.enumName }}.{{(parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample}}{% endif %}{% if not loop.last %},{% endif%} // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} -promise.then(function (response) { - console.log(response); -}, function (error) { - console.log(error); -}); \ No newline at end of file + {%~ endfor -%} +{% if method.parameters.all | length > 0 %}); +{% endif %} \ No newline at end of file diff --git a/templates/node/index.d.ts.twig b/templates/node/index.d.ts.twig index 721eae574..9180b9e7d 100644 --- a/templates/node/index.d.ts.twig +++ b/templates/node/index.d.ts.twig @@ -250,8 +250,16 @@ declare module "{{ language.params.npmPackage|caseDash }}" { * @throws {{ '{' }}{{ spec.title | caseUcfirst}}Exception} * @returns {Promise} */ - {{ method.name | caseCamel }}{% if generics %}<{{generics}}>{% endif %}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}): Promise<{% if method.type == 'location' %}Buffer{% else %}{% if method.responseModel and method.responseModel != 'any' %}{% if not spec.definitions[method.responseModel].additionalProperties %}Models.{% endif %}{{method.responseModel | caseUcfirst}}{% if generics_return %}<{{generics_return}}>{% endif %}{% else %}{% if method.method == 'delete' %}string{% else %}any{% endif %}{% endif %}{% endif %}>; + {{ method.name | caseCamel }}{% if generics %}<{{generics}}>{% endif %}({% for parameter in method.parameters.all %}{{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required or parameter.nullable %}?{% endif %}: {{ parameter | typeName }}{% if not loop.last %}, {% endif %}{% endfor %}): Promise<{% if method.type == 'webAuth' %}string{% elseif method.type == 'location' %}ArrayBuffer{% else %}{% if method.responseModel and method.responseModel != 'any' %}{% if not spec.definitions[method.responseModel].additionalProperties %}Models.{% endif %}{{method.responseModel | caseUcfirst}}{% if generics_return %}<{{generics_return}}>{% endif %}{% else %}{% if method.method == 'delete' %}string{% else %}any{% endif %}{% endif %}{% endif %}>; {% endfor %} } {% endfor %} -} +{% for enum in spec.enums %} + export const {{ enum.name | caseUcfirst }}: Readonly<{ +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseEnumKey }}: '{{ value }}', +{% endfor %} + }> +{% endfor %} +} \ No newline at end of file diff --git a/templates/node/index.js.twig b/templates/node/index.js.twig index b82be2323..16a732f94 100644 --- a/templates/node/index.js.twig +++ b/templates/node/index.js.twig @@ -8,6 +8,9 @@ const {{spec.title | caseUcfirst}}Exception = require('./lib/exception.js'); {% for service in spec.services %} const {{service.name | caseUcfirst}} = require('./lib/services/{{service.name | caseDash}}.js'); {% endfor %} +{% for enum in spec.enums %} +const {{enum.name | caseUcfirst}} = require("./lib/enums/{{enum.name | caseDash}}.js"); +{% endfor %} module.exports = { Client, @@ -19,5 +22,7 @@ module.exports = { {{spec.title | caseUcfirst}}Exception, {% for service in spec.services %} {{service.name | caseUcfirst}}, +{% endfor %}{% for enum in spec.enums %} + {{ enum.name | caseUcfirst }}, {% endfor %} }; diff --git a/templates/node/lib/client.js.twig b/templates/node/lib/client.js.twig index c3b8130e5..02332038a 100644 --- a/templates/node/lib/client.js.twig +++ b/templates/node/lib/client.js.twig @@ -1,8 +1,7 @@ const os = require('os'); const URL = require('url').URL; -const https = require("https"); -const axios = require('axios'); -const FormData = require('form-data'); +const Query = require('./query.js'); +const {fetch, FormData, Agent} = require('undici'); const {{spec.title | caseUcfirst}}Exception = require('./exception.js'); class Client { @@ -18,24 +17,24 @@ class Client { 'x-sdk-platform': '{{ sdk.platform }}', 'x-sdk-language': '{{ language.name | caseLower }}', 'x-sdk-version': '{{ sdk.version }}', -{% for key,header in spec.global.defaultHeaders %} + {%~ for key,header in spec.global.defaultHeaders %} '{{key}}' : '{{header}}', -{% endfor %} + {%~ endfor %} }; this.selfSigned = false; } -{% for header in spec.global.headers %} + {%~ for header in spec.global.headers %} /** * Set {{header.key | caseUcfirst}} * -{% if header.description %} + {%~ if header.description %} * {{header.description}} * -{% endif %} + {%~ endif %} * @param {string} {{header.key | caseLower}} * - * @return self + * @return Client */ set{{header.key | caseUcfirst}}({{header.key | caseLower}}) { this.addHeader('{{header.name}}', {{header.key | caseLower}}); @@ -43,7 +42,7 @@ class Client { return this; } -{% endfor %} + {%~ endfor %} /** * Set self signed. * @@ -72,8 +71,12 @@ class Client { } /** + * Sets a header for requests. + * * @param {string} key * @param {string} value + * + * @return this */ addHeader(key, value) { this.headers[key.toLowerCase()] = value; @@ -81,79 +84,88 @@ class Client { return this; } - async call(method, path = '', headers = {}, params = {}, responseType = 'json') { - headers = Object.assign({}, this.headers, headers); + async call(method, path = "", headers = {}, params = {}, responseType = "json") { + headers = {...this.headers, ...headers}; + const url = new URL(this.endpoint + path); - let contentType = headers['content-type'].toLowerCase(); + let body = undefined; - let formData = null; + if (method.toUpperCase() === "GET") { + url.search = new URLSearchParams(Client.flatten(params)).toString(); + } else if (headers["content-type"]?.toLowerCase().startsWith("multipart/form-data")) { + delete headers["content-type"]; + const formData = new FormData(); - // Compute FormData for axios and appwrite. - if (contentType.startsWith('multipart/form-data')) { - const form = new FormData(); - - let flatParams = Client.flatten(params); - - for (const key in flatParams) { - const value = flatParams[key]; + const flatParams = Client.flatten(params); - if(value && value.type && value.type === 'file') { - form.append(key, value.file, { filename: value.filename }); + for (const [key, value] of Object.entries(flatParams)) { + if (value && value.type && value.type === "file") { + formData.append(key, value.file, value.filename); } else { - form.append(key, flatParams[key]); + formData.append(key, value); } } - headers = { - ...headers, - ...form.getHeaders() - }; - - formData = form; + body = formData; + } else { + body = JSON.stringify(params); } - let options = { + let response = undefined; + try { + response = await fetch(url.toString(), { method: method.toUpperCase(), - url: this.endpoint + path, - params: (method.toUpperCase() === 'GET') ? params : {}, - headers: headers, - data: (method.toUpperCase() === 'GET' || contentType.startsWith('multipart/form-data')) ? formData : params, - json: (contentType.startsWith('application/json')), - responseType: responseType - }; - if (this.selfSigned) { - // Allow self signed requests - options.httpsAgent = new https.Agent({ rejectUnauthorized: false }); - } + headers, + body, + redirect: responseType === "location" ? "manual" : "follow", + dispatcher: new Agent({ + connect: { + rejectUnauthorized: !this.selfSigned, + }, + }), + }); + } catch (error) { + throw new {{spec.title | caseUcfirst}}Exception(error.message); + } + + if (response.status >= 400) { + const text = await response.text(); + let json = undefined; try { - let response = await axios(options); - return response.data; - } catch(error) { - if('response' in error && error.response !== undefined) { - if(error.response && 'data' in error.response) { - if (typeof(error.response.data) === 'string') { - throw new {{spec.title | caseUcfirst}}Exception(error.response.data, error.response.status, '', error.response.data); - } else { - throw new {{spec.title | caseUcfirst}}Exception(error.response.data.message, error.response.status, error.response.data.type, error.response.data); - } - } else { - throw new {{spec.title | caseUcfirst}}Exception(error.response.statusText, error.response.status, error.response.data); - } - } else { - throw new {{spec.title | caseUcfirst}}Exception(error.message); - } + json = JSON.parse(text); + } catch (error) { + throw new {{spec.title | caseUcfirst}}Exception(text, response.status, "", text); } + throw new {{spec.title | caseUcfirst}}Exception(json.message, json.code, json.type, json); + } + + if (responseType === "arraybuffer") { + const data = await response.arrayBuffer(); + return data; + } + + if (responseType === "location") { + return response.headers.get("location"); + } + + const text = await response.text(); + let json = undefined; + try { + json = JSON.parse(text); + } catch (error) { + return text; + } + return json; } - static flatten(data, prefix = '') { + static flatten(data, prefix = "") { let output = {}; - for (const key in data) { - let value = data[key]; - let finalKey = prefix ? prefix + '[' + key +']' : key; + for (const [key, value] of Object.entries(data)) { + let finalKey = prefix ? prefix + "[" + key + "]" : key; if (Array.isArray(value)) { - output = Object.assign(output, Client.flatten(value, finalKey)); // @todo: handle name collision here if needed + output = { ...output, ...Client.flatten(value, finalKey) }; } else { output[finalKey] = value; } diff --git a/templates/node/lib/enums/enum.js.twig b/templates/node/lib/enums/enum.js.twig new file mode 100644 index 000000000..e85a7401c --- /dev/null +++ b/templates/node/lib/enums/enum.js.twig @@ -0,0 +1,9 @@ +const {{ enum.name | caseUcfirst | overrideIdentifier }} = Object.freeze({ +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseEnumKey }}: '{{value}}' {% if not loop.last %},{% endif %} + +{% endfor %} +}); + +module.exports = {{ enum.name | caseUcfirst | overrideIdentifier }}; \ No newline at end of file diff --git a/templates/node/lib/inputFile.js.twig b/templates/node/lib/inputFile.js.twig index de7f0d9c7..5790b8b41 100644 --- a/templates/node/lib/inputFile.js.twig +++ b/templates/node/lib/inputFile.js.twig @@ -1,40 +1,112 @@ -const { Readable } = require('stream'); -const fs = require('fs'); +const fs = require("fs"); +const { ReadableStream } = require("stream/web"); + +/** + * @param {fs.ReadStream} readStream + * @returns {ReadableStream} + */ +function convertReadStreamToReadableStream(readStream) { + return new ReadableStream({ + start(controller) { + readStream.on("data", (chunk) => { + controller.enqueue(chunk); + }); + readStream.on("end", () => { + controller.close(); + }); + readStream.on("error", (err) => { + controller.error(err); + }); + }, + cancel() { + readStream.destroy(); + }, + }); +} + +/** + * @param {Buffer} buffer + * @returns {ReadableStream} + */ +function bufferToReadableStream(buffer) { + return new ReadableStream({ + start(controller) { + controller.enqueue(buffer); + controller.close(); + }, + }); +} class InputFile { - stream; // Content of file, readable stream - size; // Total final size of the file content - filename; // File name + /** @type {ReadableStream} Content of file as a stream */ + stream; + /** @type {number} Total final size of the file content */ + size; + + /** @type {string} File name */ + filename; + + /** + * @param {string} filePath + * @param {string} filename + * @returns {InputFile} + */ static fromPath = (filePath, filename) => { - const stream = fs.createReadStream(filePath); - const { size } = fs.statSync(filePath); + const nodeStream = fs.createReadStream(filePath); + const stream = convertReadStreamToReadableStream(nodeStream); + const size = fs.statSync(filePath).size; return new InputFile(stream, filename, size); }; + /** + * @param {Buffer} buffer + * @param {string} filename + * @returns {InputFile} + */ static fromBuffer = (buffer, filename) => { - const stream = Readable.from(buffer); - const size = Buffer.byteLength(buffer); + const stream = bufferToReadableStream(buffer); + const size = buffer.byteLength; return new InputFile(stream, filename, size); }; - static fromBlob = async (blob, filename) => { - const arrayBuffer = await blob.arrayBuffer(); - const buffer = Buffer.from(arrayBuffer); + /** + * @param {string} content + * @param {string} filename + * @returns {InputFile} + */ + static fromPlainText = (content, filename) => { + const array = new TextEncoder().encode(content); + const buffer = Buffer.from(array); return InputFile.fromBuffer(buffer, filename); }; + /** + * @param {ReadableStream} stream + * @param {string} filename + * @param {number} size + * @returns {InputFile} + */ static fromStream = (stream, filename, size) => { return new InputFile(stream, filename, size); }; - static fromPlainText = (content, filename) => { - const buffer = Buffer.from(content, "utf-8"); - const stream = Readable.from(buffer); - const size = Buffer.byteLength(buffer); + /** + * @param {Blob} blob + * @param {string} filename + * @returns {InputFile} + */ + static fromBlob = (blob, filename) => { + const stream = blob.stream(); + const size = blob.size; return new InputFile(stream, filename, size); }; + /** + * @param {ReadableStream} stream + * @param {string} filename + * @param {number} size + */ constructor(stream, filename, size) { this.stream = stream; this.filename = filename; diff --git a/templates/node/lib/query.js.twig b/templates/node/lib/query.js.twig index 675b02b49..ab6354e9a 100644 --- a/templates/node/lib/query.js.twig +++ b/templates/node/lib/query.js.twig @@ -1,72 +1,90 @@ class Query { + constructor(method, attribute, values) { + this.method = method + this.attribute = attribute + + if (values !== undefined) { + if (Array.isArray(values)) { + this.values = values + } else { + this.values = [values] + } + } + } + static equal = (attribute, value) => - Query.addQuery(attribute, "equal", value); + new Query("equal", attribute, value).toString() static notEqual = (attribute, value) => - Query.addQuery(attribute, "notEqual", value); + new Query("notEqual", attribute, value).toString() static lessThan = (attribute, value) => - Query.addQuery(attribute, "lessThan", value); + new Query("lessThan", attribute, value).toString() static lessThanEqual = (attribute, value) => - Query.addQuery(attribute, "lessThanEqual", value); + new Query("lessThanEqual", attribute, value).toString() static greaterThan = (attribute, value) => - Query.addQuery(attribute, "greaterThan", value); + new Query("greaterThan", attribute, value).toString() static greaterThanEqual = (attribute, value) => - Query.addQuery(attribute, "greaterThanEqual", value); + new Query("greaterThanEqual", attribute, value).toString() - static isNull = (attribute) => - `isNull("${attribute}")`; + static isNull = attribute => + new Query("isNull", attribute).toString() - static isNotNull = (attribute) => - `isNotNull("${attribute}")`; + static isNotNull = attribute => + new Query("isNotNull", attribute).toString() static between = (attribute, start, end) => - `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})` + new Query("between", attribute, [start, end]).toString() static startsWith = (attribute, value) => - Query.addQuery(attribute, "startsWith", value); + new Query("startsWith", attribute, value).toString() static endsWith = (attribute, value) => - Query.addQuery(attribute, "endsWith", value); + new Query("endsWith", attribute, value).toString() - static select = (attributes) => - `select([${attributes.map((attr) => `"${attr}"`).join(",")}])`; + static select = attributes => + new Query("select", undefined, attributes).toString() static search = (attribute, value) => - Query.addQuery(attribute, "search", value); + new Query("search", attribute, value).toString() + + static orderDesc = attribute => + new Query("orderDesc", attribute).toString() - static orderDesc = (attribute) => - `orderDesc("${attribute}")`; + static orderAsc = attribute => + new Query("orderAsc", attribute).toString() - static orderAsc = (attribute) => - `orderAsc("${attribute}")`; + static cursorAfter = documentId => + new Query("cursorAfter", undefined, documentId).toString() - static cursorAfter = (documentId) => - `cursorAfter("${documentId}")`; + static cursorBefore = documentId => + new Query("cursorBefore", undefined, documentId).toString() - static cursorBefore = (documentId) => - `cursorBefore("${documentId}")`; + static limit = limit => + new Query("limit", undefined, limit).toString() - static limit = (limit) => - `limit(${limit})`; + static offset = offset => + new Query("offset", undefined, offset).toString() - static offset = (offset) => - `offset(${offset})`; + static contains = (attribute, value) => + new Query("contains", attribute, value).toString() - static addQuery = (attribute, method, value) => - value instanceof Array - ? `${method}("${attribute}", [${value - .map((v) => Query.parseValues(v)) - .join(",")}])` - : `${method}("${attribute}", [${Query.parseValues(value)}])`; + static or = (queries) => + new Query("or", undefined, queries.map((query) => JSON.parse(query))).toString() - static parseValues = (value) => - typeof value === "string" || value instanceof String - ? `"${value}"` - : `${value}`; + static and = (queries) => + new Query("and", undefined, queries.map((query) => JSON.parse(query))).toString(); } +Query.prototype.toString = function () { + return JSON.stringify({ + method: this.method, + attribute: this.attribute, + values: this.values + }) +} + module.exports = Query; diff --git a/templates/node/lib/services/service.js.twig b/templates/node/lib/services/service.js.twig index acfa340a3..fcf204a39 100644 --- a/templates/node/lib/services/service.js.twig +++ b/templates/node/lib/services/service.js.twig @@ -5,6 +5,8 @@ const client = require('../client.js'); const Stream = require('stream'); const { promisify } = require('util'); const fs = require('fs'); +const { File } = require('undici'); +const Query = require('../query.js'); class {{ service.name | caseUcfirst }} extends Service { @@ -25,6 +27,9 @@ class {{ service.name | caseUcfirst }} extends Service { {% for parameter in method.parameters.all %} * @param {{ '{' }}{{ parameter | typeName }}{{ '}' }} {{ parameter.name | caseCamel | escapeKeyword }} {% endfor %} +{% if 'multipart/form-data' in method.consumes %} + * @param {CallableFunction} onProgress +{% endif %} * @throws {{ '{' }}{{ spec.title | caseUcfirst}}Exception} * @returns {Promise} */ diff --git a/templates/node/package.json.twig b/templates/node/package.json.twig index 6a8eae40c..468b0094e 100644 --- a/templates/node/package.json.twig +++ b/templates/node/package.json.twig @@ -14,7 +14,6 @@ "@types/node": "^18.16.1" }, "dependencies": { - "axios": "^1.4.0", - "form-data": "^4.0.0" + "undici": "^5.28.2" } } diff --git a/templates/php/base/params.twig b/templates/php/base/params.twig index 8557222a4..67a6fee93 100644 --- a/templates/php/base/params.twig +++ b/templates/php/base/params.twig @@ -11,13 +11,11 @@ if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) { $apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }}; } - {% endfor %} {% for parameter in method.parameters.body %} if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) { $apiParams['{{ parameter.name }}'] = ${{ parameter.name | caseCamel | escapeKeyword }}; } - {% endfor %} {% for parameter in method.parameters.formData %} if (!is_null(${{ parameter.name | caseCamel | escapeKeyword }})) { diff --git a/templates/php/base/requests/api.twig b/templates/php/base/requests/api.twig index b22cf8ca8..acb6aadd5 100644 --- a/templates/php/base/requests/api.twig +++ b/templates/php/base/requests/api.twig @@ -1,8 +1,14 @@ - return $this->client->call(Client::METHOD_{{ method.method | caseUpper }}, $apiPath, [ -{% for parameter in method.parameters.header %} - '{{ parameter.name }}' => ${{ parameter.name | caseCamel | escapeKeyword }}, -{% endfor %} -{% for key, header in method.headers %} - '{{ key }}' => '{{ header }}', -{% endfor %} - ], $apiParams); \ No newline at end of file + return $this->client->call( + Client::METHOD_{{ method.method | caseUpper }}, + $apiPath, + [ + {%~ for parameter in method.parameters.header %} + '{{ parameter.name }}' => ${{ parameter.name | caseCamel | escapeKeyword }}, + {%~ endfor %} + {%~ for key, header in method.headers %} + '{{ key }}' => '{{ header }}', + {%~ endfor %} + ], + $apiParams{% if method.type == 'webAuth' -%}, 'location'{% endif %} + + ); \ No newline at end of file diff --git a/templates/php/docs/example.md.twig b/templates/php/docs/example.md.twig index 12dedebfe..ded2f259d 100644 --- a/templates/php/docs/example.md.twig +++ b/templates/php/docs/example.md.twig @@ -5,25 +5,39 @@ use {{ spec.title | caseUcfirst }}\Client; use {{ spec.title | caseUcfirst }}\InputFile; {% endif %} use {{ spec.title | caseUcfirst }}\Services\{{ service.name | caseUcfirst }}; +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +use {{ spec.title | caseUcfirst }}\Enums\{{parameter.enumName | caseUcfirst}}; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} -$client = new Client(); - -{% if method.auth|length > 0 %} -$client +$client = (new Client()) + {%~ if method.auth|length > 0 %} ->setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - ->set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %}; + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + ->set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last%};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} -{% endif %} -${{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}($client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); +${{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}($client); -$result = ${{ service.name | caseCamel }}->{{ method.name | caseCamel }}({% for parameter in method.parameters.all %}{% if loop.first %} -{% endif %} - {% if parameter.required %}{{ parameter.name }}: {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} +$result = ${{ service.name | caseCamel }}->{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} + + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseCamel }}: {% if parameter.enumValues | length > 0%}{{ parameter.enumName }}::{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}(){% else%}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %}{% if not parameter.required %} // optional{% endif %} -{% else %}{{ parameter.name }}: {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // optional -{% endif %}{% endfor %} -); \ No newline at end of file + {%~ endfor -%} +{% if method.parameters.all | length > 0 %});{% endif %} diff --git a/templates/php/src/Client.php.twig b/templates/php/src/Client.php.twig index c7443274d..b1e192fa6 100644 --- a/templates/php/src/Client.php.twig +++ b/templates/php/src/Client.php.twig @@ -120,14 +120,11 @@ class Client * @return array|string * @throws {{spec.title | caseUcfirst}}Exception */ - public function call($method, $path = '', $headers = array(), array $params = array()) + public function call($method, $path = '', $headers = array(), array $params = array(), ?string $responseType = null) { - $headers = array_merge($this->headers, $headers); - $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); - $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; + $headers = array_merge($this->headers, $headers); + $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); + $responseHeaders = []; switch ($headers['content-type']) { case 'application/json': @@ -152,7 +149,7 @@ class Client curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':{{ language.name | caseLower }}-' . phpversion()); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $responseType !== 'location'); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', strtolower($header), 2); @@ -177,10 +174,10 @@ class Client } $responseBody = curl_exec($ch); - $responseType = $responseHeaders['content-type'] ?? ''; + $contentType = $responseHeaders['content-type'] ?? ''; $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - switch(substr($responseType, 0, strpos($responseType, ';'))) { + switch(substr($contentType, 0, strpos($contentType, ';'))) { case 'application/json': $responseBody = json_decode($responseBody, true); break; @@ -200,6 +197,9 @@ class Client } } + if ($responseType === 'location') { + return $responseHeaders['location']; + } return $responseBody; } diff --git a/templates/php/src/Enums/Enum.php.twig b/templates/php/src/Enums/Enum.php.twig new file mode 100644 index 000000000..08f74c840 --- /dev/null +++ b/templates/php/src/Enums/Enum.php.twig @@ -0,0 +1,41 @@ +value = $value; + } + + public function __toString(): string + { + return $this->value; + } + + public function jsonSerialize(): string + { + return $this->value; + } + +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + public static function {{ key | caseEnumKey }}(): {{ enum.name | caseUcfirst | overrideIdentifier}} + { + if (!isset(self::${{ key | caseEnumKey }})) { + self::${{ key | caseEnumKey }} = new {{ enum.name | caseUcfirst | overrideIdentifier }}('{{ value }}'); + } + return self::${{ key | caseEnumKey }}; + } +{% endfor %} +} \ No newline at end of file diff --git a/templates/php/src/Query.php.twig b/templates/php/src/Query.php.twig index e249e8fcc..cff70af56 100644 --- a/templates/php/src/Query.php.twig +++ b/templates/php/src/Query.php.twig @@ -2,8 +2,39 @@ namespace {{ spec.title | caseUcfirst }}; -class Query +class Query implements \JsonSerializable { + protected string $method; + protected ?string $attribute; + protected ?array $values; + + public function __construct(string $method, ?string $attribute = null, $values = null) + { + $this->method = $method; + $this->attribute = $attribute; + + if (is_null($values) || is_array($values)) { + $this->values = $values; + } else { + $this->values = [$values]; + } + + } + + public function __toString(): string + { + return json_encode($this->jsonSerialize()); + } + + public function jsonSerialize() + { + return array_filter([ + 'method' => $this->method, + 'attribute' => $this->attribute, + 'values' => $this->values, + ]); + } + /** * Equal * @@ -13,7 +44,7 @@ class Query */ public static function equal(string $attribute, $value): string { - return self::addQuery($attribute, 'equal', $value); + return (new Query('equal', $attribute, $value))->__toString(); } /** @@ -25,7 +56,7 @@ class Query */ public static function notEqual(string $attribute, $value): string { - return self::addQuery($attribute, 'notEqual', $value); + return (new Query('notEqual', $attribute, $value))->__toString(); } /** @@ -37,7 +68,7 @@ class Query */ public static function lessThan(string $attribute, $value): string { - return self::addQuery($attribute, 'lessThan', $value); + return (new Query('lessThan', $attribute, $value))->__toString(); } /** @@ -49,7 +80,7 @@ class Query */ public static function lessThanEqual(string $attribute, $value): string { - return self::addQuery($attribute, 'lessThanEqual', $value); + return (new Query('lessThanEqual', $attribute, $value))->__toString(); } /** @@ -61,7 +92,7 @@ class Query */ public static function greaterThan(string $attribute, $value): string { - return self::addQuery($attribute, 'greaterThan', $value); + return (new Query('greaterThan', $attribute, $value))->__toString(); } /** @@ -73,7 +104,7 @@ class Query */ public static function greaterThanEqual(string $attribute, $value): string { - return self::addQuery($attribute, 'greaterThanEqual', $value); + return (new Query('greaterThanEqual', $attribute, $value))->__toString(); } /** @@ -85,7 +116,7 @@ class Query */ public static function search(string $attribute, string $value): string { - return self::addQuery($attribute, 'search', $value); + return (new Query('search', $attribute, $value))->__toString(); } /** @@ -96,7 +127,7 @@ class Query */ public static function isNull(string $attribute): string { - return 'isNull("' . $attribute . '")'; + return (new Query('isNull', $attribute, null))->__toString(); } /** @@ -107,7 +138,7 @@ class Query */ public static function isNotNull(string $attribute): string { - return 'isNotNull("' . $attribute . '")'; + return (new Query('isNotNull', $attribute, null))->__toString(); } /** @@ -120,10 +151,7 @@ class Query */ public static function between(string $attribute, $start, $end): string { - $start = self::parseValues($start); - $end = self::parseValues($end); - - return "between(\"{$attribute}\", {$start}, {$end})"; + return (new Query('between', $attribute, [$start, $end]))->__toString(); } /** @@ -135,7 +163,7 @@ class Query */ public static function startsWith(string $attribute, string $value): string { - return self::addQuery($attribute, 'startsWith', $value); + return (new Query('startsWith', $attribute, $value))->__toString(); } /** @@ -147,7 +175,7 @@ class Query */ public static function endsWith(string $attribute, string $value): string { - return self::addQuery($attribute, 'endsWith', $value); + return (new Query('endsWith', $attribute, $value))->__toString(); } /** @@ -158,7 +186,7 @@ class Query */ public static function select(array $attributes): string { - return 'select([' . implode(",", array_map(function ($attr) {return '"' . $attr . '"';}, $attributes)) . '])'; + return (new Query('select', null, $attributes))->__toString(); } /** @@ -167,8 +195,9 @@ class Query * @param string $documentId * @return string */ - public static function cursorAfter(string $documentId): string { - return 'cursorAfter("' . $documentId . '")'; + public static function cursorAfter(string $documentId): string + { + return (new Query('cursorAfter', null, $documentId))->__toString(); } /** @@ -177,8 +206,9 @@ class Query * @param string $documentId * @return string */ - public static function cursorBefore(string $documentId): string { - return 'cursorBefore("' . $documentId . '")'; + public static function cursorBefore(string $documentId): string + { + return (new Query('cursorBefore', null, $documentId))->__toString(); } /** @@ -187,8 +217,9 @@ class Query * @param string $attribute * @return string */ - public static function orderAsc(string $attribute): string { - return 'orderAsc("' . $attribute . '")'; + public static function orderAsc(string $attribute): string + { + return (new Query('orderAsc', $attribute, null))->__toString(); } /** @@ -197,8 +228,9 @@ class Query * @param string $attribute * @return string */ - public static function orderDesc(string $attribute): string { - return 'orderDesc("' . $attribute . '")'; + public static function orderDesc(string $attribute): string + { + return (new Query('orderDesc', $attribute, null))->__toString(); } /** @@ -207,8 +239,9 @@ class Query * @param int $limit * @return string */ - public static function limit(int $limit): string { - return 'limit(' . $limit . ')'; + public static function limit(int $limit): string + { + return (new Query('limit', null, $limit))->__toString(); } /** @@ -217,35 +250,48 @@ class Query * @param int $offset * @return string */ - public static function offset(int $offset): string { - return 'offset(' . $offset . ')'; + public static function offset(int $offset): string + { + return (new Query('offset', null, $offset))->__toString(); } /** - * Add Query + * Contains * * @param string $attribute - * @param string $method - * @param mixed $value + * @param string $value * @return string */ - private static function addQuery(string $attribute, string $method, $value) + public static function contains(string $attribute, string $value): string { - return is_array($value) ? $method . '("' . $attribute . '", [' . implode(",", array_map(function ($item) {return self::parseValues($item);}, $value)) . '])' : $method . '("' . $attribute . '", [' . self::parseValues($value) . '])'; + return (new Query('contains', $attribute, $value))->__toString(); } /** - * @param mixed $value + * Or + * + * @param array $queries * @return string */ - private static function parseValues($value): string + public static function or(array $queries): string { - if (is_string($value)) { - return '"' . $value . '"'; + foreach ($queries as &$query) { + $query = \json_decode($query, true); } - if (is_bool($value)) { - return $value ? 'true' : 'false'; + return (new Query('or', null, $queries))->__toString(); + } + + /** + * And + * + * @param array $queries + * @return string + */ + public static function and(array $queries): string + { + foreach ($queries as &$query) { + $query = \json_decode($query, true); } - return $value; + return (new Query('and', null, $queries))->__toString(); } } diff --git a/templates/php/src/Services/Service.php.twig b/templates/php/src/Services/Service.php.twig index 73b9c42c8..8d0b54201 100644 --- a/templates/php/src/Services/Service.php.twig +++ b/templates/php/src/Services/Service.php.twig @@ -6,6 +6,22 @@ use {{ spec.title | caseUcfirst }}\{{spec.title | caseUcfirst}}Exception; use {{ spec.title | caseUcfirst }}\Client; use {{ spec.title | caseUcfirst }}\Service; use {{ spec.title | caseUcfirst }}\InputFile; +{% set added = [] %} +{% for method in service.methods %} +{% for parameter in method.parameters.all %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +use {{ spec.title | caseUcfirst }}\Enums\{{ name | caseUcfirst }}; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} class {{ service.name | caseUcfirst }} extends Service { @@ -23,25 +39,25 @@ class {{ service.name | caseUcfirst }} extends Service * {% endif %} {% for parameter in method.parameters.all %} - * @param {{ parameter | typeName }}${{ parameter.name | caseCamel | escapeKeyword }} + * @param {{ parameter | typeName }} ${{ parameter.name | caseCamel | escapeKeyword }} {% endfor %} * @throws {{spec.title | caseUcfirst}}Exception * @return {{ method | getReturn }} */ - public function {{ method.name | caseCamel }}({% for parameter in method.parameters.all %}{{ parameter | typeName }}${{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required %} = null{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, callable $onProgress = null{% endif %}): {{ method | getReturn }} + public function {{ method.name | caseCamel }}({% for parameter in method.parameters.all %}{{ parameter | typeName }} ${{ parameter.name | caseCamel | escapeKeyword }}{% if not parameter.required %} = null{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, callable $onProgress = null{% endif %}): {{ method | getReturn }} { $apiPath = str_replace([{% for parameter in method.parameters.path %}'{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}'{% if not loop.last %}, {% endif %}{% endfor %}], [{% for parameter in method.parameters.path %}${{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last %}, {% endif %}{% endfor %}], '{{ method.path }}'); -{{ include('php/base/params.twig') }} -{% if 'multipart/form-data' in method.consumes %} -{{ include('php/base/requests/file.twig') }} -{% else %} -{{ include('php/base/requests/api.twig') }} -{% endif %} + {{~ include('php/base/params.twig') -}} + {%~ if 'multipart/form-data' in method.consumes %} + {{~ include('php/base/requests/file.twig') }} + {%~ else %} + {{~ include('php/base/requests/api.twig') }} + {%~ endif %} } -{% if not loop.last %} + {%~ if not loop.last %} -{% endif %} -{% endfor %} + {%~ endif %} + {%~ endfor %} } diff --git a/templates/python/base/requests/api.twig b/templates/python/base/requests/api.twig index e305a8d9b..82ef6299f 100644 --- a/templates/python/base/requests/api.twig +++ b/templates/python/base/requests/api.twig @@ -5,4 +5,4 @@ {% for key, header in method.headers %} '{{ key }}': '{{ header }}', {% endfor %} - }, api_params) \ No newline at end of file + }, api_params{% if method.type == 'webAuth' %}, response_type='location'{% endif %}) \ No newline at end of file diff --git a/templates/python/docs/example.md.twig b/templates/python/docs/example.md.twig index b0fb3c6bc..cbfba96e1 100644 --- a/templates/python/docs/example.md.twig +++ b/templates/python/docs/example.md.twig @@ -2,27 +2,40 @@ from {{ spec.title | caseSnake }}.client import Client {% if method.parameters.all | filter((param) => param.type == 'file') | length > 0 %} from {{ spec.title | caseSnake }}.input_file import InputFile {% endif %} -from {{ spec.title | caseSnake }}.services.{{ service.name | caseSnake }} import {{ service.name | caseUcfirst }} +{% set added = [] %} +{% for parameter in method.parameters.all %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +from {{ spec.title | caseSnake }}.enums import {{parameter.enumName | caseUcfirst}} +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} client = Client() - {% if method.auth|length > 0 %} -(client - .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint +client.set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint {% for node in method.auth %} {% for key,header in node|keys %} - .set_{{header | caseSnake}}('{{node[header]['x-appwrite']['demo']}}') # {{node[header].description}} +client.set_{{header | caseSnake}}('{{node[header]['x-appwrite']['demo']}}') # {{node[header].description}} +{% endfor %} {% endfor %} -{% endfor %}) {% endif %} -{{ service.name | caseSnake }} = {{ service.name | caseUcfirst }}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}) +{{ service.name | caseSnake }} = {{ service.name | caseUcfirst }}(client) -result = {{ service.name | caseSnake }}.{{ method.name | caseSnake }}({% for parameter in method.parameters.all %}{% if loop.first %} -{% endif %} - {% if parameter.required %}{{ parameter.name | caseSnake }} = {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} +result = {{ service.name | caseSnake }}.{{ method.name | caseSnake }}({% if method.parameters.all | length == 0 %}){% endif %} -{% else %}{{ parameter.name | caseSnake }} = {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} # optional -{% endif %}{% endfor %} -) + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseSnake }} = {% if parameter.enumValues | length > 0 %}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %}{% if not parameter.required %} # optional{% endif %} + {%~ endfor %} +{% if method.parameters.all | length > 0 %}) +{% endif %} diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index 48ed835a4..aad449831 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -1,8 +1,10 @@ import io -import requests +import json import os +import requests from .input_file import InputFile from .exception import {{spec.title | caseUcfirst}}Exception +from .encoders.value_class_encoder import ValueClassEncoder class Client: def __init__(self): @@ -43,7 +45,7 @@ class Client: return self {% endfor %} - def call(self, method, path='', headers=None, params=None): + def call(self, method, path='', headers=None, params=None, response_type='json'): if headers is None: headers = {} @@ -53,7 +55,6 @@ class Client: params = {k: v for k, v in params.items() if v is not None} # Remove None values from params dictionary data = {} - json = {} files = {} stringify = False @@ -64,8 +65,7 @@ class Client: params = {} if headers['content-type'].startswith('application/json'): - json = data - data = {} + data = json.dumps(data, cls=ValueClassEncoder) if headers['content-type'].startswith('multipart/form-data'): del headers['content-type'] @@ -74,23 +74,28 @@ class Client: if isinstance(data[key], InputFile): files[key] = (data[key].filename, data[key].data) del data[key] + data = self.flatten(data, stringify=stringify) + response = None try: response = requests.request( # call method dynamically https://stackoverflow.com/a/4246075/2299554 method=method, url=self._endpoint + path, params=self.flatten(params, stringify=stringify), - data=self.flatten(data), - json=json, + data=data, files=files, headers=headers, verify=(not self._self_signed), + allow_redirects=False if response_type == 'location' else True ) response.raise_for_status() content_type = response.headers['Content-Type'] + if response_type == 'location': + return response.headers.get('Location') + if content_type.startswith('application/json'): return response.json() diff --git a/templates/python/package/encoders/value_class_encoder.py.twig b/templates/python/package/encoders/value_class_encoder.py.twig new file mode 100644 index 000000000..ee0bb49c6 --- /dev/null +++ b/templates/python/package/encoders/value_class_encoder.py.twig @@ -0,0 +1,13 @@ +import json +{%~ for enum in spec.enums %} +from ..enums.{{ enum.name | caseSnake }} import {{ enum.name | caseUcfirst | overrideIdentifier }} +{%~ endfor %} + +class ValueClassEncoder(json.JSONEncoder): + def default(self, o): + {%~ for enum in spec.enums %} + if isinstance(o, {{ enum.name | caseUcfirst | overrideIdentifier }}): + return o.value + + {%~ endfor %} + return super().default(o) \ No newline at end of file diff --git a/templates/python/package/enums/enum.py.twig b/templates/python/package/enums/enum.py.twig new file mode 100644 index 000000000..e8883e914 --- /dev/null +++ b/templates/python/package/enums/enum.py.twig @@ -0,0 +1,7 @@ +from enum import Enum + +class {{ enum.name | caseUcfirst | overrideIdentifier }}(Enum): +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseUpper | replace({'-': '',' ':'_', '(': '', ')': '', '.': ''})}} = "{{ value }}" +{% endfor %} \ No newline at end of file diff --git a/templates/python/package/query.py.twig b/templates/python/package/query.py.twig index d970bd8c9..f25e33d56 100644 --- a/templates/python/package/query.py.twig +++ b/templates/python/package/query.py.twig @@ -1,92 +1,108 @@ -class Query: +import json + + +# Inherit from dict to allow for easy serialization +class Query(): + def __init__(self, method, attribute=None, values=None): + self.method = method + + if attribute is not None: + self.attribute = attribute + + if values is not None: + self.values = values if isinstance(values, list) else [values] + + def __str__(self): + return json.dumps( + self.__dict__, + separators=(",", ":"), + default=lambda obj: obj.__dict__ + ) + @staticmethod def equal(attribute, value): - return Query.add_query(attribute, "equal", value) + return str(Query("equal", attribute, value)) @staticmethod def not_equal(attribute, value): - return Query.add_query(attribute, "notEqual", value) - + return str(Query("notEqual", attribute, value)) + @staticmethod def less_than(attribute, value): - return Query.add_query(attribute, "lessThan", value) - + return str(Query("lessThan", attribute, value)) + @staticmethod def less_than_equal(attribute, value): - return Query.add_query(attribute, "lessThanEqual", value) - + return str(Query("lessThanEqual", attribute, value)) + @staticmethod def greater_than(attribute, value): - return Query.add_query(attribute, "greaterThan", value) - + return str(Query("greaterThan", attribute, value)) + @staticmethod def greater_than_equal(attribute, value): - return Query.add_query(attribute, "greaterThanEqual", value) + return str(Query("greaterThanEqual", attribute, value)) @staticmethod def is_null(attribute): - return f'isNull("{attribute}")' + return str(Query("isNull", attribute, None)) @staticmethod def is_not_null(attribute): - return f'isNotNull("{attribute}")' + return str(Query("isNotNull", attribute, None)) @staticmethod def between(attribute, start, end): - return f'between("{attribute}", {Query.parseValues(start)}, {Query.parseValues(end)})' + return str(Query("between", attribute, [start, end])) @staticmethod def starts_with(attribute, value): - return Query.add_query(attribute, "startsWith", value) + return str(Query("startsWith", attribute, value)) @staticmethod def ends_with(attribute, value): - return Query.add_query(attribute, "endsWith", value) + return str(Query("endsWith", attribute, value)) @staticmethod def select(attributes): - return f'select([{",".join(map(Query.parseValues, attributes))}])' + return str(Query("select", None, attributes)) @staticmethod def search(attribute, value): - return Query.add_query(attribute, "search", value) + return str(Query("search", attribute, value)) @staticmethod def order_asc(attribute): - return f'orderAsc("{attribute}")' + return str(Query("orderAsc", attribute, None)) @staticmethod def order_desc(attribute): - return f'orderDesc("{attribute}")' + return str(Query("orderDesc", attribute, None)) @staticmethod def cursor_before(id): - return f'cursorBefore("{id}")' + return str(Query("cursorBefore", None, id)) @staticmethod def cursor_after(id): - return f'cursorAfter("{id}")' + return str(Query("cursorAfter", None, id)) @staticmethod def limit(limit): - return f'limit({limit})' + return str(Query("limit", None, limit)) @staticmethod def offset(offset): - return f'offset({offset})' + return str(Query("offset", None, offset)) + + @staticmethod + def contains(attribute, value): + return str(Query("contains", attribute, value)) @staticmethod - def add_query(attribute, method, value): - if type(value) == list: - return f'{method}("{attribute}", [{",".join(map(Query.parseValues, value))}])' - else: - return f'{method}("{attribute}", [{Query.parseValues(value)}])' + def or_queries(queries): + return str(Query("or", None, [json.loads(query) for query in queries])) @staticmethod - def parseValues(value): - if type(value) == str: - return f'"{value}"' - elif type(value) == bool: - return str(value).lower() - else: - return str(value) \ No newline at end of file + def and_queries(queries): + return str(Query("and", None, [json.loads(query) for query in queries])) diff --git a/templates/ruby/base/requests/api.twig b/templates/ruby/base/requests/api.twig index 312e078e8..c56a87c03 100644 --- a/templates/ruby/base/requests/api.twig +++ b/templates/ruby/base/requests/api.twig @@ -3,7 +3,5 @@ path: api_path, headers: api_headers, params: api_params, -{% if method.responseModel and method.responseModel != 'any' %} - response_type: Models::{{method.responseModel | caseUcfirst}} -{% endif %} + {% if method.type == "webAuth" %}response_type: "location"{% elseif method.responseModel and method.responseModel != 'any' %}response_type: Models::{{method.responseModel | caseUcfirst}}{% endif %} ) \ No newline at end of file diff --git a/templates/ruby/docs/example.md.twig b/templates/ruby/docs/example.md.twig index 8dc40ab22..2f258554b 100644 --- a/templates/ruby/docs/example.md.twig +++ b/templates/ruby/docs/example.md.twig @@ -1,24 +1,33 @@ require '{{ spec.title | lower }}' include {{ spec.title | caseUcfirst }} +{% set break = false %} +{% for parameter in method.parameters.all %} +{% if not break %} +{% if method == parameter.required %} +{% if parameter.enumValues is not empty %} +include {{ spec.title | caseUcfirst }}::Enums +{% set break = true %} +{% endif %} +{% endif %} +{% endif %} +{% endfor %} client = Client.new .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} .set_{{header|caseSnake}}('{{node[header]['x-appwrite']['demo']}}') # {{node[header].description}} -{% endfor %} -{% endfor %} + {%~ endfor %} + {%~ endfor %} -{{ service.name | caseSnake }} = {{ service.name | caseUcfirst }}.new(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{parameter.name | caseSnake}}:{{ parameter | paramExample }}{% endfor %}{% endif %}) +{{ service.name | caseSnake }} = {{ service.name | caseUcfirst }}.new(client) -response = {{ service.name | caseSnake }}.{{ method.name | caseSnake }}({% for parameter in method.parameters.all %}{% if loop.first %} +result = {{ service.name | caseSnake }}.{{ method.name | caseSnake }}({% if method.parameters.all | length == 0 %}){% endif %} -{% endif %} - {% if parameter.required %}{{parameter.name|caseSnake}}: {% if parameter.type == 'file' %}{{ parameter | paramExample }}{% else %}{{ parameter | paramExample }}{% if not loop.last %}, {% endif %}{% endif %} + {%~ for parameter in method.parameters.all %} + {{ parameter.name | caseSnake }}: {% if parameter.enumValues | length > 0%}{{ parameter.enumName }}::{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %}{% if not parameter.required %} # optional{% endif %} -{% else %}{{parameter.name|caseSnake}}: {% if parameter.type == 'file' %}{{ parameter | paramExample }}{% else %}{{ parameter | paramExample }}{% if not loop.last %}, {% endif %} # optional + {%~ endfor -%} +{% if method.parameters.all | length > 0 %}) {% endif %} -{% endif %}{% endfor %}) - -puts response.inspect diff --git a/templates/ruby/lib/container.rb.twig b/templates/ruby/lib/container.rb.twig index 688d54357..fd83f4b57 100644 --- a/templates/ruby/lib/container.rb.twig +++ b/templates/ruby/lib/container.rb.twig @@ -16,6 +16,10 @@ require_relative '{{ spec.title | caseSnake }}/id' require_relative '{{ spec.title | caseSnake }}/models/{{ defintion.name | caseSnake }}' {% endfor %} +{% for enum in spec.enums %} +require_relative '{{ spec.title | caseSnake }}/enums/{{ enum.name | caseSnake }}' +{% endfor %} + {% for service in spec.services %} require_relative '{{ spec.title | caseSnake }}/services/{{ service.name | caseSnake }}' {% endfor %} \ No newline at end of file diff --git a/templates/ruby/lib/container/client.rb.twig b/templates/ruby/lib/container/client.rb.twig index b0c872809..db00c686c 100644 --- a/templates/ruby/lib/container/client.rb.twig +++ b/templates/ruby/lib/container/client.rb.twig @@ -170,7 +170,7 @@ module {{ spec.title | caseUcfirst }} offset += @chunk_size if defined? result['$id'] - headers['x-{{ spec.title }}-id'] = result['$id'] + headers['x-{{ spec.title | caseLower }}-id'] = result['$id'] end on_progress.call({ @@ -225,12 +225,15 @@ module {{ spec.title | caseUcfirst }} rescue => error raise {{spec.title | caseUcfirst}}::Exception.new(error.message) end + + location = response['location'] + if response_type == "location" + return location + end # Handle Redirects - if response.class == Net::HTTPRedirection || response.class == Net::HTTPMovedPermanently - location = response['location'] + if (response.class == Net::HTTPRedirection || response.class == Net::HTTPMovedPermanently) uri = URI.parse(uri.scheme + "://" + uri.host + "" + location) - return fetch(method, uri, headers, {}, response_type, limit - 1) end diff --git a/templates/ruby/lib/container/enums/enum.rb.twig b/templates/ruby/lib/container/enums/enum.rb.twig new file mode 100644 index 000000000..c640146e4 --- /dev/null +++ b/templates/ruby/lib/container/enums/enum.rb.twig @@ -0,0 +1,10 @@ +module {{ spec.title | caseUcfirst }} + module Enums + module {{ enum.name | caseUcfirst | overrideIdentifier }} + {%~ for value in enum.enum %} + {%~ set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | caseUpper | replace({'-': '_', ' ':'_', '(': '', ')': '', '.': ''}) }} = '{{ value }}' + {%~ endfor %} + end + end +end \ No newline at end of file diff --git a/templates/ruby/lib/container/query.rb.twig b/templates/ruby/lib/container/query.rb.twig index e62ee4b92..61558485f 100644 --- a/templates/ruby/lib/container/query.rb.twig +++ b/templates/ruby/lib/container/query.rb.twig @@ -1,94 +1,119 @@ +require 'json' + module {{spec.title | caseUcfirst}} class Query + def initialize(method, attribute = nil, values = nil) + @method = method + @attribute = attribute + + if values != nil + if values.is_a?(Array) + @values = values + else + @values = [values] + end + end + end + + def to_json(*args) + { + method: @method, + attribute: @attribute, + values: @values + }.compact.to_json(*args) + end + + def to_s + return self.to_json + end + class << Query def equal(attribute, value) - return add_query(attribute, "equal", value) + return Query.new("equal", attribute, value).to_s end def not_equal(attribute, value) - return add_query(attribute, "notEqual", value) + return Query.new("notEqual", attribute, value).to_s end def less_than(attribute, value) - return add_query(attribute, "lessThan", value) + return Query.new("lessThan", attribute, value).to_s end def less_than_equal(attribute, value) - return add_query(attribute, "lessThanEqual", value) + return Query.new("lessThanEqual", attribute, value).to_s end def greater_than(attribute, value) - return add_query(attribute, "greaterThan", value) + return Query.new("greaterThan", attribute, value).to_s end def greater_than_equal(attribute, value) - return add_query(attribute, "greaterThanEqual", value) + return Query.new("greaterThanEqual", attribute, value).to_s end def is_null(attribute) - return "isNull(\"#{attribute}\")" + return Query.new("isNull", attribute, nil).to_s end def is_not_null(attribute) - return "isNotNull(\"#{attribute}\")" + return Query.new("isNotNull", attribute, nil).to_s end def between(attribute, start, ending) - return "between(\"#{attribute}\", #{parse_values(start)}, #{parse_values(ending)})" + return Query.new("between", attribute, [start, ending]).to_s end def starts_with(attribute, value) - return add_query(attribute, "startsWith", value) + return Query.new("startsWith", attribute, value).to_s end def ends_with(attribute, value) - return add_query(attribute, "endsWith", value) + return Query.new("endsWith", attribute, value).to_s end def select(attributes) - return "select([#{attributes.map {|attribute| "\"#{attribute}\""}.join(',')}])" + return Query.new("select", nil, attributes).to_s end def search(attribute, value) - return add_query(attribute, "search", value) + return Query.new("search", attribute, value).to_s end def order_asc(attribute) - return "orderAsc(\"#{attribute}\")" + return Query.new("orderAsc", attribute, nil).to_s end def order_desc(attribute) - return "orderDesc(\"#{attribute}\")" + return Query.new("orderDesc", attribute, nil).to_s end def cursor_before(id) - return "cursorBefore(\"#{id}\")" + return Query.new("cursorBefore", nil, id).to_s end def cursor_after(id) - return "cursorAfter(\"#{id}\")" + return Query.new("cursorAfter", nil, id).to_s end def limit(limit) - return "limit(#{limit})" + return Query.new("limit", nil, limit).to_s end def offset(offset) - return "offset(#{offset})" + return Query.new("offset", nil, offset).to_s end - private + def contains(attribute, value) + return Query.new("contains", attribute, value).to_s + end - def add_query(attribute, method, value) - if value.is_a?(Array) - "#{method}(\"#{attribute}\", [#{value.map {|item| parse_values(item)}.join(',')}])" - else - return "#{method}(\"#{attribute}\", [#{parse_values(value)}])" - end + def or(queries) + return Query.new("or", nil, queries.map { |query| JSON.parse(query) }).to_s end - def parse_values(value) - return value.is_a?(String) ? "\"#{value}\"" : value + def and(queries) + return Query.new("and", nil, queries.map { |query| JSON.parse(query) }).to_s end end end diff --git a/templates/swift/.travis.yml b/templates/swift/.travis.yml deleted file mode 100644 index 567f78035..000000000 --- a/templates/swift/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -language: swift -osx_image: xcode12.5 -before_install: - - gem install cocoapods - - pod install - -jobs: - include: - - stage: Pod Lint - script: - - pod lib lint --allow-warnings - on: - tags: true \ No newline at end of file diff --git a/templates/swift/Package.swift.twig b/templates/swift/Package.swift.twig index 0204ec6f2..132ae387f 100644 --- a/templates/swift/Package.swift.twig +++ b/templates/swift/Package.swift.twig @@ -13,12 +13,17 @@ let package = Package( products: [ .library( name: "{{spec.title | caseUcfirst}}", - targets: ["{{spec.title | caseUcfirst}}", "{{spec.title | caseUcfirst}}Models", "JSONCodable"] + targets: [ + "{{spec.title | caseUcfirst}}", + "{{spec.title | caseUcfirst}}Enums", + "{{spec.title | caseUcfirst}}Models", + "JSONCodable" + ] ), ], dependencies: [ .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), - .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0") + .package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"), ], targets: [ .target( @@ -26,16 +31,28 @@ let package = Package( dependencies: [ .product(name: "AsyncHTTPClient", package: "async-http-client"), .product(name: "NIOWebSocket", package: "swift-nio"), + {%~ if spec.definitions is not empty %} "{{spec.title | caseUcfirst}}Models", + {%~ endif %} + {%~ if spec.enums is not empty %} + "{{spec.title | caseUcfirst}}Enums", + {%~ endif %} "JSONCodable" ] ), + {%~ if spec.definitions is not empty %} .target( name: "{{spec.title | caseUcfirst}}Models", dependencies: [ "JSONCodable" ] ), + {%~ endif %} + {%~ if spec.enums is not empty %} + .target( + name: "{{spec.title | caseUcfirst}}Enums" + ), + {%~ endif %} .target( name: "JSONCodable" ), diff --git a/templates/swift/Sources/Client.swift.twig b/templates/swift/Sources/Client.swift.twig index 8be157d3a..23e7297be 100644 --- a/templates/swift/Sources/Client.swift.twig +++ b/templates/swift/Sources/Client.swift.twig @@ -16,47 +16,45 @@ open class Client { open var endPoint = "{{spec.endpoint}}" - open var endPointRealtime: String? = nil - open var headers: [String: String] = [ - "content-type": "", + "content-type": "application/json", "x-sdk-name": "{{ sdk.name }}", "x-sdk-platform": "{{ sdk.platform }}", "x-sdk-language": "{{ language.name | caseLower }}", "x-sdk-version": "{{ sdk.version }}"{% if spec.global.defaultHeaders | length > 0 %},{% endif %} -{% for key,header in spec.global.defaultHeaders %} - "{{key}}": "{{header}}"{% if not loop.last %},{% endif %} + {%~ for key,header in spec.global.defaultHeaders %} + "{{key | caseLower }}": "{{header}}"{% if not loop.last %},{% endif %} -{% endfor %} + {%~ endfor %} ] - open var config: [String: String] = [:] + internal var config: [String: String] = [:] + + internal var selfSigned: Bool = false - open var selfSigned: Bool = false + internal var http: HTTPClient - open var http: HTTPClient + internal var httpForRedirect: HTTPClient - private static let boundaryChars = - "abcdefghijklmnopqrstuvwxyz1234567890" + private static let boundaryChars = "abcdefghijklmnopqrstuvwxyz1234567890" private static let boundary = randomBoundary() - private static var eventLoopGroupProvider = - HTTPClient.EventLoopGroupProvider.createNew + private static var eventLoopGroupProvider = HTTPClient.EventLoopGroupProvider.singleton // MARK: Methods public init() { http = Client.createHTTP() + httpForRedirect = Client.createHTTP(redirectConfiguration: .disallow) addUserAgentHeader() addOriginHeader() } private static func createHTTP( selfSigned: Bool = false, - maxRedirects: Int = 5, - alloweRedirectCycles: Bool = false, + redirectConfiguration: HTTPClient.Configuration.RedirectConfiguration = .follow(max: 5, allowCycles: false), connectTimeout: TimeAmount = .seconds(30), readTimeout: TimeAmount = .seconds(30) ) -> HTTPClient { @@ -64,10 +62,6 @@ open class Client { connect: connectTimeout, read: readTimeout ) - let redirect = HTTPClient.Configuration.RedirectConfiguration.follow( - max: 5, - allowCycles: false - ) var tls = TLSConfiguration .makeClientConfiguration() @@ -79,7 +73,7 @@ open class Client { eventLoopGroupProvider: eventLoopGroupProvider, configuration: HTTPClient.Configuration( tlsConfiguration: tls, - redirectConfiguration: redirect, + redirectConfiguration: redirectConfiguration, timeout: timeout, decompression: .enabled(limit: .none) ) @@ -90,19 +84,20 @@ open class Client { deinit { do { try http.syncShutdown() + try httpForRedirect.syncShutdown() } catch { print(error) } } -{% for header in spec.global.headers %} + {%~ for header in spec.global.headers %} /// /// Set {{header.key | caseUcfirst}} /// -{% if header.description %} + {%~ if header.description %} /// {{header.description}} /// -{% endif %} + {%~ endif %} /// @param String value /// /// @return Client @@ -113,7 +108,7 @@ open class Client { return self } -{% endfor %} + {%~ endfor %} /// /// Set self signed @@ -139,25 +134,6 @@ open class Client { open func setEndpoint(_ endPoint: String) -> Client { self.endPoint = endPoint - if (self.endPointRealtime == nil && endPoint.starts(with: "http")) { - self.endPointRealtime = endPoint - .replacingOccurrences(of: "http://", with: "ws://") - .replacingOccurrences(of: "https://", with: "wss://") - } - - return self - } - - /// - /// Set realtime endpoint. - /// - /// @param String endPoint - /// - /// @return Client - /// - open func setEndpointRealtime(_ endPoint: String) -> Client { - self.endPointRealtime = endPoint - return self } @@ -234,6 +210,74 @@ open class Client { sink: ((ByteBuffer) -> Void)? = nil, converter: ((Any) -> T)? = nil ) async throws -> T { + let request = try prepareRequest( + method: method, + path: path, + headers: headers, + params: params + ) + + return try await execute(request, converter: converter) + } + + /// + /// Make an redirect API call + /// + /// @param String method + /// @param String path + /// @param Dictionary params + /// @param Dictionary headers + /// @return String + /// @throws Exception + /// + open func redirect( + method: String, + path: String = "", + headers: [String: String] = [:], + params: [String: Any?] = [:] + ) async throws -> String? { + let request = try prepareRequest( + method: method, + path: path, + headers: headers, + params: params + ) + + let response = try await httpForRedirect.execute( + request, + timeout: .seconds(30) + ) + + if response.status.code >= 400 { + var message = "" + var data = try await response.body.collect(upTo: Int.max) + var type = "" + + do { + let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] + + message = dict?["message"] as? String ?? response.status.reasonPhrase + type = dict?["type"] as? String ?? "" + } catch { + message = data.readString(length: data.readableBytes)! + } + + throw {{ spec.title | caseUcfirst }}Error( + message: message, + code: Int(response.status.code), + type: type + ) + } + + return response.headers["location"].first + } + + private func prepareRequest( + method: String, + path: String = "", + headers: [String: String] = [:], + params: [String: Any?] = [:] + ) throws -> HTTPClientRequest { let validParams = params.filter { $0.value != nil } let queryParameters = method == "GET" && !validParams.isEmpty @@ -250,13 +294,11 @@ open class Client { request.addDomainCookies() - if "GET" == method { - return try await execute(request, converter: converter) + if "GET" != method { + try buildBody(for: &request, with: validParams) } - try buildBody(for: &request, with: validParams) - - return try await execute(request, withSink: sink, converter: converter) + return request } private func buildBody( @@ -272,67 +314,49 @@ open class Client { private func execute( _ request: HTTPClientRequest, - withSink bufferSink: ((ByteBuffer) -> Void)? = nil, converter: ((Any) -> T)? = nil ) async throws -> T { - func complete(with response: HTTPClientResponse) async throws -> T { - switch response.status.code { - case 0..<400: - if response.headers["Set-Cookie"].count > 0 { - UserDefaults.standard.set( - response.headers["Set-Cookie"], - forKey: URL(string: request.url)!.host! + "-cookies" - ) - } - switch T.self { - case is Bool.Type: - return true as! T - case is ByteBuffer.Type: - return try await response.body.collect(upTo: Int.max) as! T - default: - let data = try await response.body.collect(upTo: Int.max) - if data.readableBytes == 0 { - return true as! T - } - let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] + let response = try await http.execute( + request, + timeout: .seconds(30) + ) - return converter?(dict!) ?? dict! as! T - } + switch response.status.code { + case 0..<400: + switch T.self { + case is Bool.Type: + return true as! T + case is ByteBuffer.Type: + return try await response.body.collect(upTo: Int.max) as! T default: - var message = "" - var data = try await response.body.collect(upTo: Int.max) - var type = "" - - do { - let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] - - message = dict?["message"] as? String ?? response.status.reasonPhrase - type = dict?["type"] as? String ?? "" - } catch { - message = data.readString(length: data.readableBytes)! + let data = try await response.body.collect(upTo: Int.max) + if data.readableBytes == 0 { + return true as! T } + let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] - throw {{ spec.title | caseUcfirst }}Error( - message: message, - code: Int(response.status.code), - type: type - ) + return converter?(dict!) ?? dict! as! T + } + default: + var message = "" + var data = try await response.body.collect(upTo: Int.max) + var type = "" + + do { + let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] + + message = dict?["message"] as? String ?? response.status.reasonPhrase + type = dict?["type"] as? String ?? "" + } catch { + message = data.readString(length: data.readableBytes)! } - } - if bufferSink == nil { - let response = try await http.execute( - request, - timeout: .seconds(30) + throw {{ spec.title | caseUcfirst }}Error( + message: message, + code: Int(response.status.code), + type: type ) - return try await complete(with: response) } - - let response = try await http.execute( - request, - timeout: .seconds(30) - ) - return try await complete(with: response) } func chunkedUpload( @@ -391,7 +415,7 @@ open class Client { while offset < size { let slice = (input.data as! ByteBuffer).getSlice(at: offset, length: Client.chunkSize) ?? (input.data as! ByteBuffer).getSlice(at: offset, length: Int(size - offset)) - + params[paramName] = InputFile.fromBuffer(slice!, filename: input.filename, mimeType: input.mimeType) headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size - 1))/\(size)" @@ -446,7 +470,12 @@ open class Client { || param is [Bool: Any] { encodedParams[key] = param } else { - encodedParams[key] = try! (param as! Encodable).toJson() + let value = try! (param as! Encodable).toJson() + + let range = value.index(value.startIndex, offsetBy: 1).. [String: Any] { return [ {%~ for property in definition.properties %} - "{{ property.name | escapeKeyword }}": {% if property.sub_schema %}{% if property.type == 'array' %}{{property.name | escapeKeyword | removeDollarSign}}.map { $0.toMap() }{% else %}{{property.name | escapeKeyword | removeDollarSign}}.toMap(){% endif %}{% else %}{{property.name | escapeKeyword | removeDollarSign}}{% endif %} as Any{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + "{{ property.name | escapeSwiftKeyword }}": {% if property.sub_schema %}{% if property.type == 'array' %}{{property.name | escapeSwiftKeyword | removeDollarSign}}.map { $0.toMap() }{% else %}{{property.name | escapeSwiftKeyword | removeDollarSign}}.toMap(){% endif %}{% else %}{{property.name | escapeSwiftKeyword | removeDollarSign}}{% endif %} as Any{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} {%~ endfor %} {%~ if definition.additionalProperties %} @@ -50,7 +50,7 @@ public class {{ definition | modelType(spec) | raw }} { public static func from(map: [String: Any] ) -> {{ definition.name | caseUcfirst }} { return {{ definition.name | caseUcfirst }}( {%~ for property in definition.properties %} - {{ property.name | escapeKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} + {{ property.name | escapeSwiftKeyword | removeDollarSign }}: {% if property.sub_schema %}{% if property.type == 'array' %}(map["{{property.name }}"] as! [[String: Any]]).map { {{property.sub_schema | caseUcfirst}}.from(map: $0) }{% else %}{{property.sub_schema | caseUcfirst}}.from(map: map["{{property.name }}"] as! [String: Any]){% endif %}{% else %}map["{{property.name }}"] as{% if property.required %}!{% else %}?{% endif %} {{ property | propertyType(spec) | raw }}{% endif %}{% if not loop.last or (loop.last and definition.additionalProperties) %},{% endif %} {%~ endfor %} {%~ if definition.additionalProperties %} diff --git a/templates/swift/Sources/OAuth/WebAuthComponent.swift.twig b/templates/swift/Sources/OAuth/WebAuthComponent.swift.twig index 1e35d7acf..9083bad3e 100644 --- a/templates/swift/Sources/OAuth/WebAuthComponent.swift.twig +++ b/templates/swift/Sources/OAuth/WebAuthComponent.swift.twig @@ -92,7 +92,10 @@ public class WebAuthComponent { cookie += "; secure" } - UserDefaults.standard.set([cookie], forKey: "\(domain)-cookies") + let existing = UserDefaults.standard.stringArray(forKey: domain) + let new = [cookie] + + UserDefaults.standard.set(new, forKey: domain) WebAuthComponent.onCallback( scheme: components.scheme!, diff --git a/templates/swift/Sources/Query.swift.twig b/templates/swift/Sources/Query.swift.twig index ce1415d75..20c499eea 100644 --- a/templates/swift/Sources/Query.swift.twig +++ b/templates/swift/Sources/Query.swift.twig @@ -1,104 +1,324 @@ -public class Query { +import Foundation + +enum QueryValue: Codable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case query(Query) + + init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + // Attempt to decode each type + if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let intValue = try? container.decode(Int.self) { + self = .int(intValue) + } else if let doubleValue = try? container.decode(Double.self) { + self = .double(doubleValue) + } else if let boolValue = try? container.decode(Bool.self) { + self = .bool(boolValue) + } else if let queryValue = try? container.decode(Query.self) { + self = .query(queryValue) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "QueryValue cannot be decoded") + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let value): + try container.encode(value) + case .int(let value): + try container.encode(value) + case .double(let value): + try container.encode(value) + case .bool(let value): + try container.encode(value) + case .query(let value): + try container.encode(value) + } + } +} + +public struct Query : Codable, CustomStringConvertible { + var method: String + var attribute: String? + var values: [QueryValue]? + + init(method: String, attribute: String? = nil, values: Any? = nil) { + self.method = method + self.attribute = attribute + self.values = Query.convertToQueryValueArray(values) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.method = try container.decode(String.self, forKey: .method) + self.attribute = try container.decodeIfPresent(String.self, forKey: .attribute) + self.values = try container.decodeIfPresent([QueryValue].self, forKey: .values) + } + + private static func convertToQueryValueArray(_ values: Any?) -> [QueryValue]? { + switch values { + case let valueArray as [QueryValue]: + return valueArray + case let stringArray as [String]: + return stringArray.map { .string($0) } + case let intArray as [Int]: + return intArray.map { .int($0) } + case let doubleArray as [Double]: + return doubleArray.map { .double($0) } + case let boolArray as [Bool]: + return boolArray.map { .bool($0) } + case let queryArray as [Query]: + return queryArray.map { .query($0) } + case let stringValue as String: + return [.string(stringValue)] + case let intValue as Int: + return [.int(intValue)] + case let doubleValue as Double: + return [.double(doubleValue)] + case let boolValue as Bool: + return [.bool(boolValue)] + case let queryValue as Query: + return [.query(queryValue)] + default: + return nil + } + } + + enum CodingKeys: String, CodingKey { + case method + case attribute + case values + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(method, forKey: .method) + + if (self.attribute != nil) { + try container.encode(attribute, forKey: .attribute) + } + + if (values != nil) { + try container.encode(values, forKey: .values) + } + } + + public var description: String { + guard let data = try? JSONEncoder().encode(self) else { + return "" + } + + return String(data: data, encoding: .utf8) ?? "" + } public static func equal(_ attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "equal", to: value) + return Query( + method: "equal", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func notEqual(_ attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "notEqual", to: value) + return Query( + method: "notEqual", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func lessThan(_ attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "lessThan", to: value) + return Query( + method: "lessThan", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func lessThanEqual(attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "lessThanEqual", to: value) + return Query( + method: "lessThanEqual", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func greaterThan(_ attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "greaterThan", to: value) + return Query( + method: "greaterThan", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func greaterThanEqual(_ attribute: String, value: Any) -> String { - buildQueryWhere(attribute, is: "greaterThanEqual", to: value) + return Query( + method: "greaterThanEqual", + attribute: attribute, + values: Query.parseValue(value) + ).description } public static func isNull(_ attribute: String) -> String { - "isNull(\"\(attribute)\")" + return Query( + method: "isNull", + attribute: attribute + ).description } public static func isNotNull(_ attribute: String) -> String { - "isNotNull(\"\(attribute)\")" + return Query( + method: "isNotNull", + attribute: attribute + ).description } public static func between(_ attribute: String, start: Int, end: Int) -> String { - "between(\"\(attribute)\", \(start), \(end))" + return Query( + method: "between", + attribute: attribute, + values: [start, end] + ).description } public static func between(_ attribute: String, start: Double, end: Double) -> String { - "between(\"\(attribute)\", \(start), \(end))" + return Query( + method: "between", + attribute: attribute, + values: [start, end] + ).description } public static func between(_ attribute: String, start: String, end: String) -> String { - "between(\"\(attribute)\", \"\(start)\", \"\(end)\")" + return Query( + method: "between", + attribute: attribute, + values: [start, end] + ).description } public static func startsWith(_ attribute: String, value: String) -> String { - buildQueryWhere(attribute, is: "startsWith", to: value) + return Query( + method: "startsWith", + attribute: attribute, + values: [value] + ).description } public static func endsWith(_ attribute: String, value: String) -> String { - buildQueryWhere(attribute, is: "endsWith", to: value) + return Query( + method: "endsWith", + attribute: attribute, + values: [value] + ).description } public static func select(_ attributes: [String]) -> String { - "select([\(attributes.map { "\"\($0)\"" }.joined(separator: ","))])" + return Query( + method: "select", + values: attributes + ).description } public static func search(_ attribute: String, value: String) -> String { - buildQueryWhere(attribute, is: "search", to: value) + return Query( + method: "search", + attribute: attribute, + values: [value] + ).description } public static func orderAsc(_ attribute: String) -> String { - "orderAsc(\"\(attribute)\")" + return Query( + method: "orderAsc", + attribute: attribute + ).description } public static func orderDesc(_ attribute: String) -> String { - "orderDesc(\"\(attribute)\")" + return Query( + method: "orderDesc", + attribute: attribute + ).description } public static func cursorBefore(_ id: String) -> String { - "cursorBefore(\"\(id)\")" + return Query( + method: "cursorBefore", + values: [id] + ).description } public static func cursorAfter(_ id: String) -> String { - "cursorAfter(\"\(id)\")" + return Query( + method: "cursorAfter", + values: [id] + ).description } public static func limit(_ limit: Int) -> String { - "limit(\(limit))" + return Query( + method: "limit", + values: [limit] + ).description } public static func offset(_ offset: Int) -> String { - "offset(\(offset))" + return Query( + method: "offset", + values: [offset] + ).description } - public static func buildQueryWhere(_ attribute: String, is method: String, to value: Any) -> String { - switch value { - case let value as Array: - return "\(method)(\"\(attribute)\", [\(value.map { parseValues($0) }.joined(separator: ",") )])" - default: - return "\(method)(\"\(attribute)\", [\(parseValues(value))])" + public static func contains(_ attribute: String, value: Any) -> String { + return Query( + method: "contains", + attribute: attribute, + values: Query.parseValue(value) + ).description + } + + public static func or(_ queries: [String]) -> String { + let decoder = JSONDecoder() + let decodedQueries = queries.compactMap { queryStr -> Query? in + guard let data = queryStr.data(using: .utf8) else { + return nil + } + return try? decoder.decode(Query.self, from: data) } + + return Query( + method: "or", + values: decodedQueries + ).description } - private static func parseValues(_ value: Any) -> String { - switch value { - case let value as String: - return "\"\(value)\"" - default: - return "\(value)" + public static func and(_ queries: [String]) -> String { + let decoder = JSONDecoder() + let decodedQueries = queries.compactMap { queryStr -> Query? in + guard let data = queryStr.data(using: .utf8) else { + return nil + } + return try? decoder.decode(Query.self, from: data) + } + + return Query( + method: "and", + values: decodedQueries + ).description + } + + private static func parseValue(_ value: Any) -> [Any] { + if let value = value as? [Any] { + return value + } else { + return [value] } } -} \ No newline at end of file +} diff --git a/templates/swift/Sources/Services/Service.swift.twig b/templates/swift/Sources/Services/Service.swift.twig index aad435162..c14e9b975 100644 --- a/templates/swift/Sources/Services/Service.swift.twig +++ b/templates/swift/Sources/Services/Service.swift.twig @@ -2,10 +2,11 @@ import AsyncHTTPClient import Foundation import NIO import JSONCodable +import {{spec.title | caseUcfirst}}Enums import {{spec.title | caseUcfirst}}Models /// {{ service.description }} -open class {{ service.name | caseUcfirst }}: Service { +open class {{ service.name | caseUcfirst | overrideIdentifier }}: Service { {%~ for method in service.methods %} /// @@ -16,17 +17,14 @@ open class {{ service.name | caseUcfirst }}: Service { /// {%~ endif %} {%~ for parameter in method.parameters.all %} - /// @param {{ parameter | typeName | raw}} {{ parameter.name | caseCamel }} + /// @param {{ parameter | typeName(spec) | raw}} {{ parameter.name | caseCamel }} {%~ endfor %} /// @throws Exception /// @return array /// - {%~ if method.type == "webAuth" %} - @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) - {%~ endif %} - open func {{ method.name | caseCamel }}{% if method.responseModel | hasGenericType(spec) %}{% endif %}( + open func {{ method.name | caseCamel | overrideIdentifier }}{% if method.responseModel | hasGenericType(spec) %}{% endif %}( {%~ for parameter in method.parameters.all %} - {{ parameter.name | caseCamel | escapeKeyword }}: {{ parameter | typeName | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes or method.responseModel | hasGenericType(spec) %},{% endif %} + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter | typeName(spec) | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes or method.responseModel | hasGenericType(spec) %},{% endif %} {%~ endfor %} {%~ if method.responseModel | hasGenericType(spec) %} @@ -37,11 +35,6 @@ open class {{ service.name | caseUcfirst }}: Service { {%~ endif %} ) async throws -> {{ method | returnType(spec) | raw }} { {{~ include('swift/base/params.twig') }} - {%~ if method.type == 'webAuth' %} - {{~ include('swift/base/requests/oauth.twig') }} - {%~ elseif method.type == 'location' %} - {{~ include('swift/base/requests/location.twig')}} - {%~ else %} {%~ if method.headers | length <= 0 %} let apiHeaders: [String: String] = [:] {%~ else %} @@ -55,6 +48,11 @@ open class {{ service.name | caseUcfirst }}: Service { ] {%~ endif %} + {%~ if method.type == 'webAuth' %} + {{~ include('swift/base/requests/oauth.twig') }} + {%~ elseif method.type == 'location' %} + {{~ include('swift/base/requests/location.twig')}} + {%~ else %} {%~ if method.responseModel %} let converter: (Any) -> {{ method | returnType(spec) | raw }} = { response in {%~ if method.responseModel == 'any' %} @@ -82,17 +80,14 @@ open class {{ service.name | caseUcfirst }}: Service { /// {%~ endif %} {%~ for parameter in method.parameters.all %} - /// @param {{ parameter | typeName | raw}} {{ parameter.name | caseCamel }} + /// @param {{ parameter | typeName(spec) | raw}} {{ parameter.name | caseCamel }} {%~ endfor %} /// @throws Exception /// @return array /// - {%~ if method.type == "webAuth" %} - @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) - {%~ endif %} open func {{ method.name | caseCamel }}( {%~ for parameter in method.parameters.all %} - {{ parameter.name | caseCamel | escapeKeyword }}: {{ parameter | typeName | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes %},{% endif %} + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter | typeName(spec) | raw }}{% if not parameter.required or parameter.nullable %}? = nil{% endif %}{% if not loop.last or 'multipart/form-data' in method.consumes %},{% endif %} {%~ endfor %} {%~ if 'multipart/form-data' in method.consumes %} @@ -101,7 +96,7 @@ open class {{ service.name | caseUcfirst }}: Service { ) async throws -> {{ method | returnType(spec, '[String: AnyCodable]') | raw }} { return try await {{ method.name | caseCamel }}( {%~ for parameter in method.parameters.all %} - {{ parameter.name | caseCamel | escapeKeyword }}: {{ parameter.name | caseCamel | escapeKeyword }}, + {{ parameter.name | caseCamel | escapeSwiftKeyword }}: {{ parameter.name | caseCamel | escapeSwiftKeyword }}, {%~ endfor %} nestedType: [String: AnyCodable].self {%~ if 'multipart/form-data' in method.consumes %} diff --git a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig index 6426fcb78..4da31d725 100644 --- a/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig +++ b/templates/swift/Sources/WebSockets/WebSocketClient.swift.twig @@ -267,7 +267,7 @@ public class WebSocketClient { } } - private func upgradePipelineHandler(channel: Channel, response: HTTPResponseHead) -> EventLoopFuture { + @Sendable private func upgradePipelineHandler(channel: Channel, response: HTTPResponseHead) -> EventLoopFuture { let handler = MessageHandler(client: self) if response.status == .switchingProtocols { diff --git a/templates/swift/base/params.twig b/templates/swift/base/params.twig index 518090ea8..1ca566d79 100644 --- a/templates/swift/base/params.twig +++ b/templates/swift/base/params.twig @@ -1,6 +1,6 @@ let apiPath: String = "{{ method.path }}" {%~ for parameter in method.parameters.path %} - .replacingOccurrences(of: "{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}", with: {{ parameter.name | caseCamel | escapeKeyword }}) + .replacingOccurrences(of: "{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}", with: {{ parameter.name | caseCamel | escapeSwiftKeyword }}{% if parameter.enumValues is not empty %}.rawValue{% endif %}) {%~ endfor %} {%~ if method.parameters.query | merge(method.parameters.body) | length <= 0 %} @@ -10,7 +10,7 @@ {%- else -%} let {%- endif %} apiParams: [String: Any?] = [ {%~ for parameter in method.parameters.query | merge(method.parameters.body) %} - "{{ parameter.name }}": {{ parameter.name | caseCamel | escapeKeyword }}{% if not loop.last or (method.type == 'location' or method.type == 'webAuth' and method.auth | length > 0) %},{% endif %} + "{{ parameter.name }}": {{ parameter.name | caseCamel | escapeSwiftKeyword }}{% if not loop.last or (method.type == 'location' or method.type == 'webAuth' and method.auth | length > 0) %},{% endif %} {%~ endfor %} {%~ if method.type == 'location' or method.type == 'webAuth' %} diff --git a/templates/swift/base/requests/api.twig b/templates/swift/base/requests/api.twig index e93264245..9c3a7b10f 100644 --- a/templates/swift/base/requests/api.twig +++ b/templates/swift/base/requests/api.twig @@ -4,5 +4,5 @@ headers: apiHeaders, params: apiParams{% if method.responseModel %}, converter: converter -{% endif %} + {%~ endif %} ) \ No newline at end of file diff --git a/templates/swift/base/requests/oauth.twig b/templates/swift/base/requests/oauth.twig index 02174947a..b533326ad 100644 --- a/templates/swift/base/requests/oauth.twig +++ b/templates/swift/base/requests/oauth.twig @@ -1,11 +1,6 @@ - let query = "?\(client.parametersToQueryString(params: apiParams))" - let url = URL(string: client.endPoint + apiPath + query)! - let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")" - - try await withCheckedThrowingContinuation { continuation in - WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in - continuation.resume(with: result) - } - } - - return true + return try await client.redirect( + method: "{{ method.method | caseUpper }}", + path: apiPath, + headers: apiHeaders, + params: apiParams + ) \ No newline at end of file diff --git a/templates/swift/docs/example.md.twig b/templates/swift/docs/example.md.twig index 5d2fbeb57..866e5d94f 100644 --- a/templates/swift/docs/example.md.twig +++ b/templates/swift/docs/example.md.twig @@ -1,4 +1,11 @@ -import Appwrite +import {{ spec.title | caseUcfirst }} +{% set addedEnum = false %} +{% for parameter in method.parameters.all %} +{% if parameter.enumValues | length > 0 and not addedEnum %} +import {{ spec.title | caseUcfirst }}Enums +{% set addedEnum = true %} +{% endif %} +{% endfor %} let client = Client() {% if method.auth|length > 0 %} @@ -12,12 +19,11 @@ let client = Client() let {{ service.name | caseCamel }} = {{ service.name | caseUcfirst }}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}) -let {% if method.type == 'webAuth' %}success{% elseif method.type == 'location' %}byteBuffer{% elseif method.responseModel | length == 0 %}result{% else %}{{ method.responseModel | caseCamel | escapeKeyword }}{% endif %} = try await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %}){{ '\n' }}{% endif %} +let {% if method.type == 'webAuth' %}success{% elseif method.type == 'location' %}bytes{% elseif method.responseModel | length == 0 %}result{% else %}{{ method.responseModel | caseCamel | escapeSwiftKeyword }}{% endif %} = try await {{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %}){{ '\n' }}{% endif %} -{% for parameter in method.parameters.all %}{% if loop.first %} -{% endif %}{% if parameter.required %} {{parameter.name}}: {{ parameter | paramExample | escapeKeyword }}{% if not loop.last %},{% endif %} -{% else %} {{parameter.name}}: {{ parameter | paramExample | escapeKeyword }}{% if not loop.last %},{% endif %} // optional{% endif %} -{% if loop.last %} + {%~ for parameter in method.parameters.all %} + {{ parameter.name }}: {% if parameter.enumValues | length > 0 %}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else %}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %}{% if not parameter.required %} // optional{% endif %} + {%~ if loop.last %} ) {% endif %} diff --git a/templates/swift/example-swiftui/Example.xcodeproj/project.pbxproj b/templates/swift/example-swiftui/Example.xcodeproj/project.pbxproj index c9bf53166..142cd6297 100644 --- a/templates/swift/example-swiftui/Example.xcodeproj/project.pbxproj +++ b/templates/swift/example-swiftui/Example.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -26,6 +26,11 @@ 1D54699029932DF900AAB591 /* ExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D34C2522702E4A000D1DA8D /* ExampleViewModel.swift */; }; 1D54699129932DF900AAB591 /* ExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D34C2542702E4B500D1DA8D /* ExampleView.swift */; }; 1D54699229932E0500AAB591 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B115883E8645C18835765B2 /* Assets.xcassets */; }; + 1DB1714C2B84402E00318590 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1DB1714B2B84402E00318590 /* FirebaseMessaging */; }; + 1DB1714E2B84403C00318590 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1DB1714D2B84403C00318590 /* FirebaseMessaging */; }; + 1DB171502B84404500318590 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1DB1714F2B84404500318590 /* FirebaseMessaging */; }; + 1DB171522B84404E00318590 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1DB171512B84404E00318590 /* FirebaseMessaging */; }; + 1DB171592B8458B700318590 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1DB171582B8458B600318590 /* GoogleService-Info.plist */; }; 1DBB414827E87CA000ECF86F /* Appwrite in Frameworks */ = {isa = PBXBuildFile; productRef = 1DBB414727E87CA000ECF86F /* Appwrite */; }; 1DBB414A27E87CA800ECF86F /* Appwrite in Frameworks */ = {isa = PBXBuildFile; productRef = 1DBB414927E87CA800ECF86F /* Appwrite */; }; 2358B1EE270AC9DC0016EFBA /* ExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D34C2522702E4A000D1DA8D /* ExampleViewModel.swift */; }; @@ -82,6 +87,10 @@ 1D43900726FC8B2500C71E3E /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 1D54695829932D3500AAB591 /* test (watchOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "test (watchOS).app"; sourceTree = BUILT_PRODUCTS_DIR; }; 1D54697429932D3700AAB591 /* Test watchOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Test watchOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DB171552B8457B100318590 /* test (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "test (iOS).entitlements"; sourceTree = ""; }; + 1DB171582B8458B600318590 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 1DB1715A2B845EAB00318590 /* test (tvOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "test (tvOS).entitlements"; sourceTree = ""; }; + 1DB1715B2B845EB800318590 /* test (watchOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "test (watchOS).entitlements"; sourceTree = ""; }; 1DBB414627E87BDB00ECF86F /* apple */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = apple; path = ../../../examples/apple; sourceTree = ""; }; 23DDF5922709A457006EFAFA /* ImagePicker+iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImagePicker+iOS.swift"; sourceTree = ""; }; 23DDF5942709A46A006EFAFA /* ImagePicker+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImagePicker+macOS.swift"; sourceTree = ""; }; @@ -106,6 +115,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1DB171502B84404500318590 /* FirebaseMessaging in Frameworks */, 1D0647F92991E16D00ADFADC /* Appwrite in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -121,6 +131,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1DB171522B84404E00318590 /* FirebaseMessaging in Frameworks */, 1D54698A29932D9100AAB591 /* Appwrite in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -150,6 +161,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1DB1714E2B84403C00318590 /* FirebaseMessaging in Frameworks */, 1DBB414A27E87CA800ECF86F /* Appwrite in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -158,6 +170,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1DB1714C2B84402E00318590 /* FirebaseMessaging in Frameworks */, 1DBB414827E87CA000ECF86F /* Appwrite in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -205,6 +218,10 @@ 4B1152396B94D0F596CEBB52 = { isa = PBXGroup; children = ( + 1DB1715B2B845EB800318590 /* test (watchOS).entitlements */, + 1DB1715A2B845EAB00318590 /* test (tvOS).entitlements */, + 1DB171582B8458B600318590 /* GoogleService-Info.plist */, + 1DB171552B8457B100318590 /* test (iOS).entitlements */, 1DBB414527E87BDB00ECF86F /* Packages */, 4B115B49441350FF784C7745 /* Products */, 4B11566FD1E1FA1ABD88E438 /* Shared */, @@ -302,6 +319,7 @@ name = "test (tvOS)"; packageProductDependencies = ( 1D0647F82991E16D00ADFADC /* Appwrite */, + 1DB1714F2B84404500318590 /* FirebaseMessaging */, ); productName = Test; productReference = 1D0647BD2991E01C00ADFADC /* test (tvOS).app */; @@ -340,6 +358,7 @@ name = "test (watchOS)"; packageProductDependencies = ( 1D54698929932D9100AAB591 /* Appwrite */, + 1DB171512B84404E00318590 /* FirebaseMessaging */, ); productName = "test Watch App"; productReference = 1D54695829932D3500AAB591 /* test (watchOS).app */; @@ -378,6 +397,7 @@ name = "test (iOS)"; packageProductDependencies = ( 1DBB414727E87CA000ECF86F /* Appwrite */, + 1DB1714B2B84402E00318590 /* FirebaseMessaging */, ); productName = "test (iOS)"; productReference = 4B115DA4916DAA4E8F13734F /* test.app */; @@ -398,6 +418,7 @@ name = "test (macOS)"; packageProductDependencies = ( 1DBB414927E87CA800ECF86F /* Appwrite */, + 1DB1714D2B84403C00318590 /* FirebaseMessaging */, ); productName = "test (macOS)"; productReference = 4B11505C9899942E695B59FF /* test.app */; @@ -478,6 +499,9 @@ en, ); mainGroup = 4B1152396B94D0F596CEBB52; + packageReferences = ( + 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + ); productRefGroup = 4B115B49441350FF784C7745 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -545,6 +569,7 @@ buildActionMask = 2147483647; files = ( 4B1157C67CF5E569FBCB65DD /* Assets.xcassets in Resources */, + 1DB171592B8458B700318590 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -671,6 +696,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test (tvOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; @@ -698,6 +724,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test (tvOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; @@ -767,6 +794,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test (watchOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; @@ -796,6 +824,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test (watchOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; @@ -901,7 +930,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = GWZ23QTYB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -911,6 +943,7 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.mac; PRODUCT_NAME = test; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; @@ -921,14 +954,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "test (iOS).entitlements"; + DEVELOPMENT_TEAM = GWZ23QTYB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.ios; + PRODUCT_BUNDLE_IDENTIFIER = "io.appwrite.ios-example"; PRODUCT_NAME = test; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; @@ -959,14 +994,16 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "test (iOS).entitlements"; + DEVELOPMENT_TEAM = GWZ23QTYB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.ios; + PRODUCT_BUNDLE_IDENTIFIER = "io.appwrite.ios-example"; PRODUCT_NAME = test; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; @@ -980,7 +1017,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = GWZ23QTYB5; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -990,6 +1030,7 @@ MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = io.appwrite.mac; PRODUCT_NAME = test; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; @@ -1209,6 +1250,17 @@ }; /* End XCConfigurationList section */ +/* Begin XCRemoteSwiftPackageReference section */ + 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 10.21.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 1D0647F82991E16D00ADFADC /* Appwrite */ = { isa = XCSwiftPackageProductDependency; @@ -1218,6 +1270,26 @@ isa = XCSwiftPackageProductDependency; productName = Appwrite; }; + 1DB1714B2B84402E00318590 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + 1DB1714D2B84403C00318590 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + 1DB1714F2B84404500318590 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + 1DB171512B84404E00318590 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 1DB171482B84400300318590 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; 1DBB414727E87CA000ECF86F /* Appwrite */ = { isa = XCSwiftPackageProductDependency; productName = Appwrite; diff --git a/templates/swift/example-swiftui/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/templates/swift/example-swiftui/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9634ad4be..5ae6bebd3 100644 --- a/templates/swift/example-swiftui/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/templates/swift/example-swiftui/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,13 +1,130 @@ { "object": { "pins": [ + { + "package": "abseil", + "repositoryURL": "https://github.com/google/abseil-cpp-binary.git", + "state": { + "branch": null, + "revision": "bfc0b6f81adc06ce5121eb23f628473638d67c5c", + "version": "1.2022062300.0" + } + }, + { + "package": "AppCheck", + "repositoryURL": "https://github.com/google/app-check.git", + "state": { + "branch": null, + "revision": "3e464dad87dad2d29bb29a97836789bf0f8f67d2", + "version": "10.18.1" + } + }, { "package": "async-http-client", "repositoryURL": "https://github.com/swift-server/async-http-client.git", "state": { "branch": null, - "revision": "7f05a8da46cc2a4ab43218722298b81ac7a08031", - "version": "1.13.2" + "revision": "291438696abdd48d2a83b52465c176efbd94512b", + "version": "1.20.1" + } + }, + { + "package": "Firebase", + "repositoryURL": "https://github.com/firebase/firebase-ios-sdk.git", + "state": { + "branch": null, + "revision": "f91c8167141d0279726c6f6d9d4a47c026785cbc", + "version": "10.21.0" + } + }, + { + "package": "GoogleAppMeasurement", + "repositoryURL": "https://github.com/google/GoogleAppMeasurement.git", + "state": { + "branch": null, + "revision": "cb8617fab75d181270a1d8f763f26b15c73e2e1e", + "version": "10.21.0" + } + }, + { + "package": "GoogleDataTransport", + "repositoryURL": "https://github.com/google/GoogleDataTransport.git", + "state": { + "branch": null, + "revision": "a732a4b47f59e4f725a2ea10f0c77e93a7131117", + "version": "9.3.0" + } + }, + { + "package": "GoogleUtilities", + "repositoryURL": "https://github.com/google/GoogleUtilities.git", + "state": { + "branch": null, + "revision": "bc27fad73504f3d4af235de451f02ee22586ebd3", + "version": "7.12.1" + } + }, + { + "package": "gRPC", + "repositoryURL": "https://github.com/google/grpc-binary.git", + "state": { + "branch": null, + "revision": "a673bc2937fbe886dd1f99c401b01b6d977a9c98", + "version": "1.49.1" + } + }, + { + "package": "GTMSessionFetcher", + "repositoryURL": "https://github.com/google/gtm-session-fetcher.git", + "state": { + "branch": null, + "revision": "76135c9f4e1ac85459d5fec61b6f76ac47ab3a4c", + "version": "3.3.1" + } + }, + { + "package": "InteropForGoogle", + "repositoryURL": "https://github.com/google/interop-ios-for-google-sdks.git", + "state": { + "branch": null, + "revision": "2d12673670417654f08f5f90fdd62926dc3a2648", + "version": "100.0.0" + } + }, + { + "package": "leveldb", + "repositoryURL": "https://github.com/firebase/leveldb.git", + "state": { + "branch": null, + "revision": "9d108e9112aa1d65ce508facf804674546116d9c", + "version": "1.22.3" + } + }, + { + "package": "nanopb", + "repositoryURL": "https://github.com/firebase/nanopb.git", + "state": { + "branch": null, + "revision": "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version": "2.30909.0" + } + }, + { + "package": "Promises", + "repositoryURL": "https://github.com/google/promises.git", + "state": { + "branch": null, + "revision": "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", + "version": "2.3.1" + } + }, + { + "package": "swift-algorithms", + "repositoryURL": "https://github.com/apple/swift-algorithms", + "state": { + "branch": null, + "revision": "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version": "1.2.0" } }, { @@ -15,8 +132,8 @@ "repositoryURL": "https://github.com/apple/swift-atomics.git", "state": { "branch": null, - "revision": "ff3d2212b6b093db7f177d0855adbc4ef9c5f036", - "version": "1.0.3" + "revision": "cd142fd2f64be2100422d658e7411e39489da985", + "version": "1.2.0" } }, { @@ -24,8 +141,17 @@ "repositoryURL": "https://github.com/apple/swift-collections.git", "state": { "branch": null, - "revision": "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version": "1.0.4" + "revision": "d029d9d39c87bed85b1c50adee7c41795261a192", + "version": "1.0.6" + } + }, + { + "package": "swift-http-types", + "repositoryURL": "https://github.com/apple/swift-http-types", + "state": { + "branch": null, + "revision": "12358d55a3824bd5fed310b999ea8cf83a9a1a65", + "version": "1.0.3" } }, { @@ -33,8 +159,8 @@ "repositoryURL": "https://github.com/apple/swift-log.git", "state": { "branch": null, - "revision": "32e8d724467f8fe623624570367e3d50c5638e46", - "version": "1.5.2" + "revision": "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version": "1.5.4" } }, { @@ -42,8 +168,8 @@ "repositoryURL": "https://github.com/apple/swift-nio.git", "state": { "branch": null, - "revision": "45167b8006448c79dda4b7bd604e07a034c15c49", - "version": "2.48.0" + "revision": "635b2589494c97e48c62514bc8b37ced762e0a62", + "version": "2.63.0" } }, { @@ -51,8 +177,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-extras.git", "state": { "branch": null, - "revision": "91dd2d61fb772e1311bb5f13b59266b579d77e42", - "version": "1.15.0" + "revision": "363da63c1966405764f380c627409b2f9d9e710b", + "version": "1.21.0" } }, { @@ -60,8 +186,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-http2.git", "state": { "branch": null, - "revision": "d6656967f33ed8b368b38e4b198631fc7c484a40", - "version": "1.23.1" + "revision": "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", + "version": "1.30.0" } }, { @@ -69,8 +195,8 @@ "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", "state": { "branch": null, - "revision": "4fb7ead803e38949eb1d6fabb849206a72c580f3", - "version": "2.23.0" + "revision": "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version": "2.26.0" } }, { @@ -78,8 +204,35 @@ "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", "state": { "branch": null, - "revision": "c0d9a144cfaec8d3d596aadde3039286a266c15c", - "version": "1.15.0" + "revision": "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", + "version": "1.20.1" + } + }, + { + "package": "swift-numerics", + "repositoryURL": "https://github.com/apple/swift-numerics.git", + "state": { + "branch": null, + "revision": "0a5bc04095a675662cf24757cc0640aa2204253b", + "version": "1.0.2" + } + }, + { + "package": "SwiftProtobuf", + "repositoryURL": "https://github.com/apple/swift-protobuf.git", + "state": { + "branch": null, + "revision": "65e8f29b2d63c4e38e736b25c27b83e012159be8", + "version": "1.25.2" + } + }, + { + "package": "swift-system", + "repositoryURL": "https://github.com/apple/swift-system.git", + "state": { + "branch": null, + "revision": "025bcb1165deab2e20d4eaba79967ce73013f496", + "version": "1.2.1" } } ] diff --git a/templates/swift/example-swiftui/Shared/ExampleApp.swift b/templates/swift/example-swiftui/Shared/ExampleApp.swift index 19c5592e8..15ff0c930 100644 --- a/templates/swift/example-swiftui/Shared/ExampleApp.swift +++ b/templates/swift/example-swiftui/Shared/ExampleApp.swift @@ -1,9 +1,94 @@ import SwiftUI import Appwrite import NIO +import Firebase +import FirebaseMessaging + +let host = "https://cloud.appwrite.io/v1" +let projectId = "[YOUR_PROJECT_ID]" + +let client = Client() + .setEndpoint(host) + .setProject(projectId) + +let account = Account(client) +let storage = Storage(client) +let realtime = Realtime(client) + +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil + ) -> Bool { + FirebaseApp.configure() + + Messaging.messaging().delegate = self + + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: { granted, error in + DispatchQueue.main.async { + if granted { + application.registerForRemoteNotifications() + } + } + } + ) + + return true + } + + func messaging( + _ messaging: FirebaseMessaging.Messaging, + didReceiveRegistrationToken fcmToken: String? + ) { + guard let fcmToken = fcmToken else { + return + } + + UserDefaults.standard.set(fcmToken , forKey: "fcmToken") + + let targetId = UserDefaults.standard.string(forKey: "targetId") + + Task { + do { + _ = try await account.get() + } catch { + return + } + + if targetId == nil { + let target = try? await account.createPushTarget( + targetId: ID.unique(), + identifier: fcmToken + ) + + UserDefaults.standard.set(target?.id , forKey: "targetId") + } else { + _ = try? await account.updatePushTarget( + targetId: targetId!, + identifier: fcmToken + ) + } + } + } + + func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + Messaging.messaging().apnsToken = deviceToken + } +} @main struct ExampleApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + var body: some Scene { WindowGroup { ExampleView(viewModel: ExampleView.ViewModel()) diff --git a/templates/swift/example-swiftui/Shared/ExampleView.swift b/templates/swift/example-swiftui/Shared/ExampleView.swift index c1a93e75e..d2f6354f4 100644 --- a/templates/swift/example-swiftui/Shared/ExampleView.swift +++ b/templates/swift/example-swiftui/Shared/ExampleView.swift @@ -17,7 +17,7 @@ struct ExampleView: View { .aspectRatio(contentMode: .fit) .frame(height: 200) - TextField("", text: $viewModel.response) + TextField("", text: $viewModel.response, axis: .vertical) .padding() Button("Login") { diff --git a/templates/swift/example-swiftui/Shared/ExampleViewModel.swift b/templates/swift/example-swiftui/Shared/ExampleViewModel.swift index d3c0c1f02..817a9064f 100644 --- a/templates/swift/example-swiftui/Shared/ExampleViewModel.swift +++ b/templates/swift/example-swiftui/Shared/ExampleViewModel.swift @@ -3,23 +3,11 @@ import SwiftUI import Appwrite import NIO -let host = "https://localhost/v1" -let projectId = "test" - extension ExampleView { - + class ViewModel : ObservableObject { - let client = Client() - .setEndpoint(host) - .setProject(projectId) - - lazy var account = Account(client) - lazy var storage = Storage(client) - lazy var realtime = Realtime(client) - @Published var downloadedImage: Image? = nil - @Published public var username: String = "test@test.test" @Published public var password: String = "password" @Published public var userId: String = "unique()" @@ -29,7 +17,7 @@ extension ExampleView { @Published public var collectionId: String = "test" @Published public var isShowPhotoLibrary = false @Published public var response: String = "" - + func register() async { do { let user = try await account.create( @@ -38,46 +26,77 @@ extension ExampleView { password: password ) self.userId = user.id - self.response = String(describing: user.toMap()) + + DispatchQueue.main.async { + self.response = String(describing: user.toMap()) + } } catch { - self.response = error.localizedDescription + DispatchQueue.main.async { + self.response = error.localizedDescription + } } } - + func login() async { do { - let session = try await account.createEmailSession( + let session = try await account.createEmailPasswordSession( email: username, password: password ) - self.response = String(describing: session.toMap()) + + guard let token = UserDefaults.standard.string(forKey: "fcmToken") else { + return + } + + guard let target = try? await account.createPushTarget( + targetId: ID.unique(), + identifier: token + ) else { + return + } + + UserDefaults.standard.set(target.id, forKey: "targetId") + + DispatchQueue.main.async { + self.response = String(describing: session.toMap()) + } } catch { - self.response = error.localizedDescription + DispatchQueue.main.async { + self.response = error.localizedDescription + } } } - + func loginWithFacebook() async { do { - _ = try await account.createOAuth2Session(provider: "facebook") + _ = try await account.createOAuth2Session(provider: .facebook) - self.response = "Success!" + DispatchQueue.main.async { + self.response = "Success!" + } } catch { - self.response = error.localizedDescription + DispatchQueue.main.async { + self.response = error.localizedDescription + } } } - + func download() async { do { let data = try await storage.getFileDownload( bucketId: bucketId, fileId: fileId ) - self.downloadedImage = Image(data: Data(buffer: data)) + DispatchQueue.main.async { + self.downloadedImage = Image(data: Data(buffer: data)) + } } catch { - self.response = error.localizedDescription + DispatchQueue.main.async { + self.response = error.localizedDescription + } } } - + func upload(image: OSImage) async { #if os(macOS) let fileName = "file.tiff" @@ -86,22 +105,26 @@ extension ExampleView { let fileName = "file.png" let mime = "image/png" #endif - + let file = InputFile.fromData( image.data, filename: fileName, mimeType: mime ) - + do { let file = try await storage.createFile( bucketId: bucketId, fileId: fileId, file: file ) - self.response = String(describing: file.toMap()) + DispatchQueue.main.async { + self.response = String(describing: file.toMap()) + } } catch { - self.response = error.localizedDescription + DispatchQueue.main.async { + self.response = error.localizedDescription + } } } diff --git a/templates/swift/example-swiftui/test (iOS).entitlements b/templates/swift/example-swiftui/test (iOS).entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/templates/swift/example-swiftui/test (iOS).entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/templates/swift/example-swiftui/test (tvOS).entitlements b/templates/swift/example-swiftui/test (tvOS).entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/templates/swift/example-swiftui/test (tvOS).entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/templates/swift/example-swiftui/test (watchOS).entitlements b/templates/swift/example-swiftui/test (watchOS).entitlements new file mode 100644 index 000000000..903def2af --- /dev/null +++ b/templates/swift/example-swiftui/test (watchOS).entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/templates/web/.travis.yml.twig b/templates/web/.travis.yml.twig index 5a81edf61..aae23993a 100644 --- a/templates/web/.travis.yml.twig +++ b/templates/web/.travis.yml.twig @@ -5,7 +5,7 @@ node_js: jobs: include: - stage: NPM RC Release - if: tag == *-RC* + if: tag == *-rc* node_js: "14.16" script: - npm install @@ -17,7 +17,7 @@ jobs: api_key: $NPM_API_KEY tag: next - stage: NPM Release - if: tag != *-RC* + if: tag != *-rc* node_js: "14.16" script: - npm install diff --git a/templates/web/docs/example.md.twig b/templates/web/docs/example.md.twig index ffc1f24b3..654d39d31 100644 --- a/templates/web/docs/example.md.twig +++ b/templates/web/docs/example.md.twig @@ -1,33 +1,26 @@ -import {{ '{' }} Client, {{service.name | caseUcfirst}} {{ '}' }} from "{{ spec.title | caseDash }}"; +import { Client, {{ service.name | caseUcfirst }}{% for parameter in method.parameters.all %}{% if parameter.enumValues | length > 0%}, {{ parameter.enumName | caseUcfirst}}{% endif %}{% endfor %} } from "{{ language.params.npmPackage }}"; -const client = new Client(); +const client = new Client() + {%~ if method.auth|length > 0 %} + .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint + {%~ for node in method.auth %} + {%~ for key,header in node|keys %} + .set{{header}}('{{node[header]['x-appwrite']['demo']}}'){% if loop.last %};{% endif%} // {{node[header].description}} + {%~ endfor %} + {%~ endfor %} + {%~ endif %} const {{ service.name | caseCamel }} = new {{service.name | caseUcfirst}}(client{% if service.globalParams | length %}{% for parameter in service.globalParams %}, {{ parameter | paramExample }}{% endfor %}{% endif %}); -{% if method.auth|length > 0 %} -client - .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint -{% for node in method.auth %} -{% for key,header in node|keys %} - .set{{header}}('{{node[header]['x-appwrite']['demo']}}') // {{node[header].description}} -{% endfor %} -{% endfor %}; +{% if method.type == 'location' %}const result = {% elseif method.type != 'webAuth' %}const result = await {% endif %}{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% if method.parameters.all | length == 0 %});{% endif %} -{% endif %} -{% if method.type == 'webAuth' %}// Go to OAuth provider login page -{% endif %} -{% if method.type == 'webAuth' %}{% elseif method.type == 'location' %}const result = {% else %}const promise = {% endif %}{{ service.name | caseCamel }}.{{ method.name | caseCamel }}({% for parameter in method.parameters.all %} -{% if loop.first %} + {%~ for parameter in method.parameters.all %} + {% if parameter.enumValues | length > 0 %}{{ parameter.enumName }}.{{ (parameter.enumKeys[0] ?? parameter.enumValues[0]) | caseEnumKey }}{% else%}{{ parameter | paramExample }}{% endif %}{% if not loop.last %},{% endif %} // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} - {% endif %}{% if parameter.required %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} - - {% else %} - {{ parameter | paramExample }}{% if not loop.last %}, {% endif %} // {{parameter.name}} (optional) - {% endif %}{% endfor %}); + {%~ endfor %} +{% if method.parameters.all | length > 0 %}); +{% endif %} -{% if method.type == 'webAuth' %}{% elseif method.type == 'location' %}console.log(result); // Resource URL{% else %}promise.then(function (response) { - console.log(response); // Success -}, function (error) { - console.log(error); // Failure -});{% endif %} \ No newline at end of file +{% if method.type != 'webAuth' %} +console.log({% if method.type == 'location' %}result{% else %}response{% endif %}); +{% endif %} \ No newline at end of file diff --git a/templates/web/src/enums/enum.ts.twig b/templates/web/src/enums/enum.ts.twig new file mode 100644 index 000000000..f656f93d5 --- /dev/null +++ b/templates/web/src/enums/enum.ts.twig @@ -0,0 +1,6 @@ +export enum {{ enum.name | caseUcfirst }} { +{% for value in enum.enum %} +{% set key = enum.keys is empty ? value : enum.keys[loop.index0] %} + {{ key | replace({'-': ''}) | caseEnumKey }} = '{{ value }}', +{% endfor %} +} \ No newline at end of file diff --git a/templates/web/src/index.ts.twig b/templates/web/src/index.ts.twig index 75ecd534d..27cb52883 100644 --- a/templates/web/src/index.ts.twig +++ b/templates/web/src/index.ts.twig @@ -6,4 +6,7 @@ export type { Models, Payload, RealtimeResponseEvent, UploadProgress } from './c export type { QueryTypes, QueryTypesList } from './query'; export { Permission } from './permission'; export { Role } from './role'; -export { ID } from './id'; \ No newline at end of file +export { ID } from './id'; +{% for enum in spec.enums %} +export { {{ enum.name | caseUcfirst }} } from './enums/{{enum.name | caseDash}}'; +{% endfor %} \ No newline at end of file diff --git a/templates/web/src/query.ts.twig b/templates/web/src/query.ts.twig index 1f880cb1c..229d6c858 100644 --- a/templates/web/src/query.ts.twig +++ b/templates/web/src/query.ts.twig @@ -1,74 +1,101 @@ type QueryTypesSingle = string | number | boolean; -export type QueryTypesList = string[] | number[] | boolean[]; +export type QueryTypesList = string[] | number[] | boolean[] | Query[]; export type QueryTypes = QueryTypesSingle | QueryTypesList; +type AttributesTypes = string | string[]; export class Query { + method: string; + attribute: AttributesTypes | undefined; + values: QueryTypesList | undefined; + + constructor( + method: string, + attribute?: AttributesTypes, + values?: QueryTypes + ) { + this.method = method; + this.attribute = attribute; + + if (values !== undefined) { + if (Array.isArray(values)) { + this.values = values; + } else { + this.values = [values] as QueryTypesList; + } + } + } + + toString(): string { + return JSON.stringify({ + method: this.method, + attribute: this.attribute, + values: this.values, + }); + } + static equal = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "equal", value); + new Query("equal", attribute, value).toString(); static notEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "notEqual", value); + new Query("notEqual", attribute, value).toString(); static lessThan = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "lessThan", value); + new Query("lessThan", attribute, value).toString(); static lessThanEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "lessThanEqual", value); + new Query("lessThanEqual", attribute, value).toString(); static greaterThan = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "greaterThan", value); + new Query("greaterThan", attribute, value).toString(); static greaterThanEqual = (attribute: string, value: QueryTypes): string => - Query.addQuery(attribute, "greaterThanEqual", value); + new Query("greaterThanEqual", attribute, value).toString(); static isNull = (attribute: string): string => - `isNull("${attribute}")`; + new Query("isNull", attribute).toString(); static isNotNull = (attribute: string): string => - `isNotNull("${attribute}")`; + new Query("isNotNull", attribute).toString(); - static between = (attribute: string, start: string|number, end: string|number): string => - `between("${attribute}", ${Query.parseValues(start)}, ${Query.parseValues(end)})`; + static between = (attribute: string, start: string | number, end: string | number) => + new Query("between", attribute, [start, end] as QueryTypesList).toString(); static startsWith = (attribute: string, value: string): string => - Query.addQuery(attribute, "startsWith", value); + new Query("startsWith", attribute, value).toString(); static endsWith = (attribute: string, value: string): string => - Query.addQuery(attribute, "endsWith", value); + new Query("endsWith", attribute, value).toString(); static select = (attributes: string[]): string => - `select([${attributes.map((attr: string) => `"${attr}"`).join(",")}])`; + new Query("select", undefined, attributes).toString(); static search = (attribute: string, value: string): string => - Query.addQuery(attribute, "search", value); + new Query("search", attribute, value).toString(); static orderDesc = (attribute: string): string => - `orderDesc("${attribute}")`; + new Query("orderDesc", attribute).toString(); static orderAsc = (attribute: string): string => - `orderAsc("${attribute}")`; + new Query("orderAsc", attribute).toString(); static cursorAfter = (documentId: string): string => - `cursorAfter("${documentId}")`; + new Query("cursorAfter", undefined, documentId).toString(); static cursorBefore = (documentId: string): string => - `cursorBefore("${documentId}")`; + new Query("cursorBefore", undefined, documentId).toString(); static limit = (limit: number): string => - `limit(${limit})`; + new Query("limit", undefined, limit).toString(); static offset = (offset: number): string => - `offset(${offset})`; - - private static addQuery = (attribute: string, method: string, value: QueryTypes): string => - value instanceof Array - ? `${method}("${attribute}", [${value - .map((v: QueryTypesSingle) => Query.parseValues(v)) - .join(",")}])` - : `${method}("${attribute}", [${Query.parseValues(value)}])`; - - private static parseValues = (value: QueryTypes): string => - typeof value === "string" || value instanceof String - ? `"${value}"` - : `${value}`; -} \ No newline at end of file + new Query("offset", undefined, offset).toString(); + + static contains = (attribute: string, value: string | string[]): string => + new Query("contains", attribute, value).toString(); + + static or = (queries: string[]) => + new Query("or", undefined, queries.map((query) => JSON.parse(query))).toString(); + + static and = (queries: string[]) => + new Query("and", undefined, queries.map((query) => JSON.parse(query))).toString(); +} diff --git a/templates/web/src/service.ts.twig b/templates/web/src/service.ts.twig index 0c9662be3..fe1769929 100644 --- a/templates/web/src/service.ts.twig +++ b/templates/web/src/service.ts.twig @@ -13,14 +13,11 @@ export class Service { static flatten(data: Payload, prefix = ''): Payload { let output: Payload = {}; - for (const key in data) { - let value = data[key]; - let finalKey = prefix ? `${prefix}[${key}]` : key; - + for (const [key, value] of Object.entries(data)) { + let finalKey = prefix ? prefix + '[' + key +']' : key; if (Array.isArray(value)) { - output = Object.assign(output, this.flatten(value, finalKey)); - } - else { + output = { ...output, ...Service.flatten(value, finalKey) }; + } else { output[finalKey] = value; } } diff --git a/templates/web/src/services/template.ts.twig b/templates/web/src/services/template.ts.twig index 585e5de60..70f4c4a53 100644 --- a/templates/web/src/services/template.ts.twig +++ b/templates/web/src/services/template.ts.twig @@ -2,6 +2,22 @@ import { Service } from '../service'; import { {{ spec.title | caseUcfirst}}Exception, Client } from '../client'; import type { Models } from '../models'; import type { UploadProgress, Payload } from '../client'; +{% set added = [] %} +{% for method in service.methods %} +{% for parameter in method.parameters.all %} +{% if parameter.enumValues is not empty %} +{% if parameter.enumName is not empty %} +{% set name = parameter.enumName %} +{% else %} +{% set name = parameter.name %} +{% endif %} +{% if name not in added %} +import { {{ name | caseUcfirst }} } from '../enums/{{ name | caseDash }}'; +{% set added = added|merge([name]) %} +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} export class {{ service.name | caseUcfirst }} extends Service { diff --git a/tests/Android11Java8Test.php b/tests/Android11Java8Test.php deleted file mode 100644 index e79656ee8..000000000 --- a/tests/Android11Java8Test.php +++ /dev/null @@ -1,36 +0,0 @@ -sdkName}; x-sdk-platform: {$this->sdkPlatform}; x-sdk-language: {$this->sdkLanguage}; x-sdk-version: {$this->version}"; - array_push($this->expectedOutput, $headers); + + $this->expectedOutput[] = $headers; // Figure out if mock-server is running - $isMockAPIRunning = (strlen(exec('docker ps | grep mock-server')) > 0); + $isMockAPIRunning = \strlen(\exec('docker ps | grep mock-server')) > 0; if (!$isMockAPIRunning) { echo "Starting Mock API Server"; - exec('cd ./mock-server && docker-compose up -d --force-recreate'); + + \exec(' + cd ./mock-server && \ + docker-compose build && \ + docker compose up -d --force-recreate + '); } } @@ -174,49 +198,40 @@ public function testHTTPSuccess(): void /** * Build SDK */ - foreach ($this->build as $key => $command) { - echo "Building phase #{$key} for {$this->language} package...\n"; - echo "Executing: {$command}\n"; - - $buildOutput = []; - - ob_end_clean(); + foreach ($this->build as $command) { echo "Build Executing: {$command}\n"; - ob_start(); - - exec($command, $buildOutput); - foreach ($buildOutput as $i => $row) { - echo "{$i}. {$row}\n"; - } + exec($command); } $output = []; - ob_end_clean(); echo "Env Executing: {$this->command}\n"; - ob_start(); exec($this->command, $output); - foreach ($output as $i => $row) { - echo "{$row}\n"; - } - $this->assertIsArray($output); do { - $removed = array_shift($output); + $removed = \array_shift($output); } while ($removed != 'Test Started' && sizeof($output) != 0); - $this->assertGreaterThanOrEqual(count($this->expectedOutput), count($output)); + echo \implode("\n", $output); - foreach ($this->expectedOutput as $i => $row) { - $this->assertEquals($output[$i], $row); + foreach ($this->expectedOutput as $index => $expected) { + // HACK: Swift does not guarantee the order of the JSON parameters + if (\str_starts_with($expected, '{')) { + $this->assertEquals( + \json_decode($expected, true), + \json_decode($output[$index], true) + ); + } else { + $this->assertEquals($expected, $output[$index]); + } } } - private function rmdirRecursive($dir) + private function rmdirRecursive($dir): void { if (!\is_dir($dir)) { return; @@ -231,7 +246,7 @@ private function rmdirRecursive($dir) \unlink("$dir/$file"); } } - rmdir($dir); + \rmdir($dir); } public function getLanguage(): Language diff --git a/tests/CLINode16Test.php b/tests/CLINode16Test.php index 0dc378ca4..19e5e21bf 100644 --- a/tests/CLINode16Test.php +++ b/tests/CLINode16Test.php @@ -25,7 +25,7 @@ class CLINode16Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - 'POST:/v1/mock/tests/general/upload:passed', //large file + ...Base::UPLOAD_RESPONSES, ]; public function getLanguage(): Language diff --git a/tests/CLINode18Test.php b/tests/CLINode18Test.php index 4e8b6b8a8..b4f58c9c0 100644 --- a/tests/CLINode18Test.php +++ b/tests/CLINode18Test.php @@ -25,7 +25,7 @@ class CLINode18Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - 'POST:/v1/mock/tests/general/upload:passed', //large file + ...Base::UPLOAD_RESPONSES, ]; public function getLanguage(): Language diff --git a/tests/DartBetaTest.php b/tests/DartBetaTest.php index 85a8e8b9e..816620260 100644 --- a/tests/DartBetaTest.php +++ b/tests/DartBetaTest.php @@ -22,8 +22,10 @@ class DartBetaTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + ...Base::UPLOAD_RESPONSES, + ...Base::ENUM_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES diff --git a/tests/DartStableTest.php b/tests/DartStableTest.php index ac21d95d5..f55acae99 100644 --- a/tests/DartStableTest.php +++ b/tests/DartStableTest.php @@ -22,8 +22,10 @@ class DartStableTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + ...Base::UPLOAD_RESPONSES, + ...Base::ENUM_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES diff --git a/tests/Deno1193Test.php b/tests/Deno1193Test.php index a16066f85..0dfc163b3 100644 --- a/tests/Deno1193Test.php +++ b/tests/Deno1193Test.php @@ -19,8 +19,10 @@ class Deno1193Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + ...Base::UPLOAD_RESPONSES, + ...Base::ENUM_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES diff --git a/tests/Deno1303Test.php b/tests/Deno1303Test.php index ef28031f7..31f392bff 100644 --- a/tests/Deno1303Test.php +++ b/tests/Deno1303Test.php @@ -19,8 +19,10 @@ class Deno1303Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, - ...Base::LARGE_FILE_RESPONSES, + ...Base::UPLOAD_RESPONSES, + ...Base::ENUM_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES diff --git a/tests/DotNet31Test.php b/tests/DotNet31Test.php deleted file mode 100644 index 0852909fb..000000000 --- a/tests/DotNet31Test.php +++ /dev/null @@ -1,34 +0,0 @@ - { diff --git a/tests/languages/dotnet/Tests.cs b/tests/languages/dotnet/Tests.cs index c01946a74..7007ac3bd 100644 --- a/tests/languages/dotnet/Tests.cs +++ b/tests/languages/dotnet/Tests.cs @@ -5,6 +5,7 @@ using Appwrite; using Appwrite.Models; +using Appwrite.Enums; using Appwrite.Services; using NUnit.Framework; @@ -21,7 +22,10 @@ public void Setup() [Test] public async Task Test1() { - var client = new Client(); + var client = new Client() + .AddHeader("Origin", "http://localhost") + .SetSelfSigned(true); + var foo = new Foo(client); var bar = new Bar(client); var general = new General(client); @@ -77,6 +81,9 @@ public async Task Test1() mock = await general.Upload("string", 123, new List() { "string in array" }, InputFile.FromStream(info.OpenRead(), "large_file.mp4", "video/mp4")); TestContext.WriteLine(mock.Result); + mock = await general.Enum(MockType.First); + TestContext.WriteLine(mock.Result); + try { await general.Error400(); @@ -106,6 +113,15 @@ public async Task Test1() await general.Empty(); + var url = await general.Oauth2( + clientId: "clientId", + scopes: new List() {"test"}, + state: "123456", + success: "https://localhost", + failure: "https://localhost" + ); + TestContext.WriteLine(url); + // Query helper tests TestContext.WriteLine(Query.Equal("released", new List { true })); TestContext.WriteLine(Query.Equal("title", new List { "Spiderman", "Dr. Strange" })); @@ -127,6 +143,20 @@ public async Task Test1() TestContext.WriteLine(Query.CursorBefore("my_movie_id")); TestContext.WriteLine(Query.Limit(50)); TestContext.WriteLine(Query.Offset(20)); + TestContext.WriteLine(Query.Contains("title", "Spider")); + TestContext.WriteLine(Query.Contains("labels", "first")); + TestContext.WriteLine(Query.Or( + new List { + Query.Equal("released", true), + Query.LessThan("releasedYear", 1990) + } + )); + TestContext.WriteLine(Query.And( + new List { + Query.Equal("released", false), + Query.GreaterThan("releasedYear", 2015) + } + )); // Permission & Roles helper tests TestContext.WriteLine(Permission.Read(Role.Any())); diff --git a/tests/languages/flutter/tests.dart b/tests/languages/flutter/tests.dart index b6fb89e3b..2df554ccd 100644 --- a/tests/languages/flutter/tests.dart +++ b/tests/languages/flutter/tests.dart @@ -4,6 +4,8 @@ import 'package:path_provider_platform_interface/path_provider_platform_interfac import '../lib/packageName.dart'; import '../lib/client_io.dart'; import '../lib/models.dart'; +import '../lib/enums.dart'; +import '../lib/src/input_file.dart'; import 'dart:io'; class FakePathProvider extends PathProviderPlatform { @@ -17,7 +19,9 @@ class FakePathProvider extends PathProviderPlatform { void main() async { WidgetsFlutterBinding.ensureInitialized(); PathProviderPlatform.instance = FakePathProvider(); - Client client = Client(); + Client client = Client() + .addHeader("Origin", "http://localhost") + .setSelfSigned(); Foo foo = Foo(client); Bar bar = Bar(client); General general = General(client); @@ -28,7 +32,7 @@ void main() async { "wss://demo.appwrite.io/v1"); // change this later to appwrite.io Realtime realtime = Realtime(client); - // final rtsub = realtime.subscribe(["tests"]); + final rtsub = realtime.subscribe(["tests"]); await Future.delayed(Duration(seconds: 5)); client.addHeader('Origin', 'http://localhost'); @@ -79,13 +83,26 @@ void main() async { print(res['result']); var file = InputFile.fromPath(path: '../../resources/file.png', filename: 'file.png'); - response = await general.upload( - x: 'string', y: 123, z: ['string in array'], file: file); + response = await general.upload(x: 'string', y: 123, z: ['string in array'], file: file); print(response.result); file = InputFile.fromPath(path: '../../resources/large_file.mp4', filename: 'large_file.mp4'); - response = await general.upload( - x: 'string', y: 123, z: ['string in array'], file: file); + response = await general.upload(x: 'string', y: 123, z: ['string in array'], file: file); + print(response.result); + + var resource = File.fromUri(Uri.parse('../../resources/file.png')); + var bytes = await resource.readAsBytes(); + file = InputFile.fromBytes(bytes: bytes, filename: 'file.png'); + response = await general.upload(x: 'string', y: 123, z: ['string in array'], file: file); + print(response.result); + + resource = File.fromUri(Uri.parse('../../resources/large_file.mp4')); + bytes = await resource.readAsBytes(); + file = InputFile.fromBytes(bytes: bytes, filename: 'large_file.mp4'); + response = await general.upload(x: 'string', y: 123, z: ['string in array'], file: file); + print(response.result); + + response = await general.xenum(mockType: MockType.first); print(response.result); try { @@ -106,10 +123,10 @@ void main() async { print(e.message); } - // rtsub.stream.listen((message) { - // print(message.payload["response"]); - // rtsub.close(); - // }); + rtsub.stream.listen((message) { + print(message.payload["response"]); + rtsub.close(); + }); await Future.delayed(Duration(seconds: 5)); @@ -142,6 +159,16 @@ void main() async { print(Query.cursorBefore("my_movie_id")); print(Query.limit(50)); print(Query.offset(20)); + print(Query.contains("title", "Spider")); + print(Query.contains("labels", "first")); + print(Query.or([ + Query.equal("released", true), + Query.lessThan("releasedYear", 1990) + ])); + print(Query.and([ + Query.equal("released", false), + Query.greaterThan("releasedYear", 2015) + ])); // Permission & Role helper tests print(Permission.read(Role.any())); diff --git a/tests/languages/kotlin/Tests.kt b/tests/languages/kotlin/Tests.kt index 33b4ae06a..13044b210 100644 --- a/tests/languages/kotlin/Tests.kt +++ b/tests/languages/kotlin/Tests.kt @@ -5,6 +5,7 @@ import io.appwrite.Permission import io.appwrite.Role import io.appwrite.ID import io.appwrite.Query +import io.appwrite.enums.MockType import io.appwrite.exceptions.AppwriteException import io.appwrite.extensions.fromJson import io.appwrite.extensions.toJson @@ -100,6 +101,9 @@ class ServiceTest { writeToFile(ex.toString()) } + mock = general.enum(MockType.FIRST) + writeToFile(mock.result) + try { general.error400() } catch (e: AppwriteException) { @@ -120,6 +124,15 @@ class ServiceTest { general.empty() + val url = general.oauth2( + clientId = "clientId", + scopes = listOf("test"), + state = "123456", + success = "https://localhost", + failure = "https://localhost", + ) + writeToFile(url) + // Query helper tests writeToFile(Query.equal("released", listOf(true))) writeToFile(Query.equal("title", listOf("Spiderman", "Dr. Strange"))) @@ -141,6 +154,10 @@ class ServiceTest { writeToFile(Query.cursorBefore("my_movie_id")) writeToFile(Query.limit(50)) writeToFile(Query.offset(20)) + writeToFile(Query.contains("title", listOf("Spider"))) + writeToFile(Query.contains("labels", listOf("first"))) + writeToFile(Query.or(listOf(Query.equal("released", listOf(true)), Query.lessThan("releasedYear", 1990)))) + writeToFile(Query.and(listOf(Query.equal("released", listOf(false)), Query.greaterThan("releasedYear", 2015)))) // Permission & Roles helper tests writeToFile(Permission.read(Role.any())) diff --git a/tests/languages/node/test.js b/tests/languages/node/test.js index 259e7c0ea..a6b7ff419 100644 --- a/tests/languages/node/test.js +++ b/tests/languages/node/test.js @@ -1,7 +1,7 @@ const appwrite = require('../../sdks/node/index'); const InputFile = require('../../sdks/node/lib/inputFile'); -const fs = require('fs'); +const fs = require('fs').promises; async function start() { var response; @@ -10,9 +10,12 @@ async function start() { let Query = appwrite.Query; let Role = appwrite.Role; let ID = appwrite.ID; + let MockType = appwrite.MockType; // Init SDK - let client = new appwrite.Client(); + let client = new appwrite.Client() + .addHeader("Origin", "http://localhost") + .setSelfSigned(true); let foo = new appwrite.Foo(client); let bar = new appwrite.Bar(client); @@ -65,6 +68,17 @@ async function start() { response = await general.upload('string', 123, ['string in array'], InputFile.fromPath(__dirname + '/../../resources/large_file.mp4', 'large_file.mp4')); console.log(response.result); + let buffer= await fs.readFile('./tests/resources/file.png'); + response = await general.upload('string', 123, ['string in array'], appwrite.InputFile.fromBuffer(buffer, 'file.png')) + console.log(response.result); + + buffer = await fs.readFile('./tests/resources/large_file.mp4'); + response = await general.upload('string', 123, ['string in array'], appwrite.InputFile.fromBuffer(buffer, 'large_file.mp4')) + console.log(response.result); + + response = await general.enum(MockType.First); + console.log(response.result); + try { response = await general.error400(); } catch(error) { @@ -85,27 +99,46 @@ async function start() { await general.empty(); + const url = await general.oauth2( + 'clientId', + ['test'], + '123456', + 'https://localhost', + 'https://localhost' + ) + console.log(url) + // Query helper tests - console.log(Query.equal('released', [true])); - console.log(Query.equal('title', ['Spiderman', 'Dr. Strange'])); - console.log(Query.notEqual('title', 'Spiderman')); - console.log(Query.lessThan('releasedYear', 1990)); - console.log(Query.greaterThan('releasedYear', 1990)); - console.log(Query.search('name', "john")); - console.log(Query.isNull("name")) - console.log(Query.isNotNull("name")) - console.log(Query.between("age", 50, 100)) - console.log(Query.between("age", 50.5, 100.5)) - console.log(Query.between("name", "Anna", "Brad")) - console.log(Query.startsWith("name", "Ann")) - console.log(Query.endsWith("name", "nne")) - console.log(Query.select(["name", "age"])) + console.log(Query.equal("released", [true])); + console.log(Query.equal("title", ["Spiderman", "Dr. Strange"])); + console.log(Query.notEqual("title", "Spiderman")); + console.log(Query.lessThan("releasedYear", 1990)); + console.log(Query.greaterThan("releasedYear", 1990)); + console.log(Query.search("name", "john")); + console.log(Query.isNull("name")); + console.log(Query.isNotNull("name")); + console.log(Query.between("age", 50, 100)); + console.log(Query.between("age", 50.5, 100.5)); + console.log(Query.between("name", "Anna", "Brad")); + console.log(Query.startsWith("name", "Ann")); + console.log(Query.endsWith("name", "nne")); + console.log(Query.select(["name", "age"])); console.log(Query.orderAsc("title")); console.log(Query.orderDesc("title")); console.log(Query.cursorAfter("my_movie_id")); console.log(Query.cursorBefore("my_movie_id")); console.log(Query.limit(50)); console.log(Query.offset(20)); + console.log(Query.contains("title", "Spider")); + console.log(Query.contains("labels", "first")); + console.log(Query.or([ + Query.equal("released", true), + Query.lessThan("releasedYear", 1990) + ])); + console.log(Query.and([ + Query.equal("released", false), + Query.greaterThan("releasedYear", 2015) + ])); // Permission & Role helper tests console.log(Permission.read(Role.any())); diff --git a/tests/languages/php/test.php b/tests/languages/php/test.php index 4f74f4e60..d8f62724a 100644 --- a/tests/languages/php/test.php +++ b/tests/languages/php/test.php @@ -8,6 +8,7 @@ include __DIR__ . '/../../sdks/php/src/Appwrite/Role.php'; include __DIR__ . '/../../sdks/php/src/Appwrite/ID.php'; include __DIR__ . '/../../sdks/php/src/Appwrite/AppwriteException.php'; +include __DIR__ . '/../../sdks/php/src/Appwrite/Enums/MockType.php'; include __DIR__ . '/../../sdks/php/src/Appwrite/Services/Foo.php'; include __DIR__ . '/../../sdks/php/src/Appwrite/Services/Bar.php'; include __DIR__ . '/../../sdks/php/src/Appwrite/Services/General.php'; @@ -19,17 +20,19 @@ use Appwrite\Permission; use Appwrite\Role; use Appwrite\ID; +use Appwrite\Enums\MockType; use Appwrite\Services\Bar; use Appwrite\Services\Foo; use Appwrite\Services\General; -$client = new Client(); +$client = (new Client()) + ->addHeader("Origin", "http://localhost") + ->setSelfSigned(); + $foo = new Foo($client); $bar = new Bar($client); $general = new General($client); -$client->addHeader('Origin', 'http://localhost'); - echo "\nTest Started\n"; // Foo Service @@ -83,6 +86,9 @@ $response = $general->upload('string', 123, ['string in array'], InputFile::withPath(__DIR__ .'/../../resources/large_file.mp4')); echo "{$response['result']}\n"; +$response = $general->enum(MockType::FIRST()); +echo "{$response['result']}\n"; + try { $response = $general->error400(); } catch (AppwriteException $e) { @@ -103,6 +109,15 @@ $general->empty(); +$url = $general->oauth2( + 'clientId', + ['test'], + '123456', + 'https://localhost', + 'https://localhost' +); +echo $url . "\n"; + // Query helper tests echo Query::equal('released', [true]) . "\n"; echo Query::equal('title', ['Spiderman', 'Dr. Strange']) . "\n"; @@ -124,6 +139,16 @@ echo Query::cursorBefore('my_movie_id') . "\n"; echo Query::limit(50) . "\n"; echo Query::offset(20) . "\n"; +echo Query::contains('title', 'Spider') . "\n"; +echo Query::contains('labels', 'first') . "\n"; +echo Query::or([ + Query::equal('released', [true]), + Query::lessThan('releasedYear', 1990) +]) . "\n"; +echo Query::and([ + Query::equal('released', [false]), + Query::greaterThan('releasedYear', 2015) +]) . "\n"; // Permission & Role helper tests echo Permission::read(Role::any()) . "\n"; diff --git a/tests/languages/python/tests.py b/tests/languages/python/tests.py index 99f70c301..789373789 100644 --- a/tests/languages/python/tests.py +++ b/tests/languages/python/tests.py @@ -8,10 +8,10 @@ from appwrite.permission import Permission from appwrite.role import Role from appwrite.id import ID +from appwrite.enums.mock_type import MockType import os.path - client = Client() foo = Foo(client) bar = Bar(client) @@ -75,6 +75,9 @@ response = general.upload('string', 123, ['string in array'], InputFile.from_bytes(data, 'large_file.mp4','video/mp4')) print(response['result']) +response = general.enum(MockType.FIRST) +print(response['result']) + try: response = general.error400() except AppwriteException as e: @@ -92,13 +95,22 @@ general.empty() +url = general.oauth2( + 'clientId', + ['test'], + '123456', + 'https://localhost', + 'https://localhost' +) +print(url) + # Query helper tests -print(Query.equal('released', [True])) -print(Query.equal('title', ['Spiderman', 'Dr. Strange'])) -print(Query.not_equal('title', 'Spiderman')) -print(Query.less_than('releasedYear', 1990)) -print(Query.greater_than('releasedYear', 1990)) -print(Query.search('name', 'john')) +print(Query.equal("released", [True])) +print(Query.equal("title", ["Spiderman", "Dr. Strange"])) +print(Query.not_equal("title", "Spiderman")) +print(Query.less_than("releasedYear", 1990)) +print(Query.greater_than("releasedYear", 1990)) +print(Query.search("name", "john")) print(Query.is_null("name")) print(Query.is_not_null("name")) print(Query.between("age", 50, 100)) @@ -113,6 +125,14 @@ print(Query.cursor_before("my_movie_id")) print(Query.limit(50)) print(Query.offset(20)) +print(Query.contains("title", "Spider")) +print(Query.contains("labels", "first")) +print(Query.or_queries( + [Query.equal("released", True), Query.less_than("releasedYear", 1990)] +)) +print(Query.and_queries( + [Query.equal("released", False), Query.greater_than("releasedYear", 2015)] +)) # Permission & Role helper tests print(Permission.read(Role.any())) diff --git a/tests/languages/ruby/tests.rb b/tests/languages/ruby/tests.rb index 4a76966b0..ffff2d137 100644 --- a/tests/languages/ruby/tests.rb +++ b/tests/languages/ruby/tests.rb @@ -1,6 +1,7 @@ require_relative '../../sdks/ruby/lib/appwrite' include Appwrite +include Appwrite::Enums client = Client.new client.set_self_signed(true) @@ -82,6 +83,9 @@ puts e end +response = general.enum(mock_type: MockType::FIRST) +puts response.result + begin general.error400() rescue Exception => error @@ -102,6 +106,15 @@ general.empty() +url = general.oauth2( + client_id: 'clientId', + scopes: ['test'], + state: '123456', + success: 'https://localhost', + failure: 'https://localhost' +) +puts url + # Query helper tests puts Query.equal('released', [true]) puts Query.equal('title', ['Spiderman', 'Dr. Strange']) @@ -123,6 +136,10 @@ puts Query.cursor_before("my_movie_id") puts Query.limit(50) puts Query.offset(20) +puts Query.contains("title", "Spider") +puts Query.contains("labels", "first") +puts Query.or([Query.equal("released", true), Query.less_than("releasedYear", 1990)]) +puts Query.and([Query.equal("released", false), Query.greater_than("releasedYear", 2015)]) # Permission & Role helper tests puts Permission.read(Role.any()) diff --git a/tests/languages/swift/Tests.swift b/tests/languages/swift/Tests.swift index 948de3601..3c89e6b89 100644 --- a/tests/languages/swift/Tests.swift +++ b/tests/languages/swift/Tests.swift @@ -19,6 +19,7 @@ class Tests: XCTestCase { } func test() async throws { + do { let client = Client() .setProject("console") .addHeader(key: "Origin", value: "http://localhost") @@ -48,19 +49,19 @@ class Tests: XCTestCase { // Bar Tests - mock = try await bar.get(xrequired: "string", xdefault: 123, z: ["string in array"]) + mock = try await bar.get(required: "string", default: 123, z: ["string in array"]) print(mock.result) - mock = try await bar.post(xrequired: "string", xdefault: 123, z: ["string in array"]) + mock = try await bar.post(required: "string", default: 123, z: ["string in array"]) print(mock.result) - mock = try await bar.put(xrequired: "string", xdefault: 123, z: ["string in array"]) + mock = try await bar.put(required: "string", default: 123, z: ["string in array"]) print(mock.result) - mock = try await bar.patch(xrequired: "string", xdefault: 123, z: ["string in array"]) + mock = try await bar.patch(required: "string", default: 123, z: ["string in array"]) print(mock.result) - mock = try await bar.delete(xrequired: "string", xdefault: 123, z: ["string in array"]) + mock = try await bar.delete(required: "string", default: 123, z: ["string in array"]) print(mock.result) @@ -104,6 +105,9 @@ class Tests: XCTestCase { print(error.localizedDescription) } + mock = try await general.xenum(mockType: .first) + print(mock.result) + do { try await general.error400() } catch { @@ -124,6 +128,15 @@ class Tests: XCTestCase { try! await general.empty() + let url = try? await general.oauth2( + clientId: "clientId", + scopes: ["test"], + state: "123456", + success: "https://localhost", + failure: "https://localhost" + ) + print(url!) + // Query helper tests print(Query.equal("released", value: [true])) print(Query.equal("title", value: ["Spiderman", "Dr. Strange"])) @@ -145,6 +158,14 @@ class Tests: XCTestCase { print(Query.cursorBefore("my_movie_id")) print(Query.limit(50)) print(Query.offset(20)) + print(Query.contains("title", value: "Spider")) + print(Query.contains("labels", value: "first")) + print(Query.or( + [Query.equal("released", value: true), Query.lessThan("releasedYear", value: 1990)] + )) + print(Query.and( + [Query.equal("released", value: false), Query.greaterThan("releasedYear", value: 2015)] + )) // Permission & Role helper tests print(Permission.read(Role.any())) @@ -164,5 +185,8 @@ class Tests: XCTestCase { mock = try await general.headers() print(mock.result) + } catch { + print(error.localizedDescription) + } } } diff --git a/tests/languages/web/index.html b/tests/languages/web/index.html index 17387180f..e3fcfe1f5 100644 --- a/tests/languages/web/index.html +++ b/tests/languages/web/index.html @@ -20,8 +20,9 @@ let response; let responseRealtime = 'Realtime failed!'; // Init SDK - const { Client, Foo, Bar, General, Query, Permission, Role, ID } = Appwrite; + const { Client, Foo, Bar, General, Query, Permission, Role, ID, MockType } = Appwrite; const client = new Client(); + const foo = new Foo(client); const bar = new Bar(client); const general = new General(client); @@ -85,6 +86,12 @@ ); console.log(response.result); + console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip InputFile tests + console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip InputFile tests + + response = await general.enum(MockType.First); + console.log(response.result); + try { response = await general.empty(); } catch (error) { @@ -114,26 +121,36 @@ console.log(responseRealtime) // Query helper tests - console.log(Query.equal('released', [true])); - console.log(Query.equal('title', ['Spiderman', 'Dr. Strange'])); - console.log(Query.notEqual('title', 'Spiderman')); - console.log(Query.lessThan('releasedYear', 1990)); - console.log(Query.greaterThan('releasedYear', 1990)); - console.log(Query.search('name', "john")); - console.log(Query.isNull("name")) - console.log(Query.isNotNull("name")) - console.log(Query.between("age", 50, 100)) - console.log(Query.between("age", 50.5, 100.5)) - console.log(Query.between("name", "Anna", "Brad")) - console.log(Query.startsWith("name", "Ann")) - console.log(Query.endsWith("name", "nne")) - console.log(Query.select(["name", "age"])) + console.log(Query.equal("released", [true])); + console.log(Query.equal("title", ["Spiderman", "Dr. Strange"])); + console.log(Query.notEqual("title", "Spiderman")); + console.log(Query.lessThan("releasedYear", 1990)); + console.log(Query.greaterThan("releasedYear", 1990)); + console.log(Query.search("name", "john")); + console.log(Query.isNull("name")); + console.log(Query.isNotNull("name")); + console.log(Query.between("age", 50, 100)); + console.log(Query.between("age", 50.5, 100.5)); + console.log(Query.between("name", "Anna", "Brad")); + console.log(Query.startsWith("name", "Ann")); + console.log(Query.endsWith("name", "nne")); + console.log(Query.select(["name", "age"])); console.log(Query.orderAsc("title")); console.log(Query.orderDesc("title")); console.log(Query.cursorAfter("my_movie_id")); console.log(Query.cursorBefore("my_movie_id")); console.log(Query.limit(50)); console.log(Query.offset(20)); + console.log(Query.contains("title", "Spider")); + console.log(Query.contains("labels", "first")); + console.log(Query.or([ + Query.equal("released", true), + Query.lessThan("releasedYear", 1990) + ])); + console.log(Query.and([ + Query.equal("released", false), + Query.greaterThan("releasedYear", 2015) + ])); // Permission & Role helper tests console.log(Permission.read(Role.any())); diff --git a/tests/languages/web/node.js b/tests/languages/web/node.js index 0ec235512..590779735 100644 --- a/tests/languages/web/node.js +++ b/tests/languages/web/node.js @@ -1,4 +1,4 @@ -const { Client, Foo, Bar, General, Query, Permission, Role, ID } = require('./dist/cjs/sdk.js'); +const { Client, Foo, Bar, General, Query, Permission, Role, ID, MockType } = require('./dist/cjs/sdk.js'); async function start() { let response; @@ -46,6 +46,11 @@ async function start() { console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip file upload test on Node.js console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip big file upload test on Node.js + console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip file upload test on Node.js + console.log('POST:/v1/mock/tests/general/upload:passed'); // Skip big file upload test on Node.js + + response = await general.enum(MockType.First); + console.log(response.result); try { response = await general.empty(); @@ -71,26 +76,36 @@ async function start() { console.log('WS:/v1/realtime:passed'); // Skip realtime test on Node.js // Query helper tests - console.log(Query.equal('released', [true])); - console.log(Query.equal('title', ['Spiderman', 'Dr. Strange'])); - console.log(Query.notEqual('title', 'Spiderman')); - console.log(Query.lessThan('releasedYear', 1990)); - console.log(Query.greaterThan('releasedYear', 1990)); - console.log(Query.search('name', "john")); - console.log(Query.isNull("name")) - console.log(Query.isNotNull("name")) - console.log(Query.between("age", 50, 100)) - console.log(Query.between("age", 50.5, 100.5)) - console.log(Query.between("name", "Anna", "Brad")) - console.log(Query.startsWith("name", "Ann")) - console.log(Query.endsWith("name", "nne")) - console.log(Query.select(["name", "age"])) + console.log(Query.equal("released", [true])); + console.log(Query.equal("title", ["Spiderman", "Dr. Strange"])); + console.log(Query.notEqual("title", "Spiderman")); + console.log(Query.lessThan("releasedYear", 1990)); + console.log(Query.greaterThan("releasedYear", 1990)); + console.log(Query.search("name", "john")); + console.log(Query.isNull("name")); + console.log(Query.isNotNull("name")); + console.log(Query.between("age", 50, 100)); + console.log(Query.between("age", 50.5, 100.5)); + console.log(Query.between("name", "Anna", "Brad")); + console.log(Query.startsWith("name", "Ann")); + console.log(Query.endsWith("name", "nne")); + console.log(Query.select(["name", "age"])); console.log(Query.orderAsc("title")); console.log(Query.orderDesc("title")); console.log(Query.cursorAfter("my_movie_id")); console.log(Query.cursorBefore("my_movie_id")); console.log(Query.limit(50)); console.log(Query.offset(20)); + console.log(Query.contains("title", "Spider")); + console.log(Query.contains("labels", "first")); + console.log(Query.or([ + Query.equal("released", true), + Query.lessThan("releasedYear", 1990) + ])); + console.log(Query.and([ + Query.equal("released", false), + Query.greaterThan("releasedYear", 2015) + ])); // Permission & Role helper tests console.log(Permission.read(Role.any())); diff --git a/tests/resources/spec.json b/tests/resources/spec.json index 772602681..d3ddf0676 100644 --- a/tests/resources/spec.json +++ b/tests/resources/spec.json @@ -1,93 +1,131 @@ { "swagger": "2.0", "info": { - "version": "0.15.3", + "version": "1.4.2", "title": "Appwrite", - "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)", - "termsOfService": "https://appwrite.io/policy/terms", + "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", + "termsOfService": "https:\/\/appwrite.io\/policy\/terms", "contact": { "name": "Appwrite Team", - "url": "https://appwrite.io/support", + "url": "https:\/\/appwrite.io\/support", "email": "team@appwrite.io" }, "license": { "name": "BSD-3-Clause", - "url": "https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE" + "url": "https:\/\/raw.githubusercontent.com\/appwrite\/appwrite\/master\/LICENSE" } }, "host": "mockapi", "basePath": "/v1", "schemes": ["http"], - "consumes": ["application/json", "multipart/form-data"], - "produces": ["application/json"], + "consumes": [ + "application/json", + "multipart/form-data" + ], + "produces": [ + "application/json" + ], "securityDefinitions": { "Project": { "type": "apiKey", "name": "X-Appwrite-Project", "description": "Your project ID", "in": "header", - "x-appwrite": { "demo": "5df5acd0d48c2" } + "x-appwrite": { + "demo": "5df5acd0d48c2" + } }, "Key": { "type": "apiKey", "name": "X-Appwrite-Key", "description": "Your secret API key", "in": "header", - "x-appwrite": { "demo": "919c2d18fb5d4...a2ae413da83346ad2" } + "x-appwrite": { + "demo": "919c2d18fb5d4...a2ae413da83346ad2" + } }, "JWT": { "type": "apiKey", "name": "X-Appwrite-JWT", "description": "Your secret JSON Web Token", "in": "header", - "x-appwrite": { "demo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..." } + "x-appwrite": { + "demo": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..." + } }, "Locale": { "type": "apiKey", "name": "X-Appwrite-Locale", "description": "", "in": "header", - "x-appwrite": { "demo": "en" } + "x-appwrite": { + "demo": "en" + } }, "Mode": { "type": "apiKey", "name": "X-Appwrite-Mode", "description": "", "in": "header", - "x-appwrite": { "demo": "" } + "x-appwrite": { + "demo": "" + } } }, "paths": { - "/mock/tests/bar": { + "\/mock\/tests\/bar": { "get": { "summary": "Get Bar", "operationId": "barGet", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["bar"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "bar" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "get", - "weight": 232, + "weight": 270, "cookies": false, "type": "", - "demo": "bar/get.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a get request.", + "demo": "bar\/get.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a get request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "required", @@ -111,7 +149,9 @@ "required": true, "type": "array", "collectionFormat": "multi", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "in": "query" } ] @@ -119,32 +159,55 @@ "post": { "summary": "Post Bar", "operationId": "barPost", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["bar"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "bar" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "post", - "weight": 233, + "weight": 271, "cookies": false, "type": "", - "demo": "bar/post.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a post request.", + "demo": "bar\/post.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a post request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "\/mock\/tests\/bar", + "offline-key": "{required}", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -169,10 +232,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["required", "default", "z"] + "required": [ + "required", + "default", + "z" + ] } } ] @@ -180,32 +249,55 @@ "put": { "summary": "Put Bar", "operationId": "barPut", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["bar"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "bar" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "put", - "weight": 235, + "weight": 273, "cookies": false, "type": "", - "demo": "bar/put.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a put request.", + "demo": "bar\/put.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a put request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -230,10 +322,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["required", "default", "z"] + "required": [ + "required", + "default", + "z" + ] } } ] @@ -241,32 +339,55 @@ "patch": { "summary": "Patch Bar", "operationId": "barPatch", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["bar"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "bar" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "patch", - "weight": 234, + "weight": 272, "cookies": false, "type": "", - "demo": "bar/patch.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a patch request.", + "demo": "bar\/patch.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a patch request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -291,10 +412,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["required", "default", "z"] + "required": [ + "required", + "default", + "z" + ] } } ] @@ -302,32 +429,55 @@ "delete": { "summary": "Delete Bar", "operationId": "barDelete", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["bar"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "bar" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "delete", - "weight": 236, + "weight": 274, "cookies": false, "type": "", - "demo": "bar/delete.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a delete request.", + "demo": "bar\/delete.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a delete request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -352,45 +502,74 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["required", "default", "z"] + "required": [ + "required", + "default", + "z" + ] } } ] } }, - "/mock/tests/foo": { + "\/mock\/tests\/foo": { "get": { "summary": "Get Foo", "operationId": "fooGet", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["foo"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "foo" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "get", - "weight": 227, + "weight": 265, "cookies": false, "type": "", - "demo": "foo/get.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a get request.", + "demo": "foo\/get.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a get request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "x", @@ -414,7 +593,9 @@ "required": true, "type": "array", "collectionFormat": "multi", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "in": "query" } ] @@ -422,32 +603,55 @@ "post": { "summary": "Post Foo", "operationId": "fooPost", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["foo"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "foo" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "post", - "weight": 228, + "weight": 266, "cookies": false, "type": "", - "demo": "foo/post.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a post request.", + "demo": "foo\/post.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a post request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -472,10 +676,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] } } ] @@ -483,32 +693,55 @@ "put": { "summary": "Put Foo", "operationId": "fooPut", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["foo"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "foo" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "put", - "weight": 230, + "weight": 268, "cookies": false, "type": "", - "demo": "foo/put.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a put request.", + "demo": "foo\/put.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a put request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -533,10 +766,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] } } ] @@ -544,32 +783,55 @@ "patch": { "summary": "Patch Foo", "operationId": "fooPatch", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["foo"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "foo" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "patch", - "weight": 229, + "weight": 267, "cookies": false, "type": "", - "demo": "foo/patch.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a patch request.", + "demo": "foo\/patch.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a patch request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -594,10 +856,16 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] } } ] @@ -605,32 +873,55 @@ "delete": { "summary": "Delete Foo", "operationId": "fooDelete", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["foo"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "foo" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "delete", - "weight": 231, + "weight": 269, "cookies": false, "type": "", - "demo": "foo/delete.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a delete request.", + "demo": "foo\/delete.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a delete request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "payload", @@ -655,352 +946,785 @@ "description": "Sample array param", "default": null, "x-example": null, - "items": { "type": "string" } + "items": { + "type": "string" + } } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] } } ] } }, - "/mock/tests/general/400-error": { + "\/mock\/tests\/general\/400-error": { "get": { "summary": "400 Error", "operationId": "generalError400", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "400": { "description": "Error", - "schema": { "$ref": "#/definitions/error" } + "schema": { + "$ref": "#\/definitions\/error" + } } }, "x-appwrite": { "method": "error400", - "weight": 245, + "weight": 285, "cookies": false, "type": "", - "demo": "general/error400.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a 400 failed request.", + "demo": "general\/error400.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a 400 failed request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/500-error": { + "\/mock\/tests\/general\/500-error": { "get": { "summary": "500 Error", "operationId": "generalError500", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "500": { "description": "Error", - "schema": { "$ref": "#/definitions/error" } + "schema": { + "$ref": "#\/definitions\/error" + } } }, "x-appwrite": { "method": "error500", - "weight": 246, + "weight": 286, "cookies": false, "type": "", - "demo": "general/error500.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a 500 failed request.", + "demo": "general\/error500.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a 500 failed request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/502-error": { + "\/mock\/tests\/general\/502-error": { "get": { "summary": "502 Error", "operationId": "generalError502", - "consumes": ["application/json"], - "produces": ["text/plain"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "text\/plain" + ], + "tags": [ + "general" + ], "description": "", "responses": { "502": { "description": "Any", - "schema": { "$ref": "#/definitions/any" } + "schema": { + "$ref": "#\/definitions\/any" + } } }, "x-appwrite": { "method": "error502", - "weight": 247, + "weight": 287, "cookies": false, "type": "", - "demo": "general/error502.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a 502 bad gateway.", + "demo": "general\/error502.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a 502 bad gateway.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client"], + "platforms": [ + "client" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [] }] + "security": [ + { + "Project": [] + } + ] } }, - "/mock/tests/general/download": { + "\/mock\/tests\/general\/download": { "get": { "summary": "Download File", "operationId": "generalDownload", - "consumes": ["application/json"], - "produces": ["*/*"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "*\/*" + ], + "tags": [ + "general" + ], "description": "", "responses": { - "200": { "description": "File", "schema": { "type": "file" } } + "200": { + "description": "File", + "schema": { + "type": "file" + } + } }, "x-appwrite": { "method": "download", - "weight": 237, + "weight": 276, "cookies": false, "type": "location", - "demo": "general/download.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a file download request.", + "demo": "general\/download.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a file download request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/empty": { + "\/mock\/tests\/general\/empty": { "get": { "summary": "Empty Response", "operationId": "generalEmpty", - "consumes": ["application/json"], + "consumes": [ + "application\/json" + ], "produces": [], - "tags": ["general"], + "tags": [ + "general" + ], "description": "", - "responses": { "204": { "description": "No content" } }, + "responses": { + "204": { + "description": "No content" + } + }, "x-appwrite": { "method": "empty", - "weight": 243, + "weight": 282, "cookies": false, "type": "", - "demo": "general/empty.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock an empty response.", + "demo": "general\/empty.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock an empty response.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/get-cookie": { + "\/mock\/tests\/general\/enum": { + "post": { + "summary": "Enum Test", + "operationId": "generalEnum", + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], + "description": "", + "responses": { + "200": { + "description": "Mock", + "schema": { + "$ref": "#\/definitions\/mock" + } + }, + "500": { + "description": "File", + "schema": { + "type": "file" + } + } + }, + "x-appwrite": { + "method": "enum", + "weight": 284, + "cookies": false, + "type": "", + "demo": "general\/enum.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock an enum parameter.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "public", + "platforms": [ + "client", + "server", + "server" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "mockType": { + "type": "string", + "description": "Sample string param", + "default": null, + "x-example": "first", + "enum": [ + "first", + "second", + "third" + ], + "x-enum-name": null, + "x-enum-keys": [] + } + }, + "required": [ + "mockType" + ] + } + } + ] + } + }, + "\/mock\/tests\/general\/get-cookie": { "get": { "summary": "Get Cookie", "operationId": "generalGetCookie", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "getCookie", - "weight": 242, + "weight": 281, "cookies": false, "type": "", - "demo": "general/get-cookie.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a cookie response.", + "demo": "general\/get-cookie.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a cookie response.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/headers": { + "\/mock\/tests\/general\/headers": { "get": { "summary": "Get headers", "operationId": "generalHeaders", - "consumes": ["application/json"], + "consumes": [ + "application\/json" + ], "produces": [], - "tags": ["general"], + "tags": [ + "general" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "headers", - "weight": 244, + "weight": 275, "cookies": false, "type": "", - "demo": "general/headers.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterReturn headers from the request", + "demo": "general\/headers.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterReturn headers from the request", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/redirect": { + "\/mock\/tests\/general\/nullable": { + "post": { + "summary": "Nullable Test", + "operationId": "generalNullable", + "consumes": [ + "application\/json" + ], + "produces": [], + "tags": [ + "general" + ], + "description": "", + "responses": { + "500": { + "description": "File", + "schema": { + "type": "file" + } + } + }, + "x-appwrite": { + "method": "nullable", + "weight": 283, + "cookies": false, + "type": "", + "demo": "general\/nullable.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a nullable parameter.", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "public", + "platforms": [ + "client", + "server", + "server" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "payload", + "in": "body", + "schema": { + "type": "object", + "properties": { + "required": { + "type": "string", + "description": "Sample string param", + "default": null, + "x-example": "[REQUIRED]" + }, + "nullable": { + "type": "string", + "description": "Sample string param", + "default": null, + "x-example": "[NULLABLE]", + "x-nullable": true + }, + "optional": { + "type": "string", + "description": "Sample string param", + "default": "", + "x-example": "[OPTIONAL]" + } + }, + "required": [ + "required", + "nullable" + ] + } + } + ] + } + }, + "\/mock\/tests\/general\/redirect": { "get": { "summary": "Redirect", "operationId": "generalRedirect", - "consumes": ["application/json"], - "produces": ["text/html"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "text\/html" + ], + "tags": [ + "general" + ], "description": "", - "responses": { "301": { "description": "No content" } }, + "responses": { + "301": { + "description": "No content" + } + }, "x-appwrite": { "method": "redirect", - "weight": 239, + "weight": 278, "cookies": false, "type": "", - "demo": "general/redirect.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a redirect request.", + "demo": "general\/redirect.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a redirect request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/redirect/done": { + "\/mock\/tests\/general\/redirect\/done": { "get": { "summary": "Redirected", "operationId": "generalRedirected", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "redirected", - "weight": 240, + "weight": 279, "cookies": false, "type": "", - "demo": "general/redirected.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a redirected request.", + "demo": "general\/redirected.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a redirected request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/set-cookie": { + "\/mock\/tests\/general\/set-cookie": { "get": { "summary": "Set Cookie", "operationId": "generalSetCookie", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "application\/json" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "setCookie", - "weight": 241, + "weight": 280, "cookies": false, "type": "", - "demo": "general/set-cookie.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a set cookie request.", + "demo": "general\/set-cookie.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a set cookie request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }] + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ] } }, - "/mock/tests/general/upload": { + "\/mock\/tests\/general\/upload": { "post": { "summary": "Upload File", "operationId": "generalUpload", - "consumes": ["multipart/form-data"], - "produces": ["application/json"], - "tags": ["general"], + "consumes": [ + "multipart\/form-data" + ], + "produces": [ + "application\/json" + ], + "tags": [ + "general" + ], "description": "", "responses": { "200": { "description": "Mock", - "schema": { "$ref": "#/definitions/mock" } + "schema": { + "$ref": "#\/definitions\/mock" + } } }, "x-appwrite": { "method": "upload", - "weight": 238, + "weight": 277, "cookies": false, "type": "", - "demo": "general/upload.md", - "edit": "https://github.com/appwrite/appwrite/edit/masterMock a file upload request.", + "demo": "general\/upload.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a file upload request.", "rate-limit": 0, "rate-time": 3600, "rate-key": "url:{url},ip:{ip}", "scope": "public", - "platforms": ["client", "server", "server"], + "platforms": [ + "client", + "server", + "server" + ], "packaging": false, - "auth": { "Project": [] } + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } }, - "security": [{ "Project": [], "Key": [], "JWT": [] }], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], "parameters": [ { "name": "x", @@ -1024,7 +1748,9 @@ "required": true, "type": "array", "collectionFormat": "multi", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "in": "formData" }, { @@ -1036,6 +1762,104 @@ } ] } + }, + "\/mock\/tests\/general\/oauth2": { + "get": { + "summary": "OAuth2", + "operationId": "generalOAuth2", + "consumes": [ + "application\/json" + ], + "produces": [ + "text\/plain" + ], + "tags": [ + "general" + ], + "description": "OAuth 2", + "responses": { + "301": { + "description": "No content" + } + }, + "x-appwrite": { + "method": "oauth2", + "weight": 265, + "cookies": false, + "type": "webAuth", + "demo": "general\/oauth2.md", + "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/masterMock a oauth2 request.", + "rate-limit": 10, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "public", + "platforms": [ + "client", + "server", + "server" + ], + "packaging": false, + "offline-model": "", + "offline-key": "", + "offline-response-key": "$id", + "auth": { + "Project": [] + } + }, + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "parameters": [ + { + "name": "clientId", + "description": "OAuth2 Client ID", + "required": true, + "type": "string", + "x-example": "12345", + "in": "query" + }, + { + "name": "scopes", + "description": "OAuth2 scope list", + "required": true, + "type": "array", + "collectionFormat": "multi", + "items": { + "type": "string" + }, + "x-example": "[]", + "in": "query" + }, + { + "name": "state", + "description": "OAuth2 state", + "required": true, + "type": "string", + "x-example": "state", + "in": "query" + }, + { + "name": "success", + "description": "OAuth2 success redirect URI", + "required": true, + "type": "string", + "x-example": "http://localhost:8000/auth/callback", + "in": "query" + }, + { + "name": "failure", + "description": "OAuth2 failure redirect URI", + "required": true, + "type": "string", + "x-example": "http://localhost:8000/auth/callback", + "in": "query" + } + ] + } } }, "tags": [ @@ -1052,7 +1876,9 @@ { "name": "databases", "description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents", - "x-globalAttributes": ["databaseId"] + "x-globalAttributes": [ + "databaseId" + ] }, { "name": "locale", @@ -1069,6 +1895,11 @@ "description": "The Project service allows you to manage all the projects in your Appwrite server.", "x-globalAttributes": [] }, + { + "name": "project", + "description": "The Project service allows you to manage all the projects in your Appwrite server.", + "x-globalAttributes": [] + }, { "name": "storage", "description": "The Storage service allows you to manage your project files.", @@ -1088,6 +1919,26 @@ "name": "functions", "description": "The Functions Service allows you view, create and manage your Cloud Functions.", "x-globalAttributes": [] + }, + { + "name": "proxy", + "description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration.", + "x-globalAttributes": [] + }, + { + "name": "graphql", + "description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.", + "x-globalAttributes": [] + }, + { + "name": "console", + "description": "The Console service allows you to interact with console relevant informations.", + "x-globalAttributes": [] + }, + { + "name": "migrations", + "description": "The Migrations service allows you to migrate third-party data to your Appwrite project.", + "x-globalAttributes": [] } ], "definitions": { @@ -1112,7 +1963,7 @@ }, "type": { "type": "string", - "description": "Error type. You can learn more about all the error types at https://appwrite.io/docs/error-codes#errorTypes", + "description": "Error type. You can learn more about all the error types at https:\/\/appwrite.io\/docs\/error-codes#errorTypes", "x-example": "not_found" }, "version": { @@ -1121,7 +1972,12 @@ "x-example": "1.0" } }, - "required": ["message", "code", "type", "version"] + "required": [ + "message", + "code", + "type", + "version" + ] }, "mock": { "description": "Mock", @@ -1133,11 +1989,13 @@ "x-example": "Success" } }, - "required": ["result"] + "required": [ + "result" + ] } }, "externalDocs": { "description": "Full API docs, specs and tutorials", - "url": "https://appwrite.io/docs" + "url": "https:\/\/appwrite.io\/docs" } } \ No newline at end of file