Skip to content

Commit

Permalink
feat(dynamite_runtime): use http.Request in generated code
Browse files Browse the repository at this point in the history
Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
  • Loading branch information
Leptopoda committed Mar 9, 2024
1 parent 2e92375 commit c9d0b24
Show file tree
Hide file tree
Showing 54 changed files with 16,191 additions and 17,106 deletions.
219 changes: 103 additions & 116 deletions packages/dynamite/dynamite/example/lib/petstore.openapi.dart

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/dynamite/dynamite/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies:
built_collection: ^5.0.0
built_value: ^8.9.0
dynamite_runtime: ^0.2.0
http: ^1.2.1
meta: ^1.0.0
uri: ^1.0.0

Expand Down
213 changes: 76 additions & 137 deletions packages/dynamite/dynamite/lib/src/builder/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ Iterable<Method> buildTags(
yield Method((b) {
b
..name = '\$${name}_Serializer'
..docs.add('/// Builds a serializer to parse the response of `\$${name}_Request`.')
..docs.add('/// Builds a serializer to parse the response of [\$${name}_Request].')
..annotations.add(refer('experimental', 'package:meta/meta.dart'));

if (operation.deprecated) {
Expand Down Expand Up @@ -342,6 +342,70 @@ ${allocate(returnType)}(
});
});

yield Method((b) {
b
..name = '\$${name}_Request'
..docs.addAll(operation.formattedDescription(name, isRequest: true))
..annotations.add(refer('experimental', 'package:meta/meta.dart'));

if (operation.deprecated) {
b.annotations.add(refer('Deprecated').call([refer("''")]));
}

final requestType = refer('Request', 'package:http/http.dart');

b
..optionalParameters.addAll(operationParameters)
..returns = requestType
..body = Code.scope((allocate) {
final code = StringBuffer();

if (pathParameters.isNotEmpty) {
code.writeln('final _parameters = <String, Object?>{};');
}

for (final parameter in pathParameters.entries) {
code.writeln(buildParameterSerialization(parameter.value, parameter.key, state, allocate));
}

buildUrlPath(pathEntry.key, parameters, code, allocate);

code
..writeln("final _uri = Uri.parse('\${$client.baseURL}\$_path');")
..writeln("final _request = ${allocate(requestType)}('$httpMethod', _uri);");

final acceptHeader = responses.keys
.map((response) => response.content?.keys)
.whereNotNull()
.expand((element) => element)
.toSet()
.join(',');

if (acceptHeader.isNotEmpty) {
code.writeln("_request.headers['Accept'] = '$acceptHeader';");
}

buildAuthCheck(
state,
operation,
spec,
client,
code,
);

for (final parameter in headerParameters.entries) {
code.writeln(buildParameterSerialization(parameter.value, parameter.key, state, allocate));
}

if (bodyParameter != null) {
resolveMimeTypeEncode(bodyParameter.mimeType, bodyParameter.result, code);
}

code.writeln('return _request;');
return code.toString();
});
});

yield Method((b) {
b
..name = name
Expand All @@ -363,6 +427,11 @@ ${allocate(returnType)}(
..url = 'package:dynamite_runtime/http_client.dart',
);

final responseConverterType = refer(
'ResponseConverter<${bodyType.name}, ${headersType.name}>',
'package:dynamite_runtime/http_client.dart',
);

b
..optionalParameters.addAll(operationParameters)
..returns = TypeReference(
Expand All @@ -372,133 +441,15 @@ ${allocate(returnType)}(
)
..body = Code.scope(
(allocate) => '''
final _rawResponse = await ${name}Raw(
$rawParameters
);
final _request = \$${name}_Request($rawParameters);
final _response = await $client.sendWithCookies(_request);
final _serializer = \$${name}_Serializer();
final _rawResponse = await ${allocate(responseConverterType)}(_serializer).convert(_response);
return ${allocate(responseType)}.fromRawResponse(_rawResponse);
''',
);
});

yield Method(
(b) {
b
..name = '${name}Raw'
..modifier = MethodModifier.async
..docs.addAll(operation.formattedDescription(name, isRawRequest: true))
..annotations.add(refer('experimental', 'package:meta/meta.dart'));

if (operation.deprecated) {
b.annotations.add(refer('Deprecated').call([refer("''")]));
}

b
..optionalParameters.addAll(operationParameters)
..returns = TypeReference(
(b) => b
..symbol = 'Future'
..types.add(
refer(
'DynamiteRawResponse<${bodyType.name}, ${headersType.name}>',
'package:dynamite_runtime/http_client.dart',
),
),
)
..body = Code.scope((allocate) {
final hasUriParameters = pathParameters.isNotEmpty;
final hasHeaderParameters = headerParameters.isNotEmpty;
final hasAuthentication = needsAuthCheck(pathEntry, operation, spec, client);
final hasContentEncoding = bodyParameter != null;

final code = StringBuffer();
final acceptHeader = responses.keys
.map((response) => response.content?.keys)
.whereNotNull()
.expand((element) => element)
.toSet()
.join(',');

if (hasUriParameters) {
code.writeln('final _parameters = <String, dynamic>{};');
}

Expression? headersExpression;
if (hasHeaderParameters || hasAuthentication || hasContentEncoding) {
headersExpression = declareFinal('_headers');
} else if (acceptHeader.isNotEmpty) {
headersExpression = declareConst('_headers');
}

if (headersExpression != null) {
final headersCode = headersExpression
.assign(
literalMap(
{if (acceptHeader.isNotEmpty) 'Accept': acceptHeader},
refer('String'),
refer('String'),
),
)
.statement
.accept(state.emitter);

code.writeln(headersCode);
}

if (hasContentEncoding) {
code.writeln('Uint8List? _body;');
}
// Separate the declarations from the assignments
code.writeln();

if (hasAuthentication) {
buildAuthCheck(
state,
operation,
spec,
client,
code,
);
}

for (final parameter in pathParameters.entries) {
code.writeln(buildParameterSerialization(parameter.value, parameter.key, state, allocate));
}
for (final parameter in headerParameters.entries) {
code.writeln(buildParameterSerialization(parameter.value, parameter.key, state, allocate));
}

if (bodyParameter != null) {
resolveMimeTypeEncode(bodyParameter.mimeType, bodyParameter.result, code);
}

buildUrlPath(pathEntry.key, parameters, code, state.emitter.allocator.allocate);

final requestExpression = refer(client).property('executeRequest').call([
literalString(httpMethod),
refer('_path'),
], {
if (acceptHeader.isNotEmpty || hasHeaderParameters || hasAuthentication || hasContentEncoding)
'headers': refer('_headers'),
if (operation.requestBody != null) 'body': refer('_body'),
});

final responseConverterType = refer(
'ResponseConverter<${bodyType.name}, ${headersType.name}>',
'package:dynamite_runtime/http_client.dart',
);

code.write('''
final _response = ${requestExpression.awaited.accept(state.emitter)};
final _serializer = \$${name}_Serializer();
return ${allocate(responseConverterType)}(_serializer).convert(_response);
''');

return code.toString();
});
},
);
}
}
}
Expand Down Expand Up @@ -652,7 +603,7 @@ String buildParameterSerialization(
if (parameter.$in == openapi.ParameterType.header) {
final encoderRef = refer('HeaderEncoder', 'package:dynamite_runtime/utils.dart');
final assignment =
"_headers['${parameter.pctEncodedName}'] = ${allocate(encoderRef)}(explode: ${parameter.explode}).convert($serializedName);";
"_request.headers['${parameter.pctEncodedName}'] = ${allocate(encoderRef)}(explode: ${parameter.explode}).convert($serializedName);";

if ($default == null) {
buffer
Expand All @@ -669,18 +620,6 @@ String buildParameterSerialization(
return buffer.toString();
}

bool needsAuthCheck(
MapEntry<String, openapi.PathItem> pathEntry,
openapi.Operation operation,
openapi.OpenAPI spec,
String client,
) {
final security = operation.security ?? spec.security ?? BuiltList();
final securityRequirements = security.where((requirement) => requirement.isNotEmpty);

return securityRequirements.isNotEmpty;
}

void buildAuthCheck(
State state,
openapi.Operation operation,
Expand Down Expand Up @@ -725,7 +664,7 @@ final authentication = $client.authentications?.firstWhereOrNull(
);
if(authentication != null) {
_headers.addAll(
_request.headers.addAll(
authentication.headers,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ void resolveMimeTypeEncode(
TypeResult result,
StringSink output,
) {
output.writeln("_headers['Content-Type'] = '$mimeType';");
output.writeln("_request.headers['Content-Type'] = '$mimeType';");
final parameterName = toDartName(result.name);

switch (mimeType) {
Expand All @@ -53,7 +53,7 @@ void resolveMimeTypeEncode(
if (result.nullable) {
output.writeln('if ($parameterName != null) {');
}
output.writeln('_body = utf8.encode(${result.encode(parameterName, mimeType: mimeType)});');
output.writeln('_request.body = ${result.encode(parameterName, mimeType: mimeType)};');
if (result.nullable) {
output.writeln('}');
}
Expand All @@ -62,7 +62,7 @@ void resolveMimeTypeEncode(
if (result.nullable) {
output.writeln('if ($parameterName != null) {');
}
output.writeln('_body = ${result.encode(parameterName, mimeType: mimeType)};');
output.writeln('_request.bodyBytes = ${result.encode(parameterName, mimeType: mimeType)};');
if (result.nullable) {
output.writeln('}');
}
Expand Down
17 changes: 8 additions & 9 deletions packages/dynamite/dynamite/lib/src/models/openapi/operation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ abstract class Operation implements Built<Operation, OperationBuilder> {

Iterable<String> formattedDescription(
String methodName, {
bool isRawRequest = false,
bool isRequest = false,
bool requiresAuth = false,
}) sync* {
if (summary != null && summary!.isNotEmpty) {
Expand All @@ -55,11 +55,8 @@ abstract class Operation implements Built<Operation, OperationBuilder> {
yield docsSeparator;
}

if (isRawRequest) {
yield '''
$docsSeparator This method and the response it returns is experimental. The API might change without a major version bump.
$docsSeparator
$docsSeparator Returns a [Future] containing a `DynamiteRawResponse` with the raw `HttpClientResponse` and serialization helpers.''';
if (isRequest) {
yield '$docsSeparator Returns a `DynamiteRequest` backing the [$methodName] operation.';
} else {
yield '$docsSeparator Returns a [Future] containing a `DynamiteResponse` with the status code, deserialized body and headers.';
}
Expand Down Expand Up @@ -96,10 +93,12 @@ $docsSeparator Returns a [Future] containing a `DynamiteRawResponse` with the ra
}

yield '$docsSeparator See:';
if (isRawRequest) {
yield '$docsSeparator * [$methodName] for an operation that returns a `DynamiteResponse` with a stable API.';
if (isRequest) {
yield '$docsSeparator * [$methodName] for a method executing this request and parsing the response.';
yield '$docsSeparator * [\$${methodName}_Serializer] for a converter to parse the `Response` from an executed this request.';
} else {
yield '$docsSeparator * [${methodName}Raw] for an experimental operation that returns a `DynamiteRawResponse` that can be serialized.';
yield '$docsSeparator * [\$${methodName}_Request] for the request send by this method.';
yield '$docsSeparator * [\$${methodName}_Serializer] for a converter to parse the `Response` from an executed request.';
}
}
}

0 comments on commit c9d0b24

Please sign in to comment.