Nikolas Rimikis
1 year ago
committed by
GitHub
38 changed files with 2156 additions and 1892 deletions
@ -1,16 +1,4 @@
|
||||
// ignore: unnecessary_library_directive |
||||
library dynamite; |
||||
|
||||
import 'dart:convert'; |
||||
|
||||
import 'package:build/build.dart'; |
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:collection/collection.dart'; |
||||
import 'package:dart_style/dart_style.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/parameter.dart' as spec_parameter; |
||||
import 'package:dynamite/src/models/path_item.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
import 'package:path/path.dart' as p; |
||||
|
||||
part 'src/openapi_builder.dart'; |
||||
export 'src/openapi_builder.dart'; |
||||
|
@ -0,0 +1,653 @@
|
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:collection/collection.dart'; |
||||
import 'package:dynamite/src/builder/resolve_object.dart'; |
||||
import 'package:dynamite/src/builder/resolve_type.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/helpers/dynamite.dart'; |
||||
import 'package:dynamite/src/helpers/type_result.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/path_item.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
List<Class> generateDynamiteOverrides(final State state) => [ |
||||
Class( |
||||
(final b) => b |
||||
..name = '${state.classPrefix}Response' |
||||
..types.addAll([ |
||||
refer('T'), |
||||
refer('U'), |
||||
]) |
||||
..extend = refer('DynamiteResponse<T, U>') |
||||
..constructors.add( |
||||
Constructor( |
||||
(final b) => b |
||||
..requiredParameters.addAll( |
||||
['data', 'headers'].map( |
||||
(final name) => Parameter( |
||||
(final b) => b |
||||
..name = name |
||||
..toSuper = true, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
) |
||||
..methods.add( |
||||
Method( |
||||
(final b) => b |
||||
..name = 'toString' |
||||
..returns = refer('String') |
||||
..annotations.add(refer('override')) |
||||
..lambda = true |
||||
..body = Code( |
||||
"'${state.classPrefix}Response(data: \$data, headers: \$headers)'", |
||||
), |
||||
), |
||||
), |
||||
), |
||||
Class( |
||||
(final b) => b |
||||
..name = '${state.classPrefix}ApiException' |
||||
..extend = refer('DynamiteApiException') |
||||
..constructors.add( |
||||
Constructor( |
||||
(final b) => b |
||||
..requiredParameters.addAll( |
||||
['statusCode', 'headers', 'body'].map( |
||||
(final name) => Parameter( |
||||
(final b) => b |
||||
..name = name |
||||
..toSuper = true, |
||||
), |
||||
), |
||||
), |
||||
), |
||||
) |
||||
..methods.addAll([ |
||||
Method( |
||||
(final b) => b |
||||
..name = 'fromResponse' |
||||
..returns = refer('Future<${state.classPrefix}ApiException>') |
||||
..static = true |
||||
..modifier = MethodModifier.async |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'response' |
||||
..type = refer('HttpClientResponse'), |
||||
), |
||||
) |
||||
..body = Code(''' |
||||
String body; |
||||
try { |
||||
body = await response.body; |
||||
} on FormatException { |
||||
body = 'binary'; |
||||
} |
||||
|
||||
return ${state.classPrefix}ApiException( |
||||
response.statusCode, |
||||
response.responseHeaders, |
||||
body, |
||||
); |
||||
'''), |
||||
), |
||||
Method( |
||||
(final b) => b |
||||
..name = 'toString' |
||||
..returns = refer('String') |
||||
..annotations.add(refer('override')) |
||||
..lambda = true |
||||
..body = Code( |
||||
"'${state.classPrefix}ApiException(statusCode: \$statusCode, headers: \$headers, body: \$body)'", |
||||
), |
||||
), |
||||
]), |
||||
), |
||||
]; |
||||
|
||||
Iterable<Class> generateClients( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
) sync* { |
||||
yield* generateDynamiteOverrides(state); |
||||
|
||||
final tags = generateTags(spec); |
||||
yield buildRootClient(spec, state, tags); |
||||
|
||||
for (final tag in tags) { |
||||
yield buildClient(spec, state, tags, tag); |
||||
} |
||||
} |
||||
|
||||
Class buildRootClient( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final List<String> tags, |
||||
) => |
||||
Class( |
||||
(final b) { |
||||
b |
||||
..extend = refer('DynamiteClient') |
||||
..name = '${state.classPrefix}Client' |
||||
..docs.addAll(spec.formattedTagsFor(null)) |
||||
..constructors.addAll([ |
||||
Constructor( |
||||
(final b) => b |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'baseURL' |
||||
..toSuper = true, |
||||
), |
||||
) |
||||
..optionalParameters.addAll([ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'baseHeaders' |
||||
..toSuper = true |
||||
..named = true, |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'userAgent' |
||||
..toSuper = true |
||||
..named = true, |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'httpClient' |
||||
..toSuper = true |
||||
..named = true, |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'cookieJar' |
||||
..toSuper = true |
||||
..named = true, |
||||
), |
||||
if (spec.hasAnySecurity) ...[ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'authentications' |
||||
..toSuper = true |
||||
..named = true, |
||||
), |
||||
], |
||||
]), |
||||
), |
||||
Constructor( |
||||
(final b) => b |
||||
..name = 'fromClient' |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'client' |
||||
..type = refer('DynamiteClient'), |
||||
), |
||||
) |
||||
..initializers.add( |
||||
const Code(''' |
||||
super( |
||||
client.baseURL, |
||||
baseHeaders: client.baseHeaders, |
||||
httpClient: client.httpClient, |
||||
cookieJar: client.cookieJar, |
||||
authentications: client.authentications, |
||||
) |
||||
'''), |
||||
), |
||||
), |
||||
]); |
||||
|
||||
for (final tag in tags.where((final t) => !t.contains('/'))) { |
||||
final client = '${state.classPrefix}${clientName(tag)}'; |
||||
|
||||
b.methods.add( |
||||
Method( |
||||
(final b) => b |
||||
..name = toDartName(tag) |
||||
..lambda = true |
||||
..type = MethodType.getter |
||||
..returns = refer(client) |
||||
..body = Code('$client(this)'), |
||||
), |
||||
); |
||||
} |
||||
|
||||
b.methods.addAll(buildTags(spec, state, tags, null)); |
||||
}, |
||||
); |
||||
|
||||
Class buildClient( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final List<String> tags, |
||||
final String tag, |
||||
) => |
||||
Class( |
||||
(final b) { |
||||
b |
||||
..name = '${state.classPrefix}${clientName(tag)}' |
||||
..docs.addAll(spec.formattedTagsFor(tag)) |
||||
..constructors.add( |
||||
Constructor( |
||||
(final b) => b.requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = '_rootClient' |
||||
..toThis = true, |
||||
), |
||||
), |
||||
), |
||||
) |
||||
..fields.add( |
||||
Field( |
||||
(final b) => b |
||||
..name = '_rootClient' |
||||
..type = refer('${state.classPrefix}Client') |
||||
..modifier = FieldModifier.final$, |
||||
), |
||||
); |
||||
|
||||
for (final t in tags.where((final t) => t.startsWith('$tag/'))) { |
||||
b.methods.add( |
||||
Method( |
||||
(final b) => b |
||||
..name = toDartName(t.substring('$tag/'.length)) |
||||
..lambda = true |
||||
..type = MethodType.getter |
||||
..returns = refer('${state.classPrefix}${clientName(t)}') |
||||
..body = Code('${state.classPrefix}${clientName(t)}(_rootClient)'), |
||||
), |
||||
); |
||||
} |
||||
|
||||
b.methods.addAll(buildTags(spec, state, tags, tag)); |
||||
}, |
||||
); |
||||
|
||||
Iterable<Method> buildTags( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final List<String> tags, |
||||
final String? tag, |
||||
) sync* { |
||||
final isRootClient = tag == null; |
||||
final paths = generatePaths(spec, tag); |
||||
|
||||
for (final pathEntry in paths.entries) { |
||||
for (final operationEntry in pathEntry.value.operations.entries) { |
||||
yield Method( |
||||
(final b) { |
||||
final httpMethod = operationEntry.key; |
||||
final operation = operationEntry.value; |
||||
final operationId = operation.operationId ?? toDartName('$httpMethod-${pathEntry.key}'); |
||||
final parameters = [ |
||||
...?pathEntry.value.parameters, |
||||
...?operation.parameters, |
||||
]..sort(sortRequiredParameters); |
||||
b |
||||
..name = toDartName(filterMethodName(operationId, tag ?? '')) |
||||
..modifier = MethodModifier.async |
||||
..docs.addAll(operation.formattedDescription); |
||||
if (operation.deprecated ?? false) { |
||||
b.annotations.add(refer('Deprecated').call([refer("''")])); |
||||
} |
||||
|
||||
final acceptHeader = operation.responses?.values |
||||
.map((final response) => response.content?.keys) |
||||
.whereNotNull() |
||||
.expand((final element) => element) |
||||
.toSet() |
||||
.join(',') ?? |
||||
''; |
||||
final code = StringBuffer(''' |
||||
var _path = '${pathEntry.key}'; |
||||
final _queryParameters = <String, dynamic>{}; |
||||
final _headers = <String, String>{${acceptHeader.isNotEmpty ? "'Accept': '$acceptHeader'," : ''}}; |
||||
Uint8List? _body; |
||||
'''); |
||||
|
||||
final security = operation.security ?? spec.security ?? []; |
||||
final securityRequirements = security.where((final requirement) => requirement.isNotEmpty); |
||||
final isOptionalSecurity = securityRequirements.length != security.length; |
||||
code.write(' // coverage:ignore-start\n'); |
||||
for (final requirement in securityRequirements) { |
||||
final securityScheme = spec.components!.securitySchemes![requirement.keys.single]!; |
||||
code.write(''' |
||||
if (${isRootClient ? 'this' : '_rootClient'}.authentications.where((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').isNotEmpty) { |
||||
_headers.addAll(${isRootClient ? 'this' : '_rootClient'}.authentications.singleWhere((final a) => a.type == '${securityScheme.type}' && a.scheme == '${securityScheme.scheme}').headers); |
||||
} |
||||
'''); |
||||
if (securityRequirements.last != requirement) { |
||||
code.write('else '); |
||||
} |
||||
} |
||||
if (securityRequirements.isNotEmpty && !isOptionalSecurity) { |
||||
code.write(''' |
||||
else { |
||||
throw Exception('Missing authentication for ${securityRequirements.map((final r) => r.keys.single).join(' or ')}'); |
||||
} |
||||
'''); |
||||
} |
||||
code.write(' // coverage:ignore-end\n'); |
||||
|
||||
for (final parameter in parameters) { |
||||
final dartParameterNullable = isDartParameterNullable( |
||||
parameter.required, |
||||
parameter.schema, |
||||
); |
||||
|
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
toDartName( |
||||
'$operationId-${parameter.name}', |
||||
uppercaseFirstCharacter: true, |
||||
), |
||||
parameter.schema!, |
||||
nullable: dartParameterNullable, |
||||
).dartType; |
||||
|
||||
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 |
||||
? valueToEscapedValue(result, parameter.schema!.default_.toString()) |
||||
: null; |
||||
|
||||
b.optionalParameters.add( |
||||
Parameter( |
||||
(final b) { |
||||
b |
||||
..named = true |
||||
..name = toDartName(parameter.name) |
||||
..required = parameter.isDartRequired; |
||||
if (parameter.schema != null) { |
||||
b.type = refer(result.nullableName); |
||||
} |
||||
if (defaultValueCode != null) { |
||||
b.defaultTo = Code(defaultValueCode); |
||||
} |
||||
}, |
||||
), |
||||
); |
||||
|
||||
if (dartParameterNullable) { |
||||
code.write('if (${toDartName(parameter.name)} != null) {'); |
||||
} |
||||
final value = result.encode( |
||||
toDartName(parameter.name), |
||||
onlyChildren: result is TypeResultList && parameter.in_ == 'query', |
||||
); |
||||
if (defaultValueCode != null && parameter.in_ == 'query') { |
||||
code.write('if (${toDartName(parameter.name)} != $defaultValueCode) {'); |
||||
} |
||||
switch (parameter.in_) { |
||||
case 'path': |
||||
code.write( |
||||
"_path = _path.replaceAll('{${parameter.name}}', Uri.encodeQueryComponent($value));", |
||||
); |
||||
case 'query': |
||||
code.write( |
||||
"_queryParameters['${parameter.name}'] = $value;", |
||||
); |
||||
case 'header': |
||||
code.write( |
||||
"_headers['${parameter.name}'] = $value;", |
||||
); |
||||
default: |
||||
throw Exception('Can not work with parameter in "${parameter.in_}"'); |
||||
} |
||||
if (defaultValueCode != null && parameter.in_ == 'query') { |
||||
code.write('}'); |
||||
} |
||||
if (dartParameterNullable) { |
||||
code.write('}'); |
||||
} |
||||
} |
||||
|
||||
if (operation.requestBody != null) { |
||||
if (operation.requestBody!.content!.length > 1) { |
||||
throw Exception('Can not work with multiple mime types right now'); |
||||
} |
||||
for (final content in operation.requestBody!.content!.entries) { |
||||
final mimeType = content.key; |
||||
final mediaType = content.value; |
||||
|
||||
code.write("_headers['Content-Type'] = '$mimeType';"); |
||||
|
||||
final dartParameterNullable = isDartParameterNullable( |
||||
operation.requestBody!.required, |
||||
mediaType.schema, |
||||
); |
||||
|
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
toDartName('$operationId-request-$mimeType', uppercaseFirstCharacter: true), |
||||
mediaType.schema!, |
||||
nullable: dartParameterNullable, |
||||
); |
||||
final parameterName = toDartName(result.name.replaceFirst(state.classPrefix, '')); |
||||
switch (mimeType) { |
||||
case 'application/json': |
||||
case 'application/x-www-form-urlencoded': |
||||
final dartParameterRequired = isRequired( |
||||
operation.requestBody!.required, |
||||
mediaType.schema?.default_, |
||||
); |
||||
b.optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = parameterName |
||||
..type = refer(result.nullableName) |
||||
..named = true |
||||
..required = dartParameterRequired, |
||||
), |
||||
); |
||||
|
||||
if (dartParameterNullable) { |
||||
code.write('if ($parameterName != null) {'); |
||||
} |
||||
code.write( |
||||
'_body = utf8.encode(${result.encode(parameterName, mimeType: mimeType)}) as Uint8List;', |
||||
); |
||||
if (dartParameterNullable) { |
||||
code.write('}'); |
||||
} |
||||
default: |
||||
throw Exception('Can not parse mime type "$mimeType"'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
code.write( |
||||
''' |
||||
final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( |
||||
'$httpMethod', |
||||
Uri(path: _path, queryParameters: _queryParameters.isNotEmpty ? _queryParameters : null), |
||||
_headers, |
||||
_body, |
||||
); |
||||
''', |
||||
); |
||||
|
||||
if (operation.responses != null) { |
||||
if (operation.responses!.length > 1) { |
||||
throw Exception('Can not work with multiple status codes right now'); |
||||
} |
||||
for (final responseEntry in operation.responses!.entries) { |
||||
final statusCode = responseEntry.key; |
||||
final response = responseEntry.value; |
||||
code.write('if (_response.statusCode == $statusCode) {'); |
||||
|
||||
String? headersType; |
||||
String? headersValue; |
||||
if (response.headers != null) { |
||||
final identifier = |
||||
'${tag != null ? toDartName(tag, uppercaseFirstCharacter: true) : null}${toDartName(operationId, uppercaseFirstCharacter: true)}Headers'; |
||||
final result = resolveObject( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
Schema( |
||||
properties: response.headers!.map( |
||||
(final headerName, final value) => MapEntry( |
||||
headerName.toLowerCase(), |
||||
value.schema!, |
||||
), |
||||
), |
||||
), |
||||
isHeader: true, |
||||
); |
||||
headersType = result.name; |
||||
headersValue = result.deserialize('_response.responseHeaders'); |
||||
} |
||||
|
||||
String? dataType; |
||||
String? dataValue; |
||||
bool? dataNeedsAwait; |
||||
if (response.content != null) { |
||||
if (response.content!.length > 1) { |
||||
throw Exception('Can not work with multiple mime types right now'); |
||||
} |
||||
for (final content in response.content!.entries) { |
||||
final mimeType = content.key; |
||||
final mediaType = content.value; |
||||
|
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
toDartName( |
||||
'$operationId-response-$statusCode-$mimeType', |
||||
uppercaseFirstCharacter: true, |
||||
), |
||||
mediaType.schema!, |
||||
); |
||||
|
||||
if (mimeType == '*/*' || mimeType == 'application/octet-stream' || mimeType.startsWith('image/')) { |
||||
dataType = 'Uint8List'; |
||||
dataValue = '_response.bodyBytes'; |
||||
dataNeedsAwait = true; |
||||
} else if (mimeType.startsWith('text/') || mimeType == 'application/javascript') { |
||||
dataType = 'String'; |
||||
dataValue = '_response.body'; |
||||
dataNeedsAwait = true; |
||||
} else if (mimeType == 'application/json') { |
||||
dataType = result.name; |
||||
if (result.name == 'dynamic') { |
||||
dataValue = ''; |
||||
} else if (result.name == 'String') { |
||||
dataValue = '_response.body'; |
||||
dataNeedsAwait = true; |
||||
} else if (result is TypeResultEnum || result is TypeResultBase) { |
||||
dataValue = result.deserialize(result.decode('await _response.body')); |
||||
dataNeedsAwait = false; |
||||
} else { |
||||
dataValue = result.deserialize('await _response.jsonBody'); |
||||
dataNeedsAwait = false; |
||||
} |
||||
} else { |
||||
throw Exception('Can not parse mime type "$mimeType"'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (headersType != null && dataType != null) { |
||||
b.returns = refer('Future<${state.classPrefix}Response<$dataType, $headersType>>'); |
||||
code.write( |
||||
'return ${state.classPrefix}Response<$dataType, $headersType>(${dataNeedsAwait ?? false ? 'await ' : ''}$dataValue, $headersValue,);', |
||||
); |
||||
} else if (headersType != null) { |
||||
b.returns = refer('Future<$headersType>'); |
||||
code.write('return $headersValue;'); |
||||
} else if (dataType != null) { |
||||
b.returns = refer('Future<$dataType>'); |
||||
code.write('return $dataValue;'); |
||||
} else { |
||||
b.returns = refer('Future'); |
||||
code.write('return;'); |
||||
} |
||||
|
||||
code.write('}'); |
||||
} |
||||
code.write( |
||||
'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', |
||||
); |
||||
} else { |
||||
b.returns = refer('Future'); |
||||
} |
||||
b.body = Code(code.toString()); |
||||
}, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Map<String, PathItem> generatePaths(final OpenAPI spec, final String? tag) { |
||||
final paths = <String, PathItem>{}; |
||||
|
||||
if (spec.paths != null) { |
||||
for (final path in spec.paths!.entries) { |
||||
for (final operationEntry in path.value.operations.entries) { |
||||
final operation = operationEntry.value; |
||||
if ((operation.tags != null && operation.tags!.contains(tag)) || |
||||
(tag == null && (operation.tags == null || operation.tags!.isEmpty))) { |
||||
paths[path.key] ??= PathItem( |
||||
description: path.value.description, |
||||
parameters: path.value.parameters, |
||||
); |
||||
paths[path.key] = paths[path.key]!.copyWithOperations({operationEntry.key: operation}); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return paths; |
||||
} |
||||
|
||||
List<String> generateTags(final OpenAPI spec) { |
||||
final tags = <String>[]; |
||||
|
||||
if (spec.paths != null) { |
||||
for (final pathItem in spec.paths!.values) { |
||||
for (final operation in pathItem.operations.values) { |
||||
if (operation.tags != null) { |
||||
for (final tag in operation.tags!) { |
||||
final tagPart = tag.split('/').first; |
||||
if (!tags.contains(tagPart)) { |
||||
tags.add(tagPart); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return tags..sort((final a, final b) => a.compareTo(b)); |
||||
} |
@ -0,0 +1,135 @@
|
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/builder/resolve_type.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/helpers/dynamite.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
Spec buildHeaderSerializer(final State state, final String identifier, final OpenAPI spec, final Schema schema) => |
||||
Class( |
||||
(final b) => b |
||||
..name = '_\$${state.classPrefix}${identifier}Serializer' |
||||
..implements.add(refer('StructuredSerializer<${state.classPrefix}$identifier>')) |
||||
..fields.addAll([ |
||||
Field( |
||||
(final b) => b |
||||
..name = 'types' |
||||
..modifier = FieldModifier.final$ |
||||
..type = refer('Iterable<Type>') |
||||
..annotations.add(refer('override')) |
||||
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'), |
||||
), |
||||
Field( |
||||
(final b) => b |
||||
..name = 'wireName' |
||||
..modifier = FieldModifier.final$ |
||||
..type = refer('String') |
||||
..annotations.add(refer('override')) |
||||
..assignment = Code("r'${state.classPrefix}$identifier'"), |
||||
), |
||||
]) |
||||
..methods.addAll([ |
||||
Method((final b) { |
||||
b |
||||
..name = 'serialize' |
||||
..returns = refer('Iterable<Object?>') |
||||
..annotations.add(refer('override')) |
||||
..requiredParameters.addAll([ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'serializers' |
||||
..type = refer('Serializers'), |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'object' |
||||
..type = refer('${state.classPrefix}$identifier'), |
||||
), |
||||
]) |
||||
..optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'specifiedType' |
||||
..type = refer('FullType') |
||||
..named = true |
||||
..defaultTo = const Code('FullType.unspecified'), |
||||
), |
||||
) |
||||
..body = const Code('throw UnimplementedError();'); |
||||
}), |
||||
Method((final b) { |
||||
b |
||||
..name = 'deserialize' |
||||
..returns = refer('${state.classPrefix}$identifier') |
||||
..annotations.add(refer('override')) |
||||
..requiredParameters.addAll([ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'serializers' |
||||
..type = refer('Serializers'), |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'serialized' |
||||
..type = refer('Iterable<Object?>'), |
||||
), |
||||
]) |
||||
..optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'specifiedType' |
||||
..type = refer('FullType') |
||||
..named = true |
||||
..defaultTo = const Code('FullType.unspecified'), |
||||
), |
||||
) |
||||
..body = Code(''' |
||||
final result = new ${state.classPrefix}${identifier}Builder(); |
||||
|
||||
final iterator = serialized.iterator; |
||||
while (iterator.moveNext()) { |
||||
final key = iterator.current! as String; |
||||
iterator.moveNext(); |
||||
final value = iterator.current! as String; |
||||
switch (key) { |
||||
${deserializeProperty(state, identifier, spec, schema).join('\n')} |
||||
} |
||||
} |
||||
|
||||
return result.build(); |
||||
'''); |
||||
}), |
||||
]), |
||||
); |
||||
|
||||
Iterable<String> deserializeProperty( |
||||
final State state, |
||||
final String identifier, |
||||
final OpenAPI spec, |
||||
final Schema schema, |
||||
) sync* { |
||||
for (final property in schema.properties!.entries) { |
||||
final propertyName = property.key; |
||||
final propertySchema = property.value; |
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', |
||||
propertySchema, |
||||
nullable: isDartParameterNullable(schema.required?.contains(propertyName), propertySchema), |
||||
); |
||||
|
||||
yield "case '$propertyName':"; |
||||
if (result.className != 'String') { |
||||
if (result is TypeResultBase || result is TypeResultEnum) { |
||||
yield 'result.${toDartName(propertyName)} = ${result.deserialize(result.decode('value!'))};'; |
||||
} else { |
||||
yield 'result.${toDartName(propertyName)}.replace(${result.deserialize(result.decode('value!'))});'; |
||||
} |
||||
} else { |
||||
yield 'result.${toDartName(propertyName)} = value!;'; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,24 @@
|
||||
import 'package:build/build.dart'; |
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:path/path.dart' as p; |
||||
|
||||
List<Spec> generateImports(final AssetId outputId) => [ |
||||
const Code('// ignore_for_file: camel_case_types'), |
||||
const Code('// ignore_for_file: public_member_api_docs'), |
||||
Directive.import('dart:convert'), |
||||
Directive.import('dart:typed_data'), |
||||
const Code(''), |
||||
Directive.import('package:built_collection/built_collection.dart'), |
||||
Directive.import('package:built_value/built_value.dart'), |
||||
Directive.import('package:built_value/json_object.dart'), |
||||
Directive.import('package:built_value/serializer.dart'), |
||||
Directive.import('package:built_value/standard_json_plugin.dart'), |
||||
Directive.import('package:dynamite_runtime/content_string.dart'), |
||||
Directive.import('package:dynamite_runtime/http_client.dart'), |
||||
Directive.import('package:universal_io/io.dart'), |
||||
const Code(''), |
||||
Directive.export('package:dynamite_runtime/http_client.dart'), |
||||
const Code(''), |
||||
Directive.part(p.basename(outputId.changeExtension('.g.dart').path)), |
||||
const Code(''), |
||||
]; |
@ -0,0 +1,229 @@
|
||||
import 'package:built_collection/built_collection.dart'; |
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/builder/resolve_type.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/helpers/built_value.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
TypeResult resolveOfs( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final String identifier, |
||||
final Schema schema, { |
||||
final bool nullable = false, |
||||
}) { |
||||
final result = TypeResultObject( |
||||
'${state.classPrefix}$identifier', |
||||
nullable: nullable, |
||||
); |
||||
|
||||
if (state.resolvedTypes.add(result)) { |
||||
final results = schema.ofs! |
||||
.map( |
||||
(final s) => resolveType( |
||||
spec, |
||||
state, |
||||
'$identifier${schema.ofs!.indexOf(s)}', |
||||
s, |
||||
nullable: !(schema.allOf?.contains(s) ?? false), |
||||
), |
||||
) |
||||
.toList(); |
||||
|
||||
final fields = <String, String>{}; |
||||
for (final result in results) { |
||||
final dartName = toDartName(result.name.replaceFirst(state.classPrefix, '')); |
||||
fields[result.name] = toFieldName(dartName, result.name.replaceFirst(state.classPrefix, '')); |
||||
} |
||||
|
||||
state.output.addAll([ |
||||
buildBuiltClass( |
||||
'${state.classPrefix}$identifier', |
||||
BuiltList.build((final b) { |
||||
b.add( |
||||
Method( |
||||
(final b) { |
||||
b |
||||
..name = 'data' |
||||
..returns = refer('JsonObject') |
||||
..type = MethodType.getter; |
||||
}, |
||||
), |
||||
); |
||||
|
||||
for (final result in results) { |
||||
b.add( |
||||
Method( |
||||
(final b) { |
||||
final s = schema.ofs![results.indexOf(result)]; |
||||
b |
||||
..name = fields[result.name] |
||||
..returns = refer(result.nullableName) |
||||
..type = MethodType.getter |
||||
..docs.addAll(s.formattedDescription); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
}), |
||||
customSerializer: true, |
||||
), |
||||
Class( |
||||
(final b) => b |
||||
..name = '_\$${state.classPrefix}${identifier}Serializer' |
||||
..implements.add(refer('PrimitiveSerializer<${state.classPrefix}$identifier>')) |
||||
..fields.addAll([ |
||||
Field( |
||||
(final b) => b |
||||
..name = 'types' |
||||
..modifier = FieldModifier.final$ |
||||
..type = refer('Iterable<Type>') |
||||
..annotations.add(refer('override')) |
||||
..assignment = Code('const [${state.classPrefix}$identifier, _\$${state.classPrefix}$identifier]'), |
||||
), |
||||
Field( |
||||
(final b) => b |
||||
..name = 'wireName' |
||||
..modifier = FieldModifier.final$ |
||||
..type = refer('String') |
||||
..annotations.add(refer('override')) |
||||
..assignment = Code("r'${state.classPrefix}$identifier'"), |
||||
), |
||||
]) |
||||
..methods.addAll([ |
||||
Method((final b) { |
||||
b |
||||
..name = 'serialize' |
||||
..returns = refer('Object') |
||||
..annotations.add(refer('override')) |
||||
..requiredParameters.addAll([ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'serializers' |
||||
..type = refer('Serializers'), |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'object' |
||||
..type = refer('${state.classPrefix}$identifier'), |
||||
), |
||||
]) |
||||
..optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'specifiedType' |
||||
..type = refer('FullType') |
||||
..named = true |
||||
..defaultTo = const Code('FullType.unspecified'), |
||||
), |
||||
) |
||||
..body = const Code('return object.data.value;'); |
||||
}), |
||||
Method((final b) { |
||||
b |
||||
..name = 'deserialize' |
||||
..returns = refer('${state.classPrefix}$identifier') |
||||
..annotations.add(refer('override')) |
||||
..requiredParameters.addAll([ |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'serializers' |
||||
..type = refer('Serializers'), |
||||
), |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'data' |
||||
..type = refer('Object'), |
||||
), |
||||
]) |
||||
..optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'specifiedType' |
||||
..type = refer('FullType') |
||||
..named = true |
||||
..defaultTo = const Code('FullType.unspecified'), |
||||
), |
||||
) |
||||
..body = Code( |
||||
<String>[ |
||||
'final result = new ${state.classPrefix}${identifier}Builder()', |
||||
'..data = JsonObject(data);', |
||||
if (schema.allOf != null) ...[ |
||||
for (final result in results) ...[ |
||||
if (result is TypeResultBase || result is TypeResultEnum) ...[ |
||||
'result.${fields[result.name]!} = ${result.deserialize('data')};', |
||||
] else ...[ |
||||
'result.${fields[result.name]!}.replace(${result.deserialize('data')});', |
||||
], |
||||
], |
||||
] else ...[ |
||||
if (schema.discriminator != null) ...[ |
||||
'if (data is! Iterable) {', |
||||
r"throw StateError('Expected an Iterable but got ${data.runtimeType}');", |
||||
'}', |
||||
'', |
||||
'String? discriminator;', |
||||
'', |
||||
'final iterator = data.iterator;', |
||||
'while (iterator.moveNext()) {', |
||||
'final key = iterator.current! as String;', |
||||
'iterator.moveNext();', |
||||
'final Object? value = iterator.current;', |
||||
"if (key == '${schema.discriminator!.propertyName}') {", |
||||
'discriminator = value! as String;', |
||||
'break;', |
||||
'}', |
||||
'}', |
||||
], |
||||
for (final result in results) ...[ |
||||
if (schema.discriminator != null) ...[ |
||||
"if (discriminator == '${result.name.replaceFirst(state.classPrefix, '')}'", |
||||
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.classPrefix, '')}'), |
||||
) |
||||
.map((final entry) => entry.key)) ...[ |
||||
" || discriminator == '$key'", |
||||
], |
||||
') {', |
||||
], |
||||
], |
||||
'try {', |
||||
if (result is TypeResultBase || result is TypeResultEnum) ...[ |
||||
'result._${fields[result.name]!} = ${result.deserialize('data')};', |
||||
] else ...[ |
||||
'result._${fields[result.name]!} = ${result.deserialize('data')}.toBuilder();', |
||||
], |
||||
'} catch (_) {', |
||||
if (schema.discriminator != null) ...[ |
||||
'rethrow;', |
||||
], |
||||
'}', |
||||
if (schema.discriminator != null) ...[ |
||||
'}', |
||||
], |
||||
], |
||||
if (schema.oneOf != null) ...[ |
||||
"assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need oneOf for \${result._data}');", |
||||
], |
||||
if (schema.anyOf != null) ...[ |
||||
"assert([${fields.values.map((final e) => 'result._$e').join(',')}].where((final x) => x != null).length >= 1, 'Need anyOf for \${result._data}');", |
||||
], |
||||
], |
||||
'return result.build();', |
||||
].join(), |
||||
); |
||||
}), |
||||
]), |
||||
), |
||||
]); |
||||
} |
||||
|
||||
return result; |
||||
} |
@ -0,0 +1,112 @@
|
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/builder/resolve_type.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/helpers/built_value.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/helpers/type_result.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
TypeResult resolveEnum( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final String identifier, |
||||
final Schema schema, |
||||
final TypeResult subResult, { |
||||
final bool nullable = false, |
||||
}) { |
||||
if (state.resolvedTypes.add(TypeResultEnum('${state.classPrefix}$identifier', subResult))) { |
||||
state.output.add( |
||||
Class( |
||||
(final b) => b |
||||
..name = '${state.classPrefix}$identifier' |
||||
..extend = refer('EnumClass') |
||||
..constructors.add( |
||||
Constructor( |
||||
(final b) => b |
||||
..name = '_' |
||||
..constant = true |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'name' |
||||
..toSuper = true, |
||||
), |
||||
), |
||||
), |
||||
) |
||||
..fields.addAll( |
||||
schema.enum_!.map( |
||||
(final value) => Field( |
||||
(final b) { |
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
'$identifier${toDartName(value.toString(), uppercaseFirstCharacter: true)}', |
||||
schema, |
||||
ignoreEnum: true, |
||||
); |
||||
b |
||||
..name = toDartName(value.toString()) |
||||
..static = true |
||||
..modifier = FieldModifier.constant |
||||
..type = refer('${state.classPrefix}$identifier') |
||||
..assignment = Code( |
||||
'_\$${toCamelCase('${state.classPrefix}$identifier')}${toDartName(value.toString(), uppercaseFirstCharacter: true)}', |
||||
); |
||||
|
||||
if (toDartName(value.toString()) != value.toString()) { |
||||
if (result.name != 'String' && result.name != 'int') { |
||||
throw Exception( |
||||
'Sorry enum values are a bit broken. ' |
||||
'See https://github.com/google/json_serializable.dart/issues/616. ' |
||||
'Please remove the enum values on ${state.classPrefix}$identifier.', |
||||
); |
||||
} |
||||
b.annotations.add( |
||||
refer('BuiltValueEnumConst').call([], { |
||||
'wireName': refer(valueToEscapedValue(result, value.toString())), |
||||
}), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
), |
||||
) |
||||
..methods.addAll([ |
||||
Method( |
||||
(final b) => b |
||||
..name = 'values' |
||||
..returns = refer('BuiltSet<${state.classPrefix}$identifier>') |
||||
..lambda = true |
||||
..static = true |
||||
..body = Code('_\$${toCamelCase('${state.classPrefix}$identifier')}Values') |
||||
..type = MethodType.getter, |
||||
), |
||||
Method( |
||||
(final b) => b |
||||
..name = 'valueOf' |
||||
..returns = refer('${state.classPrefix}$identifier') |
||||
..lambda = true |
||||
..static = true |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'name' |
||||
..type = refer(subResult.name), |
||||
), |
||||
) |
||||
..body = Code('_\$valueOf${state.classPrefix}$identifier(name)'), |
||||
), |
||||
buildSerializer('${state.classPrefix}$identifier'), |
||||
]), |
||||
), |
||||
); |
||||
} |
||||
return TypeResultEnum( |
||||
'${state.classPrefix}$identifier', |
||||
subResult, |
||||
nullable: nullable, |
||||
); |
||||
} |
@ -0,0 +1,90 @@
|
||||
import 'package:built_collection/built_collection.dart'; |
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/builder/header_serializer.dart'; |
||||
import 'package:dynamite/src/builder/resolve_type.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/helpers/built_value.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/helpers/dynamite.dart'; |
||||
import 'package:dynamite/src/helpers/type_result.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
TypeResult resolveObject( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
final String identifier, |
||||
final Schema schema, { |
||||
final bool nullable = false, |
||||
final bool isHeader = false, |
||||
}) { |
||||
final result = TypeResultObject( |
||||
'${state.classPrefix}$identifier', |
||||
nullable: nullable, |
||||
); |
||||
if (state.resolvedTypes.add(result)) { |
||||
final defaults = <String>[]; |
||||
for (final property in schema.properties!.entries) { |
||||
final propertySchema = property.value; |
||||
if (propertySchema.default_ != null) { |
||||
final value = propertySchema.default_!.toString(); |
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
propertySchema.type!, |
||||
propertySchema, |
||||
); |
||||
defaults.add('..${toDartName(property.key)} = ${valueToEscapedValue(result, value)}'); |
||||
} |
||||
} |
||||
|
||||
state.output.add( |
||||
buildBuiltClass( |
||||
'${state.classPrefix}$identifier', |
||||
BuiltList.build((final b) { |
||||
for (final property in schema.properties!.entries) { |
||||
b.add( |
||||
Method( |
||||
(final b) { |
||||
final propertyName = property.key; |
||||
final propertySchema = property.value; |
||||
final result = resolveType( |
||||
spec, |
||||
state, |
||||
'${identifier}_${toDartName(propertyName, uppercaseFirstCharacter: true)}', |
||||
propertySchema, |
||||
nullable: isDartParameterNullable( |
||||
schema.required?.contains(propertyName), |
||||
propertySchema, |
||||
), |
||||
); |
||||
|
||||
b |
||||
..name = toDartName(propertyName) |
||||
..returns = refer(result.nullableName) |
||||
..type = MethodType.getter |
||||
..docs.addAll(propertySchema.formattedDescription); |
||||
|
||||
if (toDartName(propertyName) != propertyName) { |
||||
b.annotations.add( |
||||
refer('BuiltValueField').call([], { |
||||
'wireName': literalString(propertyName), |
||||
}), |
||||
); |
||||
} |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
}), |
||||
defaults: defaults, |
||||
customSerializer: isHeader, |
||||
), |
||||
); |
||||
if (isHeader) { |
||||
state.output.add(buildHeaderSerializer(state, identifier, spec, schema)); |
||||
} |
||||
} |
||||
return result; |
||||
} |
@ -0,0 +1,165 @@
|
||||
import 'package:dynamite/src/builder/ofs_builder.dart'; |
||||
import 'package:dynamite/src/builder/resolve_enum.dart'; |
||||
import 'package:dynamite/src/builder/resolve_object.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
import 'package:dynamite/src/models/open_api.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
TypeResult resolveType( |
||||
final OpenAPI spec, |
||||
final State state, |
||||
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', |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
if (schema.ref != null) { |
||||
final name = schema.ref!.split('/').last; |
||||
result = resolveType( |
||||
spec, |
||||
state, |
||||
name, |
||||
spec.components!.schemas![name]!, |
||||
nullable: nullable, |
||||
); |
||||
} else if (schema.ofs != null) { |
||||
result = resolveOfs( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema, |
||||
nullable: nullable, |
||||
); |
||||
} else if (schema.isContentString) { |
||||
final subResult = resolveType( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema.contentSchema!, |
||||
); |
||||
|
||||
result = TypeResultObject( |
||||
'ContentString', |
||||
generics: [subResult], |
||||
nullable: nullable, |
||||
); |
||||
} else { |
||||
switch (schema.type) { |
||||
case 'boolean': |
||||
result = TypeResultBase( |
||||
'bool', |
||||
nullable: nullable, |
||||
); |
||||
case 'integer': |
||||
result = TypeResultBase( |
||||
'int', |
||||
nullable: nullable, |
||||
); |
||||
case 'number': |
||||
result = TypeResultBase( |
||||
'num', |
||||
nullable: nullable, |
||||
); |
||||
case 'string': |
||||
switch (schema.format) { |
||||
case 'binary': |
||||
result = TypeResultBase( |
||||
'Uint8List', |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
|
||||
result = TypeResultBase( |
||||
'String', |
||||
nullable: nullable, |
||||
); |
||||
case 'array': |
||||
if (schema.items != null) { |
||||
final subResult = resolveType( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema.items!, |
||||
); |
||||
result = TypeResultList( |
||||
'BuiltList', |
||||
subResult, |
||||
nullable: nullable, |
||||
); |
||||
} else { |
||||
result = TypeResultList( |
||||
'BuiltList', |
||||
TypeResultBase('JsonObject'), |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
case 'object': |
||||
if (schema.properties == null) { |
||||
if (schema.additionalProperties == null) { |
||||
result = TypeResultBase( |
||||
'JsonObject', |
||||
nullable: nullable, |
||||
); |
||||
} else if (schema.additionalProperties is EmptySchema) { |
||||
result = TypeResultMap( |
||||
'BuiltMap', |
||||
TypeResultBase('JsonObject'), |
||||
nullable: nullable, |
||||
); |
||||
} else { |
||||
final subResult = resolveType( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema.additionalProperties!, |
||||
); |
||||
result = TypeResultMap( |
||||
'BuiltMap', |
||||
subResult, |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
} else if (schema.properties!.isEmpty) { |
||||
result = TypeResultMap( |
||||
'BuiltMap', |
||||
TypeResultBase('JsonObject'), |
||||
nullable: nullable, |
||||
); |
||||
} else { |
||||
result = resolveObject( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema, |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (result != null) { |
||||
if (!ignoreEnum && schema.enum_ != null) { |
||||
result = resolveEnum( |
||||
spec, |
||||
state, |
||||
identifier, |
||||
schema, |
||||
result, |
||||
nullable: nullable, |
||||
); |
||||
} |
||||
|
||||
state.resolvedTypes.add(result); |
||||
return result; |
||||
} |
||||
|
||||
throw Exception('Can not convert OpenAPI type "${schema.toJson()}" to a Dart type'); |
||||
} |
@ -0,0 +1,34 @@
|
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/builder/state.dart'; |
||||
|
||||
List<Spec> buildSerializer(final State state) { |
||||
if (state.resolvedTypes.isNotEmpty) { |
||||
return [ |
||||
const Code('// coverage:ignore-start'), |
||||
const Code('final Serializers _serializers = (Serializers().toBuilder()'), |
||||
...state.resolvedTypes |
||||
.map((final type) => type.serializers) |
||||
.expand((final element) => element) |
||||
.toSet() |
||||
.map(Code.new), |
||||
const Code(').build();'), |
||||
const Code(''), |
||||
Code('Serializers get ${state.variablePrefix}Serializers => _serializers;'), |
||||
const Code(''), |
||||
const Code( |
||||
'final Serializers _jsonSerializers = (_serializers.toBuilder()..addPlugin(StandardJsonPlugin())..addPlugin(const ContentStringPlugin())).build();', |
||||
), |
||||
const Code(''), |
||||
Code( |
||||
'T deserialize${state.classPrefix}<T>(final Object data) => _serializers.deserialize(data, specifiedType: FullType(T))! as T;', |
||||
), |
||||
const Code(''), |
||||
Code( |
||||
'Object? serialize${state.classPrefix}<T>(final T data) => _serializers.serialize(data, specifiedType: FullType(T));', |
||||
), |
||||
const Code('// coverage:ignore-end'), |
||||
]; |
||||
} |
||||
|
||||
return []; |
||||
} |
@ -0,0 +1,15 @@
|
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
class State { |
||||
State(final String prefix) |
||||
: classPrefix = toDartName(prefix, uppercaseFirstCharacter: true), |
||||
variablePrefix = toDartName(prefix); |
||||
|
||||
final String classPrefix; |
||||
final String variablePrefix; |
||||
|
||||
final output = <Spec>[]; |
||||
final resolvedTypes = <TypeResult>{}; |
||||
} |
@ -0,0 +1,120 @@
|
||||
import 'package:built_collection/built_collection.dart'; |
||||
import 'package:code_builder/code_builder.dart'; |
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
|
||||
Spec buildBuiltClass( |
||||
final String className, |
||||
final BuiltList<Method> methods, { |
||||
final Iterable<String>? defaults, |
||||
final bool customSerializer = false, |
||||
}) => |
||||
Class( |
||||
(final b) { |
||||
b |
||||
..name = className |
||||
..abstract = true |
||||
..implements.add( |
||||
refer( |
||||
'Built<$className, ${className}Builder>', |
||||
), |
||||
) |
||||
..constructors.addAll([ |
||||
builtValueConstructor(className), |
||||
hiddenConstructor, |
||||
fromJsonConstructor, |
||||
]) |
||||
..methods.addAll([ |
||||
toJsonMethod, |
||||
...methods, |
||||
buildSerializer(className, isCustom: customSerializer), |
||||
]); |
||||
|
||||
if (defaults != null && defaults.isNotEmpty) { |
||||
b.methods.add( |
||||
Method( |
||||
(final b) => b |
||||
..name = '_defaults' |
||||
..returns = refer('void') |
||||
..static = true |
||||
..lambda = true |
||||
..annotations.add( |
||||
refer('BuiltValueHook').call([], { |
||||
'initializeBuilder': literalTrue, |
||||
}), |
||||
) |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'b' |
||||
..type = refer('${className}Builder'), |
||||
), |
||||
) |
||||
..body = Code( |
||||
<String?>[ |
||||
'b', |
||||
...defaults, |
||||
].join(), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
}, |
||||
); |
||||
|
||||
Method get toJsonMethod => Method( |
||||
(final b) => b |
||||
..name = 'toJson' |
||||
..returns = refer('Map<String, dynamic>') |
||||
..lambda = true |
||||
..body = const Code('_jsonSerializers.serializeWith(serializer, this)! as Map<String, dynamic>'), |
||||
); |
||||
|
||||
Method buildSerializer(final String className, {final bool isCustom = false}) => Method((final b) { |
||||
b |
||||
..name = 'serializer' |
||||
..returns = refer('Serializer<$className>') |
||||
..lambda = true |
||||
..static = true |
||||
..body = Code( |
||||
isCustom ? '_\$${className}Serializer()' : '_\$${toCamelCase(className)}Serializer', |
||||
) |
||||
..type = MethodType.getter; |
||||
if (isCustom) { |
||||
b.annotations.add(refer('BuiltValueSerializer').call([], {'custom': literalTrue})); |
||||
} |
||||
}); |
||||
|
||||
Constructor builtValueConstructor(final String className) => Constructor( |
||||
(final b) => b |
||||
..factory = true |
||||
..lambda = true |
||||
..optionalParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'b' |
||||
..type = refer('void Function(${className}Builder)?'), |
||||
), |
||||
) |
||||
..redirect = refer('_\$$className'), |
||||
); |
||||
|
||||
Constructor get hiddenConstructor => Constructor( |
||||
(final b) => b |
||||
..name = '_' |
||||
..constant = true, |
||||
); |
||||
|
||||
Constructor get fromJsonConstructor => Constructor( |
||||
(final b) => b |
||||
..factory = true |
||||
..name = 'fromJson' |
||||
..lambda = true |
||||
..requiredParameters.add( |
||||
Parameter( |
||||
(final b) => b |
||||
..name = 'json' |
||||
..type = refer('Map<String, dynamic>'), |
||||
), |
||||
) |
||||
..body = const Code('_jsonSerializers.deserializeWith(serializer, json)!'), |
||||
); |
@ -0,0 +1,107 @@
|
||||
String toDartName( |
||||
final String name, { |
||||
final bool uppercaseFirstCharacter = false, |
||||
}) { |
||||
var result = ''; |
||||
var upperCase = uppercaseFirstCharacter; |
||||
var firstCharacter = !uppercaseFirstCharacter; |
||||
for (final char in name.split('')) { |
||||
if (_isNonAlphaNumericString(char)) { |
||||
upperCase = true; |
||||
} else { |
||||
result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); |
||||
upperCase = false; |
||||
firstCharacter = false; |
||||
} |
||||
} |
||||
|
||||
if (_dartKeywords.contains(result) || RegExp(r'^[0-9]+$', multiLine: true).hasMatch(result)) { |
||||
return '\$$result'; |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
final _dartKeywords = [ |
||||
'assert', |
||||
'break', |
||||
'case', |
||||
'catch', |
||||
'class', |
||||
'const', |
||||
'continue', |
||||
'default', |
||||
'do', |
||||
'else', |
||||
'enum', |
||||
'extends', |
||||
'false', |
||||
'final', |
||||
'finally', |
||||
'for', |
||||
'if', |
||||
'in', |
||||
'is', |
||||
'new', |
||||
'null', |
||||
'rethrow', |
||||
'return', |
||||
'super', |
||||
'switch', |
||||
'this', |
||||
'throw', |
||||
'true', |
||||
'try', |
||||
'var', |
||||
'void', |
||||
'while', |
||||
'with', |
||||
'async', |
||||
'hide', |
||||
'on', |
||||
'show', |
||||
'sync', |
||||
'abstract', |
||||
'as', |
||||
'covariant', |
||||
'deferred', |
||||
'dynamic', |
||||
'export', |
||||
'extension', |
||||
'external', |
||||
'factory', |
||||
'function', |
||||
'get', |
||||
'implements', |
||||
'import', |
||||
'interface', |
||||
'library', |
||||
'mixin', |
||||
'operator', |
||||
'part', |
||||
'set', |
||||
'static', |
||||
'typedef', |
||||
]; |
||||
|
||||
bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); |
||||
|
||||
String toFieldName(final String dartName, final String type) => dartName == type ? '\$$dartName' : dartName; |
||||
|
||||
String toCamelCase(final String name) { |
||||
var result = ''; |
||||
var upperCase = false; |
||||
var firstCharacter = true; |
||||
for (final char in name.split('')) { |
||||
if (char == '_') { |
||||
upperCase = true; |
||||
} else if (char == r'$') { |
||||
result += r'$'; |
||||
} else { |
||||
result += firstCharacter ? char.toLowerCase() : (upperCase ? char.toUpperCase() : char); |
||||
upperCase = false; |
||||
firstCharacter = false; |
||||
} |
||||
} |
||||
return result; |
||||
} |
@ -0,0 +1,9 @@
|
||||
const docsSeparator = '///'; |
||||
|
||||
Iterable<String> descriptionToDocs(final String? description) sync* { |
||||
if (description != null && description.isNotEmpty) { |
||||
for (final line in description.split('\n')) { |
||||
yield '$docsSeparator $line'; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
// ignore_for_file: avoid_positional_boolean_parameters |
||||
|
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/models/parameter.dart'; |
||||
import 'package:dynamite/src/models/schema.dart'; |
||||
|
||||
String filterMethodName(final String operationId, final String tag) { |
||||
final expandedTag = tag.split('/').toList(); |
||||
final parts = operationId.split('-'); |
||||
final output = <String>[]; |
||||
for (var i = 0; i < parts.length; i++) { |
||||
if (expandedTag.length <= i || expandedTag[i] != parts[i]) { |
||||
output.add(parts[i]); |
||||
} |
||||
} |
||||
return output.join('-'); |
||||
} |
||||
|
||||
String clientName(final String tag) => '${toDartName(tag, uppercaseFirstCharacter: true)}Client'; |
||||
|
||||
bool isDartParameterNullable( |
||||
final bool? required, |
||||
final Schema? schema, |
||||
) => |
||||
(!(required ?? false) && schema?.default_ == null) || (schema?.nullable ?? false); |
||||
|
||||
bool isRequired( |
||||
final bool? required, |
||||
final dynamic default_, |
||||
) => |
||||
(required ?? false) && default_ == null; |
||||
|
||||
int sortRequiredParameters(final Parameter a, final Parameter b) { |
||||
if (a.isDartRequired != b.isDartRequired) { |
||||
if (a.isDartRequired && !b.isDartRequired) { |
||||
return -1; |
||||
} else { |
||||
return 1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,15 @@
|
||||
import 'package:dynamite/src/helpers/dart_helpers.dart'; |
||||
import 'package:dynamite/src/type_result/type_result.dart'; |
||||
|
||||
String valueToEscapedValue(final TypeResult result, final String value) { |
||||
if (result is TypeResultBase && result.name == 'String') { |
||||
return "'$value'"; |
||||
} |
||||
if (result is TypeResultList) { |
||||
return 'const $value'; |
||||
} |
||||
if (result is TypeResultEnum) { |
||||
return '${result.name}.${toDartName(value)}'; |
||||
} |
||||
return value; |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue