From 82fe3c95eb6bb8c669291dab20d12200a91497da Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 14 Oct 2026 19:00:36 +0100 Subject: [PATCH 1/4] fix(php,ruby,dart,flutter): generate examples that compile PHP printed JSON objects inside document lists and left apostrophes unescaped; Ruby used null for empty optional numbers and unescaped quotes in strings; Dart and Flutter typed text responses as models. and used enum members that are keywords without their escaped name. --- src/SDK/Language/Dart.php | 6 +++--- src/SDK/Language/PHP.php | 11 ++++++++--- src/SDK/Language/Ruby.php | 8 +++++--- templates/dart/docs/example.md.twig | 2 +- templates/flutter/docs/example.md.twig | 2 +- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/SDK/Language/Dart.php b/src/SDK/Language/Dart.php index 9a3bba50ab..f7ace52033 100644 --- a/src/SDK/Language/Dart.php +++ b/src/SDK/Language/Dart.php @@ -594,13 +594,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/PHP.php b/src/SDK/Language/PHP.php index bbe64fb6b9..ac486f52c5 100644 --- a/src/SDK/Language/PHP.php +++ b/src/SDK/Language/PHP.php @@ -424,7 +424,11 @@ public function getParamExample(Schema|Parameter $param, string $lang = ''): str if ($this->isPermissionString($example)) { $output .= $this->getPermissionExample($example); } else { - $output .= \is_array($example) ? $this->jsonToAssoc($example) : (string) $example; + $decoded = \is_string($example) ? \json_decode($example, true) : $example; + // A list of scalars is already valid PHP as JSON; anything holding objects needs PHP arrays + $output .= \is_array($decoded) && \array_filter($decoded, \is_array(...)) !== [] + ? $this->jsonToAssoc($decoded) + : (string) $example; } break; case self::TYPE_OBJECT: @@ -468,7 +472,7 @@ protected function jsonToAssoc(array $data, int $indent = 0): string if (is_array($node)) { $value = $this->jsonToAssoc($node, $indent + 1); } elseif (is_string($node)) { - $value = '\'' . $node . '\''; + $value = '\'' . \addcslashes($node, '\'\\') . '\''; } elseif (is_bool($node)) { $value = $node ? 'true' : 'false'; } elseif (is_null($node)) { @@ -478,7 +482,8 @@ protected function jsonToAssoc(array $data, int $indent = 0): string } $comma = ($index < count($keys) - 1) ? ',' : ''; - $output .= ' ' . $itemIndent . '\'' . $key . '\' => ' . $value . $comma . "\n"; + $prefix = \array_is_list($data) ? '' : '\'' . \addcslashes((string) $key, '\'\\') . '\' => '; + $output .= ' ' . $itemIndent . $prefix . $value . $comma . "\n"; } return $output . ($baseIndent . ' ]'); diff --git a/src/SDK/Language/Ruby.php b/src/SDK/Language/Ruby.php index aa9493d7c9..4bfcebd916 100644 --- a/src/SDK/Language/Ruby.php +++ b/src/SDK/Language/Ruby.php @@ -258,7 +258,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 .= "''"; @@ -289,7 +289,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')"; @@ -321,7 +321,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/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/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 %} From 4ee31f56128840152d2a4164cb7194bae5f2bcc2 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 14 Oct 2026 19:00:36 +0100 Subject: [PATCH 2/4] fix(python,dart): default required map and nested model fields Python rendered a map field's {} default as [], failing pydantic validation; Dart parsed a missing required nested model such as prefs from null. --- src/SDK/Language/Python.php | 11 +++++++++++ templates/dart/lib/src/models/model.dart.twig | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) 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/templates/dart/lib/src/models/model.dart.twig b/templates/dart/lib/src/models/model.dart.twig index 8b3a491b35..26d5133327 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( From ebc4f9e55b0a4878b290e3789f4f60050e4c524d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 14 Oct 2026 19:00:36 +0100 Subject: [PATCH 3/4] fix: reject NUL bytes in topic levels MQTT topic filters cannot contain U+0000. Also adds e2e coverage for the model defaults above. --- mock-server/app/http.php | 18 ++++ .../src/main/java/io/package/Topic.kt.twig | 1 + templates/dart/lib/topic.dart.twig | 3 + templates/dotnet/Package/Topic.cs.twig | 4 + templates/go/topic.go.twig | 3 + .../src/main/kotlin/io/appwrite/Topic.kt.twig | 1 + templates/php/src/Topic.php.twig | 3 + templates/python/package/topic.py.twig | 2 + templates/ruby/lib/container/topic.rb.twig | 1 + templates/rust/src/topic.rs.twig | 6 ++ templates/rust/tests/tests.rs | 1 + templates/swift/Sources/Topic.swift.twig | 3 + templates/web/src/topic.ts.twig | 3 + tests/e2e/Base.php | 9 ++ tests/e2e/DartBetaTest.php | 2 + tests/e2e/DartStableTest.php | 2 + tests/e2e/FlutterBetaTest.php | 2 + tests/e2e/FlutterStableTest.php | 2 + tests/e2e/Python310Test.php | 1 + tests/e2e/Python311Test.php | 1 + tests/e2e/Python312Test.php | 1 + tests/e2e/Python313Test.php | 1 + tests/e2e/Python39Test.php | 1 + tests/e2e/languages/android/Tests.kt | 1 + tests/e2e/languages/apple/Tests.swift | 1 + tests/e2e/languages/dart/tests.dart | 3 + tests/e2e/languages/dotnet/Tests.cs | 1 + tests/e2e/languages/flutter/tests.dart | 3 + tests/e2e/languages/go-v2/tests.go | 1 + tests/e2e/languages/go/tests.go | 1 + tests/e2e/languages/kotlin/Tests.kt | 1 + tests/e2e/languages/node/test.js | 1 + tests/e2e/languages/php/test.php | 1 + tests/e2e/languages/python/tests.py | 2 + tests/e2e/languages/react-native/browser.js | 1 + tests/e2e/languages/ruby/tests.rb | 3 +- tests/e2e/languages/swift/Tests.swift | 1 + tests/e2e/languages/unity/Tests.cs | 1 + tests/e2e/languages/web/index.html | 1 + tests/e2e/languages/web/node.js | 1 + tests/generation/GenerationTest.php | 2 +- tests/resources/spec-openapi3.json | 102 ++++++++++++++++++ 42 files changed, 197 insertions(+), 2 deletions(-) diff --git a/mock-server/app/http.php b/mock-server/app/http.php index e4203be533..34927d8d51 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']]); }); +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/templates/android/library/src/main/java/io/package/Topic.kt.twig b/templates/android/library/src/main/java/io/package/Topic.kt.twig index 81f3fcc592..ffe96e4a48 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 @@ -16,6 +16,7 @@ private fun levelsOf(levels: List): List { require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } require(level != "#") { "\"#\" is reserved, use all()" } require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character, which MQTT topics cannot hold" } } return levels.toList() } diff --git a/templates/dart/lib/topic.dart.twig b/templates/dart/lib/topic.dart.twig index 4563d0848a..74b0478c61 100644 --- a/templates/dart/lib/topic.dart.twig +++ b/templates/dart/lib/topic.dart.twig @@ -80,6 +80,9 @@ List _topicLevelsOf(List levels) { if (level.contains('#')) { throw ArgumentError('"$level" contains "#", use all() for wildcards'); } + if (level.contains('\u0000')) { + throw ArgumentError('level at index $index contains a NUL character, which MQTT topics cannot hold'); + } } return List.unmodifiable(levels); } diff --git a/templates/dotnet/Package/Topic.cs.twig b/templates/dotnet/Package/Topic.cs.twig index bcf7a69175..cb730bfb8a 100644 --- a/templates/dotnet/Package/Topic.cs.twig +++ b/templates/dotnet/Package/Topic.cs.twig @@ -96,6 +96,10 @@ namespace {{ spec.info.title | caseUcfirst }} { throw new ArgumentException($"\"{level}\" contains \"#\", use all() for wildcards"); } + if (level.Contains('\0')) + { + throw new ArgumentException($"level at index {index} contains a NUL character, which MQTT topics cannot hold"); + } } return list; diff --git a/templates/go/topic.go.twig b/templates/go/topic.go.twig index 842ee8032d..5bda309b6c 100644 --- a/templates/go/topic.go.twig +++ b/templates/go/topic.go.twig @@ -105,6 +105,9 @@ func levelsOf(levels []string) []string { if strings.Contains(level, "#") { panic(fmt.Sprintf("\"%s\" contains \"#\", use all() for wildcards", level)) } + if strings.Contains(level, "\x00") { + panic(fmt.Sprintf("level at index %d contains a NUL character, which MQTT topics cannot hold", 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 d342ae9eb9..5dfd0aadbc 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig @@ -16,6 +16,7 @@ private fun levelsOf(levels: List): List { require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } require(level != "#") { "\"#\" is reserved, use all()" } require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character, which MQTT topics cannot hold" } } return levels.toList() } diff --git a/templates/php/src/Topic.php.twig b/templates/php/src/Topic.php.twig index 230a2d3c2e..e6254e6c55 100644 --- a/templates/php/src/Topic.php.twig +++ b/templates/php/src/Topic.php.twig @@ -135,6 +135,9 @@ final readonly class Topic implements \Stringable if (\str_contains($level, '#')) { throw new \InvalidArgumentException(\sprintf('"%s" contains "#", use all() for wildcards', $level)); } + if (\str_contains($level, "\0")) { + throw new \InvalidArgumentException(\sprintf('level at index %d contains a NUL character, which MQTT topics cannot hold', $index)); + } $result[] = $level; } diff --git a/templates/python/package/topic.py.twig b/templates/python/package/topic.py.twig index b8b7c6386f..83075837ef 100644 --- a/templates/python/package/topic.py.twig +++ b/templates/python/package/topic.py.twig @@ -27,6 +27,8 @@ def _levels_of(levels): raise ValueError('"#" is reserved, use all()') if "#" in level: raise ValueError(f'"{level}" contains "#", use all() for wildcards') + if "\0" in level: + raise ValueError(f"level at index {index} contains a NUL character, which MQTT topics cannot hold") return tuple(levels) diff --git a/templates/ruby/lib/container/topic.rb.twig b/templates/ruby/lib/container/topic.rb.twig index 306cb9fd28..dffbe3179e 100644 --- a/templates/ruby/lib/container/topic.rb.twig +++ b/templates/ruby/lib/container/topic.rb.twig @@ -69,6 +69,7 @@ module {{spec.info.title | caseUcfirst }} raise ArgumentError, "\"#{level}\" contains \"+\", use any() for wildcards" if level.include?('+') raise ArgumentError, '"#" is reserved, use all()' if level == '#' raise ArgumentError, "\"#{level}\" contains \"#\", use all() for wildcards" if level.include?('#') + raise ArgumentError, "level at index #{index} contains a NUL character, which MQTT topics cannot hold" if level.include?("\0") end levels.dup diff --git a/templates/rust/src/topic.rs.twig b/templates/rust/src/topic.rs.twig index 13e8935ed2..43cf1edef0 100644 --- a/templates/rust/src/topic.rs.twig +++ b/templates/rust/src/topic.rs.twig @@ -154,6 +154,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, which MQTT topics cannot hold", + index + ))); + } Ok(level.to_string()) }) .collect() diff --git a/templates/rust/tests/tests.rs b/templates/rust/tests/tests.rs index d47a3bf21f..86c7256715 100644 --- a/templates/rust/tests/tests.rs +++ b/templates/rust/tests/tests.rs @@ -368,6 +368,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 3543d25ce9..555cedd75b 100644 --- a/templates/swift/Sources/Topic.swift.twig +++ b/templates/swift/Sources/Topic.swift.twig @@ -48,6 +48,9 @@ private func levelsOf(_ levels: [String]) throws -> [String] { if level.contains("#") { throw TopicError("\"\(level)\" contains \"#\", use all() for wildcards") } + if level.contains("\0") { + throw TopicError("level at index \(index) contains a NUL character, which MQTT topics cannot hold") + } } return levels } diff --git a/templates/web/src/topic.ts.twig b/templates/web/src/topic.ts.twig index cd6cea01f7..17259eb30f 100644 --- a/templates/web/src/topic.ts.twig +++ b/templates/web/src/topic.ts.twig @@ -40,6 +40,9 @@ function levelsOf(levels: unknown): string[] { if (level.includes('#')) { throw new Error(`"${level}" contains "#", use all() for wildcards`); } + if (level.includes('\0')) { + throw new Error(`level at index ${index} contains a NUL character, which MQTT topics cannot hold`); + } return level; }); } diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index 9f83998aad..39254d32b6 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -126,6 +126,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', ]; @@ -473,6 +481,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 00224ec053..08596df5bf 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 fefdf13625..eca5378ea8 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 00adc7e05c..5d41918318 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 9ce6d0cbe0..706aab1db9 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 d832be9977..52df262808 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 5b8e8327d6..abad361659 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 2bf3d2cbe8..def53ec1c1 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 557d86aeb8..628973137d 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 68bccaaf3f..b6ef147383 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 47d2feac20..0bf4f5aabe 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -489,6 +489,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 f3c18c6db5..da016e07d1 100644 --- a/tests/e2e/languages/apple/Tests.swift +++ b/tests/e2e/languages/apple/Tests.swift @@ -462,6 +462,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 5d4d2941e0..0ed171ffe9 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(); @@ -310,6 +312,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 eb96598037..18c6e63b3d 100644 --- a/tests/e2e/languages/dotnet/Tests.cs +++ b/tests/e2e/languages/dotnet/Tests.cs @@ -319,6 +319,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 33f2b47974..4169f93c31 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(); @@ -458,6 +460,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 953d77916a..55cf455a30 100644 --- a/tests/e2e/languages/go-v2/tests.go +++ b/tests/e2e/languages/go-v2/tests.go @@ -441,6 +441,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 0e50bf4ef8..15bcf4be2a 100644 --- a/tests/e2e/languages/go/tests.go +++ b/tests/e2e/languages/go/tests.go @@ -441,6 +441,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 29afe0e462..447b0322bb 100644 --- a/tests/e2e/languages/kotlin/Tests.kt +++ b/tests/e2e/languages/kotlin/Tests.kt @@ -334,6 +334,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 568b9ddf6d..836f29a1a7 100644 --- a/tests/e2e/languages/node/test.js +++ b/tests/e2e/languages/node/test.js @@ -444,6 +444,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 986054082d..4243448de0 100644 --- a/tests/e2e/languages/php/test.php +++ b/tests/e2e/languages/php/test.php @@ -424,6 +424,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 604e7bdedd..8e0f065ea1 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() @@ -282,6 +283,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 b1d96c2a96..219546807d 100644 --- a/tests/e2e/languages/react-native/browser.js +++ b/tests/e2e/languages/react-native/browser.js @@ -422,6 +422,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 94e3e7605f..2b8c7d917f 100644 --- a/tests/e2e/languages/ruby/tests.rb +++ b/tests/e2e/languages/ruby/tests.rb @@ -294,7 +294,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 ab370a4031..a47ed8e2fb 100644 --- a/tests/e2e/languages/swift/Tests.swift +++ b/tests/e2e/languages/swift/Tests.swift @@ -341,6 +341,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 c99dcaaae5..ddce004d54 100644 --- a/tests/e2e/languages/unity/Tests.cs +++ b/tests/e2e/languages/unity/Tests.cs @@ -452,6 +452,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 86cec02417..f31a690587 100644 --- a/tests/e2e/languages/web/index.html +++ b/tests/e2e/languages/web/index.html @@ -541,6 +541,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 4518b2ab05..a27c31f035 100644 --- a/tests/e2e/languages/web/node.js +++ b/tests/e2e/languages/web/node.js @@ -348,6 +348,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 6c1298a661..0edc01ef25 100644 --- a/tests/generation/GenerationTest.php +++ b/tests/generation/GenerationTest.php @@ -705,7 +705,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 7d37988e0b..fbd06233d1 100644 --- a/tests/resources/spec-openapi3.json +++ b/tests/resources/spec-openapi3.json @@ -2830,6 +2830,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", @@ -3581,6 +3667,22 @@ }, "required": ["name", "count", "enabled", "tier"] }, + "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", From 8950ead398c0dd6a624c14b36759f36f5aa8efdc Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 14 Oct 2026 19:16:26 +0100 Subject: [PATCH 4/4] style: shorten the topic NUL error so generated TS passes prettier The longer message pushed the throw past prettier's print width in web, node and react-native topic.ts. --- .../android/library/src/main/java/io/package/Topic.kt.twig | 2 +- templates/dart/lib/topic.dart.twig | 2 +- templates/dotnet/Package/Topic.cs.twig | 2 +- templates/go/topic.go.twig | 2 +- templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig | 2 +- templates/php/src/Topic.php.twig | 2 +- templates/python/package/topic.py.twig | 2 +- templates/ruby/lib/container/topic.rb.twig | 2 +- templates/rust/src/topic.rs.twig | 2 +- templates/swift/Sources/Topic.swift.twig | 2 +- templates/web/src/topic.ts.twig | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) 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 ffe96e4a48..2f5b11f4f4 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 @@ -16,7 +16,7 @@ private fun levelsOf(levels: List): List { require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } require(level != "#") { "\"#\" is reserved, use all()" } require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } - require(!level.contains('\u0000')) { "level at index $index contains a NUL character, which MQTT topics cannot hold" } + require(!level.contains('\u0000')) { "level at index $index contains a NUL character" } } return levels.toList() } diff --git a/templates/dart/lib/topic.dart.twig b/templates/dart/lib/topic.dart.twig index 74b0478c61..369200ee05 100644 --- a/templates/dart/lib/topic.dart.twig +++ b/templates/dart/lib/topic.dart.twig @@ -81,7 +81,7 @@ List _topicLevelsOf(List levels) { throw ArgumentError('"$level" contains "#", use all() for wildcards'); } if (level.contains('\u0000')) { - throw ArgumentError('level at index $index contains a NUL character, which MQTT topics cannot hold'); + 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 cb730bfb8a..94b6b8d551 100644 --- a/templates/dotnet/Package/Topic.cs.twig +++ b/templates/dotnet/Package/Topic.cs.twig @@ -98,7 +98,7 @@ namespace {{ spec.info.title | caseUcfirst }} } if (level.Contains('\0')) { - throw new ArgumentException($"level at index {index} contains a NUL character, which MQTT topics cannot hold"); + throw new ArgumentException($"level at index {index} contains a NUL character"); } } diff --git a/templates/go/topic.go.twig b/templates/go/topic.go.twig index 5bda309b6c..1a22a512c9 100644 --- a/templates/go/topic.go.twig +++ b/templates/go/topic.go.twig @@ -106,7 +106,7 @@ func levelsOf(levels []string) []string { panic(fmt.Sprintf("\"%s\" contains \"#\", use all() for wildcards", level)) } if strings.Contains(level, "\x00") { - panic(fmt.Sprintf("level at index %d contains a NUL character, which MQTT topics cannot hold", index)) + 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 5dfd0aadbc..91101a23d2 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Topic.kt.twig @@ -16,7 +16,7 @@ private fun levelsOf(levels: List): List { require(!level.contains("+")) { "\"$level\" contains \"+\", use any() for wildcards" } require(level != "#") { "\"#\" is reserved, use all()" } require(!level.contains("#")) { "\"$level\" contains \"#\", use all() for wildcards" } - require(!level.contains('\u0000')) { "level at index $index contains a NUL character, which MQTT topics cannot hold" } + 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 e6254e6c55..9b85fffe3d 100644 --- a/templates/php/src/Topic.php.twig +++ b/templates/php/src/Topic.php.twig @@ -136,7 +136,7 @@ final readonly class Topic implements \Stringable throw new \InvalidArgumentException(\sprintf('"%s" contains "#", use all() for wildcards', $level)); } if (\str_contains($level, "\0")) { - throw new \InvalidArgumentException(\sprintf('level at index %d contains a NUL character, which MQTT topics cannot hold', $index)); + 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 83075837ef..4a9f1d9e4c 100644 --- a/templates/python/package/topic.py.twig +++ b/templates/python/package/topic.py.twig @@ -28,7 +28,7 @@ def _levels_of(levels): if "#" in level: raise ValueError(f'"{level}" contains "#", use all() for wildcards') if "\0" in level: - raise ValueError(f"level at index {index} contains a NUL character, which MQTT topics cannot hold") + 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 dffbe3179e..66f5872df8 100644 --- a/templates/ruby/lib/container/topic.rb.twig +++ b/templates/ruby/lib/container/topic.rb.twig @@ -69,7 +69,7 @@ module {{spec.info.title | caseUcfirst }} raise ArgumentError, "\"#{level}\" contains \"+\", use any() for wildcards" if level.include?('+') raise ArgumentError, '"#" is reserved, use all()' if level == '#' raise ArgumentError, "\"#{level}\" contains \"#\", use all() for wildcards" if level.include?('#') - raise ArgumentError, "level at index #{index} contains a NUL character, which MQTT topics cannot hold" if level.include?("\0") + 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 43cf1edef0..dbf1e9e1bf 100644 --- a/templates/rust/src/topic.rs.twig +++ b/templates/rust/src/topic.rs.twig @@ -156,7 +156,7 @@ fn levels_of>(levels: &[S]) -> Result, TopicError> { } if level.contains('\0') { return Err(TopicError::new(format!( - "level at index {} contains a NUL character, which MQTT topics cannot hold", + "level at index {} contains a NUL character", index ))); } diff --git a/templates/swift/Sources/Topic.swift.twig b/templates/swift/Sources/Topic.swift.twig index 555cedd75b..c906d278a2 100644 --- a/templates/swift/Sources/Topic.swift.twig +++ b/templates/swift/Sources/Topic.swift.twig @@ -49,7 +49,7 @@ private func levelsOf(_ levels: [String]) throws -> [String] { throw TopicError("\"\(level)\" contains \"#\", use all() for wildcards") } if level.contains("\0") { - throw TopicError("level at index \(index) contains a NUL character, which MQTT topics cannot hold") + 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 17259eb30f..fbf4682245 100644 --- a/templates/web/src/topic.ts.twig +++ b/templates/web/src/topic.ts.twig @@ -41,7 +41,7 @@ function levelsOf(levels: unknown): string[] { throw new Error(`"${level}" contains "#", use all() for wildcards`); } if (level.includes('\0')) { - throw new Error(`level at index ${index} contains a NUL character, which MQTT topics cannot hold`); + throw new Error(`level at index ${index} contains a NUL character`); } return level; });