Skip to content

Commit

Permalink
Merge pull request #1953 from nextcloud/fix/dynamite/reference_resolving
Browse files Browse the repository at this point in the history
fix(dynamite): json schema reference resolving
  • Loading branch information
Leptopoda authored Apr 24, 2024
2 parents c917011 + a3c55c1 commit 23728ea
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 20 deletions.
4 changes: 2 additions & 2 deletions packages/dynamite/dynamite/lib/src/builder/resolve_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ TypeResult _resolveType(
);

case openapi.Schema(ref: != null):
final name = schema.ref!.split('/').last;
final name = schema.ref!.fragment.split('/').last;
final subResult = resolveType(
spec,
state,
toDartName(name, className: true),
spec.components!.schemas![name]!,
schema.resolveRef(state.rootJson),
nullable: nullable,
);

Expand Down
4 changes: 3 additions & 1 deletion packages/dynamite/dynamite/lib/src/builder/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import 'package:dynamite/src/models/dynamite_config/config.dart';
import 'package:dynamite/src/models/type_result.dart';

class State {
State(this.buildConfig);
State(this.buildConfig, this.rootJson);

final DynamiteConfig buildConfig;

final Map<String, dynamic> rootJson;

final output = <Spec>[];
final resolvedTypes = <TypeResult>{};
final resolvedInterfaces = <TypeResult>{};
Expand Down
34 changes: 32 additions & 2 deletions packages/dynamite/dynamite/lib/src/models/openapi/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import 'package:built_value/serializer.dart';
import 'package:dynamite/src/helpers/docs.dart';
import 'package:dynamite/src/helpers/logger.dart';
import 'package:dynamite/src/models/exceptions.dart';
import 'package:dynamite/src/models/openapi/discriminator.dart';
import 'package:dynamite/src/models/openapi.dart';
import 'package:rfc_6901/rfc_6901.dart';

part 'schema.g.dart';

Expand All @@ -16,8 +17,37 @@ abstract class Schema implements Built<Schema, SchemaBuilder> {

static Serializer<Schema> get serializer => _$schemaSerializer;

@BuiltValueField(wireName: r'$id')
Uri? get id;

@BuiltValueField(wireName: r'$ref')
String? get ref;
Uri? get ref;

Schema resolveRef(Map<String, dynamic> json) {
if (ref == null) {
throw StateError(r'Referenced schema can only be resolved when a $ref is present');
}

final rootID = json[r'$id'] as String?;

final Uri uri;
if (rootID != null) {
uri = Uri.parse(rootID).resolveUri(ref!);
} else {
uri = ref!;
}

// Only relative references are supported.
final value = JsonPointer(uri.fragment).read(json);
var schema = serializers.deserializeWith(serializer, value)!;
if (schema.id == null) {
schema = schema.rebuild((b) {
b.id = uri;
});
}

return schema;
}

BuiltList<Schema>? get oneOf;

Expand Down
35 changes: 28 additions & 7 deletions packages/dynamite/dynamite/lib/src/models/openapi/schema.g.dart

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

18 changes: 10 additions & 8 deletions packages/dynamite/dynamite/lib/src/openapi_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,27 @@ class OpenAPIBuilder implements Builder {
final outputId = inputId.changeExtension('.dart');

try {
final spec = switch (inputId.extension) {
'.json' => openapi.serializers.deserializeWith(
openapi.OpenAPI.serializer,
json.decode(await buildStep.readAsString(inputId)),
)!,
'.yaml' => checkedYamlDecode(
final json = switch (inputId.extension) {
'.json' => jsonDecode(await buildStep.readAsString(inputId)) as Map<String, dynamic>,
'.yaml' => checkedYamlDecode<Map<String, dynamic>>(
await buildStep.readAsString(inputId),
(m) => openapi.serializers.deserializeWith(openapi.OpenAPI.serializer, m)!,
(m) => m!.cast(),
),
_ => throw StateError('Openapi specs can only be yaml or json.'),
};

final spec = openapi.serializers.deserializeWith(
openapi.OpenAPI.serializer,
json,
)!;

final version = Version.parse(spec.version);
if (version < minSupportedVersion || version > maxSupportedVersion) {
throw Exception('Only OpenAPI between $minSupportedVersion and $maxSupportedVersion are supported.');
}

final config = buildConfig.configFor(inputId.path);
final state = State(config);
final state = State(config, json);

final output = Library((b) {
final analyzerIgnores = state.buildConfig.analyzerIgnores;
Expand Down
2 changes: 2 additions & 0 deletions packages/dynamite/dynamite/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ dependencies:
path: ^1.0.0
pub_semver: ^2.1.0
pubspec_parse: ^1.2.0
rfc_6901: ^0.2.0
source_helper: ^1.3.0
uri: ^1.0.0
version: ^3.0.0

dev_dependencies:
build_runner: ^2.4.9
built_value_generator: ^8.9.2
built_value_test: ^8.9.2
neon_lints:
git:
url: https://github.com/nextcloud/neon
Expand Down
68 changes: 68 additions & 0 deletions packages/dynamite/dynamite/test/openapi_spec_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// ignore_for_file: inference_failure_on_collection_literal

import 'package:built_value_test/matcher.dart';
import 'package:dynamite/src/models/openapi.dart';
import 'package:test/test.dart';

Expand All @@ -12,5 +15,70 @@ void main() {
schema = serializers.deserializeWith(Schema.serializer, value);
expect(schema, Schema());
});

test('Schema reference resolving', () {
const json = {
'openapi': '3.1.0',
'info': {
'title': 'reference resolving test',
'version': '0.0.1',
},
'components': {
'schemas': {
'SuperClass': {
'type': 'object',
'properties': {
'bool': {'type': 'boolean'},
'integer': {'type': 'integer'},
'double': {'type': 'number', 'format': 'float'},
'num': {'type': 'number'},
'string': {'type': 'string'},
'content-string': {
'type': 'string',
'nullable': true,
'contentMediaType': 'application/json',
'contentSchema': {'type': 'integer'},
},
'string-binary': {'type': 'string', 'format': 'binary'},
'list': {'type': 'array'},
'list-never': {'type': 'array', 'maxLength': 0},
'list-string': {
'type': 'array',
'items': {'type': 'string'},
},
},
},
'SubClass': {
r'$ref': '#/components/schemas/SuperClass',
},
},
},
'paths': {},
'tags': [],
};

final spec = serializers.deserializeWith(OpenAPI.serializer, json);
final superSchema = spec!.components!.schemas!['SuperClass']!;
expect(superSchema.type, equals(SchemaType.object));

final subSchema = spec.components!.schemas!['SubClass']!;
expect(
subSchema,
equalsBuilt(
Schema((b) {
b.ref = Uri.parse('#/components/schemas/SuperClass');
}),
),
);

expect(
subSchema.resolveRef(json),
equalsBuilt(
superSchema.rebuild((b) {
b.id = Uri.parse('#/components/schemas/SuperClass');
}),
),
);
});
});
}

0 comments on commit 23728ea

Please sign in to comment.