bc260f4d73
Cq-Include-Trybots: luci.dart.try:flutter-linux-try,flutter-web-try Bug: b/286184681 Change-Id: Ic9c56e6b3c95d514cc11e2f9b743ab44af2350ef Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/344020 Reviewed-by: Helin Shiah <helinx@google.com> Commit-Queue: Alexander Thomas <athom@google.com> Reviewed-by: Ben Konyi <bkonyi@google.com>
173 lines
5.3 KiB
Dart
173 lines
5.3 KiB
Dart
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:collection/collection.dart';
|
|
|
|
import 'json_schema.dart';
|
|
|
|
String _toDartType(String type) {
|
|
if (type.startsWith('#/definitions/')) {
|
|
return type.replaceAll('#/definitions/', '');
|
|
}
|
|
switch (type) {
|
|
case 'object':
|
|
return 'Map<String, Object?>';
|
|
case 'integer':
|
|
return 'int';
|
|
case 'number':
|
|
return 'num';
|
|
case 'string':
|
|
return 'String';
|
|
case 'boolean':
|
|
return 'bool';
|
|
case 'null':
|
|
return 'Null';
|
|
default:
|
|
return type;
|
|
}
|
|
}
|
|
|
|
String _toDartUnionType(List<String> types) {
|
|
const allLiteralTypes = {
|
|
'array',
|
|
'boolean',
|
|
'integer',
|
|
'null',
|
|
'number',
|
|
'object',
|
|
'string'
|
|
};
|
|
if (types.length == 7 && allLiteralTypes.containsAll(types)) {
|
|
return 'Object';
|
|
}
|
|
return 'Either${types.length}<${types.map(_toDartType).join(', ')}>';
|
|
}
|
|
|
|
extension JsonSchemaExtensions on JsonSchema {
|
|
JsonType typeFor(JsonType type) => type.dollarRef != null
|
|
// TODO(dantup): Do we need to support more than just refs to definitions?
|
|
? definitions[type.refName]!
|
|
: type;
|
|
|
|
Map<String, JsonType> propertiesFor(JsonType type,
|
|
{bool includeBase = true}) {
|
|
// Merge this types direct properties with anything from the included
|
|
// (allOf) types, but excluding those that come from the base class.
|
|
final baseType = type.baseType;
|
|
final includedBaseTypes =
|
|
(type.allOf ?? []).where((t) => includeBase || t != baseType);
|
|
final properties = {
|
|
for (final other in includedBaseTypes) ...propertiesFor(typeFor(other)),
|
|
...?type.properties,
|
|
};
|
|
|
|
return properties;
|
|
}
|
|
}
|
|
|
|
extension JsonTypeExtensions on JsonType {
|
|
String asDartType({bool isOptional = false}) {
|
|
final dartType = dollarRef != null
|
|
? _toDartType(dollarRef!)
|
|
: oneOf != null
|
|
? _toDartUnionType(oneOf!.map((item) => item.asDartType()).toList())
|
|
: type == null
|
|
? refName
|
|
: type!.valueEquals('array')
|
|
? 'List<${items!.asDartType()}>'
|
|
: type!.map(_toDartType, _toDartUnionType);
|
|
|
|
return isOptional ? '$dartType?' : dartType;
|
|
}
|
|
|
|
/// Whether this type can have any type of value (Object/dynamic/any).
|
|
bool get isAny => asDartType() == 'Object';
|
|
|
|
/// Whether this type represents a List.
|
|
bool get isList => type?.valueEquals('array') ?? false;
|
|
|
|
/// Whether this type is a simple value like `String`, `bool`, `int`.
|
|
bool get isSimpleValue => isSimple && asDartType() != 'Map<String, Object?>';
|
|
|
|
/// If this type is an alias to a simple value type, returns that type.
|
|
/// Otherwise, returns `null`.
|
|
JsonType? get aliasFor {
|
|
final targetType = dollarRef != null ? root.typeFor(this) : null;
|
|
if (targetType == null) {
|
|
return null;
|
|
}
|
|
return targetType.isSimpleValue ? targetType : null;
|
|
}
|
|
|
|
/// Whether this type is a simple type that needs no special handling for
|
|
/// deserialization (such as `String`, `bool`, `int`, `Map<String, Object?>`).
|
|
bool get isSimple {
|
|
const dartSimpleTypes = {
|
|
'bool',
|
|
'int',
|
|
'num',
|
|
'String',
|
|
'Map<String, Object?>',
|
|
'Null',
|
|
};
|
|
return type != null &&
|
|
dartSimpleTypes.contains(type!.map(_toDartType, _toDartUnionType));
|
|
}
|
|
|
|
/// Whether this type is a Union type using JSON schema's "oneOf" of where its
|
|
/// [type] is a list of types.
|
|
bool get isUnion =>
|
|
oneOf != null || type != null && type!.map((_) => false, (_) => true);
|
|
|
|
/// Whether this type is a reference to another spec type (using `dollarRef`).
|
|
bool get isSpecType => dollarRef != null;
|
|
|
|
/// Whether [propertyName] is a required for this type or its base types.
|
|
bool requiresField(String propertyName) {
|
|
if (required?.contains(propertyName) ?? false) {
|
|
return true;
|
|
}
|
|
if (allOf?.any((type) => root.typeFor(type).requiresField(propertyName)) ??
|
|
false) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// The name of the type that this one references.
|
|
String get refName => dollarRef!.replaceAll('#/definitions/', '');
|
|
|
|
/// The literal value of this type, if it can have only one.
|
|
///
|
|
/// These are represented in the spec using an enum with only a single value.
|
|
String? get literalValue => enumValues?.singleOrNull;
|
|
|
|
/// The base type for this type. Base types are inferred by a type using
|
|
/// allOf and the first listed type being a reference (dollarRef) to another
|
|
/// spec type.
|
|
JsonType? get baseType {
|
|
final all = allOf;
|
|
if (all != null && all.length > 1 && all.first.dollarRef != null) {
|
|
return all.first;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// The list of possible types allowed by this union.
|
|
///
|
|
/// May be represented using `oneOf` or a list of types in `type`.
|
|
List<JsonType> get unionTypes {
|
|
final types = oneOf ??
|
|
// Fabricate a union for types where "type" is an array of literal types:
|
|
// ['a', 'b']
|
|
type!.map(
|
|
(_) => throw 'unexpected non-union in isUnion condition',
|
|
(types) =>
|
|
types.map((t) => JsonType.fromJson(root, {'type': t})).toList(),
|
|
)!;
|
|
return types;
|
|
}
|
|
}
|