Skip to content

Commit

Permalink
Merge pull request #1020 from nextcloud/feature/dynamite/parameter-co…
Browse files Browse the repository at this point in the history
…ntent

feat(dynamite): Support content in parameters
  • Loading branch information
provokateurin authored Oct 25, 2023
2 parents ae78ef0 + 79b39f9 commit 50f1466
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 32 deletions.
9 changes: 6 additions & 3 deletions packages/dynamite/dynamite/lib/src/builder/imports.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:dynamite/src/builder/state.dart';
import 'package:path/path.dart' as p;

List<Spec> generateImports(final AssetId outputId) => [
List<Spec> generateImports(final AssetId outputId, final State state) => [
const Code('// ignore_for_file: camel_case_types'),
const Code('// ignore_for_file: discarded_futures'),
const Code('// ignore_for_file: public_member_api_docs'),
Expand All @@ -23,6 +24,8 @@ List<Spec> generateImports(final AssetId outputId) => [
Directive.import('package:meta/meta.dart'),
Directive.import('package:universal_io/io.dart'),
const Code(''),
Directive.part(p.basename(outputId.changeExtension('.g.dart').path)),
const Code(''),
if (state.resolvedTypes.isNotEmpty) ...[
Directive.part(p.basename(outputId.changeExtension('.g.dart').path)),
const Code(''),
],
];
8 changes: 1 addition & 7 deletions packages/dynamite/dynamite/lib/src/builder/serializer.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
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 [
List<Spec> buildSerializer(final State state) => [
const Code('// coverage:ignore-start'),
const Code('final Serializers _serializers = (Serializers().toBuilder()'),
...state.resolvedTypes
Expand All @@ -18,7 +16,3 @@ List<Spec> buildSerializer(final State state) {
),
const Code('// coverage:ignore-end'),
];
}

return [];
}
2 changes: 2 additions & 0 deletions packages/dynamite/dynamite/lib/src/models/openapi.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 33 additions & 2 deletions packages/dynamite/dynamite/lib/src/models/openapi/parameter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:built_value/serializer.dart';
import 'package:dynamite/src/helpers/dart_helpers.dart';
import 'package:dynamite/src/helpers/docs.dart';
import 'package:dynamite/src/models/exceptions.dart';
import 'package:dynamite/src/models/openapi/media_type.dart';
import 'package:dynamite/src/models/openapi/schema.dart';

part 'parameter.g.dart';
Expand All @@ -25,7 +26,33 @@ abstract class Parameter implements Built<Parameter, ParameterBuilder> {

bool get required;

Schema? get schema;
@Deprecated('Use [schema] instead which also automatically handles [content].')
@BuiltValueField(wireName: 'schema')
Schema? get $schema;

BuiltMap<String, MediaType>? get content;

Schema? get schema {
// ignore: deprecated_member_use_from_same_package
if ($schema != null) {
// ignore: deprecated_member_use_from_same_package
return $schema;
}

if (content != null && content!.isNotEmpty) {
if (content!.length > 1) {
print('Can not work with multiple mime types right now. Using the first supported.');
}
return Schema(
(final b) => b
..type = SchemaType.string
..contentMediaType = content!.entries.first.key
..contentSchema = content!.entries.first.value.schema!.toBuilder(),
);
}

return null;
}

@BuiltValueHook(finalizeBuilder: true)
static void _defaults(final ParameterBuilder b) {
Expand All @@ -34,9 +61,13 @@ abstract class Parameter implements Built<Parameter, ParameterBuilder> {
throw OpenAPISpecError('Path parameters must be required but ${b.name} is not.');
}

if (b.required! && b.schema.$default != null) {
if (b.required! && b._$schema != null && b.$schema.$default != null) {
print('Required parameters should not specify default values.');
}

if (b._$schema != null && b._content != null) {
throw OpenAPISpecError('Only one of schema or content must be set in parameter ${b.name}.');
}
}

String get formattedDescription {
Expand Down
53 changes: 39 additions & 14 deletions packages/dynamite/dynamite/lib/src/models/openapi/parameter.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 10 additions & 4 deletions packages/dynamite/dynamite/lib/src/openapi_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ class OpenAPIBuilder implements Builder {

final state = State();

final output = ListBuilder<Spec>()
..addAll(generateImports(outputId))
..addAll(generateClients(spec, state));
final output = ListBuilder<Spec>();

if (spec.components?.schemas != null) {
for (final schema in spec.components!.schemas!.entries) {
Expand Down Expand Up @@ -86,9 +84,17 @@ class OpenAPIBuilder implements Builder {
}
}

// Imports need to be generated after everything else so we know if we need the local part directive,
// but they need to be added to the beginning of the output.
final clients = generateClients(spec, state);
final serializer = buildSerializer(state);
final imports = generateImports(outputId, state);

output
..addAll(imports)
..addAll(clients)
..addAll(state.output)
..addAll(buildSerializer(state));
..addAll(serializer);

final patterns = [
RegExp(
Expand Down
126 changes: 126 additions & 0 deletions packages/dynamite/dynamite_end_to_end_test/lib/parameters.openapi.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// ignore_for_file: camel_case_types
// ignore_for_file: discarded_futures
// ignore_for_file: public_member_api_docs
// ignore_for_file: unreachable_switch_case
import 'dart:typed_data';

import 'package:built_collection/built_collection.dart';
import 'package:built_value/json_object.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'package:dynamite_runtime/built_value.dart';
import 'package:dynamite_runtime/http_client.dart';
import 'package:dynamite_runtime/models.dart';
import 'package:meta/meta.dart';
import 'package:universal_io/io.dart';

class Client extends DynamiteClient {
Client(
super.baseURL, {
super.baseHeaders,
super.userAgent,
super.httpClient,
super.cookieJar,
});

Client.fromClient(final DynamiteClient client)
: super(
client.baseURL,
baseHeaders: client.baseHeaders,
httpClient: client.httpClient,
cookieJar: client.cookieJar,
authentications: client.authentications,
);

/// Returns a [Future] containing a [DynamiteResponse] with the status code, deserialized body and headers.
/// Throws a [DynamiteApiException] if the API call does not return an expected status code.
///
/// Parameters:
/// * [contentString]
/// * [contentParameter]
///
/// Status codes:
/// * 200
///
/// See:
/// * [$getRaw] for an experimental operation that returns a [DynamiteRawResponse] that can be serialized.
Future<DynamiteResponse<JsonObject, void>> $get({
final ContentString<BuiltMap<String, JsonObject>>? contentString,
final ContentString<BuiltMap<String, JsonObject>>? contentParameter,
}) async {
final rawResponse = $getRaw(
contentString: contentString,
contentParameter: contentParameter,
);

return rawResponse.future;
}

/// This method and the response it returns is experimental. The API might change without a major version bump.
///
/// Returns a [Future] containing a [DynamiteRawResponse] with the raw [HttpClientResponse] and serialization helpers.
/// Throws a [DynamiteApiException] if the API call does not return an expected status code.
///
/// Parameters:
/// * [contentString]
/// * [contentParameter]
///
/// Status codes:
/// * 200
///
/// See:
/// * [$get] for an operation that returns a [DynamiteResponse] with a stable API.
@experimental
DynamiteRawResponse<JsonObject, void> $getRaw({
final ContentString<BuiltMap<String, JsonObject>>? contentString,
final ContentString<BuiltMap<String, JsonObject>>? contentParameter,
}) {
final queryParameters = <String, dynamic>{};
final headers = <String, String>{
'Accept': 'application/json',
};
Uint8List? body;

if (contentString != null) {
queryParameters['content-string'] = _jsonSerializers.serialize(
contentString,
specifiedType: const FullType(ContentString, [
FullType(BuiltMap, [FullType(String), FullType(JsonObject)]),
]),
);
}
if (contentParameter != null) {
queryParameters['content-parameter'] = _jsonSerializers.serialize(
contentParameter,
specifiedType: const FullType(ContentString, [
FullType(BuiltMap, [FullType(String), FullType(JsonObject)]),
]),
);
}
const path = '/';
final uri = Uri(path: path, queryParameters: queryParameters.isNotEmpty ? queryParameters : null);

return DynamiteRawResponse<JsonObject, void>(
response: executeRequest(
'get',
uri,
headers,
body,
const {200},
),
bodyType: const FullType(JsonObject),
headersType: null,
serializers: _jsonSerializers,
);
}
}

// coverage:ignore-start
final Serializers _serializers = Serializers().toBuilder().build();

final Serializers _jsonSerializers = (_serializers.toBuilder()
..add(DynamiteDoubleSerializer())
..addPlugin(StandardJsonPlugin())
..addPlugin(const ContentStringPlugin()))
.build();
// coverage:ignore-end
Loading

0 comments on commit 50f1466

Please sign in to comment.