diff --git a/mock-server/app/http.php b/mock-server/app/http.php index 49de5e5bb1..f16208b3c1 100644 --- a/mock-server/app/http.php +++ b/mock-server/app/http.php @@ -878,6 +878,24 @@ $response->json(['enabled' => false, 'verified' => null, 'scopes' => ['read', 'write'], 'settings' => new \stdClass()]); }); +App::get('/v1/mock/tests/general/preset/settings') + ->desc('Get Preset Settings') + ->groups(['mock']) + ->label('scope', 'public') + ->inject('response') + ->action(function (UtopiaSwooleResponse $response) { + $response->setContentType('application/json')->send('{}'); + }); + +App::get('/v1/mock/tests/general/preset/profile') + ->desc('Get Preset Profile') + ->groups(['mock']) + ->label('scope', 'public') + ->inject('response') + ->action(function (UtopiaSwooleResponse $response) { + $response->setContentType('application/json')->send('{}'); + }); + App::get('/v1/mock/tests/general/presets') ->desc('List Presets') ->groups(['mock']) diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index 3a911abc8d..d961a85afc 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -599,13 +599,13 @@ public function getFilters(): array $resolveKey = function ($value) use ($enumValues, $enumKeys): string { $index = array_search($value, $enumValues, true); if ($index !== false && isset($enumKeys[$index]) && $enumKeys[$index] !== '') { - return $this->toCamelCase($enumKeys[$index]); + return $this->escapeKeyword($this->toCamelCase($enumKeys[$index])); } if ($index !== false && isset($enumValues[$index])) { - return $this->toCamelCase($enumValues[$index]); + return $this->escapeKeyword($this->toCamelCase($enumValues[$index])); } $fallback = $enumKeys[0] ?? $enumValues[0] ?? $value; - return $this->toCamelCase((string)$fallback); + return $this->escapeKeyword($this->toCamelCase((string)$fallback)); }; if ($isArray) { diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index 9ac4fee98d..d4b38c0db9 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -702,6 +702,17 @@ protected function toPythonValue(mixed $value, int $indent = 8): string return \is_string($json) ? $json : 'None'; } + /** + * A map field's `{}` default decodes to an empty PHP array, which would render as a list. + */ + #[Override] + public function getModelDefault(Schema $property, bool $required): mixed + { + $default = parent::getModelDefault($property, $required); + + return $default === [] && !($property instanceof ArraySchema) ? new stdClass() : $default; + } + protected function toPythonLiteral(mixed $value): string { if (\is_object($value)) { diff --git a/src/SDK/Language/Ruby.php b/src/SDK/Language/Ruby.php index 929c0c19b3..c1d5871e5a 100644 --- a/src/SDK/Language/Ruby.php +++ b/src/SDK/Language/Ruby.php @@ -263,7 +263,7 @@ public function getParamExample(Schema|Parameter $param, string $lang = ''): str case self::TYPE_NUMBER: case self::TYPE_INTEGER: case self::TYPE_BOOLEAN: - $output .= 'null'; + $output .= 'nil'; break; case self::TYPE_STRING: $output .= "''"; @@ -295,7 +295,7 @@ public function getParamExample(Schema|Parameter $param, string $lang = ''): str $output .= ($example) ? 'true' : 'false'; break; case self::TYPE_STRING: - $output .= "'{$example}'"; + $output .= "'" . \addcslashes((string) $example, "'\\") . "'"; break; case self::TYPE_FILE: $output .= "InputFile.from_path('dir/file.png')"; @@ -327,7 +327,9 @@ protected function jsonToHash(array $data, int $indent = 0): string } elseif (is_bool($node)) { $value = $node ? 'true' : 'false'; } elseif (is_string($node)) { - $value = '"' . $node . '"'; + $value = "'" . \addcslashes($node, "'\\") . "'"; + } elseif (is_null($node)) { + $value = 'nil'; } else { $value = $node; } diff --git a/templates/android/library/src/main/java/io/package/Topic.kt.twig b/templates/android/library/src/main/java/io/package/Topic.kt.twig index bb5711802c..eb84a44423 100644 --- a/templates/android/library/src/main/java/io/package/Topic.kt.twig +++ b/templates/android/library/src/main/java/io/package/Topic.kt.twig @@ -14,6 +14,7 @@ private fun levelsOf(levels: List): List { } require(!level.contains("+")) { "\"$level\" contains \"+\", not allowed in a topic" } require(!level.contains("#")) { "\"$level\" contains \"#\", not allowed in a topic" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character" } } return levels.toList() } diff --git a/templates/dart/docs/example.md.twig b/templates/dart/docs/example.md.twig index d4ce2c4793..770415c9cb 100644 --- a/templates/dart/docs/example.md.twig +++ b/templates/dart/docs/example.md.twig @@ -32,7 +32,7 @@ Client client = Client() {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = {{service.name | caseUcfirst}}(client); -{% if method.method.value != 'delete' and (method | methodType) != 'webAuth' %}{% if (method | methodType) == 'location' %}Uint8List{% elseif (method | getValidResponseModels)|length > 1 %}dynamic{% else %}models.{{ (method | responseModel) | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %});{% endif %} +{% if method.method.value != 'delete' and (method | methodType) != 'webAuth' %}{% if (method | methodType) == 'location' %}Uint8List{% elseif (method | textResponse) %}String{% elseif (method | getValidResponseModels)|length > 1 %}dynamic{% else %}models.{{ (method | responseModel) | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %});{% endif %} {%~ for parameter in (method | parameters('all')) %} {{ parameter.name | caseCamel | overrideIdentifier }}: {% if (parameter | enumValues) | length > 0 %}{{ parameter | enumExample }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}{% endif %},{% if not parameter.required %} // (optional){% endif %} diff --git a/templates/dart/lib/src/models/model.dart.twig b/templates/dart/lib/src/models/model.dart.twig index f706026481..0f5db7d9ab 100644 --- a/templates/dart/lib/src/models/model.dart.twig +++ b/templates/dart/lib/src/models/model.dart.twig @@ -50,7 +50,7 @@ class {{ definitionName | caseUcfirst | overrideIdentifier }} implements Model { {% if not (property | schemaRequired) %} ) : null{% else %} ){% endif %}{% else %}{% if not (property | schemaRequired) %}map['{{ propertyName | escapeDollarSign }}'] != null ? {% endif %}{{ modelName }}.fromMap( -{% if not (property | schemaRequired) %} {% else %} {% endif %}map['{{ propertyName | escapeDollarSign }}'], +{% if not (property | schemaRequired) %} {% else %} {% endif %}map['{{ propertyName | escapeDollarSign }}']{% if property | schemaRequired %} ?? {}{% endif %}, {% if not (property | schemaRequired) %} ) : null{% else %} ){% endif %}{% endif %}{% elseif property | usesEnumType %}{% set enumName = (property | enumName) %}{% if property | arraySchema %}{% if not (property | schemaRequired) %}map['{{ propertyName | escapeDollarSign }}'] != null ? {% endif %}List.from( diff --git a/templates/dart/lib/topic.dart.twig b/templates/dart/lib/topic.dart.twig index 00f43277a5..4b9ec9faeb 100644 --- a/templates/dart/lib/topic.dart.twig +++ b/templates/dart/lib/topic.dart.twig @@ -47,6 +47,9 @@ List _topicLevelsOf(List levels) { if (level.contains('#')) { throw ArgumentError('"$level" contains "#", not allowed in a topic'); } + if (level.contains('\u0000')) { + throw ArgumentError('level at index $index contains a NUL character'); + } } return List.unmodifiable(levels); } diff --git a/templates/dotnet/Package/Topic.cs.twig b/templates/dotnet/Package/Topic.cs.twig index 3d69fc4235..2775d7472f 100644 --- a/templates/dotnet/Package/Topic.cs.twig +++ b/templates/dotnet/Package/Topic.cs.twig @@ -71,6 +71,10 @@ namespace {{ spec.info.title | caseUcfirst }} { throw new ArgumentException($"\"{level}\" contains \"#\", not allowed in a topic"); } + if (level.Contains('\0')) + { + throw new ArgumentException($"level at index {index} contains a NUL character"); + } } return list; diff --git a/templates/flutter/docs/example.md.twig b/templates/flutter/docs/example.md.twig index 573f6efec5..6f80a079e7 100644 --- a/templates/flutter/docs/example.md.twig +++ b/templates/flutter/docs/example.md.twig @@ -62,7 +62,7 @@ FutureBuilder( }, ); {% else %} -{% if method.method.value != 'delete' and (method | methodType) != 'webAuth' %}{% if (method | methodType) == 'location' %}Uint8List{% elseif (method | getValidResponseModels)|length > 1 %}dynamic{% else %}models.{{ (method | responseModel) | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %});{% endif %} +{% if method.method.value != 'delete' and (method | methodType) != 'webAuth' %}{% if (method | methodType) == 'location' %}Uint8List{% elseif (method | textResponse) %}String{% elseif (method | getValidResponseModels)|length > 1 %}dynamic{% else %}models.{{ (method | responseModel) | caseUcfirst | overrideIdentifier }}{% endif %} result = {% endif %}await {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %});{% endif %} {%~ for parameter in (method | parameters('all')) %} {{ parameter.name | caseCamel | overrideIdentifier}}: {% if (parameter | enumValues) | length > 0%}{{ parameter | enumExample }}{% else %}{{ parameter | paramExample | replace({'': (parameter.name | caseCamel) }) | raw }}{% endif %},{% if not parameter.required %} // optional{% endif %} diff --git a/templates/go/topic.go.twig b/templates/go/topic.go.twig index 49f3600ce1..3323e4d4cc 100644 --- a/templates/go/topic.go.twig +++ b/templates/go/topic.go.twig @@ -64,6 +64,9 @@ func levelsOf(levels []string) []string { if strings.Contains(level, "#") { panic(fmt.Sprintf("\"%s\" contains \"#\", not allowed in a topic", level)) } + if strings.Contains(level, "\x00") { + panic(fmt.Sprintf("level at index %d contains a NUL character", index)) + } } return append([]string(nil), levels...) } diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig index 6d5bff4435..a458ba3155 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig @@ -14,6 +14,7 @@ private fun levelsOf(levels: List): List { } require(!level.contains("+")) { "\"$level\" contains \"+\", not allowed in a topic" } require(!level.contains("#")) { "\"$level\" contains \"#\", not allowed in a topic" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character" } } return levels.toList() } diff --git a/templates/php/src/Topic.php.twig b/templates/php/src/Topic.php.twig index da3178b7b2..237e9d88d3 100644 --- a/templates/php/src/Topic.php.twig +++ b/templates/php/src/Topic.php.twig @@ -103,6 +103,9 @@ final readonly class Topic implements \Stringable if (\str_contains($level, '#')) { throw new \InvalidArgumentException(\sprintf('"%s" contains "#", not allowed in a topic', $level)); } + if (\str_contains($level, "\0")) { + throw new \InvalidArgumentException(\sprintf('level at index %d contains a NUL character', $index)); + } $result[] = $level; } diff --git a/templates/python/package/topic.py.twig b/templates/python/package/topic.py.twig index 17aa2b1650..99253d20dd 100644 --- a/templates/python/package/topic.py.twig +++ b/templates/python/package/topic.py.twig @@ -23,6 +23,8 @@ def _levels_of(levels): raise ValueError(f'"{level}" contains "+", not allowed in a topic') if "#" in level: raise ValueError(f'"{level}" contains "#", not allowed in a topic') + if "\0" in level: + raise ValueError(f"level at index {index} contains a NUL character") return tuple(levels) diff --git a/templates/ruby/lib/container/topic.rb.twig b/templates/ruby/lib/container/topic.rb.twig index 2ec63c7c2d..11049a4e82 100644 --- a/templates/ruby/lib/container/topic.rb.twig +++ b/templates/ruby/lib/container/topic.rb.twig @@ -43,6 +43,7 @@ module {{spec.info.title | caseUcfirst }} end raise ArgumentError, "\"#{level}\" contains \"+\", not allowed in a topic" if level.include?('+') raise ArgumentError, "\"#{level}\" contains \"#\", not allowed in a topic" if level.include?('#') + raise ArgumentError, "level at index #{index} contains a NUL character" if level.include?("\0") end levels.dup diff --git a/templates/rust/src/topic.rs.twig b/templates/rust/src/topic.rs.twig index 474a311db3..ac61369196 100644 --- a/templates/rust/src/topic.rs.twig +++ b/templates/rust/src/topic.rs.twig @@ -103,6 +103,12 @@ fn levels_of>(levels: &[S]) -> Result, TopicError> { level ))); } + if level.contains('\0') { + return Err(TopicError::new(format!( + "level at index {} contains a NUL character", + index + ))); + } Ok(level.to_string()) }) .collect() diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index 807f3512fc..afdc35f2af 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -407,6 +407,7 @@ fn test_topic_helpers() { ("slash", vec!["user/123"]), ("plus", vec!["user", "a+b"]), ("hash", vec!["user", "#"]), + ("nul", vec!["user", "a\0b"]), ]; for (name, levels) in cases { match topic::path(&levels) { diff --git a/templates/swift/Sources/Topic.swift.twig b/templates/swift/Sources/Topic.swift.twig index 4a479ebb2b..4072b52cbf 100644 --- a/templates/swift/Sources/Topic.swift.twig +++ b/templates/swift/Sources/Topic.swift.twig @@ -42,6 +42,9 @@ private func levelsOf(_ levels: [String]) throws -> [String] { if level.contains("#") { throw TopicError("\"\(level)\" contains \"#\", not allowed in a topic") } + if level.contains("\0") { + throw TopicError("level at index \(index) contains a NUL character") + } } return levels } diff --git a/templates/web/src/topic.ts.twig b/templates/web/src/topic.ts.twig index 1258f705e8..071d5aec0a 100644 --- a/templates/web/src/topic.ts.twig +++ b/templates/web/src/topic.ts.twig @@ -34,6 +34,9 @@ function levelsOf(levels: unknown): string[] { if (level.includes('#')) { throw new Error(`"${level}" contains "#", not allowed in a topic`); } + if (level.includes('\0')) { + throw new Error(`level at index ${index} contains a NUL character`); + } return level; }); } diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index c8343c86ad..4dfe7564da 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -130,6 +130,14 @@ abstract class Base extends TestCase 'preset tier: PresetTier.FREE', ]; + protected const MAP_MODEL_DEFAULT_RESPONSES = [ + 'preset settings: options=0', + ]; + + protected const NESTED_MODEL_DEFAULT_RESPONSES = [ + 'preset profile: prefs=0', + ]; + protected const OBJECT_ARRAY_RESPONSES = [ 'POST:/v1/mock/tests/general/documents:passed', ]; @@ -513,6 +521,7 @@ abstract class Base extends TestCase 'Topic slash:passed', 'Topic plus:passed', 'Topic hash:passed', + 'Topic nul:passed', ]; protected const ADDITIONAL_PROPERTIES_RESPONSES = [ diff --git a/tests/e2e/DartBetaTest.php b/tests/e2e/DartBetaTest.php index 5c319fa7c3..1c188ec365 100644 --- a/tests/e2e/DartBetaTest.php +++ b/tests/e2e/DartBetaTest.php @@ -52,6 +52,8 @@ final class DartBetaTest extends Base ...Base::MODEL_DEFAULT_RESPONSES, ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, + ...Base::NESTED_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/DartStableTest.php b/tests/e2e/DartStableTest.php index f4d2f29b89..587ca015e2 100644 --- a/tests/e2e/DartStableTest.php +++ b/tests/e2e/DartStableTest.php @@ -52,6 +52,8 @@ final class DartStableTest extends Base ...Base::MODEL_DEFAULT_RESPONSES, ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, + ...Base::NESTED_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/FlutterBetaTest.php b/tests/e2e/FlutterBetaTest.php index 546ed9d6a9..edea5285c5 100644 --- a/tests/e2e/FlutterBetaTest.php +++ b/tests/e2e/FlutterBetaTest.php @@ -52,6 +52,8 @@ final class FlutterBetaTest extends Base ...Base::MODEL_DEFAULT_RESPONSES, ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, + ...Base::NESTED_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, ...Base::COOKIE_RESPONSES, diff --git a/tests/e2e/FlutterStableTest.php b/tests/e2e/FlutterStableTest.php index 3e1e46b9a4..7cd08c7e56 100644 --- a/tests/e2e/FlutterStableTest.php +++ b/tests/e2e/FlutterStableTest.php @@ -72,6 +72,8 @@ final class FlutterStableTest extends Base ...Base::MODEL_DEFAULT_RESPONSES, ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, + ...Base::NESTED_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::REALTIME_RESPONSES, ...Base::COOKIE_RESPONSES, diff --git a/tests/e2e/Python310Test.php b/tests/e2e/Python310Test.php index 7f2de09ce1..e06cf7a521 100644 --- a/tests/e2e/Python310Test.php +++ b/tests/e2e/Python310Test.php @@ -53,6 +53,7 @@ final class Python310Test extends Base ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, ...Base::PYTHON_MODEL_DEFAULT_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/Python311Test.php b/tests/e2e/Python311Test.php index 1daaa12f96..b1f2cf4b2c 100644 --- a/tests/e2e/Python311Test.php +++ b/tests/e2e/Python311Test.php @@ -53,6 +53,7 @@ final class Python311Test extends Base ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, ...Base::PYTHON_MODEL_DEFAULT_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/Python312Test.php b/tests/e2e/Python312Test.php index 0dbf08158a..77f0824ff9 100644 --- a/tests/e2e/Python312Test.php +++ b/tests/e2e/Python312Test.php @@ -53,6 +53,7 @@ final class Python312Test extends Base ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, ...Base::PYTHON_MODEL_DEFAULT_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/Python313Test.php b/tests/e2e/Python313Test.php index 43d5dbe553..de5c63b877 100644 --- a/tests/e2e/Python313Test.php +++ b/tests/e2e/Python313Test.php @@ -53,6 +53,7 @@ final class Python313Test extends Base ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, ...Base::PYTHON_MODEL_DEFAULT_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/Python39Test.php b/tests/e2e/Python39Test.php index 76e11be0a1..24a3f2d877 100644 --- a/tests/e2e/Python39Test.php +++ b/tests/e2e/Python39Test.php @@ -53,6 +53,7 @@ final class Python39Test extends Base ...Base::NULLABLE_MODEL_RESPONSES, ...Base::ENUM_ARRAY_MODEL_RESPONSES, ...Base::PYTHON_MODEL_DEFAULT_RESPONSES, + ...Base::MAP_MODEL_DEFAULT_RESPONSES, ...Base::EXCEPTION_RESPONSES, ...Base::OAUTH_RESPONSES, ...Base::QUERY_HELPER_RESPONSES, diff --git a/tests/e2e/languages/android/Tests.kt b/tests/e2e/languages/android/Tests.kt index 996e56c0b6..ba83e0de7a 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -525,6 +525,7 @@ class ServiceTest { "slash" to listOf("user/123"), "plus" to listOf("user", "a+b"), "hash" to listOf("user", "#"), + "nul" to listOf("user", "a\u0000b"), ) for ((name, levels) in topicErrorCases) { try { diff --git a/tests/e2e/languages/apple/Tests.swift b/tests/e2e/languages/apple/Tests.swift index 1ff17b89c8..ff168222dc 100644 --- a/tests/e2e/languages/apple/Tests.swift +++ b/tests/e2e/languages/apple/Tests.swift @@ -499,6 +499,7 @@ class Tests: XCTestCase { ("slash", ["user/123"]), ("plus", ["user", "a+b"]), ("hash", ["user", "#"]), + ("nul", ["user", "a\u{0}b"]), ] for (name, levels) in topicCases { do { diff --git a/tests/e2e/languages/dart/tests.dart b/tests/e2e/languages/dart/tests.dart index 6694f8d822..0db4e3fbb6 100644 --- a/tests/e2e/languages/dart/tests.dart +++ b/tests/e2e/languages/dart/tests.dart @@ -156,6 +156,8 @@ void main() async { print('empty presets: ${(await general.listEmptyPresets()).presets.length}'); print('preset verified: ${preset.verified}'); print('preset scopes: ${(preset.scopes ?? []).map((scope) => scope.value).join(',')}'); + print('preset settings: options=${(await general.getPresetSettings()).options.length}'); + print('preset profile: prefs=${(await general.getPresetProfile()).prefs.data.length}'); try { await general.error400(); @@ -347,6 +349,7 @@ void main() async { 'slash': ['user/123'], 'plus': ['user', 'a+b'], 'hash': ['user', '#'], + 'nul': ['user', 'a\u0000b'], }; topicCases.forEach((name, levels) { try { diff --git a/tests/e2e/languages/dotnet/Tests.cs b/tests/e2e/languages/dotnet/Tests.cs index 6a26411032..29712cfd0e 100644 --- a/tests/e2e/languages/dotnet/Tests.cs +++ b/tests/e2e/languages/dotnet/Tests.cs @@ -357,6 +357,7 @@ public async Task Test1() ("slash", new[] { "user/123" }), ("plus", new[] { "user", "a+b" }), ("hash", new[] { "user", "#" }), + ("nul", new[] { "user", "a\0b" }), }; foreach (var (name, levels) in topicCases) { diff --git a/tests/e2e/languages/flutter/tests.dart b/tests/e2e/languages/flutter/tests.dart index 884e518ebb..60bafa8bb2 100644 --- a/tests/e2e/languages/flutter/tests.dart +++ b/tests/e2e/languages/flutter/tests.dart @@ -228,6 +228,8 @@ void main() async { print('empty presets: ${(await general.listEmptyPresets()).presets.length}'); print('preset verified: ${preset.verified}'); print('preset scopes: ${(preset.scopes ?? []).map((scope) => scope.value).join(',')}'); + print('preset settings: options=${(await general.getPresetSettings()).options.length}'); + print('preset profile: prefs=${(await general.getPresetProfile()).prefs.data.length}'); try { await general.error400(); @@ -494,6 +496,7 @@ void main() async { 'slash': ['user/123'], 'plus': ['user', 'a+b'], 'hash': ['user', '#'], + 'nul': ['user', 'a\u0000b'], }; topicCases.forEach((name, levels) { try { diff --git a/tests/e2e/languages/go-v2/tests.go b/tests/e2e/languages/go-v2/tests.go index f2c5ca80af..9f6de03c32 100644 --- a/tests/e2e/languages/go-v2/tests.go +++ b/tests/e2e/languages/go-v2/tests.go @@ -482,6 +482,7 @@ func testTopicHelpers() { {"slash", []string{"user/123"}}, {"plus", []string{"user", "a+b"}}, {"hash", []string{"user", "#"}}, + {"nul", []string{"user", "a\x00b"}}, } for _, c := range cases { if topicPanics(c.levels) { diff --git a/tests/e2e/languages/go/tests.go b/tests/e2e/languages/go/tests.go index 98463fd18a..3216944e6e 100644 --- a/tests/e2e/languages/go/tests.go +++ b/tests/e2e/languages/go/tests.go @@ -482,6 +482,7 @@ func testTopicHelpers() { {"slash", []string{"user/123"}}, {"plus", []string{"user", "a+b"}}, {"hash", []string{"user", "#"}}, + {"nul", []string{"user", "a\x00b"}}, } for _, c := range cases { if topicPanics(c.levels) { diff --git a/tests/e2e/languages/kotlin/Tests.kt b/tests/e2e/languages/kotlin/Tests.kt index 1a10d570a9..b3a84e7367 100644 --- a/tests/e2e/languages/kotlin/Tests.kt +++ b/tests/e2e/languages/kotlin/Tests.kt @@ -370,6 +370,7 @@ class ServiceTest { "slash" to listOf("user/123"), "plus" to listOf("user", "a+b"), "hash" to listOf("user", "#"), + "nul" to listOf("user", "a\u0000b"), ) for ((name, levels) in topicErrorCases) { try { diff --git a/tests/e2e/languages/node/test.js b/tests/e2e/languages/node/test.js index 9c4d4f618f..fdf9f8f2b7 100644 --- a/tests/e2e/languages/node/test.js +++ b/tests/e2e/languages/node/test.js @@ -482,6 +482,7 @@ async function start() { ['slash', ['user/123']], ['plus', ['user', 'a+b']], ['hash', ['user', '#']], + ['nul', ['user', 'a\u0000b']], ]) { try { Topic.path(levels); diff --git a/tests/e2e/languages/php/test.php b/tests/e2e/languages/php/test.php index f5d99c2f09..25204129f0 100644 --- a/tests/e2e/languages/php/test.php +++ b/tests/e2e/languages/php/test.php @@ -460,6 +460,7 @@ public function toArray(): array ['slash', ['user/123']], ['plus', ['user', 'a+b']], ['hash', ['user', '#']], + ['nul', ['user', "a\0b"]], ] as [$name, $levels]) { try { Topic::path($levels); diff --git a/tests/e2e/languages/python/tests.py b/tests/e2e/languages/python/tests.py index 9b6bff04b4..5f7e3b7831 100644 --- a/tests/e2e/languages/python/tests.py +++ b/tests/e2e/languages/python/tests.py @@ -134,6 +134,7 @@ print(f"preset verified: {'null' if preset.verified is None else preset.verified}") print(f"preset scopes: {','.join(scope.value for scope in (preset.scopes or []))}") print(f"preset tier: {preset.tier}") +print(f"preset settings: options={len(general.get_preset_settings().options)}") try: response = general.error400() @@ -318,6 +319,7 @@ ('slash', ['user/123']), ('plus', ['user', 'a+b']), ('hash', ['user', '#']), + ('nul', ['user', 'a\0b']), ]: try: Topic.path(levels) diff --git a/tests/e2e/languages/react-native/browser.js b/tests/e2e/languages/react-native/browser.js index 2a453bb3f1..95f2ee17d7 100644 --- a/tests/e2e/languages/react-native/browser.js +++ b/tests/e2e/languages/react-native/browser.js @@ -460,6 +460,7 @@ import { ['slash', ['user/123']], ['plus', ['user', 'a+b']], ['hash', ['user', '#']], + ['nul', ['user', 'a\u0000b']], ]) { try { Topic.path(levels); diff --git a/tests/e2e/languages/ruby/tests.rb b/tests/e2e/languages/ruby/tests.rb index 070d3bf85d..5b84f49ea4 100644 --- a/tests/e2e/languages/ruby/tests.rb +++ b/tests/e2e/languages/ruby/tests.rb @@ -330,7 +330,8 @@ ['empty level', ['user', '']], ['slash', ['user/123']], ['plus', ['user', 'a+b']], - ['hash', ['user', '#']] + ['hash', ['user', '#']], + ['nul', ['user', "a\0b"]] ].each do |name, levels| begin Topic.path(levels) diff --git a/tests/e2e/languages/swift/Tests.swift b/tests/e2e/languages/swift/Tests.swift index 158258fb0d..c5becc34ee 100644 --- a/tests/e2e/languages/swift/Tests.swift +++ b/tests/e2e/languages/swift/Tests.swift @@ -378,6 +378,7 @@ class Tests: XCTestCase { ("slash", ["user/123"]), ("plus", ["user", "a+b"]), ("hash", ["user", "#"]), + ("nul", ["user", "a\u{0}b"]), ] for (name, levels) in topicCases { do { diff --git a/tests/e2e/languages/unity/Tests.cs b/tests/e2e/languages/unity/Tests.cs index 1e6aef1bc3..207813a7f2 100644 --- a/tests/e2e/languages/unity/Tests.cs +++ b/tests/e2e/languages/unity/Tests.cs @@ -490,6 +490,7 @@ private async Task RunAsyncTest() ("slash", new[] { "user/123" }), ("plus", new[] { "user", "a+b" }), ("hash", new[] { "user", "#" }), + ("nul", new[] { "user", "a\0b" }), }; foreach (var (name, levels) in topicCases) { diff --git a/tests/e2e/languages/web/index.html b/tests/e2e/languages/web/index.html index 11c3c34d95..992c2b612e 100644 --- a/tests/e2e/languages/web/index.html +++ b/tests/e2e/languages/web/index.html @@ -579,6 +579,7 @@ ['slash', ['user/123']], ['plus', ['user', 'a+b']], ['hash', ['user', '#']], + ['nul', ['user', 'a\u0000b']], ]) { try { Topic.path(levels); diff --git a/tests/e2e/languages/web/node.js b/tests/e2e/languages/web/node.js index 81f0c93c8e..70d91c1451 100644 --- a/tests/e2e/languages/web/node.js +++ b/tests/e2e/languages/web/node.js @@ -386,6 +386,7 @@ async function start() { ['slash', ['user/123']], ['plus', ['user', 'a+b']], ['hash', ['user', '#']], + ['nul', ['user', 'a\u0000b']], ]) { try { Topic.path(levels); diff --git a/tests/generation/GenerationTest.php b/tests/generation/GenerationTest.php index cfbbeef2fc..98440de75d 100644 --- a/tests/generation/GenerationTest.php +++ b/tests/generation/GenerationTest.php @@ -846,7 +846,7 @@ public function testWebNestedResponseDefaultsAreApplied(): void $service = (string) \file_get_contents($dir . '/src/services/general.ts'); $this->assertStringNotContainsString('...response', $service); - $this->assertSame(5, \substr_count($service, 'return request.then((response) => applyDefaults(response, defaults));')); + $this->assertSame(6, \substr_count($service, 'return request.then((response) => applyDefaults(response, defaults));')); $this->assertSame(2, \substr_count($service, <<<'TS' const defaults = { root: 'preset', diff --git a/tests/resources/spec-openapi3.json b/tests/resources/spec-openapi3.json index 5898f1c589..789c22ba60 100644 --- a/tests/resources/spec-openapi3.json +++ b/tests/resources/spec-openapi3.json @@ -2852,6 +2852,92 @@ } } }, + "\/mock\/tests\/general\/preset\/settings": { + "get": { + "summary": "Get Preset Settings", + "operationId": "generalGetPresetSettings", + "tags": [ + "general" + ], + "description": "Return preset settings that omit a required map field.", + "x-appwrite": { + "demo": "general\/get-preset-settings.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "public", + "platforms": [ + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "parameters": [], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "responses": { + "200": { + "description": "Preset", + "content": { + "application\/json": { + "schema": {"$ref": "#\/components\/schemas\/presetSettings"} + } + } + } + } + } + }, + "\/mock\/tests\/general\/preset\/profile": { + "get": { + "summary": "Get Preset Profile", + "operationId": "generalGetPresetProfile", + "tags": [ + "general" + ], + "description": "Return a preset profile that omits its required preferences model.", + "x-appwrite": { + "demo": "general\/get-preset-profile.md", + "rate-limit": 0, + "rate-time": 3600, + "rate-key": "url:{url},ip:{ip}", + "scope": "public", + "platforms": [ + "client", + "server" + ], + "packaging": false, + "auth": { + "Project": [] + } + }, + "parameters": [], + "security": [ + { + "Project": [], + "Key": [], + "JWT": [] + } + ], + "responses": { + "200": { + "description": "Preset", + "content": { + "application\/json": { + "schema": {"$ref": "#\/components\/schemas\/presetProfile"} + } + } + } + } + } + }, "\/mock\/tests\/general\/presets": { "get": { "summary": "List Presets", @@ -3615,6 +3701,22 @@ }, "required": ["name", "count", "enabled", "tier", "tags", "settings"] }, + "presetSettings": { + "description": "Preset Settings", + "type": "object", + "properties": { + "options": {"type": "object", "additionalProperties": true, "description": "Preset options as a key-value object.", "example": {"mode": "fast"}, "default": {}} + }, + "required": ["options"] + }, + "presetProfile": { + "description": "Preset Profile", + "type": "object", + "properties": { + "prefs": {"$ref": "#/components/schemas/preferences", "description": "Preset preferences."} + }, + "required": ["prefs"] + }, "presetList": { "description": "Preset list", "type": "object",