From b7a350e6a759cbcb0cdb35e0bf0c24da52940d78 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 5 May 2023 16:15:32 +0200 Subject: [PATCH] dynamite: support nullability in TypeResult Signed-off-by: Nikolas Rimikis --- .../dynamite/lib/src/openapi_builder.dart | 108 ++++++++++++------ .../dynamite/lib/src/type_result/base.dart | 10 +- .../dynamite/lib/src/type_result/enum.dart | 5 +- .../dynamite/lib/src/type_result/list.dart | 5 +- .../dynamite/lib/src/type_result/map.dart | 5 +- .../dynamite/lib/src/type_result/object.dart | 5 +- .../lib/src/type_result/type_result.dart | 5 + 7 files changed, 95 insertions(+), 48 deletions(-) diff --git a/packages/dynamite/dynamite/lib/src/openapi_builder.dart b/packages/dynamite/dynamite/lib/src/openapi_builder.dart index 0240fa04..8f450670 100644 --- a/packages/dynamite/dynamite/lib/src/openapi_builder.dart +++ b/packages/dynamite/dynamite/lib/src/openapi_builder.dart @@ -725,6 +725,7 @@ class OpenAPIBuilder implements Builder { uppercaseFirstCharacter: true, ), parameter.schema!, + nullable: dartParameterNullable, ); state.resolvedTypeCombinations.add(result); @@ -765,12 +766,7 @@ class OpenAPIBuilder implements Builder { ..name = _toDartName(parameter.name) ..required = dartParameterRequired; if (parameter.schema != null) { - b.type = refer( - _makeNullable( - result.name, - dartParameterNullable, - ), - ); + b.type = refer(result.nullableName); } if (defaultValueCode != null) { b.defaultTo = Code(defaultValueCode); @@ -827,21 +823,23 @@ class OpenAPIBuilder implements Builder { code.write("headers['Content-Type'] = '$mimeType';"); + final dartParameterNullable = _isDartParameterNullable( + operation.requestBody!.required, + mediaType.schema?.nullable, + mediaType.schema?.default_, + ); + final result = resolveType( spec, state, _toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true), mediaType.schema!, + nullable: dartParameterNullable, ); final parameterName = _toDartName(result.name.replaceFirst(prefix, '')); switch (mimeType) { case 'application/json': case 'application/x-www-form-urlencoded': - final dartParameterNullable = _isDartParameterNullable( - operation.requestBody!.required, - mediaType.schema?.nullable, - mediaType.schema?.default_, - ); final dartParameterRequired = _isDartParameterRequired( operation.requestBody!.required, mediaType.schema?.default_, @@ -850,7 +848,7 @@ class OpenAPIBuilder implements Builder { Parameter( (final b) => b ..name = parameterName - ..type = refer(_makeNullable(result.name, dartParameterNullable)) + ..type = refer(result.nullableName) ..named = true ..required = dartParameterRequired, ), @@ -1159,8 +1157,6 @@ final _dartKeywords = [ bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); -String _makeNullable(final String type, final bool nullable) => nullable && type != 'dynamic' ? '$type?' : type; - String _toFieldName(final String dartName, final String type) => dartName == type ? '\$$dartName' : dartName; bool _isDartParameterNullable( @@ -1241,8 +1237,9 @@ TypeResult resolveObject( final OpenAPI spec, final State state, final String identifier, - final Schema schema, -) { + final Schema schema, { + final bool nullable = false, +}) { if (!state.resolvedTypes.contains('${state.prefix}$identifier')) { state.resolvedTypes.add('${state.prefix}$identifier'); state.registeredJsonObjects.add('${state.prefix}$identifier'); @@ -1310,20 +1307,16 @@ TypeResult resolveObject( state, '${identifier}_${_toDartName(propertyName, uppercaseFirstCharacter: true)}', propertySchema, + nullable: _isDartParameterNullable( + schema.required?.contains(propertyName), + propertySchema.nullable, + propertySchema.default_, + ), ); b ..name = _toDartName(propertyName) - ..returns = refer( - _makeNullable( - result.name, - _isDartParameterNullable( - schema.required?.contains(propertyName), - propertySchema.nullable, - propertySchema.default_, - ), - ), - ) + ..returns = refer(result.nullableName) ..type = MethodType.getter ..docs.addAll(_descriptionToDocs(propertySchema.description)); @@ -1398,7 +1391,10 @@ TypeResult resolveObject( ), ); } - return TypeResultObject('${state.prefix}$identifier'); + return TypeResultObject( + '${state.prefix}$identifier', + nullable: nullable, + ); } TypeResult resolveType( @@ -1407,10 +1403,14 @@ TypeResult resolveType( final String identifier, final Schema schema, { final bool ignoreEnum = false, + final bool nullable = false, }) { TypeResult? result; if (schema.ref == null && schema.ofs == null && schema.type == null) { - return TypeResultBase('JsonObject'); + return TypeResultBase( + 'JsonObject', + nullable: nullable, + ); } if (schema.ref != null) { final name = schema.ref!.split('/').last; @@ -1419,6 +1419,7 @@ TypeResult resolveType( state, name, spec.components!.schemas![name]!, + nullable: nullable, ); } else if (schema.ofs != null) { if (!state.resolvedTypes.contains('${state.prefix}$identifier')) { @@ -1430,6 +1431,7 @@ TypeResult resolveType( state, '$identifier${schema.ofs!.indexOf(s)}', s, + nullable: !(schema.allOf?.contains(s) ?? false), ), ) .toList(); @@ -1486,7 +1488,7 @@ TypeResult resolveType( final s = schema.ofs![results.indexOf(result)]; b ..name = fields[result.name] - ..returns = refer(_makeNullable(result.name, !(schema.allOf?.contains(s) ?? false))) + ..returns = refer(result.nullableName) ..type = MethodType.getter ..docs.addAll(_descriptionToDocs(s.description)); }, @@ -1673,7 +1675,10 @@ TypeResult resolveType( ]); } - result = TypeResultObject('${state.prefix}$identifier'); + result = TypeResultObject( + '${state.prefix}$identifier', + nullable: nullable, + ); } else if (schema.isContentString) { final subResult = resolveType( spec, @@ -1682,26 +1687,45 @@ TypeResult resolveType( schema.contentSchema!, ); - result = TypeResultObject('ContentString', generics: [subResult]); + result = TypeResultObject( + 'ContentString', + generics: [subResult], + nullable: nullable, + ); } else { switch (schema.type) { case 'boolean': - result = TypeResultBase('bool'); + result = TypeResultBase( + 'bool', + nullable: nullable, + ); break; case 'integer': - result = TypeResultBase('int'); + result = TypeResultBase( + 'int', + nullable: nullable, + ); break; case 'number': - result = TypeResultBase('num'); + result = TypeResultBase( + 'num', + nullable: nullable, + ); break; case 'string': switch (schema.format) { case 'binary': - result = TypeResultBase('Uint8List'); + result = TypeResultBase( + 'Uint8List', + nullable: nullable, + ); break; } - result = TypeResultBase('String'); + result = TypeResultBase( + 'String', + nullable: nullable, + ); break; case 'array': if (schema.items != null) { @@ -1714,11 +1738,13 @@ TypeResult resolveType( result = TypeResultList( 'BuiltList', subResult, + nullable: nullable, ); } else { result = TypeResultList( 'BuiltList', TypeResultBase('JsonObject'), + nullable: nullable, ); } break; @@ -1729,6 +1755,7 @@ TypeResult resolveType( result = TypeResultMap( 'BuiltMap', TypeResultBase('JsonObject'), + nullable: nullable, ); } else { final subResult = resolveType( @@ -1740,17 +1767,22 @@ TypeResult resolveType( result = TypeResultMap( 'BuiltMap', subResult, + nullable: nullable, ); } break; } - result = TypeResultBase('JsonObject'); + result = TypeResultBase( + 'JsonObject', + nullable: nullable, + ); break; } if (schema.properties!.isEmpty) { result = TypeResultMap( 'BuiltMap', TypeResultBase('JsonObject'), + nullable: nullable, ); break; } @@ -1760,6 +1792,7 @@ TypeResult resolveType( state, identifier, schema, + nullable: nullable, ); break; } @@ -1867,6 +1900,7 @@ TypeResult resolveType( result = TypeResultEnum( '${state.prefix}$identifier', result, + nullable: nullable, ); } diff --git a/packages/dynamite/dynamite/lib/src/type_result/base.dart b/packages/dynamite/dynamite/lib/src/type_result/base.dart index 941659ef..a0b4f297 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/base.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/base.dart @@ -1,7 +1,10 @@ part of '../../dynamite.dart'; class TypeResultBase extends TypeResult { - TypeResultBase(super.name); + TypeResultBase( + super.name, { + super.nullable, + }); @override String? get _builderFactory => null; @@ -20,9 +23,10 @@ class TypeResultBase extends TypeResult { @override String deserialize(final String object, {final bool toBuilder = false}) { if (name == 'JsonObject') { - return 'JsonObject($object)'; + return 'JsonObject($object)${nullable ? '?' : ''}'; } - return '($object as $name)'; + + return '($object as $name ${nullable ? '?' : ''})'; } @override diff --git a/packages/dynamite/dynamite/lib/src/type_result/enum.dart b/packages/dynamite/dynamite/lib/src/type_result/enum.dart index 735f8feb..742b8217 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/enum.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/enum.dart @@ -3,8 +3,9 @@ part of '../../dynamite.dart'; class TypeResultEnum extends TypeResult { TypeResultEnum( super.name, - this.subType, - ); + this.subType, { + super.nullable, + }); final TypeResult subType; diff --git a/packages/dynamite/dynamite/lib/src/type_result/list.dart b/packages/dynamite/dynamite/lib/src/type_result/list.dart index 6e4d24cd..5d31f9ea 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/list.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/list.dart @@ -3,8 +3,9 @@ part of '../../dynamite.dart'; class TypeResultList extends TypeResult { TypeResultList( super.name, - final TypeResult subType, - ) : super(generics: [subType]); + final TypeResult subType, { + super.nullable, + }) : super(generics: [subType]); TypeResult get subType => generics.first; diff --git a/packages/dynamite/dynamite/lib/src/type_result/map.dart b/packages/dynamite/dynamite/lib/src/type_result/map.dart index c12ea28f..90333a41 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/map.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/map.dart @@ -3,8 +3,9 @@ part of '../../dynamite.dart'; class TypeResultMap extends TypeResult { TypeResultMap( super.name, - final TypeResult subType, - ) : super(generics: [TypeResultBase('String'), subType]); + final TypeResult subType, { + super.nullable, + }) : super(generics: [TypeResultBase('String'), subType]); TypeResult get subType => generics[1]; diff --git a/packages/dynamite/dynamite/lib/src/type_result/object.dart b/packages/dynamite/dynamite/lib/src/type_result/object.dart index 460da700..85a96823 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/object.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/object.dart @@ -6,6 +6,7 @@ class TypeResultObject extends TypeResult { TypeResultObject( super.className, { super.generics, + super.nullable, }) : assert( className != 'JsonObject' && className != 'Object' && className != 'dynamic', 'Use TypeResultBase instead', @@ -47,10 +48,10 @@ class TypeResultObject extends TypeResult { @override String deserialize(final String object, {final bool toBuilder = false}) { if (className == 'ContentString') { - return 'jsonSerializers.deserialize(messages, specifiedType: const $fullType)! as $name'; + return 'jsonSerializers.deserialize(messages, specifiedType: const $fullType)! as $name${nullable ? '?' : ''}'; } - return '$name.fromJson($object as Object)${toBuilder ? '.toBuilder()' : ''}'; + return '$name.fromJson($object as Object${nullable ? '?' : ''})${nullable && toBuilder ? '?' : ''}${toBuilder ? '.toBuilder()' : ''}'; } @override diff --git a/packages/dynamite/dynamite/lib/src/type_result/type_result.dart b/packages/dynamite/dynamite/lib/src/type_result/type_result.dart index 657abba4..2807c02a 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/type_result.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/type_result.dart @@ -4,11 +4,14 @@ abstract class TypeResult { TypeResult( this.className, { this.generics = const [], + this.nullable = false, }) : assert(!className.contains('<'), 'Specifiy generics in the generics parameter.'), assert(!className.contains('?'), 'Nullability should not be specified in the type.'); final String className; final List generics; + final bool nullable; + String get name { if (generics.isNotEmpty) { final buffer = StringBuffer('$className<') @@ -54,4 +57,6 @@ abstract class TypeResult { final bool onlyChildren = false, final String? mimeType, }); + + String get nullableName => nullable ? '$name?' : name; }