diff --git a/packages/auto_mappr/README.md b/packages/auto_mappr/README.md index 841adaa..6115d43 100644 --- a/packages/auto_mappr/README.md +++ b/packages/auto_mappr/README.md @@ -715,6 +715,31 @@ static Value objectToValueObject2(Object source) { } ``` +To make it more clear, +here is a list of type converters +and for which +**source field type** -> **target field type** +combinations they can be used. +In these examples, +we've used `int` and `String` as a reference, +but the `TypeConverter`s can be adapted to various data types. + +- `TypeConverter` ... aka `String converter(int) => ...` + - source field `int` -> target field `String` + - source field `int` -> target field `String?` + - source field `int?` -> target field `String?`, when source field IS NOT null +- `TypeConverter` ... aka `String? converter(int) => ...` + - source field `int` -> target field `String?` + - source field `int?` -> target field `String?`, when source field IS NOT null +- `TypeConverter` ... aka `String converter(int?) => ...` + - source field `int` -> target field `String` + - source field `int?` -> target field `String` + - source field `int` -> target field `String?` + - source field `int?` -> target field `String?` +- `TypeConverter` ... aka `String? converter(int?) => ...` + - source field `int` -> target field `String?` + - source field `int?` -> target field `String?` + ### Reverse mapping When you want to create a bidirectional mapping (e.g. normal: source to target and reversed: target to source), diff --git a/packages/auto_mappr/build.yaml b/packages/auto_mappr/build.yaml index 2fe76f5..9d87d9d 100644 --- a/packages/auto_mappr/build.yaml +++ b/packages/auto_mappr/build.yaml @@ -4,6 +4,8 @@ targets: :auto_mappr: enabled: true generate_for: + # include: + # - test/integration/fixture/type_converters/required_to_nullable.dart exclude: - test/integration/error_fixture/** options: diff --git a/packages/auto_mappr/lib/src/builder/assignments/type_converter_builder.dart b/packages/auto_mappr/lib/src/builder/assignments/type_converter_builder.dart index 3540c00..7064d3a 100644 --- a/packages/auto_mappr/lib/src/builder/assignments/type_converter_builder.dart +++ b/packages/auto_mappr/lib/src/builder/assignments/type_converter_builder.dart @@ -23,27 +23,56 @@ class TypeConverterBuilder extends AssignmentBuilderBase { @override bool canAssign() { return assignment.typeConverters.firstWhereOrNull( - (converter) => converter.canBeUsed( - mappingSource: source, - mappingTarget: target, - ), + (converter) => + converter.canBeUsed( + mappingSource: source, + mappingTarget: target, + ) || + converter.canBeUsedNullable( + mappingSource: source, + mappingTarget: target, + ), ) != null; } @override Expression buildAssignment() { - final converter = assignment.typeConverters - // ignore: avoid-unsafe-collection-methods, checked by [canAssign] - .firstWhere((c) => c.canBeUsed(mappingSource: source, mappingTarget: target)); + final converter = assignment.typeConverters.firstWhere( + (c) => c.canBeUsed(mappingSource: source, mappingTarget: target), + // ignore: avoid-unsafe-collection-methods, checked by [canAssign] + orElse: () => assignment.typeConverters.firstWhere( + (c) => c.canBeUsedNullable( + mappingSource: source, + mappingTarget: target, + ), + ), + ); // Call. if (convertMethodArgument case final methodArgument?) { final targetRefer = EmitterHelper.current.typeRefer(type: target); - return EmitterHelper.current - .refer(converter.converter.referCallString, converter.converter.library.identifier) - .call([methodArgument]).asA(targetRefer); + // Can be used. + if (converter.canBeUsed(mappingSource: source, mappingTarget: target)) { + return EmitterHelper.current + .refer( + converter.converter.referCallString, + converter.converter.library.identifier, + ) + .call([methodArgument]).asA(targetRefer); + } + + // Can be used nullable. + return methodArgument.equalTo(literalNull).conditional( + literalNull, + EmitterHelper.current + .refer( + converter.converter.referCallString, + converter.converter.library.identifier, + ) + .call([methodArgument.nullChecked]).asA(targetRefer), + ); } final sourceEmitted = EmitterHelper.current.typeReferEmitted(type: source); @@ -51,7 +80,10 @@ class TypeConverterBuilder extends AssignmentBuilderBase { // Tear-off. return EmitterHelper.current - .refer(converter.converter.referCallString, converter.converter.library.identifier) + .refer( + converter.converter.referCallString, + converter.converter.library.identifier, + ) .asA(refer('$targetEmitted Function($sourceEmitted)')); } } diff --git a/packages/auto_mappr/lib/src/models/type_converter.dart b/packages/auto_mappr/lib/src/models/type_converter.dart index 809c66e..0ec4c78 100644 --- a/packages/auto_mappr/lib/src/models/type_converter.dart +++ b/packages/auto_mappr/lib/src/models/type_converter.dart @@ -33,6 +33,21 @@ class TypeConverter extends Equatable { _isConverterSubtype(target, mappingTarget, _ConversionRole.target); } + bool canBeUsedNullable({ + required DartType mappingSource, + required DartType mappingTarget, + }) { + // ignore: avoid-inverted-boolean-checks, this is better + if (!(mappingSource.isNullable && mappingTarget.isNullable)) return false; + + return canBeUsed( + mappingSource: mappingSource.element!.library!.typeSystem.promoteToNonNull(mappingSource), + mappingTarget: target.isNullable + ? mappingTarget + : mappingTarget.element!.library!.typeSystem.promoteToNonNull(mappingTarget), + ); + } + bool _isConverterSubtype(DartType converterType, DartType fieldType, _ConversionRole role) { // Same type. if (converterType == fieldType) return true; diff --git a/packages/auto_mappr/test/integration/fixture/type_converters/required_to_nullable.dart b/packages/auto_mappr/test/integration/fixture/type_converters/required_to_nullable.dart index 5949fa9..512c11f 100644 --- a/packages/auto_mappr/test/integration/fixture/type_converters/required_to_nullable.dart +++ b/packages/auto_mappr/test/integration/fixture/type_converters/required_to_nullable.dart @@ -10,6 +10,8 @@ import 'required_to_nullable.auto_mappr.dart'; [ // Object -> Object? MapType(), + // Object? -> Object?, when source is not null + MapType(), ], converters: [ TypeConverter?>(RequiredToNullableConverterMappr.stringToNullableValueString), diff --git a/packages/auto_mappr/test/integration/fixture/type_converters/required_to_required.dart b/packages/auto_mappr/test/integration/fixture/type_converters/required_to_required.dart index 3569650..9143577 100644 --- a/packages/auto_mappr/test/integration/fixture/type_converters/required_to_required.dart +++ b/packages/auto_mappr/test/integration/fixture/type_converters/required_to_required.dart @@ -10,6 +10,8 @@ import 'required_to_required.auto_mappr.dart'; MapType(), // Object -> Object? MapType(), + // Object? -> Object?, when source is not null + MapType(), ], converters: [ TypeConverter>(RequiredToRequiredConverterMappr.stringToValueString), diff --git a/packages/auto_mappr/test/integration/type_converters_test.dart b/packages/auto_mappr/test/integration/type_converters_test.dart index 8d6fa09..e413f3a 100644 --- a/packages/auto_mappr/test/integration/type_converters_test.dart +++ b/packages/auto_mappr/test/integration/type_converters_test.dart @@ -113,6 +113,13 @@ void main() { expect(converted, equals(const fixture.NullableOutput(fixture.Value('aaa')))); }); + + test('Object? -> Object?, when source is not null', () { + const input = fixture.NullableInput('aaa'); + final converted = mapprX.convert(input); + + expect(converted, equals(const fixture.NullableOutput(fixture.Value('aaa')))); + }); }); group('TypeConverter', () { @@ -128,6 +135,13 @@ void main() { expect(converted, equals(const fixture.NullableOutput(fixture.Value('aaa')))); }); + + test('Object? -> Object?, when source is not null', () { + const input = fixture.NullableInput('aaa'); + final converted = mapprX.convert(input); + + expect(converted, equals(const fixture.NullableOutput(fixture.Value('aaa')))); + }); }); group('TypeConverter', () { diff --git a/packages/auto_mappr_annotation/CHANGELOG.md b/packages/auto_mappr_annotation/CHANGELOG.md index 8a4b55a..d2841a3 100644 --- a/packages/auto_mappr_annotation/CHANGELOG.md +++ b/packages/auto_mappr_annotation/CHANGELOG.md @@ -1,5 +1,8 @@ [//]: # (## Unreleased) +## Unreleased +- When the source is not null, `TypeConverter` and `TypeConverter` now support mapping of source field `Object?` -> target field `Object?`. [#142](https://github.com/netglade/auto_mappr/pull/142) + ## 2.1.0 - Allow `TypeConverter` to have ``. [#130](https://github.com/netglade/auto_mappr/pull/130)