diff --git a/mock-server/app/http.php b/mock-server/app/http.php index 48dbd1f390..e258b6e4a3 100644 --- a/mock-server/app/http.php +++ b/mock-server/app/http.php @@ -555,7 +555,7 @@ ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_MOCK) ->label('sdk.mock', true) - ->param('queries', [], new ArrayList(new Text(4096), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK.') + ->param('queries', [], new ArrayList(new Text(4096), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK.', true) ->inject('response') ->action(function (array $queries, UtopiaSwooleResponse $response) { $response->json(['result' => \json_encode($queries)]); @@ -655,7 +655,9 @@ ->label('sdk.response.model', Response::MODEL_MOCK) ->label('sdk.mock', true) ->param('documents', [], new ArrayList(new Assoc(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of document objects.') - ->action(function (array $documents) { + ->param('labels', null, new Nullable(new ArrayList(new Nullable(new Text(256)), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Optional labels, including null values.', true) + ->inject('response') + ->action(function (array $documents, ?array $labels, UtopiaSwooleResponse $response) { if ($documents === []) { throw new Exception(Exception::GENERAL_MOCK, 'Documents must not be empty'); } @@ -665,6 +667,10 @@ throw new Exception(Exception::GENERAL_MOCK, 'Each document must be an object with an $id'); } } + + $response->json([ + 'result' => \json_encode(['documents' => $documents, 'labels' => $labels]), + ]); }); App::get('/v1/mock/tests/union') diff --git a/templates/deno/src/client.ts.twig b/templates/deno/src/client.ts.twig index 7f670efafb..cd7ce24b7d 100644 --- a/templates/deno/src/client.ts.twig +++ b/templates/deno/src/client.ts.twig @@ -156,6 +156,17 @@ export class Client { return json; } + static validateStringList(name: string, value: unknown, nullableItems: boolean = false): void { + // Missing values are left to the generated required-parameter checks. + if (value === null || typeof value === 'undefined') { + return; + } + + if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' && !(nullableItems && item === null))) { + throw new {{spec.info.title | caseUcfirst}}Exception(`Invalid parameter: "${name}" must be a list of strings.`, 0, 'sdk_input_validation'); + } + } + static flatten(data: Payload, prefix = ''): Payload { let output: Payload = {}; diff --git a/templates/deno/src/services/service.ts.twig b/templates/deno/src/services/service.ts.twig index e0f049e0d9..b385fe6b08 100644 --- a/templates/deno/src/services/service.ts.twig +++ b/templates/deno/src/services/service.ts.twig @@ -87,6 +87,12 @@ export class {{ service.name | caseUcfirst }} extends Service { throw new {{spec.info.title | caseUcfirst}}Exception('Missing required parameter: "{{ parameter.name | caseCamel | escapeKeyword }}"'); } +{% endif %} +{% endfor %} +{% for parameter in (method | parameters('all')) %} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} + Client.validateStringList('{{ parameter.name | caseCamel | escapeKeyword }}', {{ parameter.name | caseCamel | escapeKeyword }}{% if (parameter | arraySchema) | schemaNullable %}, true{% endif %}); + {% endif %} {% endfor %} let apiPath = '{{ method.path }}'{% for parameter in (method | parameters('path')) %}.replace('{{ '{' }}{{ parameter.name }}{{ '}' }}', {% if (parameter.extensions['x-sdk-source']|default('')) == 'security' %}this.client.config['{{ parameter.extensions['x-sdk-config'] | caseLower }}']{% else %}{{ parameter.name | caseCamel | escapeKeyword }}{% endif %}){% endfor %}; diff --git a/templates/node/src/client.ts.twig b/templates/node/src/client.ts.twig index 7d31757a64..f4629be273 100644 --- a/templates/node/src/client.ts.twig +++ b/templates/node/src/client.ts.twig @@ -696,6 +696,32 @@ class Client { return data; } + static validateStringList( + name: string, + value: unknown, + nullableItems = false, + ): void { + // Missing values are left to the generated required-parameter checks. + if (value === null || typeof value === 'undefined') { + return; + } + + if ( + !Array.isArray(value) || + value.some( + (item) => + typeof item !== 'string' && + !(nullableItems && item === null), + ) + ) { + throw new {{spec.info.title | caseUcfirst}}Exception( + `Invalid parameter: "${name}" must be a list of strings.`, + 0, + 'sdk_input_validation', + ); + } + } + static flatten(data: Payload, prefix = ''): Payload { let output: Payload = {}; diff --git a/templates/node/src/services/template.ts.twig b/templates/node/src/services/template.ts.twig index c7aba2277c..60e94e9324 100644 --- a/templates/node/src/services/template.ts.twig +++ b/templates/node/src/services/template.ts.twig @@ -138,6 +138,15 @@ export class {{ service.name | caseUcfirst }} { } {%~ endif %} {%~ endfor %} + {%~ for parameter in (method | parameters('all')) %} + {%~ if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} + {%~ set validationArgs = ["'" ~ (parameter.name | caseCamel | escapeKeyword) ~ "'", (parameter.name | caseCamel | escapeKeyword)] %} + {%~ if (parameter | arraySchema) | schemaNullable %} + {%~ set validationArgs = validationArgs | merge(['true']) %} + {%~ endif %} + {{ 'Client.validateStringList' | jsArgs(validationArgs, 8) | raw }} + {%~ endif %} + {%~ endfor %} {% set pathReplacements = [] %}{% for parameter in (method | parameters('path')) %}{% set pathReplacements = pathReplacements|merge([['{' ~ parameter.name ~ '}', 'encodeURIComponent(String(' ~ (((parameter.extensions['x-sdk-source']|default('')) == 'security') ? ('this.client.config.' ~ (parameter.extensions['x-sdk-config'] | caseLower)) : (parameter.name | caseCamel | escapeKeyword)) ~ '))']]) %}{% endfor %} {{ method.path | tsApiPath(pathReplacements, 8) | raw }} const apiPayload: Payload = {}; diff --git a/templates/php/base/params.twig b/templates/php/base/params.twig index ac781a37c6..da1f0c0b18 100644 --- a/templates/php/base/params.twig +++ b/templates/php/base/params.twig @@ -1,3 +1,9 @@ +{% for parameter in (method | parameters('all')) %} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} + $this->validateStringList('{{ parameter.name | caseCamel }}', ${{ parameter.name | caseCamel | escapeKeyword }}{% if (parameter | arraySchema) | schemaNullable %}, nullableItems: true{% endif %}); + +{% endif %} +{% endfor %} $apiParams = []; {% if (method | parameters('all')) | length %} {% for parameter in (method | parameters('all')) %} diff --git a/templates/php/src/Client.php.twig b/templates/php/src/Client.php.twig index 87b7e5e2e6..3591871f60 100644 --- a/templates/php/src/Client.php.twig +++ b/templates/php/src/Client.php.twig @@ -295,7 +295,7 @@ class Client $method === self::METHOD_GET => $this->requestFactory->query( $method, $uri, - $params, + $this->prepareParams($params), $headers ), $contentType === 'application/json' => $this->requestFactory->json( @@ -374,7 +374,8 @@ class Client } /** - * Prepare params for JSON encoding by converting model objects to arrays + * Prepare params for encoding by converting model objects to arrays and + * enum objects to their values * */ protected function prepareParams($data) @@ -387,6 +388,10 @@ class Client return $data->toArray(); } + if ($data instanceof \JsonSerializable) { + return $data->jsonSerialize(); + } + return $data; } diff --git a/templates/php/src/Service.php.twig b/templates/php/src/Service.php.twig index 394390e1d1..b121e2b760 100644 --- a/templates/php/src/Service.php.twig +++ b/templates/php/src/Service.php.twig @@ -9,4 +9,22 @@ abstract class Service public function __construct(protected Client $client) { } + + protected function validateStringList(string $name, ?array $value, bool $nullableItems = false): void + { + if ($value === null) { + return; + } + + // Enum objects serialize to their string value. + $valid = array_is_list($value) && array_all( + $value, + fn (mixed $item): bool => ($nullableItems && $item === null) + || is_string($item instanceof \JsonSerializable ? $item->jsonSerialize() : $item) + ); + + if (!$valid) { + throw new {{ namespace | split('\\') | last | caseUcfirst }}Exception('Invalid parameter: "' . $name . '" must be a list of strings.', 0, 'sdk_input_validation'); + } + } } diff --git a/templates/python/base/params.twig b/templates/python/base/params.twig index e1bbc9e7b9..44c39df1c0 100644 --- a/templates/python/base/params.twig +++ b/templates/python/base/params.twig @@ -11,6 +11,13 @@ {% endif -%} {% endif -%} {% endfor -%} +{% for parameter in (method | parameters('all')) -%} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} +{% set paramName = parameter.name | escapeKeyword | caseSnake -%} +{% set validation %} self._validate_string_list('{{ paramName }}', {{ paramName }}{% if (parameter | arraySchema) | schemaNullable %}, nullable_items=True{% endif %}){% endset %} +{{ validation | formatCall | raw }} +{% endif -%} +{% endfor -%} {% for parameter in (method | parameters('path')) %} api_path = api_path.replace('{{ '{' }}{{ parameter.name }}{{ '}' }}', str(self._normalize_value({% if (parameter.extensions['x-sdk-source']|default('')) == 'security' %}self.client.get_config('{{ parameter.extensions['x-sdk-config'] | caseLower }}'){% else %}{{ parameter.name | escapeKeyword | caseSnake }}{% endif %}))) {% endfor -%} diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index e9fb969571..2ed53c30dc 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -281,8 +281,10 @@ class Client: for key in data: value = data[key] if isinstance(data, dict) else key - finalKey = prefix + '[' + key + ']' if prefix else key - finalKey = prefix + '[' + str(i) + ']' if isinstance(data, list) else finalKey + if isinstance(data, list): + finalKey = prefix + '[' + str(i) + ']' + else: + finalKey = prefix + '[' + key + ']' if prefix else key i += 1 if isinstance(value, list) or isinstance(value, dict): diff --git a/templates/python/package/service.py.twig b/templates/python/package/service.py.twig index abc7cf4be9..226ca5cab8 100644 --- a/templates/python/package/service.py.twig +++ b/templates/python/package/service.py.twig @@ -29,6 +29,20 @@ class Service: return value + def _validate_string_list(self, name: str, value: Any, nullable_items: bool = False) -> None: + """Validate item types; generated required checks handle missing lists.""" + if value is None: + return + + if not isinstance(value, list) or any( + not isinstance(self._normalize_value(item), str) and not (nullable_items and item is None) for item in value + ): + expected = 'strings or None' if nullable_items else 'strings' + raise AppwriteException( + f'Invalid parameter: "{name}" must be a list of {expected}.', + type='sdk_input_validation', + ) + def _parse_response(self, response: Any, model: Optional[Type[ModelType]] = None) -> Any: if model is None: return response diff --git a/templates/react-native/src/client.ts.twig b/templates/react-native/src/client.ts.twig index fc5307ed63..fef258995f 100644 --- a/templates/react-native/src/client.ts.twig +++ b/templates/react-native/src/client.ts.twig @@ -785,6 +785,32 @@ class Client { throw new {{spec.info.title | caseUcfirst}}Exception((e).message); } } + + static validateStringList( + name: string, + value: unknown, + nullableItems = false, + ): void { + // Missing values are left to the generated required-parameter checks. + if (value === null || typeof value === 'undefined') { + return; + } + + if ( + !Array.isArray(value) || + value.some( + (item) => + typeof item !== 'string' && + !(nullableItems && item === null), + ) + ) { + throw new {{spec.info.title | caseUcfirst}}Exception( + `Invalid parameter: "${name}" must be a list of strings.`, + 0, + 'sdk_input_validation', + ); + } + } } export { Client, {{spec.info.title | caseUcfirst}}Exception }; diff --git a/templates/react-native/src/services/template.ts.twig b/templates/react-native/src/services/template.ts.twig index 8da92025be..ca9b292f21 100644 --- a/templates/react-native/src/services/template.ts.twig +++ b/templates/react-native/src/services/template.ts.twig @@ -142,6 +142,16 @@ export class {{ service.name | caseUcfirst }} extends Service { {{ ('throw new ' ~ (spec.info.title | caseUcfirst) ~ 'Exception') | tsCall('\'Missing required parameter: "' ~ (parameter.name | caseCamel | escapeKeyword) ~ '"\'', ';', 12) | raw }} } +{% endif %} +{% endfor %} +{% for parameter in (method | parameters('all')) %} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} +{% set validationArgs = ["'" ~ (parameter.name | caseCamel | escapeKeyword) ~ "'", (parameter.name | caseCamel | escapeKeyword)] %} +{% if (parameter | arraySchema) | schemaNullable %} +{% set validationArgs = validationArgs | merge(['true']) %} +{% endif %} + {{ 'Client.validateStringList' | jsArgs(validationArgs, 8) | raw }} + {% endif %} {% endfor %} {% set pathReplacements = [] %}{% for parameter in (method | parameters('path')) %}{% set pathReplacements = pathReplacements|merge([['{' ~ parameter.name ~ '}', 'encodeURIComponent(String(' ~ (((parameter.extensions['x-sdk-source']|default('')) == 'security') ? ('this.client.config.' ~ (parameter.extensions['x-sdk-config'] | caseLower)) : (parameter.name | caseCamel | escapeKeyword)) ~ '))']]) %}{% endfor %} @@ -462,6 +472,16 @@ export class {{ service.name | caseUcfirst }} extends Service { {{ ('throw new ' ~ (spec.info.title | caseUcfirst) ~ 'Exception') | tsCall('\'Missing required parameter: "' ~ (parameter.name | caseCamel | escapeKeyword) ~ '"\'', ';', 12) | raw }} } +{% endif %} +{% endfor %} +{% for parameter in (method | parameters('all')) %} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} +{% set validationArgs = ["'" ~ (parameter.name | caseCamel | escapeKeyword) ~ "'", (parameter.name | caseCamel | escapeKeyword)] %} +{% if (parameter | arraySchema) | schemaNullable %} +{% set validationArgs = validationArgs | merge(['true']) %} +{% endif %} + {{ 'Client.validateStringList' | jsArgs(validationArgs, 8) | raw }} + {% endif %} {% endfor %} {% set pathReplacements = [] %}{% for parameter in (method | parameters('path')) %}{% set pathReplacements = pathReplacements|merge([['{' ~ parameter.name ~ '}', 'encodeURIComponent(String(' ~ (((parameter.extensions['x-sdk-source']|default('')) == 'security') ? ('this.client.config.' ~ (parameter.extensions['x-sdk-config'] | caseLower)) : (parameter.name | caseCamel | escapeKeyword)) ~ '))']]) %}{% endfor %} diff --git a/templates/ruby/base/params.twig b/templates/ruby/base/params.twig index 83a03527bb..a01f0c4164 100644 --- a/templates/ruby/base/params.twig +++ b/templates/ruby/base/params.twig @@ -7,6 +7,12 @@ raise {{spec.info.title | caseUcfirst}}::Exception.new('Missing required parameter: "{{ parameter.name | caseCamel | escapeKeyword }}"') end +{% endif %} +{% endfor %} +{% for parameter in (method | parameters('all')) %} +{% if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} + validate_string_list('{{ parameter.name | caseCamel | escapeKeyword }}', {{ parameter.name | caseSnake | escapeKeyword }}{% if (parameter | arraySchema) | schemaNullable %}, nullable_items: true{% endif %}) + {% endif %} {% endfor %} api_path = '{{ method.path }}' diff --git a/templates/ruby/lib/container/service.rb.twig b/templates/ruby/lib/container/service.rb.twig index 7468cfd86d..0fd4047559 100644 --- a/templates/ruby/lib/container/service.rb.twig +++ b/templates/ruby/lib/container/service.rb.twig @@ -3,5 +3,14 @@ module {{spec.info.title | caseUcfirst }} def initialize(client) @client = client end + + private + + def validate_string_list(name, value, nullable_items: false) + return if value.nil? + return if value.is_a?(Array) && value.all? { |item| item.is_a?(String) || (nullable_items && item.nil?) } + + raise {{spec.info.title | caseUcfirst}}::Exception.new("Invalid parameter: \"#{name}\" must be a list of strings.", 0, 'sdk_input_validation') + end end end diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 227457870c..06f8132c5a 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -1,5 +1,6 @@ use appwrite::{ Client, + enums::MockType, id::ID, input_file::InputFile, operator::{self, Condition}, @@ -112,6 +113,19 @@ async fn test_general_service(client: &Client, string_in_array: &[String]) -> Re Err(e) => eprintln!("general.redirected => error {}", e), } + let queries = vec!["not JSON".to_string(), MockType::First.to_string()]; + match general.list_rows(Some(queries)).await { + Ok(response) => println!("{}", response.result), + Err(e) => eprintln!("general.list_rows => error {}", e), + } + + let documents = vec![json!({"$id": "first"})]; + let labels = vec!["ready".to_string()]; + match general.create_documents(documents, Some(labels)).await { + Ok(response) => println!("{}", response.result), + Err(e) => eprintln!("general.create_documents => error {}", e), + } + for (id, plain) in [("", "0"), ("0", "")] { let error = general.validate_path(plain, Some(id)).await.unwrap_err(); assert_eq!(error.code, 0); diff --git a/templates/web/src/client.ts.twig b/templates/web/src/client.ts.twig index 4d68244ca3..1ded99a1d4 100644 --- a/templates/web/src/client.ts.twig +++ b/templates/web/src/client.ts.twig @@ -1265,6 +1265,32 @@ class Client { return data; } + static validateStringList( + name: string, + value: unknown, + nullableItems = false, + ): void { + // Missing values are left to the generated required-parameter checks. + if (value === null || typeof value === 'undefined') { + return; + } + + if ( + !Array.isArray(value) || + value.some( + (item) => + typeof item !== 'string' && + !(nullableItems && item === null), + ) + ) { + throw new {{spec.info.title | caseUcfirst}}Exception( + `Invalid parameter: "${name}" must be a list of strings.`, + 0, + 'sdk_input_validation', + ); + } + } + static flatten(data: Payload, prefix = ''): Payload { let output: Payload = {}; diff --git a/templates/web/src/services/template.ts.twig b/templates/web/src/services/template.ts.twig index faa3231a0c..7785ea9711 100644 --- a/templates/web/src/services/template.ts.twig +++ b/templates/web/src/services/template.ts.twig @@ -141,6 +141,15 @@ export class {{ service.name | caseUcfirst }} { } {%~ endif %} {%~ endfor %} + {%~ for parameter in (method | parameters('all')) %} + {%~ if (parameter | schemaType) == 'array' and ((parameter | arraySchema) | schemaType) == 'string' %} + {%~ set validationArgs = ["'" ~ (parameter.name | caseCamel | escapeKeyword) ~ "'", (parameter.name | caseCamel | escapeKeyword)] %} + {%~ if (parameter | arraySchema) | schemaNullable %} + {%~ set validationArgs = validationArgs | merge(['true']) %} + {%~ endif %} + {{ 'Client.validateStringList' | jsArgs(validationArgs, 8) | raw }} + {%~ endif %} + {%~ endfor %} {% set pathReplacements = [] %}{% for parameter in (method | parameters('path')) %}{% set pathReplacements = pathReplacements|merge([['{' ~ parameter.name ~ '}', 'encodeURIComponent(String(' ~ (((parameter.extensions['x-sdk-source']|default('')) == 'security') ? ('this.client.config.' ~ (parameter.extensions['x-sdk-config'] | caseLower)) : (parameter.name | caseCamel | escapeKeyword)) ~ '))']]) %}{% endfor %} {{ method.path | tsApiPath(pathReplacements, 8) | raw }} const payload: Payload = {}; diff --git a/tests/e2e/Android16Java17Test.php b/tests/e2e/Android16Java17Test.php index 89b36066ae..806875b006 100644 --- a/tests/e2e/Android16Java17Test.php +++ b/tests/e2e/Android16Java17Test.php @@ -38,6 +38,7 @@ final class Android16Java17Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Android5Java17Test.php b/tests/e2e/Android5Java17Test.php index 919f3e411f..fe1f1f9f26 100644 --- a/tests/e2e/Android5Java17Test.php +++ b/tests/e2e/Android5Java17Test.php @@ -38,6 +38,7 @@ final class Android5Java17Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/AppleSwift61Test.php b/tests/e2e/AppleSwift61Test.php index 9824bf25e4..590664ff2a 100644 --- a/tests/e2e/AppleSwift61Test.php +++ b/tests/e2e/AppleSwift61Test.php @@ -37,13 +37,13 @@ final class AppleSwift61Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, ...Base::ENUM_RESPONSES, ...Base::MODEL_RESPONSES, - ...Base::OBJECT_ARRAY_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, ...Base::COOKIE_RESPONSES, diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index 05840af2d3..177146b5bc 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -45,6 +45,25 @@ abstract class Base extends TestCase 'GET:/v1/mock/tests/general/redirect/done:passed', ]; + // One contract for every SDK: string lists reach the API unchanged. + protected const ARRAY_PARAMETER_RESPONSES = [ + '["not JSON","first"]', + '{"documents":[{"$id":"first"}],"labels":["ready"]}', + ]; + + // SDKs that can receive a non-string list item at runtime reject it before + // any request; the mock never emits these exceptions. + protected const STRING_LIST_VALIDATION_RESPONSES = [ + '{"message":"Invalid parameter: \"queries\" must be a list of strings.","type":"sdk_input_validation","code":0,"response":null}', + '{"message":"Invalid parameter: \"z\" must be a list of strings.","type":"sdk_input_validation","code":0,"response":null}', + '{"documents":[{"$id":"first"}],"labels":["ready",null]}', + ]; + + protected const NESTED_LIST_RESPONSES = [ + 'Invalid `queries` param: Value must a valid array no longer than 100 items and Value must be a valid string and at least 1 chars and no longer than 4096 chars', + '{"documents":[{"$id":"first","values":["0","1.5","true","false"]},{"$id":"second","nested":[{"name":"Zoë","values":[["1","2"]]}]}],"labels":null}', + ]; + protected const PATH_PARAM_RESPONSES = [ 'GET:/v1/mock/tests/general/path/grant%2Fspecial%26id:passed', ]; @@ -85,10 +104,6 @@ abstract class Base extends TestCase 'POST:/v1/mock/tests/general/models/array:passed', ]; - protected const OBJECT_ARRAY_RESPONSES = [ - 'POST:/v1/mock/tests/general/documents:passed', - ]; - protected const OPTIONAL_PARAM_RESPONSES = [ 'width=-1,height=128,name=omitted', 'width=0,height=64,name=zero', diff --git a/tests/e2e/DartBetaTest.php b/tests/e2e/DartBetaTest.php index d7be20e5be..ded2aa5ceb 100644 --- a/tests/e2e/DartBetaTest.php +++ b/tests/e2e/DartBetaTest.php @@ -37,6 +37,7 @@ final class DartBetaTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/DartStableTest.php b/tests/e2e/DartStableTest.php index 8a1c8b4512..110a97fd00 100644 --- a/tests/e2e/DartStableTest.php +++ b/tests/e2e/DartStableTest.php @@ -37,6 +37,7 @@ final class DartStableTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Deno1193Test.php b/tests/e2e/Deno1193Test.php index cfeb21a39c..909a4d15a2 100644 --- a/tests/e2e/Deno1193Test.php +++ b/tests/e2e/Deno1193Test.php @@ -33,6 +33,8 @@ final class Deno1193Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Deno1303Test.php b/tests/e2e/Deno1303Test.php index 3b07a6887f..d8460e3009 100644 --- a/tests/e2e/Deno1303Test.php +++ b/tests/e2e/Deno1303Test.php @@ -33,6 +33,8 @@ final class Deno1303Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/DotNet60Test.php b/tests/e2e/DotNet60Test.php index ad8cf173f2..74c744c6ba 100644 --- a/tests/e2e/DotNet60Test.php +++ b/tests/e2e/DotNet60Test.php @@ -37,6 +37,7 @@ final class DotNet60Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/DotNet80Test.php b/tests/e2e/DotNet80Test.php index 564c5b081f..7e93f68de3 100644 --- a/tests/e2e/DotNet80Test.php +++ b/tests/e2e/DotNet80Test.php @@ -37,6 +37,7 @@ final class DotNet80Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/DotNet90Test.php b/tests/e2e/DotNet90Test.php index fd039e4ec5..7630d80fe0 100644 --- a/tests/e2e/DotNet90Test.php +++ b/tests/e2e/DotNet90Test.php @@ -37,6 +37,7 @@ final class DotNet90Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/FlutterBetaTest.php b/tests/e2e/FlutterBetaTest.php index 21fa8c35fd..32385b81ee 100644 --- a/tests/e2e/FlutterBetaTest.php +++ b/tests/e2e/FlutterBetaTest.php @@ -37,6 +37,7 @@ final class FlutterBetaTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/FlutterStableTest.php b/tests/e2e/FlutterStableTest.php index 27548c7f94..e57f6f5a2b 100644 --- a/tests/e2e/FlutterStableTest.php +++ b/tests/e2e/FlutterStableTest.php @@ -37,6 +37,7 @@ final class FlutterStableTest extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Go113Test.php b/tests/e2e/Go113Test.php index 9cef5ef9e5..68622108ac 100644 --- a/tests/e2e/Go113Test.php +++ b/tests/e2e/Go113Test.php @@ -35,6 +35,7 @@ final class Go113Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Go118Test.php b/tests/e2e/Go118Test.php index 504aeefd3a..be2f37070e 100644 --- a/tests/e2e/Go118Test.php +++ b/tests/e2e/Go118Test.php @@ -35,6 +35,7 @@ final class Go118Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/KotlinJava17Test.php b/tests/e2e/KotlinJava17Test.php index 5b5e3fae51..cd8545376f 100644 --- a/tests/e2e/KotlinJava17Test.php +++ b/tests/e2e/KotlinJava17Test.php @@ -38,6 +38,7 @@ final class KotlinJava17Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Node18Test.php b/tests/e2e/Node18Test.php index dfb5bdb3c8..798ab84a25 100644 --- a/tests/e2e/Node18Test.php +++ b/tests/e2e/Node18Test.php @@ -40,6 +40,8 @@ final class Node18Test extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Node20Test.php b/tests/e2e/Node20Test.php index d8c00978ec..499804f658 100644 --- a/tests/e2e/Node20Test.php +++ b/tests/e2e/Node20Test.php @@ -40,6 +40,8 @@ final class Node20Test extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/Node26Test.php b/tests/e2e/Node26Test.php index 60817df451..7ce07cd573 100644 --- a/tests/e2e/Node26Test.php +++ b/tests/e2e/Node26Test.php @@ -40,6 +40,8 @@ final class Node26Test extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSES, diff --git a/tests/e2e/PHP85Test.php b/tests/e2e/PHP85Test.php index 54db9357c4..8dfffe2624 100644 --- a/tests/e2e/PHP85Test.php +++ b/tests/e2e/PHP85Test.php @@ -35,6 +35,8 @@ final class PHP85Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UNION_RESPONSES, 'compound specialization: passed', diff --git a/tests/e2e/Python310Test.php b/tests/e2e/Python310Test.php index 8193b3129a..1dba9552d8 100644 --- a/tests/e2e/Python310Test.php +++ b/tests/e2e/Python310Test.php @@ -37,6 +37,9 @@ final class Python310Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, + ...Base::NESTED_LIST_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Python311Test.php b/tests/e2e/Python311Test.php index a16c23b8ae..56a15bd679 100644 --- a/tests/e2e/Python311Test.php +++ b/tests/e2e/Python311Test.php @@ -37,6 +37,9 @@ final class Python311Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, + ...Base::NESTED_LIST_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Python312Test.php b/tests/e2e/Python312Test.php index faf0664318..44bf5b4ddc 100644 --- a/tests/e2e/Python312Test.php +++ b/tests/e2e/Python312Test.php @@ -37,6 +37,9 @@ final class Python312Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, + ...Base::NESTED_LIST_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Python313Test.php b/tests/e2e/Python313Test.php index 1dc9fc15c3..1521a5dbd3 100644 --- a/tests/e2e/Python313Test.php +++ b/tests/e2e/Python313Test.php @@ -37,6 +37,9 @@ final class Python313Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, + ...Base::NESTED_LIST_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Python39Test.php b/tests/e2e/Python39Test.php index c4265b7b67..ae67c7b730 100644 --- a/tests/e2e/Python39Test.php +++ b/tests/e2e/Python39Test.php @@ -37,6 +37,9 @@ final class Python39Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, + ...Base::NESTED_LIST_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/ReactNativeTest.php b/tests/e2e/ReactNativeTest.php index de55ec0e6b..26e2ad66d9 100644 --- a/tests/e2e/ReactNativeTest.php +++ b/tests/e2e/ReactNativeTest.php @@ -46,6 +46,8 @@ final class ReactNativeTest extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::DOWNLOAD_RESPONSES, ...Base::ENUM_RESPONSES, diff --git a/tests/e2e/Ruby27Test.php b/tests/e2e/Ruby27Test.php index cd6a5fcf67..a3447c296f 100644 --- a/tests/e2e/Ruby27Test.php +++ b/tests/e2e/Ruby27Test.php @@ -35,6 +35,8 @@ final class Ruby27Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Ruby30Test.php b/tests/e2e/Ruby30Test.php index c240bf5527..e9b5c42684 100644 --- a/tests/e2e/Ruby30Test.php +++ b/tests/e2e/Ruby30Test.php @@ -35,6 +35,8 @@ final class Ruby30Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Ruby31Test.php b/tests/e2e/Ruby31Test.php index 3786ac2705..8fe4de113a 100644 --- a/tests/e2e/Ruby31Test.php +++ b/tests/e2e/Ruby31Test.php @@ -35,6 +35,8 @@ final class Ruby31Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/Rust183Test.php b/tests/e2e/Rust183Test.php index 3f50a97664..394250ca65 100644 --- a/tests/e2e/Rust183Test.php +++ b/tests/e2e/Rust183Test.php @@ -31,6 +31,7 @@ final class Rust183Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSE, diff --git a/tests/e2e/Swift61Test.php b/tests/e2e/Swift61Test.php index a93b72b224..da635d52c4 100644 --- a/tests/e2e/Swift61Test.php +++ b/tests/e2e/Swift61Test.php @@ -37,13 +37,13 @@ final class Swift61Test extends Base ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::NULL_PATH_RESPONSE, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, ...Base::ENUM_RESPONSES, ...Base::MODEL_RESPONSES, - ...Base::OBJECT_ARRAY_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/Unity2021Test.php b/tests/e2e/Unity2021Test.php index c0f245f8ea..1e82c86d43 100644 --- a/tests/e2e/Unity2021Test.php +++ b/tests/e2e/Unity2021Test.php @@ -93,6 +93,7 @@ public function tearDown(): void ...Base::FOO_RESPONSES, ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::UPLOAD_RESPONSES, ...Base::DOWNLOAD_RESPONSES, diff --git a/tests/e2e/WebChromiumTest.php b/tests/e2e/WebChromiumTest.php index 3b87428f09..96935a17ec 100644 --- a/tests/e2e/WebChromiumTest.php +++ b/tests/e2e/WebChromiumTest.php @@ -41,6 +41,8 @@ final class WebChromiumTest extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::UPLOAD_RESPONSE, diff --git a/tests/e2e/WebNodeTest.php b/tests/e2e/WebNodeTest.php index 3440c51b5a..95cb94bf9d 100644 --- a/tests/e2e/WebNodeTest.php +++ b/tests/e2e/WebNodeTest.php @@ -42,6 +42,8 @@ final class WebNodeTest extends Base ...Base::BAR_RESPONSES, ...Base::BAR_RESPONSES, // Object params ...Base::GENERAL_RESPONSES, + ...Base::ARRAY_PARAMETER_RESPONSES, + ...Base::STRING_LIST_VALIDATION_RESPONSES, ...Base::PATH_VALIDATION_RESPONSES, ...Base::PATH_PARAM_RESPONSES, ...Base::ENUM_RESPONSES, diff --git a/tests/e2e/languages/android/Tests.kt b/tests/e2e/languages/android/Tests.kt index 0a452eff55..81d3641228 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -181,6 +181,9 @@ class ServiceTest { val result = general.redirect() writeToFile((result as Map)["result"] as String) + writeToFile(general.listRows(listOf("not JSON", MockType.FIRST.value)).result) + writeToFile(general.createDocuments(listOf(mapOf("\$id" to "first")), listOf("ready")).result) + for ((id, plain) in listOf("" to "0", "0" to "")) { try { general.validatePath(plain, id) diff --git a/tests/e2e/languages/apple/Tests.swift b/tests/e2e/languages/apple/Tests.swift index fd68a57ea7..133318d2f0 100644 --- a/tests/e2e/languages/apple/Tests.swift +++ b/tests/e2e/languages/apple/Tests.swift @@ -139,6 +139,12 @@ class Tests: XCTestCase { let result = try await general.redirect() print((result as! [String: Any])["result"] as! String) + mock = try await general.listRows(queries: ["not JSON", MockType.first.rawValue]) + print(mock.result) + + mock = try await general.createDocuments(documents: [AnyCodable(["$id": "first"])], labels: ["ready"]) + print(mock.result) + for (id, plain) in [("", "0"), ("0", "")] { do { _ = try await general.validatePath(plain: plain, id: id) @@ -202,12 +208,6 @@ class Tests: XCTestCase { ]) print(mock.result) - mock = try await general.createDocuments(documents: [ - AnyCodable(["$id": "one", "title": "hello"]), - AnyCodable(["$id": "two", "title": "world"]) - ]) - print(mock.result) - do { try await general.error400() } catch let error as AppwriteError { diff --git a/tests/e2e/languages/dart/tests.dart b/tests/e2e/languages/dart/tests.dart index 3cbd915553..08e90bdd1a 100644 --- a/tests/e2e/languages/dart/tests.dart +++ b/tests/e2e/languages/dart/tests.dart @@ -67,6 +67,12 @@ void main() async { final res = await general.redirect(); print(res['result']); + response = await general.listRows(queries: ['not JSON', MockType.first.value]); + print(response.result); + + response = await general.createDocuments(documents: [{r'$id': 'first'}], labels: ['ready']); + print(response.result); + for (final ids in [['', '0'], ['0', '']]) { try { await general.validatePath(id: ids[0], plain: ids[1]); diff --git a/tests/e2e/languages/deno/tests.ts b/tests/e2e/languages/deno/tests.ts index af760f9890..ca986d9275 100644 --- a/tests/e2e/languages/deno/tests.ts +++ b/tests/e2e/languages/deno/tests.ts @@ -75,6 +75,35 @@ async function start() { // @ts-ignore console.log(response.result); + response = await general.listRows(["not JSON", appwrite.MockType.First]); + console.log(response.result); + + response = await general.createDocuments([{ $id: "first" }], ["ready"]); + console.log(response.result); + + // String-list items are checked against the schema before any request. + try { + // @ts-ignore + await general.listRows([{ method: "limit", values: [1] }]); + throw new Error("Invalid string list was accepted"); + } catch (error) { + if (!(error instanceof appwrite.AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + try { + // @ts-ignore + await foo.post("string", 123, [1]); + throw new Error("Invalid string list was accepted"); + } catch (error) { + if (!(error instanceof appwrite.AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + // @ts-ignore + response = await general.createDocuments([{ $id: "first" }], ["ready", null]); + console.log(response.result); + for (const [id, plain] of [["", "0"], ["0", ""]]) { try { await general.validatePath(plain, id); diff --git a/tests/e2e/languages/dotnet/Tests.cs b/tests/e2e/languages/dotnet/Tests.cs index 717bdfa196..ff7f8e768b 100644 --- a/tests/e2e/languages/dotnet/Tests.cs +++ b/tests/e2e/languages/dotnet/Tests.cs @@ -70,6 +70,16 @@ public async Task Test1() var result = await general.Redirect(); TestContext.WriteLine((result as Dictionary)["result"]); + // Array parameter tests + mock = await general.ListRows(new List { "not JSON", MockType.First.Value }); + TestContext.WriteLine(mock.Result); + + mock = await general.CreateDocuments( + documents: new List { new Dictionary { { "$id", "first" } } }, + labels: new List { "ready" } + ); + TestContext.WriteLine(mock.Result); + foreach (var ids in new[] { new[] { "", "0" }, new[] { "0", "" } }) { try diff --git a/tests/e2e/languages/flutter/tests.dart b/tests/e2e/languages/flutter/tests.dart index 2a1e10ec75..b73e077517 100644 --- a/tests/e2e/languages/flutter/tests.dart +++ b/tests/e2e/languages/flutter/tests.dart @@ -137,6 +137,12 @@ void main() async { final res = await general.redirect(); print(res['result']); + response = await general.listRows(queries: ['not JSON', MockType.first.value]); + print(response.result); + + response = await general.createDocuments(documents: [{r'$id': 'first'}], labels: ['ready']); + print(response.result); + for (final ids in [['', '0'], ['0', '']]) { try { await general.validatePath(id: ids[0], plain: ids[1]); diff --git a/tests/e2e/languages/go-v2/tests.go b/tests/e2e/languages/go-v2/tests.go index 96f5a51dc4..5a04e19c16 100644 --- a/tests/e2e/languages/go-v2/tests.go +++ b/tests/e2e/languages/go-v2/tests.go @@ -114,6 +114,21 @@ func testGeneralService(client client.Client, stringInArray []string) { } fmt.Printf("%s\n", (*response).(map[string]interface{})["result"].(string)) + listRowsResponse, err := general.ListRows(general.WithListRowsQueries([]string{"not JSON", "first"})) + if err != nil { + fmt.Printf("general.ListRows => error %v", err) + } + fmt.Printf("%s\n", listRowsResponse.Result) + + documentsResponse, err := general.CreateDocuments( + []interface{}{map[string]interface{}{"$id": "first"}}, + general.WithCreateDocumentsLabels([]string{"ready"}), + ) + if err != nil { + fmt.Printf("general.CreateDocuments => error %v", err) + } + fmt.Printf("%s\n", documentsResponse.Result) + for _, ids := range [][2]string{{"", "0"}, {"0", ""}} { if _, err := general.ValidatePath(ids[1], ids[0]); err != nil { fmt.Println(err) diff --git a/tests/e2e/languages/go/tests.go b/tests/e2e/languages/go/tests.go index d729c468e3..de6581be2f 100644 --- a/tests/e2e/languages/go/tests.go +++ b/tests/e2e/languages/go/tests.go @@ -114,6 +114,21 @@ func testGeneralService(client client.Client, stringInArray []string) { } fmt.Printf("%s\n", (*response).(map[string]interface{})["result"].(string)) + listRowsResponse, err := general.ListRows(general.WithListRowsQueries([]string{"not JSON", "first"})) + if err != nil { + fmt.Printf("general.ListRows => error %v", err) + } + fmt.Printf("%s\n", listRowsResponse.Result) + + documentsResponse, err := general.CreateDocuments( + []interface{}{map[string]interface{}{"$id": "first"}}, + general.WithCreateDocumentsLabels([]string{"ready"}), + ) + if err != nil { + fmt.Printf("general.CreateDocuments => error %v", err) + } + fmt.Printf("%s\n", documentsResponse.Result) + for _, ids := range [][2]string{{"", "0"}, {"0", ""}} { if _, err := general.ValidatePath(ids[1], ids[0]); err != nil { fmt.Println(err) diff --git a/tests/e2e/languages/kotlin/Tests.kt b/tests/e2e/languages/kotlin/Tests.kt index f512c28507..c680a4af62 100644 --- a/tests/e2e/languages/kotlin/Tests.kt +++ b/tests/e2e/languages/kotlin/Tests.kt @@ -101,6 +101,9 @@ class ServiceTest { val result = general.redirect() writeToFile((result as Map)["result"] as String) + writeToFile(general.listRows(listOf("not JSON", MockType.FIRST.value)).result) + writeToFile(general.createDocuments(listOf(mapOf("\$id" to "first")), listOf("ready")).result) + for ((id, plain) in listOf("" to "0", "0" to "")) { try { general.validatePath(plain, id) diff --git a/tests/e2e/languages/node/test.js b/tests/e2e/languages/node/test.js index e79f3ff513..b5d819c3ed 100644 --- a/tests/e2e/languages/node/test.js +++ b/tests/e2e/languages/node/test.js @@ -146,6 +146,32 @@ async function start() { response = await general.redirect(); console.log(response.result); + response = await general.listRows(['not JSON', MockType.First]); + console.log(response.result); + + response = await general.createDocuments([{ $id: 'first' }], ['ready']); + console.log(response.result); + + // String-list items are checked against the schema before any request. + try { + await general.listRows([{ method: 'limit', values: [1] }]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + try { + await foo.post('string', 123, [1]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + response = await general.createDocuments([{ $id: 'first' }], ['ready', null]); + console.log(response.result); + for (const [id, plain] of [['', '0'], ['0', '']]) { try { await general.validatePath({ id, plain }); diff --git a/tests/e2e/languages/php/test.php b/tests/e2e/languages/php/test.php index 23481eec3d..b6cd497ff1 100644 --- a/tests/e2e/languages/php/test.php +++ b/tests/e2e/languages/php/test.php @@ -172,6 +172,26 @@ public function toArray(): array $response = $general->redirect(); echo $response['result'] . "\n"; +echo $general->listRows(['not JSON', MockType::FIRST()])->result . "\n"; +echo $general->createDocuments([['$id' => 'first']], ['ready'])->result . "\n"; + +// String-list items are checked against the schema before any request. +try { + $general->listRows([['method' => 'limit', 'values' => [1]]]); + throw new RuntimeException('Invalid string list was accepted'); +} catch (AppwriteException $error) { + echo json_encode(['message' => $error->getMessage(), 'type' => $error->getType(), 'code' => $error->getCode(), 'response' => $error->getResponse()], JSON_THROW_ON_ERROR) . "\n"; +} + +try { + $foo->post('string', 123, [1]); + throw new RuntimeException('Invalid string list was accepted'); +} catch (AppwriteException $error) { + echo json_encode(['message' => $error->getMessage(), 'type' => $error->getType(), 'code' => $error->getCode(), 'response' => $error->getResponse()], JSON_THROW_ON_ERROR) . "\n"; +} + +echo $general->createDocuments([['$id' => 'first']], ['ready', null])->result . "\n"; + foreach ([['', '0'], ['0', '']] as [$id, $plain]) { try { $general->validatePath($plain, $id); diff --git a/tests/e2e/languages/python/tests.py b/tests/e2e/languages/python/tests.py index d4180a49b8..ea588cf460 100644 --- a/tests/e2e/languages/python/tests.py +++ b/tests/e2e/languages/python/tests.py @@ -12,6 +12,7 @@ from appwrite.enums.mock_type import MockType from appwrite.models.player import Player +import json import os.path client = Client() @@ -66,6 +67,45 @@ response = general.redirect() print(response['result']) +print(general.list_rows(['not JSON', MockType.FIRST]).result) +print(general.create_documents([{'$id': 'first'}], labels=['ready']).result) + +# String-list items are checked against the schema before any request. +try: + general.list_rows([{'method': 'limit', 'values': [1]}]) + raise AssertionError('Invalid string list was accepted') +except AppwriteException as error: + print(json.dumps({'message': error.message, 'type': error.type, 'code': error.code, 'response': error.response})) + +try: + foo.post('string', 123, [1]) + raise AssertionError('Invalid string list was accepted') +except AppwriteException as error: + print(json.dumps({'message': error.message, 'type': error.type, 'code': error.code, 'response': error.response})) + +print(general.create_documents([{'$id': 'first'}], labels=['ready', None]).result) + +# Nested lists serialize by index, so raw calls reach API validation. +try: + client.call('get', '/mock/tests/general/list-rows', params={'queries': [{'method': 'limit', 'values': [1]}]}) + raise AssertionError('Nested query was accepted') +except AppwriteException as error: + print(error.message) + +response = client.call( + 'post', + '/mock/tests/general/documents', + {'content-type': 'multipart/form-data', 'X-Appwrite-Project': 'console'}, + { + 'documents': [ + {'$id': 'first', 'values': [0, 1.5, True, False]}, + {'$id': 'second', 'nested': [{'name': 'Zoë', 'values': [[1, 2]]}]}, + ], + 'file': InputFile.from_bytes(b'fixture', 'fixture.txt', 'text/plain'), + }, +) +print(response['result']) + for id, plain in [('', '0'), ('0', '')]: try: general.validate_path(plain, id) @@ -98,8 +138,8 @@ print(response.result) response = general.create_players([ - {'id': 'player1', 'name': 'John Doe', 'score': 100}, - {'id': 'player2', 'name': 'Jane Doe', 'score': 200} + Player(id='player1', name='John Doe', score=100), + Player(id='player2', name='Jane Doe', score=200), ]) print(response.result) diff --git a/tests/e2e/languages/react-native/browser.js b/tests/e2e/languages/react-native/browser.js index 22162aa277..72ded89409 100644 --- a/tests/e2e/languages/react-native/browser.js +++ b/tests/e2e/languages/react-native/browser.js @@ -127,6 +127,32 @@ import { response = await general.redirect(); console.log(response.result); + response = await general.listRows(['not JSON', MockType.First]); + console.log(response.result); + + response = await general.createDocuments([{ $id: 'first' }], ['ready']); + console.log(response.result); + + // String-list items are checked against the schema before any request. + try { + await general.listRows([{ method: 'limit', values: [1] }]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + try { + await foo.post('string', 123, [1]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + response = await general.createDocuments([{ $id: 'first' }], ['ready', null]); + console.log(response.result); + for (const [id, plain] of [['', '0'], ['0', '']]) { try { await general.validatePath({ id, plain }); diff --git a/tests/e2e/languages/ruby/tests.rb b/tests/e2e/languages/ruby/tests.rb index 6dd975900b..928709a78e 100644 --- a/tests/e2e/languages/ruby/tests.rb +++ b/tests/e2e/languages/ruby/tests.rb @@ -56,6 +56,26 @@ response = general.redirect() puts response["result"] +puts general.list_rows(queries: ['not JSON', MockType::FIRST]).result +puts general.create_documents(documents: [{'$id': 'first'}], labels: ['ready']).result + +# String-list items are checked against the schema before any request. +begin + general.list_rows(queries: [{method: 'limit', values: [1]}]) + raise 'Invalid string list was accepted' +rescue Appwrite::Exception => error + puts({message: error.message, type: error.type, code: error.code, response: error.response}.to_json) +end + +begin + foo.post(x: 'string', y: 123, z: [1]) + raise 'Invalid string list was accepted' +rescue Appwrite::Exception => error + puts({message: error.message, type: error.type, code: error.code, response: error.response}.to_json) +end + +puts general.create_documents(documents: [{'$id': 'first'}], labels: ['ready', nil]).result + [['', '0'], ['0', '']].each do |id, plain| begin general.validate_path(id: id, plain: plain) diff --git a/tests/e2e/languages/swift/Tests.swift b/tests/e2e/languages/swift/Tests.swift index 0fa927654e..84df879a44 100644 --- a/tests/e2e/languages/swift/Tests.swift +++ b/tests/e2e/languages/swift/Tests.swift @@ -5,6 +5,7 @@ import FoundationNetworking #endif import Appwrite import JSONCodable +import AppwriteEnums import AsyncHTTPClient import NIO @@ -80,6 +81,12 @@ class Tests: XCTestCase { let result = try await general.redirect() print((result as! [String: Any])["result"] as! String) + mock = try await general.listRows(queries: ["not JSON", MockType.first.rawValue]) + print(mock.result) + + mock = try await general.createDocuments(documents: [AnyCodable(["$id": "first"])], labels: ["ready"]) + print(mock.result) + for (id, plain) in [("", "0"), ("0", "")] { do { _ = try await general.validatePath(plain: plain, id: id) @@ -143,12 +150,6 @@ class Tests: XCTestCase { ]) print(mock.result) - mock = try await general.createDocuments(documents: [ - AnyCodable(["$id": "one", "title": "hello"]), - AnyCodable(["$id": "two", "title": "world"]) - ]) - print(mock.result) - do { try await general.error400() } catch let error as AppwriteError { diff --git a/tests/e2e/languages/unity/Tests.cs b/tests/e2e/languages/unity/Tests.cs index 961c04019a..3e78a86772 100644 --- a/tests/e2e/languages/unity/Tests.cs +++ b/tests/e2e/languages/unity/Tests.cs @@ -158,6 +158,16 @@ private async Task RunAsyncTest() var result = await general.Redirect(); LogResult((result as Dictionary)["result"]); + // Array parameter tests + mock = await general.ListRows(new List { "not JSON", MockType.First.Value }); + LogResult(mock.Result); + + mock = await general.CreateDocuments( + documents: new List { new Dictionary { { "$id", "first" } } }, + labels: new List { "ready" } + ); + LogResult(mock.Result); + foreach (var ids in new[] { new[] { "", "0" }, new[] { "0", "" } }) { try diff --git a/tests/e2e/languages/web/index.html b/tests/e2e/languages/web/index.html index ebcc1ff538..c12869d8c3 100644 --- a/tests/e2e/languages/web/index.html +++ b/tests/e2e/languages/web/index.html @@ -195,6 +195,32 @@ response = await general.redirect(); console.log(response.result); + response = await general.listRows(['not JSON', MockType.First]); + console.log(response.result); + + response = await general.createDocuments([{ $id: 'first' }], ['ready']); + console.log(response.result); + + // String-list items are checked against the schema before any request. + try { + await general.listRows([{ method: 'limit', values: [1] }]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof Appwrite.AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + try { + await foo.post('string', 123, [1]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof Appwrite.AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + response = await general.createDocuments([{ $id: 'first' }], ['ready', null]); + console.log(response.result); + for (const [id, plain] of [['', '0'], ['0', '']]) { try { await general.validatePath({ id, plain }); diff --git a/tests/e2e/languages/web/node.js b/tests/e2e/languages/web/node.js index b8b3b30a15..0ae7e10788 100644 --- a/tests/e2e/languages/web/node.js +++ b/tests/e2e/languages/web/node.js @@ -124,6 +124,32 @@ async function start() { response = await general.redirect(); console.log(response.result); + response = await general.listRows(['not JSON', MockType.First]); + console.log(response.result); + + response = await general.createDocuments([{ $id: 'first' }], ['ready']); + console.log(response.result); + + // String-list items are checked against the schema before any request. + try { + await general.listRows([{ method: 'limit', values: [1] }]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + try { + await foo.post('string', 123, [1]); + throw new Error('Invalid string list was accepted'); + } catch (error) { + if (!(error instanceof AppwriteException)) throw error; + console.log(JSON.stringify({ message: error.message, type: error.type, code: error.code, response: error.response })); + } + + response = await general.createDocuments([{ $id: 'first' }], ['ready', null]); + console.log(response.result); + for (const [id, plain] of [['', '0'], ['0', '']]) { try { await general.validatePath({ id, plain }); diff --git a/tests/resources/spec-openapi3.json b/tests/resources/spec-openapi3.json index 057f47bfe6..c0ddc6fd76 100644 --- a/tests/resources/spec-openapi3.json +++ b/tests/resources/spec-openapi3.json @@ -652,6 +652,15 @@ "items": { "type": "object" } + }, + "labels": { + "type": "array", + "nullable": true, + "description": "Optional labels, including null values.", + "items": { + "type": "string", + "nullable": true + } } }, "required": [