Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #17 Dynamically create inner schemas derived from spec schemas #20

Merged
merged 1 commit into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ build:
dart format lib
dart pub get

build-test:
@dart run build_runner build test --delete-conflicting-outputs && \
dart format --set-exit-if-changed test/tmp && \
dart analyze test/tmp --fatal-infos

docs:
dart doc --validate-links --output build/docs

Expand All @@ -28,7 +33,7 @@ test:
rm -rf test/tmp && \
clear && \
dart test && \
dart run build_runner build test --delete-conflicting-outputs
make build-test

format:
dart format \
Expand Down
1 change: 0 additions & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ include: package:lints/recommended.yaml

analyzer:
exclude:
- test/tmp/**
- lib/src/**/*.freezed.dart
- lib/src/**/*.g.dart
errors:
Expand Down
19 changes: 14 additions & 5 deletions lib/src/generators/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -621,22 +621,31 @@ class $clientName {
headerParams.add(hCode);
},
query: (p) {
String pType = p.schema.toDartType();
Object? pDefaultValue = p.schema.defaultValue;
String qCode = p.schema.maybeMap(
enumeration: (o) {
// Convert enum to string for query parameter code
return "'${p.name}': ${pName.camelCase}.name";
if (pType == 'String') {
return "'${p.name}': ${pName.camelCase}";
} else {
return "'${p.name}': ${pName.camelCase}.name";
}
},
orElse: () {
return "'${p.name}': ${pName.camelCase}";
},
);
String pType = p.schema.toDartType();
Object? pDefaultValue = p.schema.defaultValue;

// Handle enumeration default values
p.schema.mapOrNull(
enumeration: (value) {
if (pDefaultValue != null && p.schema.ref != null) {
pDefaultValue = '${p.schema.ref}.$pDefaultValue';
if (pDefaultValue != null) {
if (p.schema.ref != null && pType != 'String') {
pDefaultValue = '${p.schema.ref}.$pDefaultValue';
} else {
pDefaultValue = "'$pDefaultValue'";
}
}
},
);
Expand Down
73 changes: 58 additions & 15 deletions lib/src/generators/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,17 @@ class SchemaGenerator extends BaseGenerator {
// Will check for unions in schemas, responses, and requests
_searchForUnions();

// Get a list of all extra schemas
final extraSchemas =
spec.extraSchemaMapping.entries.fold([], (p, e) => p + e.value);

// Loop through all the schemas and write
for (final s in schemas.keys) {
// Skip extra schemas, they will be written to the same file as the parent schema
if (extraSchemas.contains(s)) {
continue;
}

final filename = s.snakeCase.replaceAll(RegExp(r'(?<=\w)_(?=\w_)'), '');
String name;
if (s == s.toUpperCase()) {
Expand Down Expand Up @@ -111,6 +120,19 @@ class SchemaGenerator extends BaseGenerator {
schemas[s]?.mapOrNull(
object: (schema) {
_writeObject(name: name, schema: schema);
// Check if there are any extra schemas to write in this file
if (spec.extraSchemaMapping.containsKey(s)) {
for (final extra in spec.extraSchemaMapping[s]!) {
schemas[extra]?.mapOrNull(
object: (extraSchema) {
_writeObject(name: extra, schema: extraSchema);
},
enumeration: (extraSchema) {
_writeEnumeration(name: extra, schema: extraSchema);
},
);
}
}
},
enumeration: (schema) {
_writeEnumeration(name: name, schema: schema);
Expand Down Expand Up @@ -202,16 +224,25 @@ class SchemaGenerator extends BaseGenerator {
final uClass = "$union.$uSubClass";

// Write each property of the union type
final schema = spec.components?.schemas?[s]?.mapOrNull(object: (o) => o);
var schema = spec.components?.schemas?[s]?.mapOrNull(object: (o) => o);

if (schema == null) {
throw Exception("\n\nUnion schema '$s' not found in components\n");
}
final props = Map<String, Schema>.from(schema.properties ?? {});

// Attempt to get the union value based on the key
String? unionValue = schema.properties?[unionKey]?.mapOrNull(
String? unionValue = props[unionKey]?.mapOrNull(
string: (s) => s.defaultValue,
enumeration: (s) => s.defaultValue,
enumeration: (s) {
// Convert the union enum to a string
props[unionKey] = Schema.string(
description: s.description,
defaultValue: s.defaultValue,
nullable: s.nullable,
);
return s.defaultValue;
},
);
if (unionValue != null) {
unionValues.add(unionValue);
Expand All @@ -231,16 +262,15 @@ class SchemaGenerator extends BaseGenerator {
""", mode: FileMode.append);

// Loop over all properties
final props = schema.properties;
final propNames = props?.keys.toList() ?? <String>[];
final propNames = props.keys.toList();
List<SchemaValidation> validations = [];
for (final propName in propNames) {
var dartName = propName.camelCase;
dartName = options.onSchemaPropertyName?.call(dartName) ?? dartName;
final v = _writeProperty(
name: dartName,
jsonName: propName,
property: props![propName]!,
property: props[propName]!,
required: schema.required?.contains(propName) ?? false,
);
if (v != null && (v.constants.isNotEmpty || v.operations.isNotEmpty)) {
Expand Down Expand Up @@ -592,15 +622,15 @@ class SchemaGenerator extends BaseGenerator {
} else if (nullable) {
unknownFallback = 'JsonKey.nullForUndefinedEnumValue';
}
c += getJsonKey(nullable: nullable);
if (unknownFallback != null && p.ref != null) {
if (jsonName != name) {
c +=
"@JsonKey(name: '$jsonName', unknownEnumValue: $unknownFallback,) ";
final cTrim = c.trim();
if (cTrim.endsWith(')')) {
c = cTrim.substring(0, cTrim.length - 1);
c += ", unknownEnumValue: $unknownFallback,) ";
} else {
c += "@JsonKey(unknownEnumValue: $unknownFallback) ";
c += '@JsonKey(unknownEnumValue: $unknownFallback,) ';
}
} else if (jsonName != name) {
c += getJsonKey(nullable: nullable);
}

if (p.ref == null) {
Expand All @@ -614,7 +644,7 @@ class SchemaGenerator extends BaseGenerator {
} else {
if (p.defaultValue != null && !required) {
final value = p.defaultValue!.replaceAll('.', '').camelCase;
c += "@Default(${p.ref}.$value) ";
c += "@Default(${p.ref}.${_safeEnumValue(value)}) ";
}
if (required) {
c += "required ";
Expand All @@ -629,6 +659,18 @@ class SchemaGenerator extends BaseGenerator {
return validation;
}

// ------------------------------------------
// METHOD: _safeEnumValue
// ------------------------------------------

String _safeEnumValue(String value) {
// Dart enums cannot start with a number
if (value.startsWith(RegExp(r'[0-9]'))) {
value = 'v$value';
}
return value.replaceAll('.', '').camelCase;
}

// ------------------------------------------
// METHOD: _writeEnumeration
// ------------------------------------------
Expand All @@ -655,13 +697,14 @@ class SchemaGenerator extends BaseGenerator {

// Loop through enum values
for (var v in values) {
// Write enum value
file.writeAsStringSync("""
@JsonValue('$v')
${v.replaceAll('.', '').camelCase},
${_safeEnumValue(v)},
""", mode: FileMode.append);
}

file.writeAsStringSync('}', mode: FileMode.append);
file.writeAsStringSync('}\n\n', mode: FileMode.append);
}

// ------------------------------------------
Expand Down
57 changes: 50 additions & 7 deletions lib/src/open_api/index.freezed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13011,6 +13011,11 @@ mixin _$OpenApi {
/// can be included in the array.
List<Security>? get security => throw _privateConstructorUsedError;

/// A mapping of any extra schemas that this generator created and the parent schema
/// that they were created from. This is used to improve the generated schema library
Map<String, List<String>> get extraSchemaMapping =>
throw _privateConstructorUsedError;

@optionalTypeArgs
TResult map<TResult extends Object?>(
TResult Function(_OpenApi value) $default,
Expand Down Expand Up @@ -13047,7 +13052,8 @@ abstract class $OpenApiCopyWith<$Res> {
Map<String, PathItem>? paths,
Map<String, PathItem>? webhooks,
Components? components,
List<Security>? security});
List<Security>? security,
Map<String, List<String>> extraSchemaMapping});

$InfoCopyWith<$Res> get info;
$ExternalDocsCopyWith<$Res>? get externalDocs;
Expand Down Expand Up @@ -13077,6 +13083,7 @@ class _$OpenApiCopyWithImpl<$Res, $Val extends OpenApi>
Object? webhooks = freezed,
Object? components = freezed,
Object? security = freezed,
Object? extraSchemaMapping = null,
}) {
return _then(_value.copyWith(
version: null == version
Expand Down Expand Up @@ -13119,6 +13126,10 @@ class _$OpenApiCopyWithImpl<$Res, $Val extends OpenApi>
? _value.security
: security // ignore: cast_nullable_to_non_nullable
as List<Security>?,
extraSchemaMapping: null == extraSchemaMapping
? _value.extraSchemaMapping
: extraSchemaMapping // ignore: cast_nullable_to_non_nullable
as Map<String, List<String>>,
) as $Val);
}

Expand Down Expand Up @@ -13172,7 +13183,8 @@ abstract class _$$OpenApiImplCopyWith<$Res> implements $OpenApiCopyWith<$Res> {
Map<String, PathItem>? paths,
Map<String, PathItem>? webhooks,
Components? components,
List<Security>? security});
List<Security>? security,
Map<String, List<String>> extraSchemaMapping});

@override
$InfoCopyWith<$Res> get info;
Expand Down Expand Up @@ -13203,6 +13215,7 @@ class __$$OpenApiImplCopyWithImpl<$Res>
Object? webhooks = freezed,
Object? components = freezed,
Object? security = freezed,
Object? extraSchemaMapping = null,
}) {
return _then(_$OpenApiImpl(
version: null == version
Expand Down Expand Up @@ -13245,6 +13258,10 @@ class __$$OpenApiImplCopyWithImpl<$Res>
? _value._security
: security // ignore: cast_nullable_to_non_nullable
as List<Security>?,
extraSchemaMapping: null == extraSchemaMapping
? _value._extraSchemaMapping
: extraSchemaMapping // ignore: cast_nullable_to_non_nullable
as Map<String, List<String>>,
));
}
}
Expand All @@ -13262,12 +13279,14 @@ class _$OpenApiImpl extends _OpenApi {
final Map<String, PathItem>? paths,
final Map<String, PathItem>? webhooks,
this.components,
final List<Security>? security})
final List<Security>? security,
final Map<String, List<String>> extraSchemaMapping = const {}})
: _servers = servers,
_tags = tags,
_paths = paths,
_webhooks = webhooks,
_security = security,
_extraSchemaMapping = extraSchemaMapping,
super._();

/// This string must be the version number of the
Expand Down Expand Up @@ -13388,9 +13407,24 @@ class _$OpenApiImpl extends _OpenApi {
return EqualUnmodifiableListView(value);
}

/// A mapping of any extra schemas that this generator created and the parent schema
/// that they were created from. This is used to improve the generated schema library
final Map<String, List<String>> _extraSchemaMapping;

/// A mapping of any extra schemas that this generator created and the parent schema
/// that they were created from. This is used to improve the generated schema library
@override
@JsonKey()
Map<String, List<String>> get extraSchemaMapping {
if (_extraSchemaMapping is EqualUnmodifiableMapView)
return _extraSchemaMapping;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(_extraSchemaMapping);
}

@override
String toString() {
return 'OpenApi(version: $version, info: $info, externalDocs: $externalDocs, jsonSchemaDialect: $jsonSchemaDialect, servers: $servers, tags: $tags, paths: $paths, webhooks: $webhooks, components: $components, security: $security)';
return 'OpenApi(version: $version, info: $info, externalDocs: $externalDocs, jsonSchemaDialect: $jsonSchemaDialect, servers: $servers, tags: $tags, paths: $paths, webhooks: $webhooks, components: $components, security: $security, extraSchemaMapping: $extraSchemaMapping)';
}

@override
Expand All @@ -13410,7 +13444,9 @@ class _$OpenApiImpl extends _OpenApi {
const DeepCollectionEquality().equals(other._webhooks, _webhooks) &&
(identical(other.components, components) ||
other.components == components) &&
const DeepCollectionEquality().equals(other._security, _security));
const DeepCollectionEquality().equals(other._security, _security) &&
const DeepCollectionEquality()
.equals(other._extraSchemaMapping, _extraSchemaMapping));
}

@override
Expand All @@ -13425,7 +13461,8 @@ class _$OpenApiImpl extends _OpenApi {
const DeepCollectionEquality().hash(_paths),
const DeepCollectionEquality().hash(_webhooks),
components,
const DeepCollectionEquality().hash(_security));
const DeepCollectionEquality().hash(_security),
const DeepCollectionEquality().hash(_extraSchemaMapping));

@JsonKey(ignore: true)
@override
Expand Down Expand Up @@ -13473,7 +13510,8 @@ abstract class _OpenApi extends OpenApi {
final Map<String, PathItem>? paths,
final Map<String, PathItem>? webhooks,
final Components? components,
final List<Security>? security}) = _$OpenApiImpl;
final List<Security>? security,
final Map<String, List<String>> extraSchemaMapping}) = _$OpenApiImpl;
const _OpenApi._() : super._();

@override
Expand Down Expand Up @@ -13537,6 +13575,11 @@ abstract class _OpenApi extends OpenApi {
/// can be included in the array.
List<Security>? get security;
@override

/// A mapping of any extra schemas that this generator created and the parent schema
/// that they were created from. This is used to improve the generated schema library
Map<String, List<String>> get extraSchemaMapping;
@override
@JsonKey(ignore: true)
_$$OpenApiImplCopyWith<_$OpenApiImpl> get copyWith =>
throw _privateConstructorUsedError;
Expand Down
Loading