Browse Source

feat(dynamite): fully support application/octet-stream and more gracefully handle multiple media types

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
pull/764/head
Nikolas Rimikis 1 year ago
parent
commit
ec6e7cafd0
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 115
      packages/dynamite/dynamite/lib/src/builder/client.dart
  2. 148
      packages/dynamite/dynamite/lib/src/builder/resolve_mime_type.dart
  3. 18
      packages/dynamite/dynamite/lib/src/type_result/base.dart
  4. 5
      packages/dynamite/dynamite/lib/src/type_result/type_result.dart

115
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<Method> buildTags(
var responses = <Response, List<int>>{};
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(
resolveMimeTypeEncode(
operation,
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;',
operationId,
b,
code,
);
if (dartParameterNullable) {
code.write('}');
}
default:
throw Exception('Can not parse mime type "$mimeType"');
}
}
}
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,55 +506,16 @@ 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(
final (dataType, dataValue, dataNeedsAwait) = resolveMimeTypeDecode(
response,
spec,
state,
toDartName(
'$operationId-response${responses.entries.length > 1 ? '-${responses.entries.toList().indexOf(responseEntry)}' : ''}-$mimeType',
'$operationId-response${responses.entries.length > 1 ? '-${responses.entries.toList().indexOf(responseEntry)}' : ''}',
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(
@ -614,9 +534,14 @@ final _response = await ${isRootClient ? 'this' : '_rootClient'}.doRequest(
code.write('}');
}
if (responses.values.isNotEmpty) {
code.write(
'throw await ${state.classPrefix}ApiException.fromResponse(_response); // coverage:ignore-line\n',
);
} else {
b.returns = refer('Future<void>');
}
b.body = Code(code.toString());
},

148
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"');
}
}

18
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)';

5
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<String, dynamic>).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"');
}

Loading…
Cancel
Save