Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/auto_mappr/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[//]: # (## Unreleased)

## 2.13.0
- Added handling for classes with dynamic nested types.
- Added warning when mapping dynamic types without defined type converter. [#252](https://github.com/netglade/auto_mappr/pull/252)

## 2.12.0
- Removed (unused) dependency on `get_it` package.

Expand Down
1 change: 1 addition & 0 deletions packages/auto_mappr/dcm_global.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
version: "1.34.1"
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ mixin NestedObjectMixin on AssignmentBuilderBase {
final fieldMapping = mapping.tryGetFieldMapping(assignment.targetName);
// Source and target is the same.

if (source.isSame(target)) {
final shouldIgnoreNull = fieldMapping?.ignoreNull ??
if (source.isSame(target) || source.isDynamic || target.isDynamic) {
final shouldIgnoreNull =
fieldMapping?.ignoreNull ??
mapping.ignoreFieldNull ??
mapperConfig.mapprOptions.ignoreNullableSourceField ??
false;
Expand All @@ -40,10 +41,7 @@ mixin NestedObjectMixin on AssignmentBuilderBase {
return sourceOnModel;
}

final nestedMapping = mapperConfig.findMapping(
source: source,
target: target,
);
final nestedMapping = mapperConfig.findMapping(source: source, target: target);

// Type converters.
final typeConvertersBuilder = TypeConverterBuilder(
Expand Down Expand Up @@ -96,10 +94,7 @@ mixin NestedObjectMixin on AssignmentBuilderBase {
// name: 'test',
// )
// : _map_NestedDto_To_Nested(model.name),
return sourceOnModel.equalTo(literalNull).conditional(
fieldMapping!.whenNullExpression!,
convertCallExpression,
);
return sourceOnModel.equalTo(literalNull).conditional(fieldMapping!.whenNullExpression!, convertCallExpression);
}

// Generates code like:
Expand Down Expand Up @@ -130,14 +125,8 @@ mixin NestedObjectMixin on AssignmentBuilderBase {
// Otherwise use non-nullable.
final convertMethod = refer(
useNullableMethod
? MethodBuilderBase.constructNullableConvertMethodName(
source: source,
target: target,
)
: MethodBuilderBase.constructConvertMethodName(
source: source,
target: target,
),
? MethodBuilderBase.constructNullableConvertMethodName(source: source, target: target)
: MethodBuilderBase.constructConvertMethodName(source: source, target: target),
);

if (useNullableMethod) {
Expand All @@ -150,10 +139,7 @@ mixin NestedObjectMixin on AssignmentBuilderBase {
[convertMethodArgument],
{},
includeGenericTypes
? [
EmitterHelper.current.typeRefer(type: source),
EmitterHelper.current.typeRefer(type: target),
]
? [EmitterHelper.current.typeRefer(type: source), EmitterHelper.current.typeRefer(type: target)]
: [],
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:auto_mappr/src/builder/assignments/assignments.dart';
import 'package:auto_mappr/src/extensions/dart_type_extension.dart';
import 'package:auto_mappr/src/helpers/emitter_helper.dart';
import 'package:auto_mappr/src/models/models.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';

/// Decides how values are assigned.
Expand Down Expand Up @@ -121,6 +122,12 @@ class ValueAssignmentBuilder {
.nullChecked;
}

if (assignment.sourceType!.isDynamic && !assignment.targetType.isDynamic) {
log.warning("Casting dynamic source field '$assignment' when mapping '$mapping'. Consider providing a type converter or a custom mapping to avoid runtime casts.");

return rightSide.asA(refer(assignment.targetType.getDisplayString()));
}

return rightSide;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ extension DartTypeExtension on DartType {
return !isNullable;
}

bool get isDynamic {
return this is DynamicType;
}

/// Is special variant of integer list.
///
/// See `[Uint8List], [Uint16List], [Uint32List], [Uint64List]`.
Expand Down
29 changes: 11 additions & 18 deletions packages/auto_mappr/lib/src/models/type_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ class TypeConverter extends Equatable {
@override
List<Object?> get props => [source, target, converter];

const TypeConverter({
required this.source,
required this.target,
required this.converter,
});
const TypeConverter({required this.source, required this.target, required this.converter});

@override
String toString() {
Expand All @@ -25,18 +21,12 @@ class TypeConverter extends Equatable {
return 'typeConverter $sourceX -> $targetX';
}

bool canBeUsed({
required DartType mappingSource,
required DartType mappingTarget,
}) {
bool canBeUsed({required DartType mappingSource, required DartType mappingTarget}) {
return _isConverterSubtype(source, mappingSource, _ConversionRole.source) &&
_isConverterSubtype(target, mappingTarget, _ConversionRole.target);
}

bool canBeUsedNullable({
required DartType mappingSource,
required DartType mappingTarget,
}) {
bool canBeUsedNullable({required DartType mappingSource, required DartType mappingTarget}) {
// ignore: avoid-inverted-boolean-checks, this is better
if (!(mappingSource.isNullable && mappingTarget.isNullable)) return false;

Expand All @@ -51,7 +41,13 @@ class TypeConverter extends Equatable {
}

bool _isConverterSubtype(DartType converterType, DartType fieldType, _ConversionRole role) {
// Same type.
// Both types are dynamic, allow.
if (converterType.isDynamic && fieldType.isDynamic) return true;

// One of the types is dynamic, deny.
if (converterType.isDynamic != fieldType.isDynamic) return false;

// Same type, allow.
if (converterType == fieldType) return true;

// A TypeConverter with a non-nullable source converterType cannot handle a nullable source field
Expand All @@ -73,7 +69,4 @@ class TypeConverter extends Equatable {
}
}

enum _ConversionRole {
source,
target,
}
enum _ConversionRole { source, target }
2 changes: 1 addition & 1 deletion packages/auto_mappr/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: auto_mappr
description: Code generation for mapping between different objects with ease.
version: 2.12.0
version: 2.13.0
repository: https://github.com/netglade/auto_mappr
issue_tracker: https://github.com/netglade/auto_mappr/issues
screenshots:
Expand Down
100 changes: 100 additions & 0 deletions packages/auto_mappr/test/integration/dynamic_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:test/test.dart';

import 'fixture/dynamic.dart' as fixture;

void main() {
late final fixture.Mappr mappr;

setUpAll(() {
mappr = const fixture.Mappr();
});

group('using converter', () {
group('from dynamic to int', () {
test('string to int', () {
const source = fixture.DynamicDto(value: '5');
final converted = mappr.convert<fixture.DynamicDto, fixture.Int>(source);

expect(converted.value, equals(5));
});

test('int to int', () {
const source = fixture.DynamicDto(value: 5);
final converted = mappr.convert<fixture.DynamicDto, fixture.Int>(source);

expect(converted.value, equals(5));
});

test('unknown type to 0', () {
const source = fixture.DynamicDto();
final converted = mappr.convert<fixture.DynamicDto, fixture.Int>(source);

expect(converted.value, equals(0));
});
});

group('from int to dynamic', () {
test('to int', () {
const source = fixture.Int(value: 20);
final converted = mappr.convert<fixture.Int, fixture.Dynamic>(source);

expect(converted, isA<fixture.Dynamic>());
expect(converted.value, isA<int>());
expect(converted.value, equals(20));
});

test('to string', () {
const source = fixture.Int(value: 60);
final converted = mappr.convert<fixture.Int, fixture.Dynamic>(source);

expect(converted, isA<fixture.Dynamic>());
expect(converted.value, isA<String>());
expect(converted.value, equals('60'));
});
});

test('from dynamic to dynamic', () {
const source = fixture.DynamicDto(value: 'test');
final converted = mappr.convert<fixture.DynamicDto, fixture.Dynamic>(source);

expect(converted, isA<fixture.Dynamic>());
expect(converted.value, equals('test'));
});

test('from iterable dynamic', () {
const source = [fixture.DynamicDto(value: 5), fixture.DynamicDto(value: 1)];
final converted = mappr.convertIterable<fixture.DynamicDto, fixture.Int>(source);

expect(converted, isA<Iterable<fixture.Int>>());
expect(converted.length, equals(2));
});
});

group('without converter', () {
test('from dynamic to int', () {
const source = fixture.DynamicNoConverter(value: 5);
final converted = mappr.convert<fixture.DynamicNoConverter, fixture.IntNoConverter>(source);

expect(converted, isA<fixture.IntNoConverter>());
expect(converted.value, isA<int>());
expect(converted.value, equals(5));
});

test('from int to dynamic', () {
const source = fixture.IntNoConverter(value: 5);
final converted = mappr.convert<fixture.IntNoConverter, fixture.DynamicNoConverter>(source);

expect(converted, isA<fixture.DynamicNoConverter>());
expect(converted.value, isA<int>());
expect(converted.value, equals(5));
});

test('from iterable dynamic', () {
const source = [fixture.DynamicNoConverter(value: 5), fixture.DynamicNoConverter(value: 1)];
final converted = mappr.convertIterable<fixture.DynamicNoConverter, fixture.IntNoConverter>(source);

expect(converted, isA<Iterable<fixture.IntNoConverter>>());
expect(converted.length, equals(2));
});
});
}
59 changes: 59 additions & 0 deletions packages/auto_mappr/test/integration/fixture/dynamic.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// ignore_for_file: avoid-dynamic

import 'package:auto_mappr_annotation/auto_mappr_annotation.dart';

import 'dynamic.auto_mappr.dart';

@AutoMappr([
MapType<DynamicDto, Int>(converters: [DynamicConverter.dynamicToIntConverter]),
MapType<Int, Dynamic>(converters: [DynamicConverter.intToDynamicConverter]),
MapType<DynamicDto, Dynamic>(),
MapType<DynamicNoConverter, IntNoConverter>(),
MapType<IntNoConverter, DynamicNoConverter>(),
])
class Mappr extends $Mappr {
const Mappr();
}

class Dynamic {
final dynamic value;

const Dynamic({this.value});
}

class DynamicDto {
final dynamic value;

const DynamicDto({this.value});
}

class Int {
final int value;

const Int({required this.value});
}

class DynamicNoConverter {
final dynamic value;

const DynamicNoConverter({this.value});
}

class IntNoConverter {
final int value;

const IntNoConverter({required this.value});
}

abstract final class DynamicConverter {
static const intToDynamicConverter = TypeConverter(intToDynamic);
static const dynamicToIntConverter = TypeConverter(dynamicToInt);

static dynamic intToDynamic(int source) => source < 50 ? source : source.toString();

static int dynamicToInt(dynamic source) => switch (source) {
int() => source,
String() => int.parse(source),
_ => 0,
};
}
2 changes: 0 additions & 2 deletions packages/auto_mappr/test/integration/fixture/rename.dart
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,7 @@ class NoConstructorWithLateDto {

// ignore: must_be_immutable, ok in tests
class NoConstructorWithLate with EquatableMixin {
// ignore: avoid-unassigned-late-fields, will be set using Mappr
late int alpha;
// ignore: avoid-unassigned-late-fields, will be set using Mappr
late String beta;

@override
Expand Down
21 changes: 5 additions & 16 deletions packages/auto_mappr/test/integration/fixture/type_converters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@ import 'type_converters/module_alpha.dart';

@AutoMappr(
[
MapType<PrimitivesDto, Primitives>(
converters: [TypeConverter<Object, String>(Mappr.objectToString)],
),
MapType<NormalFieldDto, NormalField>(
converters: [TypeConverter<int, Value<int>>(Mappr.intToValueInt)],
),
MapType<PrimitivesDto, Primitives>(converters: [TypeConverter<Object, String>(Mappr.objectToString)]),
MapType<NormalFieldDto, NormalField>(converters: [TypeConverter<int, Value<int>>(Mappr.intToValueInt)]),
MapType<InListDto, InList>(),
MapType<InMapDto, InMap>(),
MapType<IncludesDto, Includes>(),
Expand Down Expand Up @@ -42,6 +38,7 @@ class Mappr extends $Mappr {

static Value<Object> objectToValueObject2(Object source) {
if (source is int) {
// ignore: avoid-inferrable-type-arguments, ok here
return Value<int>(source);
}

Expand Down Expand Up @@ -83,11 +80,7 @@ class NormalFieldDto {
final String xString;
final bool normalBool;

const NormalFieldDto({
required this.xInt,
required this.xString,
required this.normalBool,
});
const NormalFieldDto({required this.xInt, required this.xString, required this.normalBool});
}

class NormalField with EquatableMixin {
Expand All @@ -108,11 +101,7 @@ class InListDto {
final String xString;
final bool normalBool;

const InListDto({
required this.xInt,
required this.xString,
required this.normalBool,
});
const InListDto({required this.xInt, required this.xString, required this.normalBool});
}

class InList with EquatableMixin {
Expand Down
Loading