Skip to content

Commit

Permalink
Support string and null as schema representation
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Aug 7, 2024
1 parent 5b70e02 commit 844f185
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 13 deletions.
52 changes: 52 additions & 0 deletions pkgs/dart_model/lib/src/dart_model.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ extension type Model.fromJson(Map<String, Object?> node) {
Map<String, Library> get uris => (node['uris'] as Map).cast();
}

/// A Dart type of the form `T?` for an inner type `T`
extension type NullableType.fromJson(Map<String, Object?> node) {
NullableType({
StaticType? inner,
}) : this.fromJson({
if (inner != null) 'inner': inner,
});
StaticType get inner => node['inner'] as StaticType;
}

/// Set of boolean properties.
extension type Properties.fromJson(Map<String, Object?> node) {
Properties({
Expand Down Expand Up @@ -131,3 +141,45 @@ extension type QualifiedName.fromJson(String string) {
extension type Query.fromJson(Map<String, Object?> node) {
Query() : this.fromJson({});
}

enum StaticTypeType {
unknown,
nullableType,
voidType;
}

extension type StaticType.fromJson(Map<String, Object?> node) {
static StaticType nullableType(NullableType nullableType) =>
StaticType.fromJson({'type': 'NullableType', 'value': nullableType.node});
static StaticType voidType(VoidType voidType) =>
StaticType.fromJson({'type': 'VoidType', 'value': voidType.string});
StaticTypeType get type {
switch (node['type'] as String) {
case 'NullableType':
return StaticTypeType.nullableType;
case 'VoidType':
return StaticTypeType.voidType;
default:
return StaticTypeType.unknown;
}
}

NullableType get asNullableType {
if (node['type'] != 'NullableType') {
throw StateError('Not a NullableType.');
}
return NullableType.fromJson(node['value'] as Map<String, Object?>);
}

VoidType get asVoidType {
if (node['type'] != 'VoidType') {
throw StateError('Not a VoidType.');
}
return VoidType.fromJson(node['value'] as String);
}
}

/// The type-hierarchy representation of the type `void`.
extension type VoidType.fromJson(String string) {
VoidType(String string) : this.fromJson(string);
}
66 changes: 53 additions & 13 deletions tool/dart_model_generator/lib/generate_dart_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,45 @@ String generate(String schemaJson,
final schema = JsonSchema.create(schemaJson,
refProvider: LocalRefProvider(dartModelJson ??
File('schemas/dart_model.schema.json').readAsStringSync()));
for (final def in schema.defs.entries) {
if (def.value.oneOf.isNotEmpty) {
result.add(_generateUnion(def.key, def.value.oneOf));
final allDefinitions = <String, JsonSchema>{
for (final def in schema.defs.entries) def.key: def.value,
};

for (final MapEntry(:key, :value) in allDefinitions.entries) {
if (value.oneOf.isNotEmpty) {
result.add(
_generateUnion(key, value.oneOf, allDefinitions: allDefinitions));
} else {
result.add(_generateExtensionType(def.key, def.value));
result.add(_generateExtensionType(key, value));
}
}
return DartFormatter().formatSource(SourceCode(result.join('\n'))).text;
}

/// The Dart type used to represent the JSON value for [definition].
///
/// This is most commonly a `Map<String, Object?>, but can also be a JSON
/// primitive type for simpler definitions.
String _dartJsonType(JsonSchema definition) {
return switch (definition.type) {
SchemaType.object => 'Map<String, Object?>',
SchemaType.string => 'String',
SchemaType.nullValue => 'Null',
_ => throw UnsupportedError('Unsupported type: ${definition.type}'),
};
}

/// Expands a [wrapper] expression evaluating to a generated extension type
/// instance to obtain the underlying JSON representation.
String _dartWrapperToJson(String wrapper, JsonSchema definition) {
return switch (definition.type) {
SchemaType.object => '$wrapper.node',
SchemaType.string => '$wrapper.string',
SchemaType.nullValue => 'null',
_ => throw UnsupportedError('Unsupported type: ${definition.type}'),
};
}

String _generateExtensionType(String name, JsonSchema definition) {
final result = StringBuffer();

Expand All @@ -55,6 +84,7 @@ String _generateExtensionType(String name, JsonSchema definition) {
final jsonType = switch (definition.type) {
SchemaType.object => 'Map<String, Object?> node',
SchemaType.string => 'String string',
SchemaType.nullValue => 'Null _',
_ => throw UnsupportedError('Schema type ${definition.type}.'),
};
if (definition.description != null) {
Expand Down Expand Up @@ -96,6 +126,8 @@ String _generateExtensionType(String name, JsonSchema definition) {
}
case SchemaType.string:
result.writeln('$name(String string) : this.fromJson(string);');
case SchemaType.nullValue:
result.writeln('$name(): this.fromJson(null);');
default:
throw UnsupportedError('Unsupported type: ${definition.type}');
}
Expand Down Expand Up @@ -142,32 +174,39 @@ String _generateExtensionType(String name, JsonSchema definition) {
/// and `asFoo` getters that "cast" to each type.
///
/// On the wire the union type is: `{"type": <name>, "value": <value>}`
String _generateUnion(String name, List<JsonSchema> oneOf) {
String _generateUnion(
String name,
List<JsonSchema> oneOf, {
required Map<String, JsonSchema> allDefinitions,
}) {
final result = StringBuffer();
final types =
oneOf.map((s) => _refName(s.schemaMap![r'$ref'] as String)).toList();
final unionEntries = oneOf
.map((s) => _refName(s.schemaMap![r'$ref'] as String))
.map((name) => (name, allDefinitions[name]!));

// TODO(davidmorgan): add description(s).
result
..writeln('enum ${name}Type {')
..writeln(['unknown'].followedBy(types.map(_firstToLowerCase)).join(', '))
..writeln(['unknown']
.followedBy(unionEntries.map((e) => _firstToLowerCase(e.$1)))
.join(', '))
..writeln(';}');

// TODO(davidmorgan): add description.
result.writeln('extension type $name.fromJson(Map<String, Object?> node) {');
for (final type in types) {
for (final (type, def) in unionEntries) {
final lowerType = _firstToLowerCase(type);
result
..writeln('static $name $lowerType($type $lowerType) =>')
..writeln('$name.fromJson({')
..writeln("'type': '$type',")
..writeln("'value': $lowerType.node});");
..writeln("'value': ${_dartWrapperToJson(lowerType, def)}});");
}

result
..writeln('${name}Type get type {')
..writeln("switch(node['type'] as String) {");
for (final type in types) {
for (final (type, _) in unionEntries) {
final lowerType = _firstToLowerCase(type);
result.writeln("case '$type': return ${name}Type.$lowerType;");
}
Expand All @@ -176,12 +215,13 @@ String _generateUnion(String name, List<JsonSchema> oneOf) {
..writeln('}')
..writeln('}');

for (final type in types) {
for (final (type, schema) in unionEntries) {
result
..writeln('$type get as$type {')
..writeln("if (node['type'] != '$type') "
"{ throw StateError('Not a $type.'); }")
..writeln("return $type.fromJson(node['value'] as Map<String, Object?>);")
..writeln(
"return $type.fromJson(node['value'] as ${_dartJsonType(schema)});")
..writeln('}');
}
result.writeln('}');
Expand Down

0 comments on commit 844f185

Please sign in to comment.