From 25f0deabaa36432a82c26f3343e3b8b634a56bb2 Mon Sep 17 00:00:00 2001 From: jld3103 Date: Sat, 4 Feb 2023 22:26:14 +0100 Subject: [PATCH 1/4] dynamite: Fix objects without properties but with additionalProperties set --- packages/dynamite/lib/src/openapi_builder.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/lib/src/openapi_builder.dart index 8b484da8..c2c04f89 100644 --- a/packages/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/lib/src/openapi_builder.dart @@ -1543,6 +1543,13 @@ TypeResult resolveType( break; case 'object': if (schema.properties == null) { + if (schema.additionalProperties ?? false) { + result = TypeResultMap( + 'Map', + TypeResultBase('dynamic'), + ); + break; + } result = TypeResultBase('dynamic'); break; } From 30a9b4bff050221b85aee1f65f070465cc5dc7c7 Mon Sep 17 00:00:00 2001 From: jld3103 Date: Mon, 6 Feb 2023 19:12:42 +0100 Subject: [PATCH 2/4] dynamite: Support objects with unknown key names --- packages/dynamite/lib/src/models/schema.dart | 23 +++++++++++++++- .../dynamite/lib/src/models/schema.g.dart | 4 +-- .../dynamite/lib/src/openapi_builder.dart | 26 ++++++++++++++----- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/dynamite/lib/src/models/schema.dart b/packages/dynamite/lib/src/models/schema.dart index fb94b4c5..6ee10557 100644 --- a/packages/dynamite/lib/src/models/schema.dart +++ b/packages/dynamite/lib/src/models/schema.dart @@ -60,7 +60,10 @@ class Schema { final Schema? items; - final bool? additionalProperties; + @JsonKey( + fromJson: _parseAdditionalProperties, + ) + final Schema? additionalProperties; final String? contentMediaType; @@ -72,3 +75,21 @@ class Schema { bool get isContentString => type == 'string' && (contentMediaType?.isNotEmpty ?? false) && contentSchema != null; } + +class EmptySchema extends Schema {} + +Schema? _parseAdditionalProperties(final dynamic data) { + if (data == null) { + return null; + } + + if (data is bool) { + return data ? EmptySchema() : null; + } + + if (data is Map) { + return Schema.fromJson(data); + } + + throw Exception('Can not parse additionalProperties from $data'); +} diff --git a/packages/dynamite/lib/src/models/schema.g.dart b/packages/dynamite/lib/src/models/schema.g.dart index afd37486..ce6a33cc 100644 --- a/packages/dynamite/lib/src/models/schema.g.dart +++ b/packages/dynamite/lib/src/models/schema.g.dart @@ -46,7 +46,7 @@ Schema _$SchemaFromJson(Map json) { ), required: (json['required'] as List?)?.map((e) => e as String).toList(), items: json['items'] == null ? null : Schema.fromJson(json['items'] as Map), - additionalProperties: json['additionalProperties'] as bool?, + additionalProperties: _parseAdditionalProperties(json['additionalProperties']), contentMediaType: json['contentMediaType'] as String?, contentSchema: json['contentSchema'] == null ? null : Schema.fromJson(json['contentSchema'] as Map), @@ -78,7 +78,7 @@ Map _$SchemaToJson(Schema instance) { writeNotNull('properties', instance.properties?.map((k, e) => MapEntry(k, e.toJson()))); writeNotNull('required', instance.required); writeNotNull('items', instance.items?.toJson()); - writeNotNull('additionalProperties', instance.additionalProperties); + writeNotNull('additionalProperties', instance.additionalProperties?.toJson()); writeNotNull('contentMediaType', instance.contentMediaType); writeNotNull('contentSchema', instance.contentSchema?.toJson()); writeNotNull('discriminator', instance.discriminator?.toJson()); diff --git a/packages/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/lib/src/openapi_builder.dart index c2c04f89..57e2ff1d 100644 --- a/packages/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/lib/src/openapi_builder.dart @@ -1082,7 +1082,7 @@ TypeResult resolveObject( refer('JsonSerializable').call( [], { - if (schema.additionalProperties ?? false) ...{ + if (schema.additionalProperties != null) ...{ 'disallowUnrecognizedKeys': refer('false'), }, if (extraJsonSerializableValues != null) ...{ @@ -1543,11 +1543,25 @@ TypeResult resolveType( break; case 'object': if (schema.properties == null) { - if (schema.additionalProperties ?? false) { - result = TypeResultMap( - 'Map', - TypeResultBase('dynamic'), - ); + if (schema.additionalProperties != null) { + if (schema.additionalProperties is EmptySchema) { + result = TypeResultMap( + 'Map', + TypeResultBase('dynamic'), + ); + } else { + final subResult = resolveType( + spec, + state, + identifier, + schema.additionalProperties!, + extraJsonSerializableValues: extraJsonSerializableValues, + ); + result = TypeResultMap( + 'Map', + TypeResultBase('dynamic'), + ); + } break; } result = TypeResultBase('dynamic'); From 93befad334508d969f8eea37f95d25ebfd58ecbc Mon Sep 17 00:00:00 2001 From: jld3103 Date: Thu, 23 Feb 2023 14:31:46 +0100 Subject: [PATCH 3/4] dynamite: Support minLength and maxLength --- packages/dynamite/lib/src/models/schema.dart | 6 +++++ .../dynamite/lib/src/models/schema.g.dart | 8 +++++- .../dynamite/lib/src/openapi_builder.dart | 26 +++++++++++++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/dynamite/lib/src/models/schema.dart b/packages/dynamite/lib/src/models/schema.dart index 6ee10557..723632cc 100644 --- a/packages/dynamite/lib/src/models/schema.dart +++ b/packages/dynamite/lib/src/models/schema.dart @@ -24,6 +24,8 @@ class Schema { this.contentSchema, this.discriminator, this.pattern, + this.minLength, + this.maxLength, }); factory Schema.fromJson(final Map json) => _$SchemaFromJson(json); @@ -73,6 +75,10 @@ class Schema { final String? pattern; + final int? minLength; + + final int? maxLength; + bool get isContentString => type == 'string' && (contentMediaType?.isNotEmpty ?? false) && contentSchema != null; } diff --git a/packages/dynamite/lib/src/models/schema.g.dart b/packages/dynamite/lib/src/models/schema.g.dart index ce6a33cc..0cf8e5fd 100644 --- a/packages/dynamite/lib/src/models/schema.g.dart +++ b/packages/dynamite/lib/src/models/schema.g.dart @@ -27,7 +27,9 @@ Schema _$SchemaFromJson(Map json) { 'contentMediaType', 'contentSchema', 'discriminator', - 'pattern' + 'pattern', + 'minLength', + 'maxLength' ], ); return Schema( @@ -53,6 +55,8 @@ Schema _$SchemaFromJson(Map json) { discriminator: json['discriminator'] == null ? null : Discriminator.fromJson(json['discriminator'] as Map), pattern: json['pattern'] as String?, + minLength: json['minLength'] as int?, + maxLength: json['maxLength'] as int?, ); } @@ -83,5 +87,7 @@ Map _$SchemaToJson(Schema instance) { writeNotNull('contentSchema', instance.contentSchema?.toJson()); writeNotNull('discriminator', instance.discriminator?.toJson()); writeNotNull('pattern', instance.pattern); + writeNotNull('minLength', instance.minLength); + writeNotNull('maxLength', instance.maxLength); return val; } diff --git a/packages/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/lib/src/openapi_builder.dart index 57e2ff1d..2690f676 100644 --- a/packages/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/lib/src/openapi_builder.dart @@ -582,12 +582,28 @@ class OpenAPIBuilder implements Builder { parameter.schema!, ); - if (result.name == 'String' && parameter.schema?.pattern != null) { - code.write(''' - if (!RegExp(r'${parameter.schema!.pattern!}').hasMatch(${_toDartName(parameter.name)})) { - throw Exception('Invalid value "\$${_toDartName(parameter.name)}" for parameter "${_toDartName(parameter.name)}" with pattern "' r'${parameter.schema!.pattern!}"'); // coverage:ignore-line + if (result.name == 'String') { + if (parameter.schema?.pattern != null) { + code.write(''' + if (!RegExp(r'${parameter.schema!.pattern!}').hasMatch(${_toDartName(parameter.name)})) { + throw Exception('Invalid value "\$${_toDartName(parameter.name)}" for parameter "${_toDartName(parameter.name)}" with pattern "' r'${parameter.schema!.pattern!}"'); // coverage:ignore-line + } + '''); + } + if (parameter.schema?.minLength != null) { + code.write(''' + if (${_toDartName(parameter.name)}.length < ${parameter.schema!.minLength!}) { + throw Exception('Parameter "${_toDartName(parameter.name)}" has to be at least ${parameter.schema!.minLength!} characters long'); // coverage:ignore-line + } + '''); + } + if (parameter.schema?.maxLength != null) { + code.write(''' + if (${_toDartName(parameter.name)}.length > ${parameter.schema!.maxLength!}) { + throw Exception('Parameter "${_toDartName(parameter.name)}" has to be at most ${parameter.schema!.maxLength!} characters long'); // coverage:ignore-line + } + '''); } - '''); } final defaultValueCode = parameter.schema?.default_ != null From 9a28723be4fc301ac68001541375bdf3d3a5f08b Mon Sep 17 00:00:00 2001 From: jld3103 Date: Thu, 16 Feb 2023 16:48:58 +0100 Subject: [PATCH 4/4] dynamite: Fix allOf's --- .../dynamite/lib/src/openapi_builder.dart | 83 ++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/packages/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/lib/src/openapi_builder.dart index 2690f676..a2185a2b 100644 --- a/packages/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/lib/src/openapi_builder.dart @@ -1367,7 +1367,7 @@ TypeResult resolveType( final s = schema.ofs![results.indexOf(result)]; b ..name = fields[result.name] - ..type = refer(_makeNullable(result.name, true)) + ..type = refer(_makeNullable(result.name, !(schema.allOf?.contains(s) ?? false))) ..modifier = FieldModifier.final$ ..docs.addAll(_descriptionToDocs(s.description)); }, @@ -1390,7 +1390,8 @@ TypeResult resolveType( (final b) => b ..name = fields[result.name]! ..toThis = true - ..named = true, + ..named = true + ..required = schema.allOf != null, ), ], ]), @@ -1409,48 +1410,54 @@ TypeResult resolveType( ) ..body = Code( [ - for (final result in results) ...[ - '${result.name}? ${fields[result.name]!};', - ], - for (final result in results) ...[ - if (schema.discriminator != null) ...[ - "if (data['${schema.discriminator!.propertyName}'] == '${result.name.replaceFirst(state.prefix, '')}'", - if (schema.discriminator!.mapping != null && - schema.discriminator!.mapping!.isNotEmpty) ...[ - for (final key in schema.discriminator!.mapping!.entries - .where( - (final entry) => - entry.value.endsWith('/${result.name.replaceFirst(state.prefix, '')}'), - ) - .map((final entry) => entry.key)) ...[ - " || data['${schema.discriminator!.propertyName}'] == '$key'", + if (schema.allOf != null) ...[ + 'return ${state.prefix}$identifier(', + 'data,', + for (final result in results) ...[ + '${fields[result.name]!}: ${result.deserialize('data')},', + ], + ');', + ] else ...[ + for (final result in results) ...[ + '${result.name}? ${fields[result.name]!};', + ], + for (final result in results) ...[ + if (schema.discriminator != null) ...[ + "if (data['${schema.discriminator!.propertyName}'] == '${result.name.replaceFirst(state.prefix, '')}'", + if (schema.discriminator!.mapping != null && + schema.discriminator!.mapping!.isNotEmpty) ...[ + for (final key in schema.discriminator!.mapping!.entries + .where( + (final entry) => + entry.value.endsWith('/${result.name.replaceFirst(state.prefix, '')}'), + ) + .map((final entry) => entry.key)) ...[ + " || data['${schema.discriminator!.propertyName}'] == '$key'", + ], ], + ') {', + ], + 'try {', + '${fields[result.name]!} = ${result.deserialize('data')};', + '} catch (_) {', + if (schema.discriminator != null) ...[ + 'rethrow;', + ], + '}', + if (schema.discriminator != null) ...[ + '}', ], - ') {', ], - 'try {', - '${fields[result.name]!} = ${result.deserialize('data')};', - '} catch (_) {', - if (schema.discriminator != null) ...[ - 'rethrow;', + if (schema.oneOf != null) ...[ + "assert([${fields.values.join(',')}].where((final x) => x != null).length == 1, 'Need oneOf for \$data');", ], - '}', - if (schema.discriminator != null) ...[ - '}', + 'return ${state.prefix}$identifier(', + 'data,', + for (final result in results) ...[ + '${fields[result.name]!}: ${fields[result.name]!},', ], + ');', ], - if (schema.oneOf != null) ...[ - "assert([${fields.values.join(',')}].where((final x) => x != null).length == 1, 'Need oneOf for \$data');", - ], - if (schema.allOf != null) ...[ - "assert([${fields.values.join(',')}].where((final x) => x != null).length == ${fields.length}, 'Need allOf for \$data');", - ], - 'return ${state.prefix}$identifier(', - 'data,', - for (final result in results) ...[ - '${fields[result.name]!}: ${fields[result.name]!},', - ], - ');', ].join(), ); },