Skip to content

Commit

Permalink
Avoid allowCustom argument
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsenko committed Aug 2, 2024
1 parent 154fa13 commit 93e4d9d
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 57 deletions.
3 changes: 0 additions & 3 deletions packages/CHANGELOG.ejson.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

- `fromEJson<T>` now accepts a `defaultValue` argument that is returned if
`null` is passed as `ejson`.
- `fromEJson<T>` now accepts a `allowCustom` argument that can be used to specify
the custom decoders are allowed to be used. Defaults to `null`, which specify that
currently set value on the stack should be used. At top level the default is `true`.
- `register<T>` takes an optional `superTypes` argument to specify the super
types of `T` if needed.

Expand Down
73 changes: 28 additions & 45 deletions packages/ejson/lib/src/decoding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,19 @@ final _decoders = CombinedMapView([customDecoders, commonDecoders]);
///
/// Throws [InvalidEJson] if [ejson] is not valid for [T].
/// Throws [MissingDecoder] if no decoder is registered for [T].
T fromEJson<T>(EJsonValue ejson, {bool? allowCustom, T? defaultValue}) {
final oldAllowCustom = _allowCustom;
_allowCustom = allowCustom ?? _allowCustom;
try {
final type = T;
final nullable = type.isNullable;
if (!nullable && ejson == null && defaultValue != null) return defaultValue;
final decoder = nullable ? _decodeNullable : _decoders[type.base];
if (decoder == null) {
throw MissingDecoder._(ejson, type);
}
final args = nullable ? [type.nonNull] : type.args;
if (args.isEmpty) {
return decoder(ejson) as T; // minor optimization
}
return decoder.callWith(typeArguments: args, parameters: [ejson]) as T;
} finally {
_allowCustom = oldAllowCustom;
T fromEJson<T>(EJsonValue ejson, {T? defaultValue}) {
final type = T;
final nullable = type.isNullable;
if (ejson == null && defaultValue != null) return defaultValue;
final decoder = nullable ? _decodeNullable : _decoders[type.base];
if (decoder == null) {
throw MissingDecoder._(ejson, type);
}
final args = nullable ? [type.nonNull] : type.args;
if (args.isEmpty) {
return decoder(ejson) as T; // minor optimization
}
return decoder.callWith(typeArguments: args, parameters: [ejson]) as T;
}

/// Parses [source] to [EJsonValue] and convert to type [T].
Expand Down Expand Up @@ -116,37 +110,23 @@ dynamic _decodeAny(EJsonValue ejson) {
{'\$oid': _} => _decodeObjectId(ejson),
{'\$binary': {'base64': _, 'subType': '04'}} => _decodeUuid(ejson),
{'\$binary': _} => _decodeBinary(ejson),
List<dynamic> _ => _decodeArray<dynamic>(ejson),
Set<dynamic> _ => _decodeSet<dynamic>(ejson),
Map<dynamic, dynamic> _ => _tryDecodeCustomIfAllowed(ejson) ?? _decodeDocument<String, dynamic>(ejson), // other maps goes last!!
List _ => _decodeArray<dynamic>(ejson),
Set _ => _decodeSet<dynamic>(ejson),
Map _ => _decodeDocument<String, dynamic>(ejson), // other maps goes last!!
_ => raiseInvalidEJson<dynamic>(ejson),
};
}

bool _allowCustom = true;
dynamic _tryDecodeCustomIfAllowed(EJsonValue ejson) {
if (_allowCustom) {
for (final decoder in customDecoders.values) {
try {
return decoder(ejson);
} catch (_) {
// ignore
}
}
}
return null;
}

List<T> _decodeArray<T>(EJsonValue ejson) {
return switch (ejson) {
Iterable<dynamic> i => i.map((ejson) => fromEJson<T>(ejson)).toList(),
Iterable i => i.map((ejson) => fromEJson<T>(ejson)).toList(),
_ => raiseInvalidEJson(ejson),
};
}

Set<T> _decodeSet<T>(EJsonValue ejson) {
return switch (ejson) {
Iterable<dynamic> i => i.map((ejson) => fromEJson<T>(ejson)).toSet(),
Iterable i => i.map((ejson) => fromEJson<T>(ejson)).toSet(),
_ => raiseInvalidEJson(ejson),
};
}
Expand All @@ -167,20 +147,23 @@ DateTime _decodeDate(EJsonValue ejson) {
}

DBRef<KeyT> _decodeDBRef<KeyT>(EJsonValue ejson) {
return switch (ejson) {
{'\$ref': String collection, '\$id': EJsonValue id} => DBRef<KeyT>(collection, fromEJson<KeyT>(id)),
_ => raiseInvalidEJson(ejson),
};
switch (ejson) {
case {'\$ref': String collection, '\$id': EJsonValue id}:
KeyT key = fromEJson(id);
return DBRef.new.callWith(parameters: [collection, key], typeArguments: [key.runtimeType]) as DBRef<KeyT>;
default:
return raiseInvalidEJson(ejson);
}
}

Defined<T> _decodeDefined<T>(EJsonValue ejson) {
if (ejson case {'\$undefined': 1}) return raiseInvalidEJson(ejson);
return Defined<T>(fromEJson<T>(ejson));
return Defined(fromEJson(ejson));
}

Map<K, V> _decodeDocument<K, V>(EJsonValue ejson) {
return switch (ejson) {
Map<dynamic, dynamic> m => m.map((key, value) => MapEntry(key as K, fromEJson<V>(value))),
Map m => m.map((key, value) => MapEntry(key as K, fromEJson(value))),
_ => raiseInvalidEJson(ejson),
};
}
Expand Down Expand Up @@ -263,14 +246,14 @@ Symbol _decodeSymbol(EJsonValue ejson) {

Undefined<T> _decodeUndefined<T>(EJsonValue ejson) {
return switch (ejson) {
{'\$undefined': 1} => Undefined<T>(),
{'\$undefined': 1} => Undefined(),
_ => raiseInvalidEJson(ejson),
};
}

UndefinedOr<T> _decodeUndefinedOr<T>(EJsonValue ejson) {
return switch (ejson) {
{'\$undefined': 1} => Undefined<T>(),
{'\$undefined': 1} => Undefined(),
_ => _decodeDefined(ejson),
};
}
Expand Down
2 changes: 2 additions & 0 deletions packages/ejson/lib/src/types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ enum EJsonType {
/// and [MinKey](https://www.mongodb.com/docs/manual/reference/mongodb-extended-json/#mongodb-bsontype-MinKey)
enum BsonKey { min, max }

/// See [DBRef](https://github.com/mongodb/specifications/blob/master/source/dbref.md)
/// This is not technically a BSON type, but a common convention.
final class DBRef<KeyT> {
// Do we need to support the database name?
final String collection;
Expand Down
34 changes: 33 additions & 1 deletion packages/ejson/test/ejson_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,41 @@ import 'package:ejson/ejson.dart';
import 'package:objectid/objectid.dart';
import 'package:sane_uuid/uuid.dart';
import 'package:test/test.dart';
import 'package:type_plus/type_plus.dart';

import 'person.dart';

bool _canDecodeAny<T>([Type? type]) {
commonDecoders; // ensure common types has been registered;
type ??= T;
if (type.isNullable) return _canDecodeAny(type.base);
if ([
dynamic,
Null,
Object,
bool,
double,
int,
num,
String,
DateTime,
BsonKey,
Symbol,
ObjectId,
Uuid,
Uint8List,
].contains(type)) return true;
if ([
List,
Set,
Map,
DBRef,
Undefined,
UndefinedOr,
].contains(type.base)) return type.args.every(_canDecodeAny);
return false;
}

void _testCase<T>(T value, EJsonValue expected) {
test('encode from $value of type $T', () {
expect(toEJson(value), expected);
Expand Down Expand Up @@ -46,7 +78,7 @@ void _testCase<T>(T value, EJsonValue expected) {
expect(() => fromEJson(expected), returnsNormally);
});

if (value is! Defined && value is! DBRef) {
if (_canDecodeAny<T>()) {
test('roundtrip $value of type $T as dynamic', () {
// no <T> here, so dynamic
expect(fromEJson(toEJson(value)), value);
Expand Down
7 changes: 0 additions & 7 deletions packages/ejson_generator/test/ctor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,6 @@ void _testCase<T>(T value, EJsonValue expected) {
test('roundtrip $expected as $T', () {
expect(toEJson(fromEJson<T>(expected)), expected);
});

test('roundtrip $expected of type $T as dynamic', () {
// no <T> here, so dynamic
final decoded = fromEJson<dynamic>(expected);
expect(decoded, isA<T>());
expect(toEJson(decoded), expected);
});
}

void main() {
Expand Down
2 changes: 1 addition & 1 deletion packages/realm_dart/lib/src/handles/native/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ EJsonValue encodeRealmValue(RealmValue value) {
}

RealmValue decodeRealmValue(EJsonValue ejson) {
Object? decoded = fromEJson(ejson, allowCustom: false);
final decoded = fromEJson<dynamic>(ejson);
if (decoded is DBRef) {
final t = TypePlus.fromId(decoded.collection);
final o = RealmObjectBase.createObject(t, null);
Expand Down

0 comments on commit 93e4d9d

Please sign in to comment.