diff --git a/packages/dynamite/dynamite/lib/src/builder/client.dart b/packages/dynamite/dynamite/lib/src/builder/client.dart index 3bb46694..a51fb921 100644 --- a/packages/dynamite/dynamite/lib/src/builder/client.dart +++ b/packages/dynamite/dynamite/lib/src/builder/client.dart @@ -1,6 +1,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:code_builder/code_builder.dart'; import 'package:collection/collection.dart'; +import 'package:dynamite/src/builder/resolve_mime_type.dart'; import 'package:dynamite/src/builder/resolve_object.dart'; import 'package:dynamite/src/builder/resolve_type.dart'; import 'package:dynamite/src/builder/state.dart'; @@ -302,7 +303,11 @@ Iterable buildTags( var responses = >{}; if (operation.responses != null) { for (final responseEntry in operation.responses!.entries) { - final statusCode = int.parse(responseEntry.key); + final statusCode = int.tryParse(responseEntry.key); + if (statusCode == null) { + print('Default responses are not supported right now. Skipping it for $operationId'); + continue; + } final response = responseEntry.value; responses[response] ??= []; @@ -448,64 +453,18 @@ if (${toDartName(parameter.name)}.length > ${parameter.schema!.maxLength!}) { } } - 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"'); - } - } - } + resolveMimeTypeEncode( + operation, + spec, + state, + operationId, + b, + code, + ); code.write( ''' -final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( +${responses.values.isNotEmpty ? 'final _response =' : ''} await ${isRootClient ? 'this' : '_rootClient'}.doRequest( '$httpMethod', Uri(path: _path, queryParameters: _queryParameters.isNotEmpty ? _queryParameters : null), _headers, @@ -547,54 +506,15 @@ final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( 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${responses.entries.length > 1 ? '-${responses.entries.toList().indexOf(responseEntry)}' : ''}-$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"'); - } - } - } + final (dataType, dataValue, dataNeedsAwait) = resolveMimeTypeDecode( + response, + spec, + state, + toDartName( + '$operationId-response${responses.entries.length > 1 ? '-${responses.entries.toList().indexOf(responseEntry)}' : ''}', + uppercaseFirstCharacter: true, + ), + ); if (headersType != null && dataType != null) { b.returns = refer('Future<${state.classPrefix}Response<$dataType, $headersType>>'); @@ -614,9 +534,14 @@ final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest( code.write('}'); } - code.write( - 'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', - ); + + if (responses.values.isNotEmpty) { + code.write( + 'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n', + ); + } else { + b.returns = refer('Future'); + } b.body = Code(code.toString()); }, diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_mime_type.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_mime_type.dart new file mode 100644 index 00000000..f11e1990 --- /dev/null +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_mime_type.dart @@ -0,0 +1,148 @@ +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/operation.dart'; +import 'package:dynamite/src/models/response.dart'; +import 'package:dynamite/src/type_result/type_result.dart'; + +(String? dataType, String? dataValue, bool? dataNeedsAwait) resolveMimeTypeDecode( + final Response response, + final OpenAPI spec, + final State state, + final String identifier, +) { + if (response.content != null) { + if (response.content!.length > 1) { + print('Can not work with multiple mime types right now. Using the first supported.'); + } + + for (final content in response.content!.entries) { + final mimeType = content.key; + final mediaType = content.value; + + final result = resolveType( + spec, + state, + 'identifier-$mimeType', + mediaType.schema!, + ); + + if (mimeType == '*/*' || mimeType == 'application/octet-stream' || mimeType.startsWith('image/')) { + return ('Uint8List', '_response.bodyBytes', true); + } else if (mimeType.startsWith('text/') || mimeType == 'application/javascript') { + return ('String', '_response.body', true); + } else if (mimeType == 'application/json') { + String? dataValue; + bool? dataNeedsAwait; + 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; + } + return (result.name, dataValue, dataNeedsAwait); + } + } + throw Exception('Can not parse any mime type of Operation:"$identifier"'); + } + return (null, null, null); +} + +void resolveMimeTypeEncode( + final Operation operation, + final OpenAPI spec, + final State state, + final String identifier, + final MethodBuilder b, + final StringBuffer code, +) { + if (operation.requestBody != null) { + if (operation.requestBody!.content!.length > 1) { + print('Can not work with multiple mime types right now. Using the first supported.'); + } + 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('$identifier-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('}'); + } + return; + case 'application/octet-stream': + 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 = ${result.encode(parameterName, mimeType: mimeType)};', + ); + if (dartParameterNullable) { + code.write('}'); + } + return; + } + } + + throw Exception('Can not parse any mime type of Operation:"$identifier"'); + } +} diff --git a/packages/dynamite/dynamite/lib/src/type_result/base.dart b/packages/dynamite/dynamite/lib/src/type_result/base.dart index cec6ccef..b1832166 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/base.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/base.dart @@ -21,8 +21,22 @@ class TypeResultBase extends TypeResult { final String object, { final bool onlyChildren = false, final String? mimeType, - }) => - name == 'String' ? object : '$object.toString()'; + }) { + switch (mimeType) { + case null: + case 'application/json': + case 'application/x-www-form-urlencoded': + if (className == 'String') { + return object; + } else { + return '$object.toString()'; + } + case 'application/octet-stream': + return 'utf8.encode($object) as Uint8List'; + default: + throw Exception('Can not encode mime type "$mimeType"'); + } + } @override String deserialize(final String object, {final bool toBuilder = false}) => '($object as $nullableName)'; 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 7067ddfc..4c874588 100644 --- a/packages/dynamite/dynamite/lib/src/type_result/type_result.dart +++ b/packages/dynamite/dynamite/lib/src/type_result/type_result.dart @@ -87,6 +87,11 @@ abstract class TypeResult { return 'json.encode($serialized)'; case 'application/x-www-form-urlencoded': return 'Uri(queryParameters: $serialized! as Map).query'; + case 'application/octet-stream': + if (className != 'Uint8List') { + throw Exception('octet-stream can only be applied to binary data. Expected Uint8List but got $className'); + } + return '$object as Uint8List'; default: throw Exception('Can not encode mime type "$mimeType"'); }