diff --git a/examples/drift/lib/mappr.auto_mappr.dart b/examples/drift/lib/mappr.auto_mappr.dart index 341c1c79..a31ec4b5 100644 --- a/examples/drift/lib/mappr.auto_mappr.dart +++ b/examples/drift/lib/mappr.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -198,12 +198,12 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$Todo__To___i3$TodoItem((model as _i2.Todo?)) as TARGET); + return (_map__i2$Todo_To__i3$TodoItem((model as _i2.Todo?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i3.TodoItem _map___i2$Todo__To___i3$TodoItem(_i2.Todo? input) { + _i3.TodoItem _map__i2$Todo_To__i3$TodoItem(_i2.Todo? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/example/lib/enum.auto_mappr.dart b/examples/example/lib/enum.auto_mappr.dart index 97bbe7e9..c1c90492 100644 --- a/examples/example/lib/enum.auto_mappr.dart +++ b/examples/example/lib/enum.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -211,7 +211,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserType__To___i2$PersonType((model as _i2.UserType?)) + return (_map__i2$UserType_To__i2$PersonType((model as _i2.UserType?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.Vehicle>() || @@ -221,7 +221,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$Vehicle__To___i2$Vehicle((model as _i2.Vehicle?)) + return (_map__i2$Vehicle_To__i2$Vehicle((model as _i2.Vehicle?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.Vehicle>() || @@ -231,13 +231,13 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$Vehicle__To___i2$VehicleX((model as _i2.Vehicle?)) + return (_map__i2$Vehicle_To__i2$VehicleX((model as _i2.Vehicle?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.PersonType _map___i2$UserType__To___i2$PersonType(_i2.UserType? input) { + _i2.PersonType _map__i2$UserType_To__i2$PersonType(_i2.UserType? input) { final model = input; if (model == null) { throw Exception( @@ -247,7 +247,7 @@ class $Mappr implements _i1.AutoMapprInterface { return _i2.PersonType.values.firstWhere((x) => x.name == model.name); } - _i2.Vehicle _map___i2$Vehicle__To___i2$Vehicle(_i2.Vehicle? input) { + _i2.Vehicle _map__i2$Vehicle_To__i2$Vehicle(_i2.Vehicle? input) { final model = input; if (model == null) { throw Exception( @@ -257,7 +257,7 @@ class $Mappr implements _i1.AutoMapprInterface { return _i2.Vehicle.values.firstWhere((x) => x.name == model.name); } - _i2.VehicleX _map___i2$Vehicle__To___i2$VehicleX(_i2.Vehicle? input) { + _i2.VehicleX _map__i2$Vehicle_To__i2$VehicleX(_i2.Vehicle? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/example/lib/equatable.auto_mappr.dart b/examples/example/lib/equatable.auto_mappr.dart index 5fa12e51..018399df 100644 --- a/examples/example/lib/equatable.auto_mappr.dart +++ b/examples/example/lib/equatable.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -197,13 +197,12 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/example/lib/nested.auto_mappr.dart b/examples/example/lib/nested.auto_mappr.dart index d7c10103..7bc1de54 100644 --- a/examples/example/lib/nested.auto_mappr.dart +++ b/examples/example/lib/nested.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -211,8 +211,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.NestedDto>() || sourceTypeOf == _typeOf<_i2.NestedDto?>()) && @@ -221,7 +220,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$NestedDto__To___i2$Nested((model as _i2.NestedDto?)) + return (_map__i2$NestedDto_To__i2$Nested((model as _i2.NestedDto?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.NestedTagDto>() || @@ -231,13 +230,13 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$NestedTagDto__To___i2$NestedTag( + return (_map__i2$NestedTagDto_To__i2$NestedTag( (model as _i2.NestedTagDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( @@ -246,29 +245,31 @@ class $Mappr implements _i1.AutoMapprInterface { } return _i2.User( id: model.id, - name: _map___i2$NestedDto__To___i2$Nested(model.name), + name: _map__i2$NestedDto_To__i2$Nested(model.name), nestedItems: model.nestedItems - .map<_i2.Nested>(_map___i2$NestedDto__To___i2$Nested) + .map<_i2.Nested>((value) => _map__i2$NestedDto_To__i2$Nested(value)) .toList(), nestedItemsNullable: model.nestedItemsNullable - ?.map<_i2.Nested>(_map___i2$NestedDto__To___i2$Nested) + ?.map<_i2.Nested>( + (value) => _map__i2$NestedDto_To__i2$Nested(value)) .toList() ?? <_i2.Nested>[], nestedItemsNullable2: model.nestedItemsNullable2 - .map<_i2.Nested>(_map___i2$NestedDto__To___i2$Nested) + .map<_i2.Nested>((value) => _map__i2$NestedDto_To__i2$Nested(value)) .toList(), itemsWithNullableItem: model.itemsWithNullableItem .whereNotNull() - .map<_i2.Nested>(_map___i2$NestedDto__To___i2$Nested) + .map<_i2.Nested>((value) => _map__i2$NestedDto_To__i2$Nested(value)) .toList(), itemsWithNullableItem2: model.itemsWithNullableItem2 - .map<_i2.Nested?>(_map___i2$NestedDto__To___i2$Nested_Nullable) + .map<_i2.Nested?>( + (value) => _map__i2$NestedDto_To__i2$Nested_Nullable(value)) .toList(), tag: null, ); } - _i2.Nested _map___i2$NestedDto__To___i2$Nested(_i2.NestedDto? input) { + _i2.Nested _map__i2$NestedDto_To__i2$Nested(_i2.NestedDto? input) { final model = input; if (model == null) { throw Exception( @@ -278,11 +279,11 @@ class $Mappr implements _i1.AutoMapprInterface { return _i2.Nested( id: model.id, name: model.name, - tag: _map___i2$NestedTagDto__To___i2$NestedTag(model.tag), + tag: _map__i2$NestedTagDto_To__i2$NestedTag(model.tag), ); } - _i2.NestedTag _map___i2$NestedTagDto__To___i2$NestedTag( + _i2.NestedTag _map__i2$NestedTagDto_To__i2$NestedTag( _i2.NestedTagDto? input) { final model = input; if (model == null) { @@ -293,8 +294,7 @@ class $Mappr implements _i1.AutoMapprInterface { return _i2.NestedTag(); } - _i2.Nested? _map___i2$NestedDto__To___i2$Nested_Nullable( - _i2.NestedDto? input) { + _i2.Nested? _map__i2$NestedDto_To__i2$Nested_Nullable(_i2.NestedDto? input) { final model = input; if (model == null) { return null; @@ -302,7 +302,7 @@ class $Mappr implements _i1.AutoMapprInterface { return _i2.Nested( id: model.id, name: model.name, - tag: _map___i2$NestedTagDto__To___i2$NestedTag(model.tag), + tag: _map__i2$NestedTagDto_To__i2$NestedTag(model.tag), ); } } diff --git a/examples/example/lib/nullable.auto_mappr.dart b/examples/example/lib/nullable.auto_mappr.dart index e78d8190..15855f48 100644 --- a/examples/example/lib/nullable.auto_mappr.dart +++ b/examples/example/lib/nullable.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i2; @@ -210,8 +210,7 @@ class $Mappr implements _i2.AutoMapprInterface { ), ) as TARGET); } - return (_map___i1$UserDto__To___i1$User((model as _i1.UserDto?)) - as TARGET); + return (_map__i1$UserDto_To__i1$User((model as _i1.UserDto?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i1.NestedDto>() || sourceTypeOf == _typeOf<_i1.NestedDto?>()) && @@ -220,13 +219,13 @@ class $Mappr implements _i2.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i1$NestedDto__To___i1$Nested((model as _i1.NestedDto?)) + return (_map__i1$NestedDto_To__i1$Nested((model as _i1.NestedDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i1.User _map___i1$UserDto__To___i1$User(_i1.UserDto? input) { + _i1.User _map__i1$UserDto_To__i1$User(_i1.UserDto? input) { final model = input; if (model == null) { return const _i1.User( @@ -241,12 +240,12 @@ class $Mappr implements _i2.AutoMapprInterface { id: model.id, tag: model.tag == null ? _i1.Mappr.defaultNested() - : _map___i1$NestedDto__To___i1$Nested(model.tag), - name: _map___i1$NestedDto__To___i1$Nested(model.name), + : _map__i1$NestedDto_To__i1$Nested(model.tag), + name: _map__i1$NestedDto_To__i1$Nested(model.name), ); } - _i1.Nested _map___i1$NestedDto__To___i1$Nested(_i1.NestedDto? input) { + _i1.Nested _map__i1$NestedDto_To__i1$Nested(_i1.NestedDto? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/example/lib/rename.auto_mappr.dart b/examples/example/lib/rename.auto_mappr.dart index 7c3cd1af..c1c49985 100644 --- a/examples/example/lib/rename.auto_mappr.dart +++ b/examples/example/lib/rename.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -197,13 +197,12 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/freezed/lib/freezed_example.auto_mappr.dart b/examples/freezed/lib/freezed_example.auto_mappr.dart index 2502248b..569c0bd9 100644 --- a/examples/freezed/lib/freezed_example.auto_mappr.dart +++ b/examples/freezed/lib/freezed_example.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -197,13 +197,13 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserInfo__To___i2$UserInfoCompanion( + return (_map__i2$UserInfo_To__i2$UserInfoCompanion( (model as _i2.UserInfo?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.UserInfoCompanion _map___i2$UserInfo__To___i2$UserInfoCompanion( + _i2.UserInfoCompanion _map__i2$UserInfo_To__i2$UserInfoCompanion( _i2.UserInfo? input) { final model = input; if (model == null) { diff --git a/examples/injectable/lib/mappr.auto_mappr.dart b/examples/injectable/lib/mappr.auto_mappr.dart index 961b48f8..eac09338 100644 --- a/examples/injectable/lib/mappr.auto_mappr.dart +++ b/examples/injectable/lib/mappr.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -197,13 +197,12 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( diff --git a/examples/json_serializable/lib/serializable.auto_mappr.dart b/examples/json_serializable/lib/serializable.auto_mappr.dart index 195b3a0e..9d58f240 100644 --- a/examples/json_serializable/lib/serializable.auto_mappr.dart +++ b/examples/json_serializable/lib/serializable.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -204,8 +204,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.ValueHolderDto>() || sourceTypeOf == _typeOf<_i2.ValueHolderDto?>()) && @@ -214,13 +213,13 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$ValueHolderDto__To___i2$ValueHolder( + return (_map__i2$ValueHolderDto_To__i2$ValueHolder( (model as _i2.ValueHolderDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( @@ -233,7 +232,7 @@ class $Mappr implements _i1.AutoMapprInterface { ); } - _i2.ValueHolder _map___i2$ValueHolderDto__To___i2$ValueHolder( + _i2.ValueHolder _map__i2$ValueHolderDto_To__i2$ValueHolder( _i2.ValueHolderDto? input) { final model = input; if (model == null) { diff --git a/examples/json_serializable/test/fixture/json_serializable.auto_mappr.dart b/examples/json_serializable/test/fixture/json_serializable.auto_mappr.dart index d93e4e43..9ebc5566 100644 --- a/examples/json_serializable/test/fixture/json_serializable.auto_mappr.dart +++ b/examples/json_serializable/test/fixture/json_serializable.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -204,8 +204,7 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } if ((sourceTypeOf == _typeOf<_i2.ValueHolderDto>() || sourceTypeOf == _typeOf<_i2.ValueHolderDto?>()) && @@ -214,13 +213,13 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$ValueHolderDto__To___i2$ValueHolder( + return (_map__i2$ValueHolderDto_To__i2$ValueHolder( (model as _i2.ValueHolderDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( @@ -233,7 +232,7 @@ class $Mappr implements _i1.AutoMapprInterface { ); } - _i2.ValueHolder _map___i2$ValueHolderDto__To___i2$ValueHolder( + _i2.ValueHolder _map__i2$ValueHolderDto_To__i2$ValueHolder( _i2.ValueHolderDto? input) { final model = input; if (model == null) { diff --git a/packages/auto_mappr/CHANGELOG.md b/packages/auto_mappr/CHANGELOG.md index 468fccd9..dadb50e1 100644 --- a/packages/auto_mappr/CHANGELOG.md +++ b/packages/auto_mappr/CHANGELOG.md @@ -1,8 +1,11 @@ [//]: # (## Unreleased) +## 2.0.0-beta2 +- Add type converters, use `converters` on `AutoMappr` or `MapType`. [#119](https://github.com/netglade/auto_mappr/pull/119) + ## 2.0.0-beta1 -- **Breaking**: Allow "absorbing" modules using `includes` on `@AutoMappr`. Previous `modules` is now `delegates`. -- **Breaking**: Remove shared AutoMappr builder that used PartBuilder, now `.auto_mappr.dart` is generated using LibraryBuilder. +- **Breaking**: Allow "absorbing" modules using `includes` on `@AutoMappr`. Previous `modules` is now `delegates`. [#117](https://github.com/netglade/auto_mappr/pull/117) +- **Breaking**: Remove shared AutoMappr builder that used PartBuilder, now `.auto_mappr.dart` is generated using LibraryBuilder. [#117](https://github.com/netglade/auto_mappr/pull/117) - Add a `reverse` option on `MapType`, which includes the reverse mapping. [#115](https://github.com/netglade/auto_mappr/pull/115) - Add a support for Dart 3 and Records feature. [#116](https://github.com/netglade/auto_mappr/pull/116) diff --git a/packages/auto_mappr/README.md b/packages/auto_mappr/README.md index 7536685b..f61ba4a0 100644 --- a/packages/auto_mappr/README.md +++ b/packages/auto_mappr/README.md @@ -45,6 +45,7 @@ Heavily inspired by [C# AutoMapper][auto_mapper_net_link]. - [Mapping from source](#mapping-from-source) - [Nullability handling](#nullability-handling) - [Forced non-nullable field for nullable source](#forced-non-nullable-field-for-nullable-source) + - [Type converters](#type-converters) - [Generics](#generics) - [Library import aliases](#library-import-aliases) - [Modules](#modules) @@ -638,6 +639,80 @@ final User user = mappr.convert(UserDto(...)); // delegates to user feature mapp That can be handy for example with dependency injection, so you can only provide one main mappr that can handle everything by delegating to other mapprs. +### Type converters + +`MapType`s are usefull for mappings that you can use from the **outside**. +AutoMappr mapps one object to another using constructors and fields +and you use the `.convert()` method on it. + +But when you need to customize an **inner** converting of types, +there are `TypeConverter`s that help you with that. +Boxing, `String` to `DateTime`, and stuff like that, `TypeConverter`s are your friend. +Note since type converters are only used internally, +you cannot in any way use them using the `.convert()` method. + +Global type converters are also "absorbed" from included modules. +To make the priority crystal clear: + +1. (local) type converters from `MapType`, in order +1. (global) type converters from `AutoMappr`, in order +1. (global included) type converters from `included` modules, in order + +Use `MapType` for most of the things. +Use `TypeConverter` only for cases that cannot be solve otherwise. + +Imagine you have an `UserDto` with a date in a `String` and a `User` model with a date in `DateTime`. +`MapType` will handle mapping of all the constructor parameters and other fields, +while `TypeConverter` will convert `String` to `DateTime`. + +```dart +@AutoMappr( + [ + MapType( + // MapType specific + converters: [ + TypeConverter() + ], + ), + ], + converters: [ + // or globally here + ], +) +class Mappr extends $Mappr { + static DateTime stringToDateTime(String source) { + // ... + } +} +``` + +You can also create methods that can convert "any" (to "any") using `Object`. +But if you work with type parameters (generics) +note that you have to either return the correct type with correct type parameters +or initialize it inside correctly. +It cannot be cast successfully otherwise. +Therefore if you need a method that converts `"any"` to `Value("any")`, +and to make it work for `int` and `String`, +it must look like one of these: + +```dart +static Value objectToValueObject(T source) { + return Value(source); +} + +static Value objectToValueObject2(Object source) { + if (source is int) { + return Value(source); + } + + if (source is String) { + return Value(source); + } + + return Value(source); +} +``` + ### 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/example/lib/mappr.auto_mappr.dart b/packages/auto_mappr/example/lib/mappr.auto_mappr.dart index 884c0f17..ab771404 100644 --- a/packages/auto_mappr/example/lib/mappr.auto_mappr.dart +++ b/packages/auto_mappr/example/lib/mappr.auto_mappr.dart @@ -4,7 +4,7 @@ // AutoMapprGenerator // ************************************************************************** -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unnecessary_cast, unused_local_variable // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as _i1; @@ -197,13 +197,12 @@ class $Mappr implements _i1.AutoMapprInterface { if (canReturnNull && model == null) { return null; } - return (_map___i2$UserDto__To___i2$User((model as _i2.UserDto?)) - as TARGET); + return (_map__i2$UserDto_To__i2$User((model as _i2.UserDto?)) as TARGET); } throw Exception('No ${model.runtimeType} -> $targetTypeOf mapping.'); } - _i2.User _map___i2$UserDto__To___i2$User(_i2.UserDto? input) { + _i2.User _map__i2$UserDto_To__i2$User(_i2.UserDto? input) { final model = input; if (model == null) { throw Exception( diff --git a/packages/auto_mappr/lib/src/builder/assignments/assignments.dart b/packages/auto_mappr/lib/src/builder/assignments/assignments.dart index 5df7a1c5..916cc770 100644 --- a/packages/auto_mappr/lib/src/builder/assignments/assignments.dart +++ b/packages/auto_mappr/lib/src/builder/assignments/assignments.dart @@ -3,3 +3,4 @@ export 'iterable_assignment_builder.dart'; export 'map_assignment_builder.dart'; export 'nested_object_assignment_builder.dart'; export 'record_assignment_builder.dart'; +export 'type_converter_builder.dart'; diff --git a/packages/auto_mappr/lib/src/builder/assignments/iterable_assignment_builder.dart b/packages/auto_mappr/lib/src/builder/assignments/iterable_assignment_builder.dart index 20692d70..c6fafe79 100644 --- a/packages/auto_mappr/lib/src/builder/assignments/iterable_assignment_builder.dart +++ b/packages/auto_mappr/lib/src/builder/assignments/iterable_assignment_builder.dart @@ -81,10 +81,13 @@ class IterableAssignmentBuilder extends AssignmentBuilderBase with NestedObjectM final targetListType = assignment.targetType.genericParameterTypeOrSelf; final sourceListType = assignment.sourceType!.genericParameterTypeOrSelf; - return assignNestedObject( + final body = assignNestedObject( assignment: assignment, source: sourceListType, target: targetListType, + convertMethodArgument: refer('value'), ); + + return refer('(value) => ${body.accept(EmitterHelper.current.emitter)}'); } } diff --git a/packages/auto_mappr/lib/src/builder/assignments/nested_object_mixin.dart b/packages/auto_mappr/lib/src/builder/assignments/nested_object_mixin.dart index ddd49796..3f7f984b 100644 --- a/packages/auto_mappr/lib/src/builder/assignments/nested_object_mixin.dart +++ b/packages/auto_mappr/lib/src/builder/assignments/nested_object_mixin.dart @@ -1,5 +1,5 @@ import 'package:analyzer/dart/element/type.dart'; -import 'package:auto_mappr/src/builder/assignments/assignment_builder_base.dart'; +import 'package:auto_mappr/src/builder/assignments/assignments.dart'; import 'package:auto_mappr/src/builder/methods/method_builder_base.dart'; import 'package:auto_mappr/src/extensions/dart_type_extension.dart'; import 'package:auto_mappr/src/helpers/emitter_helper.dart'; @@ -22,8 +22,11 @@ mixin NestedObjectMixin on AssignmentBuilderBase { Expression? convertMethodArgument, bool includeGenericTypes = false, }) { + final sourceOnModel = refer('model').property(assignment.sourceField!.displayName); + + // Source and target is the same. if (source.isSame(target)) { - return refer('model').property(assignment.sourceField!.displayName); + return sourceOnModel; } final nestedMapping = mapperConfig.findMapping( @@ -31,6 +34,21 @@ mixin NestedObjectMixin on AssignmentBuilderBase { target: target, ); + // Type converters. + final typeConvertersBuilder = TypeConverterBuilder( + assignment: assignment, + mapperConfig: mapperConfig, + mapping: mapping, + usedNullableMethodCallback: null, + convertMethodArgument: convertMethodArgument, + source: source, + target: target, + ); + if (typeConvertersBuilder.canAssign()) { + return typeConvertersBuilder.buildAssignment(); + } + + // Unknown mapping. if (nestedMapping == null) { final sourceName = assignment.sourceField?.getDisplayString(withNullability: true); @@ -66,7 +84,7 @@ mixin NestedObjectMixin on AssignmentBuilderBase { // name: 'test', // ) // : _map_NestedDto_To_Nested(model.name), - return refer('model').property(assignment.sourceField!.displayName).equalTo(literalNull).conditional( + return sourceOnModel.equalTo(literalNull).conditional( fieldMapping!.whenNullExpression!, convertCallExpression, ); 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 new file mode 100644 index 00000000..3540c004 --- /dev/null +++ b/packages/auto_mappr/lib/src/builder/assignments/type_converter_builder.dart @@ -0,0 +1,57 @@ +import 'package:analyzer/dart/element/type.dart'; +import 'package:auto_mappr/src/builder/assignments/assignment_builder_base.dart'; +import 'package:auto_mappr/src/extensions/executable_element_extension.dart'; +import 'package:auto_mappr/src/helpers/emitter_helper.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:collection/collection.dart'; + +class TypeConverterBuilder extends AssignmentBuilderBase { + final Expression? convertMethodArgument; + final DartType source; + final DartType target; + + const TypeConverterBuilder({ + required super.assignment, + required super.mapperConfig, + required super.mapping, + required super.usedNullableMethodCallback, + required this.convertMethodArgument, + required this.source, + required this.target, + }); + + @override + bool canAssign() { + return assignment.typeConverters.firstWhereOrNull( + (converter) => converter.canBeUsed( + 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)); + + // 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); + } + + final sourceEmitted = EmitterHelper.current.typeReferEmitted(type: source); + final targetEmitted = EmitterHelper.current.typeReferEmitted(type: target); + + // Tear-off. + return EmitterHelper.current + .refer(converter.converter.referCallString, converter.converter.library.identifier) + .asA(refer('$targetEmitted Function($sourceEmitted)')); + } +} diff --git a/packages/auto_mappr/lib/src/builder/auto_mappr_builder.dart b/packages/auto_mappr/lib/src/builder/auto_mappr_builder.dart index 449d5f45..892ce834 100644 --- a/packages/auto_mappr/lib/src/builder/auto_mappr_builder.dart +++ b/packages/auto_mappr/lib/src/builder/auto_mappr_builder.dart @@ -15,8 +15,9 @@ class AutoMapprBuilder { static const List fileIgnores = [ // ignore everything - // ignore: unnecessary-trailing-comma 'type=lint', + 'unused_local_variable', + 'unnecessary_cast', ]; const AutoMapprBuilder({ diff --git a/packages/auto_mappr/lib/src/builder/map_bodies/class_body_builder.dart b/packages/auto_mappr/lib/src/builder/map_bodies/class_body_builder.dart index dd372c83..16f1d6ad 100644 --- a/packages/auto_mappr/lib/src/builder/map_bodies/class_body_builder.dart +++ b/packages/auto_mappr/lib/src/builder/map_bodies/class_body_builder.dart @@ -141,6 +141,7 @@ class ClassBodyBuilder extends MapBodyBuilderBase { targetField: targetField, targetConstructorParam: constructorAssignment, fieldMapping: mapping.tryGetFieldMapping(targetField.displayName), + typeConverters: mapping.typeConverters, ); mappedTargetConstructorParams.add(sourceAssignment); diff --git a/packages/auto_mappr/lib/src/builder/methods/method_builder_base.dart b/packages/auto_mappr/lib/src/builder/methods/method_builder_base.dart index e0dc25d4..ac2d1a7c 100644 --- a/packages/auto_mappr/lib/src/builder/methods/method_builder_base.dart +++ b/packages/auto_mappr/lib/src/builder/methods/method_builder_base.dart @@ -33,7 +33,7 @@ abstract class MethodBuilderBase { required DartType target, required AutoMapprConfig config, }) => - '_map__${source.toConvertMethodName()}__To__${target.toConvertMethodName()}'; + '_map_${source.toConvertMethodName()}_To_${target.toConvertMethodName()}'; static String constructNullableConvertMethodName({ required DartType source, diff --git a/packages/auto_mappr/lib/src/builder/value_assignment_builder.dart b/packages/auto_mappr/lib/src/builder/value_assignment_builder.dart index b606802d..e94bd41a 100644 --- a/packages/auto_mappr/lib/src/builder/value_assignment_builder.dart +++ b/packages/auto_mappr/lib/src/builder/value_assignment_builder.dart @@ -75,6 +75,16 @@ class ValueAssignmentBuilder { target: assignment.targetType, convertMethodArgument: rightSide, ), + // Type converter for primitive type. + TypeConverterBuilder( + assignment: assignment, + mapperConfig: mapperConfig, + mapping: mapping, + usedNullableMethodCallback: usedNullableMethodCallback, + source: assignment.sourceType!, + target: assignment.targetType, + convertMethodArgument: rightSide, + ), ]; // Try to assign value using one of assignment builder. @@ -84,6 +94,9 @@ class ValueAssignmentBuilder { } } + // Primitive types (based on DartTypeExtension.isPrimitiveType) + + // When null. if (fieldMapping?.whenNullExpression != null) { return rightSide.ifNullThen(fieldMapping!.whenNullExpression!); } diff --git a/packages/auto_mappr/lib/src/generator/auto_mappr_generator.dart b/packages/auto_mappr/lib/src/generator/auto_mappr_generator.dart index 369d537e..bc73fcc8 100644 --- a/packages/auto_mappr/lib/src/generator/auto_mappr_generator.dart +++ b/packages/auto_mappr/lib/src/generator/auto_mappr_generator.dart @@ -10,23 +10,26 @@ import 'package:auto_mappr/src/helpers/emitter_helper.dart'; import 'package:auto_mappr/src/helpers/run_zoned_auto_mappr.dart'; import 'package:auto_mappr/src/models/auto_mappr_options.dart'; import 'package:auto_mappr/src/models/models.dart'; -import 'package:auto_mappr_annotation/auto_mappr_annotation.dart'; +import 'package:auto_mappr/src/models/type_converter.dart'; +import 'package:auto_mappr_annotation/auto_mappr_annotation.dart' as annotation; import 'package:build/build.dart'; import 'package:collection/collection.dart'; import 'package:source_gen/source_gen.dart'; /// Code generator to generate implemented mapping classes. -class AutoMapprGenerator extends GeneratorForAnnotation { +class AutoMapprGenerator extends GeneratorForAnnotation { final BuilderOptions builderOptions; // Constants for AutoMappr. static const String annotationAutoMappr = 'AutoMappr'; - static const String annotationFieldDelegates = 'delegates'; static const String annotationFieldMappers = 'mappers'; + static const String annotationFieldConverters = 'converters'; + static const String annotationFieldDelegates = 'delegates'; static const String annotationFieldIncludes = 'includes'; // Constants for MapType. static const String mapTypeFieldFields = 'fields'; + static const String mapTypeFieldConverters = 'converters'; static const String mapTypeFieldWhenSourceIsNull = 'whenSourceIsNull'; static const String mapTypeFieldConstructor = 'constructor'; static const String mapTypeFieldIgnoreFieldNull = 'ignoreFieldNull'; @@ -40,6 +43,9 @@ class AutoMapprGenerator extends GeneratorForAnnotation { static const String fieldFieldWhenNull = 'whenNull'; static const String fieldFieldIgnoreNull = 'ignoreNull'; + // Constants for TypeConverter. + static const String typeConverterFieldConverter = 'converter'; + const AutoMapprGenerator({required this.builderOptions}); @override @@ -59,13 +65,16 @@ class AutoMapprGenerator extends GeneratorForAnnotation { final mapprOptions = AutoMapprOptions.fromJson(builderOptions.config); final constant = annotation.objectValue; - final mappersList = constant.getField(annotationFieldMappers)!.toListValue()!; - final delegatesExpression = constant.getField(annotationFieldDelegates)!.toCodeExpression(); - final delegatesList = constant.getField(annotationFieldDelegates)!.toListValue(); - final includesList = constant.getField(annotationFieldIncludes)!.toListValue(); + final mappersList = constant.getField(annotationFieldMappers)?.toListValue() ?? []; + final convertersList = constant.getField(annotationFieldConverters)?.toListValue() ?? []; + final delegatesExpression = constant.getField(annotationFieldDelegates)?.toCodeExpression(); + final delegatesList = constant.getField(annotationFieldDelegates)?.toListValue() ?? []; + final includesList = constant.getField(annotationFieldIncludes)?.toListValue() ?? []; - final allMappers = [...mappersList, ..._mappersFromRecursiveIncludes(includesList: includesList ?? [])]; - final mappers = _processMappers(mappers: allMappers, element: element); + final allMappers = [...mappersList, ..._mappersFromRecursiveIncludes(includesList: includesList)]; + final allConverters = + _toTypeConverters([...convertersList, ..._convertersFromRecursiveIncludes(includesList: includesList)]); + final mappers = _processMappers(mappers: allMappers, globalConverters: allConverters, element: element); final duplicates = mappers.duplicates; if (duplicates.isNotEmpty) { @@ -80,7 +89,7 @@ class AutoMapprGenerator extends GeneratorForAnnotation { mappers: mappers, availableMappingsMacroId: element.library.identifier, modulesCode: delegatesExpression, - delegatesList: delegatesList ?? [], + delegatesList: delegatesList, mapprOptions: mapprOptions, ); @@ -94,6 +103,7 @@ class AutoMapprGenerator extends GeneratorForAnnotation { List _processMappers({ required List mappers, + required List globalConverters, required ClassElement element, }) { return mappers @@ -123,6 +133,7 @@ class AutoMapprGenerator extends GeneratorForAnnotation { } final fields = mapper.getField(mapTypeFieldFields)?.toListValue(); + final mapTypeConverters = mapper.getField(mapTypeFieldConverters)?.toListValue() ?? []; final whenSourceIsNull = mapper.getField(mapTypeFieldWhenSourceIsNull)?.toCodeExpression(); final constructor = mapper.getField(mapTypeFieldConstructor)?.toStringValue(); final ignoreFieldNull = mapper.getField(mapTypeFieldIgnoreFieldNull)?.toBoolValue(); @@ -136,7 +147,7 @@ class AutoMapprGenerator extends GeneratorForAnnotation { from: fieldMapping.getField(fieldFieldFrom)!.toStringValue(), customExpression: fieldMapping.getField(fieldFieldCustom)!.toCodeExpression(passModelArgument: true), whenNullExpression: fieldMapping.getField(fieldFieldWhenNull)!.toCodeExpression(), - ignoreNull: fieldMapping.getField(fieldFieldIgnoreNull)?.toBoolValue(), + ignoreNull: fieldMapping.getField(fieldFieldIgnoreNull)!.toBoolValue(), ), ) .toList(); @@ -145,7 +156,8 @@ class AutoMapprGenerator extends GeneratorForAnnotation { TypeMapping( source: sourceType, target: targetType, - fieldMappings: fieldMappings, + fieldMappings: fieldMappings ?? [], + typeConverters: [..._toTypeConverters(mapTypeConverters), ...globalConverters], whenSourceIsNullExpression: whenSourceIsNull, constructor: constructor, ignoreFieldNull: ignoreFieldNull, @@ -154,7 +166,7 @@ class AutoMapprGenerator extends GeneratorForAnnotation { TypeMapping( source: targetType, target: sourceType, - fieldMappings: fieldMappings, + fieldMappings: fieldMappings ?? [], whenSourceIsNullExpression: whenSourceIsNull, constructor: constructor, ignoreFieldNull: ignoreFieldNull, @@ -192,4 +204,48 @@ class AutoMapprGenerator extends GeneratorForAnnotation { return mappings; } + + List _toTypeConverters(List source) { + return source.map((converter) { + final converterType = converter.type! as ParameterizedType; + // ignore: avoid-unsafe-collection-methods, is checked by the TypeConverter interface + final sourceType = converterType.typeArguments.first; + // ignore: avoid-unsafe-collection-methods, is checked by the TypeConverter interface + final targetType = converterType.typeArguments.last; + + return TypeConverter( + source: sourceType, + target: targetType, + converter: converter.getField(typeConverterFieldConverter)!.toFunctionValue()!, + ); + }).toList(); + } + + /// Recursively returns all type converters from includes. + Iterable _convertersFromRecursiveIncludes({required List includesList}) { + final mappings = []; + + for (final include in includesList) { + // For each include locate AutoMappr annotation. + if (include.type?.element?.metadata + .firstWhereOrNull((data) => data.element?.displayName == annotationAutoMappr) + ?.computeConstantValue() + case final includeConstant?) { + // This -- converters. + final converters = includeConstant.getField(annotationFieldConverters)?.toListValue(); + if (converters != null) { + mappings.addAll(converters); + } + + // Bellow -- recursive includes. + final includes = includeConstant.getField(annotationFieldIncludes)?.toListValue(); + if (includes != null) { + // ignore: avoid-recursive-calls, it's handled + mappings.addAll(_convertersFromRecursiveIncludes(includesList: includes)); + } + } + } + + return mappings; + } } diff --git a/packages/auto_mappr/lib/src/helpers/di.dart b/packages/auto_mappr/lib/src/helpers/di.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/auto_mappr/lib/src/models/source_assignment.dart b/packages/auto_mappr/lib/src/models/source_assignment.dart index 3e6fc7a1..be9447f1 100644 --- a/packages/auto_mappr/lib/src/models/source_assignment.dart +++ b/packages/auto_mappr/lib/src/models/source_assignment.dart @@ -5,6 +5,7 @@ import 'package:analyzer/dart/element/type.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:auto_mappr/src/models/type_converter.dart'; import 'package:code_builder/code_builder.dart' show Expression, literalList, literalMap, literalNull, literalSet; class ConstructorAssignment { @@ -22,6 +23,8 @@ class SourceAssignment { final ConstructorAssignment? targetConstructorParam; final PropertyAccessorElement? targetField; + final List typeConverters; + /// Field mapping. /// /// Like filed 'name' from 'userName' etc. @@ -39,6 +42,7 @@ class SourceAssignment { required this.sourceField, required this.targetField, this.targetConstructorParam, + this.typeConverters = const [], this.fieldMapping, }); diff --git a/packages/auto_mappr/lib/src/models/type_converter.dart b/packages/auto_mappr/lib/src/models/type_converter.dart new file mode 100644 index 00000000..cbce2712 --- /dev/null +++ b/packages/auto_mappr/lib/src/models/type_converter.dart @@ -0,0 +1,43 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:equatable/equatable.dart'; + +class TypeConverter extends Equatable { + final DartType source; + final DartType target; + final ExecutableElement converter; + + @override + List get props => [source, target, converter]; + + const TypeConverter({ + required this.source, + required this.target, + required this.converter, + }); + + @override + String toString() { + final sourceX = source.getDisplayString(withNullability: true); + final targetX = target.getDisplayString(withNullability: true); + + return 'typeConverter $sourceX -> $targetX'; + } + + bool canBeUsed({ + required DartType mappingSource, + required DartType mappingTarget, + }) { + return _isConverterSubtype(source, mappingSource) && _isConverterSubtype(target, mappingTarget); + } + + bool _isConverterSubtype(DartType self, DartType other) { + // Same type. + if (self == other) return true; + + // Other must be subtype of self. + // + // When [self] is Object, it is always success. + return self.element?.library?.typeSystem.leastUpperBound(self, other) == self; + } +} diff --git a/packages/auto_mappr/lib/src/models/type_mapping.dart b/packages/auto_mappr/lib/src/models/type_mapping.dart index 2715595a..180859b6 100644 --- a/packages/auto_mappr/lib/src/models/type_mapping.dart +++ b/packages/auto_mappr/lib/src/models/type_mapping.dart @@ -3,6 +3,7 @@ import 'package:analyzer/dart/element/type.dart'; import 'package:auto_mappr/src/builder/methods/method_builder_base.dart'; import 'package:auto_mappr/src/models/auto_mappr_config.dart'; import 'package:auto_mappr/src/models/field_mapping.dart'; +import 'package:auto_mappr/src/models/type_converter.dart'; import 'package:code_builder/code_builder.dart'; import 'package:collection/collection.dart'; import 'package:equatable/equatable.dart'; @@ -10,7 +11,8 @@ import 'package:equatable/equatable.dart'; class TypeMapping extends Equatable { final InterfaceType source; final InterfaceType target; - final List? fieldMappings; + final List fieldMappings; + final List typeConverters; final Expression? whenSourceIsNullExpression; final String? constructor; final bool? ignoreFieldNull; @@ -23,6 +25,7 @@ class TypeMapping extends Equatable { source, target, fieldMappings, + typeConverters, whenSourceIsNullExpression, constructor, ignoreFieldNull, @@ -33,7 +36,8 @@ class TypeMapping extends Equatable { required this.source, required this.target, required this.ignoreFieldNull, - this.fieldMappings, + this.fieldMappings = const [], + this.typeConverters = const [], this.whenSourceIsNullExpression, this.constructor, }); @@ -55,11 +59,11 @@ class TypeMapping extends Equatable { return whenSourceIsNullExpression != null; } - bool hasFieldMapping(String field) => fieldMappings?.any((x) => x.field == field) ?? false; + bool hasFieldMapping(String field) => fieldMappings.any((x) => x.field == field); - FieldMapping? getFieldMapping(String field) => fieldMappings?.firstWhereOrNull((x) => x.field == field); + FieldMapping? getFieldMapping(String field) => fieldMappings.firstWhereOrNull((x) => x.field == field); - FieldMapping? tryGetFieldMapping(String field) => fieldMappings?.firstWhereOrNull((x) => x.field == field); + FieldMapping? tryGetFieldMapping(String field) => fieldMappings.firstWhereOrNull((x) => x.field == field); bool fieldShouldBeIgnored(String field) => hasFieldMapping(field) && (getFieldMapping(field)?.ignore ?? false); diff --git a/packages/auto_mappr/pubspec.yaml b/packages/auto_mappr/pubspec.yaml index 7c38a8ec..e7a20e1f 100644 --- a/packages/auto_mappr/pubspec.yaml +++ b/packages/auto_mappr/pubspec.yaml @@ -1,6 +1,6 @@ name: auto_mappr description: Code generation for mapping between different objects with ease. -version: 2.0.0-beta1 +version: 2.0.0-beta2 repository: https://github.com/netglade/auto_mappr issue_tracker: https://github.com/netglade/auto_mappr/issues screenshots: @@ -13,7 +13,7 @@ environment: dependencies: # TODO: Support analyzer same way as json_serializable https://github.com/google/json_serializable.dart/pull/1333 analyzer: ">=5.4.0 <7.0.0" - auto_mappr_annotation: ^2.0.0-beta1 + auto_mappr_annotation: ^2.0.0-beta2 build: ^2.3.1 built_collection: ^5.1.1 code_builder: ^4.4.0 diff --git a/packages/auto_mappr/test/integration/fixture/type_converters.dart b/packages/auto_mappr/test/integration/fixture/type_converters.dart new file mode 100644 index 00000000..3b893270 --- /dev/null +++ b/packages/auto_mappr/test/integration/fixture/type_converters.dart @@ -0,0 +1,163 @@ +// ignore_for_file: prefer-named-boolean-parameters, avoid_positional_boolean_parameters + +import 'package:auto_mappr_annotation/auto_mappr_annotation.dart'; +import 'package:equatable/equatable.dart'; + +import 'type_converters.auto_mappr.dart'; +import 'type_converters/module_alpha.dart'; + +@AutoMappr( + [ + MapType( + converters: [TypeConverter(Mappr.objectToString)], + ), + MapType( + converters: [TypeConverter>(Mappr.intToValueInt)], + ), + MapType(), + MapType(), + MapType(), + ], + converters: [ + TypeConverter>(Mappr.stringToValueString), + TypeConverter>(Mappr.objectToValueObject2), + ], + includes: [MapprAlpha()], +) +class Mappr extends $Mappr { + const Mappr(); + + static String objectToString(T source) { + return '--- $source ---'; + } + + static Value objectToValueObject(T source) { + return Value(source); + } + + static Value objectToValueObject2(Object source) { + if (source is int) { + return Value(source); + } + + return Value(source); + } + + static Value intToValueInt(int source) { + return Value(source); + } + + static Value stringToValueString(String source) { + return Value(source); + } +} + +// Primitives. + +class PrimitivesDto { + final int alpha; + final bool beta; + + const PrimitivesDto({required this.alpha, required this.beta}); +} + +class Primitives with EquatableMixin { + final String alpha; + final String beta; + + @override + List get props => [alpha, beta]; + + const Primitives(this.alpha, this.beta); +} + +// Fields. + +class NormalFieldDto { + final int xInt; + final String xString; + final bool normalBool; + + const NormalFieldDto({required this.xInt, required this.xString, required this.normalBool}); +} + +class NormalField with EquatableMixin { + final Value xInt; + final Value xString; + final bool normalBool; + + @override + List get props => [xInt, xString, normalBool]; + + const NormalField(this.xInt, this.xString, this.normalBool); +} + +// In list. + +class InListDto { + final List xInt; + final String xString; + final bool normalBool; + + const InListDto({required this.xInt, required this.xString, required this.normalBool}); +} + +class InList with EquatableMixin { + final List> xInt; + final Value xString; + final bool normalBool; + + @override + List get props => [xInt, xString, normalBool]; + + const InList(this.xInt, this.xString, this.normalBool); +} + +// In map. + +class InMapDto { + final Map alpha; + final Map beta; + final Map gama; + + const InMapDto({required this.alpha, required this.beta, required this.gama}); +} + +class InMap with EquatableMixin { + final Map, int> alpha; + final Map> beta; + final Map, Value> gama; + + @override + List get props => [alpha, beta, gama]; + + const InMap(this.alpha, this.beta, this.gama); +} + +// Includes. + +class IncludesDto { + final int alpha; + + const IncludesDto({required this.alpha}); +} + +class Includes with EquatableMixin { + final bool alpha; + + @override + List get props => [alpha]; + + const Includes(this.alpha); +} + +// Box + +class Value with EquatableMixin { + final T value; + + @override + List get props => [value]; + + const Value(this.value); +} diff --git a/packages/auto_mappr/test/integration/fixture/type_converters/module_alpha.dart b/packages/auto_mappr/test/integration/fixture/type_converters/module_alpha.dart new file mode 100644 index 00000000..31a52d59 --- /dev/null +++ b/packages/auto_mappr/test/integration/fixture/type_converters/module_alpha.dart @@ -0,0 +1,12 @@ +import 'package:auto_mappr_annotation/auto_mappr_annotation.dart'; + +import 'module_alpha.auto_mappr.dart'; + +@AutoMappr([], converters: [TypeConverter(MapprAlpha.intToBool)]) +class MapprAlpha extends $MapprAlpha { + const MapprAlpha(); + + static bool intToBool(int source) { + return source == 1; + } +} diff --git a/packages/auto_mappr/test/integration/type_converters_test.dart b/packages/auto_mappr/test/integration/type_converters_test.dart new file mode 100644 index 00000000..c4f3725c --- /dev/null +++ b/packages/auto_mappr/test/integration/type_converters_test.dart @@ -0,0 +1,75 @@ +import 'package:test/test.dart'; + +import 'fixture/type_converters.dart' as fixture; + +void main() { + late final fixture.Mappr mappr; + + setUpAll(() { + mappr = const fixture.Mappr(); + }); + + test('primitives', () { + const dto = fixture.PrimitivesDto(alpha: 123456, beta: false); + final converted = mappr.convert(dto); + + expect(converted, equals(const fixture.Primitives('--- 123456 ---', '--- false ---'))); + }); + + test('fields', () { + const dto = fixture.NormalFieldDto(xInt: 5, xString: 'Command', normalBool: true); + final converted = mappr.convert(dto); + + expect(converted, equals(const fixture.NormalField(fixture.Value(5), fixture.Value('Command'), true))); + }); + + test('list', () { + const dto = fixture.InListDto(xInt: [789, 5, 1], xString: 'Dunno', normalBool: false); + final converted = mappr.convert(dto); + + expect( + converted, + equals( + const fixture.InList( + [fixture.Value(789), fixture.Value(5), fixture.Value(1)], + fixture.Value('Dunno'), + false, + ), + ), + ); + }); + + test('map', () { + const dto = fixture.InMapDto( + alpha: {'aaa': 123, 'bbb': 456}, + beta: {'ccc': 789, 'ddd': 741}, + gama: {'eee': 852, 'fff': 963}, + ); + final converted = mappr.convert(dto); + + expect( + converted, + equals( + fixture.InMap( + {const fixture.Value('aaa'): 123, const fixture.Value('bbb'): 456}, + {'ccc': const fixture.Value(789), 'ddd': const fixture.Value(741)}, + {const fixture.Value('eee'): const fixture.Value(852), const fixture.Value('fff'): const fixture.Value(963)}, + ), + ), + ); + }); + + test('includes', () { + const dto = fixture.IncludesDto(alpha: 1); + final converted = mappr.convert(dto); + + expect(converted, equals(const fixture.Includes(true))); + }); + + test('includes 2', () { + const dto = fixture.IncludesDto(alpha: 2); + final converted = mappr.convert(dto); + + expect(converted, equals(const fixture.Includes(false))); + }); +} diff --git a/packages/auto_mappr_annotation/CHANGELOG.md b/packages/auto_mappr_annotation/CHANGELOG.md index d5074e29..0f2ce292 100644 --- a/packages/auto_mappr_annotation/CHANGELOG.md +++ b/packages/auto_mappr_annotation/CHANGELOG.md @@ -1,8 +1,11 @@ [//]: # (## Unreleased) +## 2.0.0-beta2 +- Add type converters, use `converters` on `AutoMappr` or `MapType`. [#119](https://github.com/netglade/auto_mappr/pull/119) + ## 2.0.0-beta1 -- Adhere to netglade_analysis 4.0.0 -- Add `reverse` option to `MapType`. +- Adhere to netglade_analysis 4.0.0. [#111](https://github.com/netglade/auto_mappr/pull/111) +- Add `reverse` option to `MapType`. [#115](https://github.com/netglade/auto_mappr/pull/115) ## 1.2.0 - Add `ignoreFieldNull` in `MapType` to force non-nullable field for when source's field is nullable diff --git a/packages/auto_mappr_annotation/lib/auto_mappr_annotation.dart b/packages/auto_mappr_annotation/lib/auto_mappr_annotation.dart index 22784c00..28ba0560 100644 --- a/packages/auto_mappr_annotation/lib/auto_mappr_annotation.dart +++ b/packages/auto_mappr_annotation/lib/auto_mappr_annotation.dart @@ -7,3 +7,4 @@ export 'src/field.dart'; export 'src/iterable_nullable_extension.dart'; export 'src/map_extension.dart'; export 'src/map_type.dart'; +export 'src/type_converter.dart'; diff --git a/packages/auto_mappr_annotation/lib/src/auto_mappr.dart b/packages/auto_mappr_annotation/lib/src/auto_mappr.dart index cac577f1..0a2b2aa2 100644 --- a/packages/auto_mappr_annotation/lib/src/auto_mappr.dart +++ b/packages/auto_mappr_annotation/lib/src/auto_mappr.dart @@ -1,22 +1,31 @@ import 'package:auto_mappr_annotation/src/auto_mappr_interface.dart'; import 'package:auto_mappr_annotation/src/map_type.dart'; +import 'package:auto_mappr_annotation/src/type_converter.dart'; /// Annotates class which will be used as base for generated mappr. final class AutoMappr { /// List of mapprs. final List> mappers; + /// List of type converters. + final List> converters; + /// List of mapprs that should be included to this mappr. /// /// Imagine copy-pasting mappings from included mappr to this one. - final List? includes; + final List includes; /// List of mapprs used as delegates. /// /// When current mappr cannot convert source to target, /// it lets modules in order try to convert it instead. - final List? delegates; + final List delegates; /// Constructs AutoMappr. - const AutoMappr(this.mappers, {this.includes, this.delegates}); + const AutoMappr( + this.mappers, { + this.converters = const [], + this.includes = const [], + this.delegates = const [], + }); } diff --git a/packages/auto_mappr_annotation/lib/src/map_type.dart b/packages/auto_mappr_annotation/lib/src/map_type.dart index 53ebb459..7292111c 100644 --- a/packages/auto_mappr_annotation/lib/src/map_type.dart +++ b/packages/auto_mappr_annotation/lib/src/map_type.dart @@ -1,10 +1,14 @@ import 'package:auto_mappr_annotation/src/field.dart'; +import 'package:auto_mappr_annotation/src/type_converter.dart'; /// Configured mapping from [SOURCE] to [TARGET]. final class MapType { /// Configuration for [TARGET]'s fields. final List fields; + /// List of type converters. + final List> converters; + /// Provides default value if SOURCE is null. /// /// Additionally if mapping an enum "unknown" values in SOURCE will be mapped @@ -35,6 +39,7 @@ final class MapType { /// Constructs mapping between [SOURCE] and [TARGET] types. const MapType({ this.fields = const [], + this.converters = const [], this.whenSourceIsNull, this.constructor, this.ignoreFieldNull, diff --git a/packages/auto_mappr_annotation/lib/src/type_converter.dart b/packages/auto_mappr_annotation/lib/src/type_converter.dart new file mode 100644 index 00000000..caca4217 --- /dev/null +++ b/packages/auto_mappr_annotation/lib/src/type_converter.dart @@ -0,0 +1,8 @@ +/// Configured type converter from [SOURCE] to [TARGET]. +final class TypeConverter { + /// A function that does type converter. + final TARGET Function(SOURCE source) converter; + + /// Constructs type converter between [SOURCE] and [TARGET]. + const TypeConverter(this.converter); +} diff --git a/packages/auto_mappr_annotation/pubspec.yaml b/packages/auto_mappr_annotation/pubspec.yaml index f30d5f06..b31761a6 100644 --- a/packages/auto_mappr_annotation/pubspec.yaml +++ b/packages/auto_mappr_annotation/pubspec.yaml @@ -1,6 +1,6 @@ name: auto_mappr_annotation description: Annotations for the auto_mappr code-generator of mapping between objects with ease. -version: 2.0.0-beta1 +version: 2.0.0-beta2 repository: https://github.com/netglade/auto_mappr issue_tracker: https://github.com/netglade/auto_mappr/issues