Skip to content

fix: validate string-list parameters across SDKs - #1899

Open
HarshMN2345 wants to merge 7 commits into
mainfrom
codex/python-nested-query-serialization
Open

fix: validate string-list parameters across SDKs#1899
HarshMN2345 wants to merge 7 commits into
mainfrom
codex/python-nested-query-serialization

Conversation

@HarshMN2345

@HarshMN2345 HarshMN2345 commented Sep 11, 2026

Copy link
Copy Markdown
Member

Generated SDKs accepted any value for parameters the spec declares as an array of strings. Python crashed while serializing a non-string item (can only concatenate str (not "dict") to str), and Node, Web, Deno and React Native sent one as [object Object] without complaint. Services now validate these parameters from the schema, whatever the parameter is called, and every SDK's e2e prints the same responses for them.

Generated code

Each method calls the validator after its required-parameter checks, once per parameter whose schema is an array of strings (enum lists included), passing the nullable flag when the item schema is nullable. TablesDB.listRows from the Console spec, in Node:

Client.validateStringList('queries', queries);

Python, on Service:

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 strings.',
            type='sdk_input_validation',
        )

Node, Web, React Native and Deno, on Client:

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 AppwriteException(
            `Invalid parameter: "${name}" must be a list of strings.`,
            0,
            'sdk_input_validation',
        );
    }
}

PHP, on Service:

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 AppwriteException('Invalid parameter: "' . $name . '" must be a list of strings.', 0, 'sdk_input_validation');
    }
}

Ruby, on Service:

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 Appwrite::Exception.new("Invalid parameter: \"#{name}\" must be a list of strings.", 0, 'sdk_input_validation')
end

Callers get the SDK's exception with type sdk_input_validation, code 0 and no response, and no request is sent. String contents aren't inspected, so JSON query syntax is still validated by the API.

Swift/Apple, Kotlin/Android, Dart/Flutter, .NET/Unity, Go and Rust type these parameters as string lists, so a non-string item only gets in if the caller subverts the type system (an unchecked cast, or Dart's .cast<String>()). They get no validator.

Python's Client.flatten also picks the list index before building keys:

-            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

PHP GET requests now prepare params the way JSON bodies are, so enum objects in query lists are sent as their values instead of being dropped by http_build_query.

E2E

Expected lines in tests/e2e/Base.php:

  • ARRAY_PARAMETER_RESPONSES, every SDK: a string list with a non-JSON string and an enum value reaches the mock unchanged, in a query (listRows) and in a body (createDocuments labels). The mock's documents route now echoes documents and labels inside result, so typed models can print them; this replaces Swift and Apple's OBJECT_ARRAY_RESPONSES call.
  • STRING_LIST_VALIDATION_RESPONSES, SDKs with the validator: the exception JSON for a query parameter (queries) and a body parameter (z), then nullable items passing.
  • NESTED_LIST_RESPONSES, Python only: raw client.call with nested lists in a query string and in multipart fields.

Unity (licence disabled) and Deno are not in the CI matrix; their scripts follow the same contract.

Not in this PR

  • Raw client.call with maps inside lists is mis-serialized outside Python (Kotlin/Android, Swift/Apple, .NET/Unity, Go, Rust, the JS family, Dart and Ruby). Generated methods don't send such values.
  • The Swift client percent-encodes the joined query string with .urlHostAllowed, so & and = inside a value split it into extra parameters.

Addresses MCP-X; appwrite/mcp#117 consumes the new exception type.

@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR is not safe to merge until Kotlin and Android again reject non-string list items supplied through JVM-erased Java or casted Kotlin calls.

Summary

  • Adds string-list validators and generated calls for JavaScript-family, PHP, Python, and Ruby SDKs.
  • Verifies query and body list values through observable mock-server responses.
  • Fixes Python list-index selection during nested flattening.
  • Converts PHP query enum objects to their serialized values.
  • Removes Kotlin/Android validation despite Java generic erasure permitting invalid runtime values.

Reviews (8) · Last reviewed commit: "fix: leave Kotlin and Android string lis..."

Comment thread tests/e2e/languages/python/tests.py
@HarshMN2345 HarshMN2345 changed the title fix(python): serialize nested list parameters by index fix(python): validate string-list parameters from OpenAPI Sep 11, 2026
Comment thread templates/python/base/params.twig
Comment thread tests/e2e/languages/python/tests.py Outdated
@HarshMN2345

Copy link
Copy Markdown
Member Author

@greptile-apps Please refresh the review summary for current head 462184ab460e89d348ea78f98071f0715aad8592. The summary still lists the query-boundary finding, but your reassessment explicitly withdraws it under the updated, maintainer-requested spec-driven validation scope. The test-boundary finding is also addressed with real SDK calls and HTTP observation. No production changes were made after the reviewed head.

Comment thread tests/e2e/languages/python/tests.py Outdated

# String-list validation follows the declared item type across query and body
# parameters, before reaching the request boundary.
with requests_mock.Mocker() as http:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of this pattern of testing, we should follow existing pattern of logging the same response across all sdks. makes it concrete all sdks assert the same behaviour

Comment thread tests/e2e/Base.php Outdated
];

protected const ARRAY_PARAMETER_RESPONSES = [
'String list validation:passed',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try never print "passed". it should print a response specific to the test

@ChiragAgg5k ChiragAgg5k left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets update pr description to add code snippets of what new validation will be generated in the sdks as its hard to tell my twig templates

Print the SDK validation messages and the mock's responses so
ARRAY_PARAMETER_RESPONSES compares real output, replacing the
requests_mock matrix and its passed markers.
Comment thread tests/e2e/languages/python/tests.py Outdated
Include type, code and response alongside the message so the e2e
output covers the exception fields callers branch on.
Generate the schema-driven string-list check for Node, Web, Deno,
React Native, PHP, Ruby, Kotlin and Android, matching Python, and send
PHP enum objects in GET query lists as their values.

Split the e2e contract into ARRAY_PARAMETER_RESPONSES for every SDK,
STRING_LIST_VALIDATION_RESPONSES for SDKs with the check, and
Python-only NESTED_LIST_RESPONSES. The mock echoes createDocuments input
inside result so typed models can print it.
@HarshMN2345 HarshMN2345 changed the title fix(python): validate string-list parameters from OpenAPI fix: validate string-list parameters across SDKs Sep 11, 2026
Comment thread tests/e2e/languages/android/Tests.kt Outdated
Their List<String> parameters only take other items through unchecked
casts, the same boundary as Swift, Dart and .NET, so drop the generated
check and assert only the pass-through lines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants