blob: 9ee90a0647e5eae659f0ffae2fb6d5f656ff3b75 [file] [log] [blame]
// 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.
/// Enables building up dynamic schemas with deep casts.
///
/// These schemas are built up "inside out" using [getAsTypedCast] to extract
/// the reified type argument from a [Cast], and pass that to another [Cast]
/// instance.
class Cast<T> {
const Cast();
/// All casts happen in this method, custom [Cast] implementations must
/// override this method, and no other methods.
T _cast(Object? from) => from is T
? from
: throw FailedCast(
'expected type $T but got type ${from.runtimeType} for: $from');
T cast(Object? from) => _cast(from);
Cast<T?> get nullable => NullableCast._(this);
/// Enables building up deeply nested generic types without requiring any
/// static knowledge or type inference.
///
/// Example usage:
///
/// Cast<dynamic> x = Cast<int>();
/// final y = x.getAsTypedCast(<T>(_) => Cast<Foo<T>>());
/// print(y.runtimeType); // Cast<Foo<int>>
R getAsTypedCast<R>(R Function<CastType>(Cast<CastType> self) callback) =>
callback<T>(this);
}
/// Wraps a [Cast] such that it also accepts `null`.
class NullableCast<T> extends Cast<T?> {
final Cast<T> _original;
@override
Cast<T?> get nullable => this;
NullableCast._(this._original);
@override
T? _cast(Object? from) {
if (from == null) return null;
return _original._cast(from);
}
}
/// Specialized [Cast] implementation for [Map]s which does deep casting of keys
/// and values.
class MapCast<K, V> extends Cast<Map<K, V>> {
final Cast<K> _key;
final Cast<V> _value;
const MapCast._(Cast<K> key, Cast<V> value)
: _key = key,
_value = value;
/// Builds a [MapCast] whose runtime type is built from the runtime type
/// arguments of [keyCast] and [valueCast].
///
/// The static type arguments are generally not interesting for these objects,
/// and so `<Object?, Object?>` is used to avoid unnecessary casts.
static MapCast<Object?, Object?> from(
Cast<Object?> keyCast, Cast<Object?> valueCast) =>
keyCast.getAsTypedCast(<KK>(keyCast) => valueCast.getAsTypedCast(
<VV>(valueCast) => MapCast<KK, VV>._(keyCast, valueCast)));
@override
Map<K, V> _cast(Object? from) {
if (from is! Map) {
return super._cast(from);
}
Map<K, V> result = {};
for (Object? key in from.keys) {
K newKey = _key._cast(key);
result[newKey] = _value._cast(from[key]);
}
return result;
}
}
/// Specialized [Cast] implementation for [List]s which does deep casting of
/// entries.
class ListCast<E> extends Cast<List<E>> {
final Cast<E> _entryCast;
const ListCast._(this._entryCast);
/// Builds a [ListCast] whose runtime type is built from the runtime type
/// arguments of [entryCast].
///
/// The static type argument is generally not interesting for these objects,
/// and so `<Object?>` is used to avoid unnecessary casts.
static ListCast<Object?> from(Cast entryCast) =>
entryCast.getAsTypedCast(ListCast._);
@override
List<E> _cast(Object? from) {
if (from is! List) {
return super._cast(from);
}
return List<E>.generate(from.length, (i) => _entryCast._cast(from[i]));
}
}
/// Specialized [Cast] implementation for [Set]s which does deep casting of
/// entries.
class SetCast<E> extends Cast<Set<E>> {
final Cast<E> _entryCast;
const SetCast._(this._entryCast);
/// Builds a [SetCast] whose runtime type is built from the runtime type
/// arguments of [entryCast].
///
/// The static type argument is generally not interesting for these objects,
/// and so `<Object?>` is used to avoid unnecessary casts.
static SetCast<Object?> from(Cast entryCast) =>
entryCast.getAsTypedCast(SetCast._);
@override
Set<E> _cast(Object? from) {
if (from is! Set) {
return super._cast(from);
}
return {
for (int i = 0; i < from.length; i++) _entryCast._cast(from.elementAt(i)),
};
}
}
/// A specific [Exception] for failed casts with information about the full path
/// to the failed cast.
class FailedCast implements Exception {
String message;
FailedCast(this.message);
@override
toString() => "Failed cast: $message";
}