Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions mock-server/app/http.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)]);
Expand Down Expand Up @@ -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');
}
Expand All @@ -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')
Expand Down
11 changes: 11 additions & 0 deletions templates/deno/src/client.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand Down
6 changes: 6 additions & 0 deletions templates/deno/src/services/service.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 %};
Expand Down
26 changes: 26 additions & 0 deletions templates/node/src/client.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand Down
9 changes: 9 additions & 0 deletions templates/node/src/services/template.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down
6 changes: 6 additions & 0 deletions templates/php/base/params.twig
Original file line number Diff line number Diff line change
@@ -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')) %}
Expand Down
9 changes: 7 additions & 2 deletions templates/php/src/Client.php.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand All @@ -387,6 +388,10 @@ class Client
return $data->toArray();
}

if ($data instanceof \JsonSerializable) {
return $data->jsonSerialize();
}

return $data;
}

Expand Down
18 changes: 18 additions & 0 deletions templates/php/src/Service.php.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
}
7 changes: 7 additions & 0 deletions templates/python/base/params.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 -%}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
{% 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 -%}
Expand Down
6 changes: 4 additions & 2 deletions templates/python/package/client.py.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
14 changes: 14 additions & 0 deletions templates/python/package/service.py.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions templates/react-native/src/client.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,32 @@ class Client {
throw new {{spec.info.title | caseUcfirst}}Exception((<Error>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 };
Expand Down
20 changes: 20 additions & 0 deletions templates/react-native/src/services/template.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down Expand Up @@ -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 %}
Expand Down
6 changes: 6 additions & 0 deletions templates/ruby/base/params.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}'
Expand Down
9 changes: 9 additions & 0 deletions templates/ruby/lib/container/service.rb.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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
14 changes: 14 additions & 0 deletions templates/rust/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use appwrite::{
Client,
enums::MockType,
id::ID,
input_file::InputFile,
operator::{self, Condition},
Expand Down Expand Up @@ -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);
Expand Down
26 changes: 26 additions & 0 deletions templates/web/src/client.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

Expand Down
9 changes: 9 additions & 0 deletions templates/web/src/services/template.ts.twig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand Down
Loading
Loading