Skip to content

Commit

Permalink
cleanup and bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
DevNico committed May 2, 2023
1 parent 9920fb7 commit dabc193
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 60 deletions.
18 changes: 7 additions & 11 deletions packages/auto_mappr/lib/src/builder/value_assignment_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ class ValueAssignmentBuilder {
return fieldMapping.apply(assignment);
}

final assignNestedObject = !assignment.targetType.isPrimitiveType;

if (assignment.shouldAssignIterable()) {
return _assignIterableValue(assignment);
}
Expand All @@ -51,20 +49,12 @@ class ValueAssignmentBuilder {
final rightSide =
refer(sourceField.isStatic ? '${sourceField.enclosingElement.name}' : 'model').property(sourceField.name);

if (assignNestedObject) {
return _assignNestedObject(
source: assignment.sourceType!,
target: assignment.targetType,
assignment: assignment,
convertMethodArgument: rightSide,
);
}

if (fieldMapping?.hasWhenNullDefault() ?? false) {
return rightSide.ifNullThen(fieldMapping!.whenNullExpression!);
}

return rightSide;
}

Expression _assignIterableValue(SourceAssignment assignment) {
Expand Down Expand Up @@ -212,7 +202,13 @@ class ValueAssignmentBuilder {
bool includeGenericTypes = false,
}) {
if (source.isSame(target)) {
return refer('model').property(assignment.sourceField!.displayName);
final expression = convertMethodArgument ?? refer('model').property(assignment.sourceField!.displayName);

if(assignment.fieldMapping?.hasWhenNullDefault() ?? false) {
return expression.ifNullThen(assignment.fieldMapping!.whenNullExpression!);
}

return expression;
}

final nestedMapping = mapperConfig.findMapping(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ extension DartObjectExtension on DartObject {
final target = function.returnType;

return TypeConversion(
source: source,
target: target,
sourceType: source,
targetType: target,
convertExpression: refer(function.referCallString),
field: getField('field')?.toStringValue(),
);
Expand Down
16 changes: 13 additions & 3 deletions packages/auto_mappr/lib/src/extensions/dart_type_extension.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:auto_mappr/src/extensions/element_extension.dart';
Expand All @@ -19,6 +20,7 @@ extension DartTypeExtension on DartType {
bool isSame(
DartType other, {
bool withNullability = false,
bool allowSubtypes = false,
}) {
// Not the same type of type.
if ((this is InterfaceType) ^ (other is InterfaceType)) {
Expand All @@ -31,17 +33,25 @@ extension DartTypeExtension on DartType {
// return element!.thisOrAncestorMatching((p0) => p0 == other.element) != null;
// }

// Name matches.
// Type matches.
final thisName = getDisplayString(withNullability: withNullability);
final otherName = other.getDisplayString(withNullability: withNullability);
final isSameName = thisName == otherName;
var isTypeMatch = thisName == otherName;

if(!isTypeMatch && allowSubtypes) {
if(other.element != null && other.element is InterfaceElement) {
final instanceOf = asInstanceOf(other.element! as InterfaceElement);

isTypeMatch = instanceOf != null && (element?.library?.typeSystem.isAssignableTo(instanceOf, other) ?? false);
}
}

// Library matches.
final thisLibrary = element?.library?.identifier;
final otherLibrary = other.element?.library?.identifier;
final isSameLibrary = thisLibrary == otherLibrary;

final isSameExceptNullability = isSameName && isSameLibrary;
final isSameExceptNullability = isTypeMatch && isSameLibrary;

if (!withNullability) {
return isSameExceptNullability;
Expand Down
56 changes: 35 additions & 21 deletions packages/auto_mappr/lib/src/models/type_conversion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,63 @@ import 'package:auto_mappr/src/extensions/dart_type_extension.dart';
import 'package:auto_mappr/src/models/source_assignment.dart';
import 'package:code_builder/code_builder.dart';
import 'package:equatable/equatable.dart';
import 'package:source_gen/source_gen.dart';

class TypeConversion extends Equatable {
final DartType source;
final DartType target;
final DartType sourceType;
final DartType targetType;
final Expression convertExpression;
final String? field;

@override
List<Object?> get props => [source, target, convertExpression, field];
List<Object?> get props => [sourceType, targetType, convertExpression, field];

const TypeConversion({
required this.source,
required this.target,
required this.sourceType,
required this.targetType,
required this.convertExpression,
required this.field,
});

/// Wether this type conversion applies to the the given [SourceAssignment].
bool matchesAssignment(SourceAssignment assignment) {
final typeMatches = assignment.sourceType != null &&
assignment.sourceType!.isSame(source) &&
assignment.targetType.isSame(target);
assignment.sourceType!.isSame(sourceType, allowSubtypes: true) &&
assignment.targetType.isSame(targetType);
final nameMatches = field == null || assignment.sourceName == field;

return typeMatches && nameMatches;
}

Expression apply(SourceAssignment assignment) {
if (assignment.sourceType?.nullabilitySuffix == NullabilitySuffix.question &&
target.nullabilitySuffix == NullabilitySuffix.none) {
return refer('model')
.property(assignment.sourceName!)
.notEqualTo(literalNull)
.conditional(
convertExpression.call([
refer('model').property(assignment.sourceName!).nullChecked,
]),
literalNull,
);
final assignmentSourceNullable = assignment.sourceType?.nullabilitySuffix != NullabilitySuffix.none;
final assignmentTargetNullable = assignment.targetType.nullabilitySuffix != NullabilitySuffix.none;
final sourceNullable = sourceType.nullabilitySuffix != NullabilitySuffix.none;
final targetNullable = targetType.nullabilitySuffix != NullabilitySuffix.none;
final whenNullExpression = assignment.fieldMapping?.whenNullExpression;

final sourceExpression = refer('model').property(assignment.sourceName!);

if((assignmentSourceNullable || targetNullable) && !assignmentTargetNullable && whenNullExpression == null) {
throw InvalidGenerationSourceError(
'Cannot convert nullable source type to non-nullable target type requirement without a default value.',
element: assignment.sourceField,
);
}

return convertExpression.call([
refer('model').property(assignment.sourceName!),
]);
if(assignmentSourceNullable && !sourceNullable) {
return sourceExpression.notEqualTo(literalNull).conditional(
convertExpression.call([sourceExpression.nullChecked]),
whenNullExpression ?? literalNull,
);
} else {
final expression = convertExpression.call([sourceExpression]);

if(targetNullable) {
return expression.ifNullThen(whenNullExpression!);
}

return expression;
}
}
}
51 changes: 29 additions & 22 deletions packages/auto_mappr/test/integration/fixture/type_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,36 +37,45 @@ class DateDto2 {
});
}

class UserId extends Equatable {
final String value;
abstract class ValueId<T> extends Equatable {
final T value;

@override
List<Object?> get props => [value];

const UserId(this.value);
const ValueId(this.value);

static T toValue<T>(ValueId<T> id) => id.value;

static String toValueString(ValueId<String> id) => id.value;
}

class AccountId extends Equatable {
final String value;
class UserId extends ValueId<String> {
static const defaultUserId = UserId('defaultUserId');

@override
List<Object?> get props => [value];
const UserId(super.value);

const AccountId(this.value);
static UserId? newNullable(String? value) => value == null ? null : UserId(value);
}

class AccountId extends ValueId<String> {
const AccountId(super.value);

static AccountId? newNullable(String? value) => value == null ? null : AccountId(value);
}

class User {
final UserId? id;
final UserId id;
final AccountId? accountId;

User(this.id, this.accountId);
const User(this.id, this.accountId);
}

class UserDto {
final String? id;
final String? accountId;

UserDto(this.id, this.accountId);
const UserDto(this.id, this.accountId);
}

@AutoMappr(
Expand All @@ -85,30 +94,33 @@ class UserDto {
MapType<DateDto2, DateDomain>(),
MapType<UserDto, User>(
fields: [
Field('id', type: UserId.new),
Field('id', type: UserId.newNullable, whenNull: UserId.defaultUserId),
],
),
MapType<User, UserDto>(),
],
types: [
Mappr.parseISO8601,
TypeConverter(AccountId.new, field: 'accountId'),
UserId.new,
AccountId.new,
// This doesn't work currently, maybe analyzer bug?
ValueId.toValue<String>,
ValueId.toValueString,
],
)
class Mappr extends $Mappr {
/// Expects a year as input and returns the first of december in that year.
///
/// Falls back to first of december in 1970 if parsing fails.
static DateTime tryParseFirstOfDecemberInYear(String input) {
return int.tryParse(input)?.let((v) => DateTime(v, 12)) ??
DateTime(1970, 12);
return DateTime(int.tryParse(input) ?? 1970, 12);
}

/// Expects a year as input and returns the second of december in that year.
///
/// Falls back to second of december in 1970 if parsing fails.
static DateTime parseSecondOfDecemberInYear(String input) {
return int.tryParse(input)?.let((v) => DateTime(v, 12, 2)) ??
DateTime(1970, 12, 2);
return DateTime(int.tryParse(input) ?? 1970, 12, 2);
}

/// Expects a ISO8601 formatted string as input and returns a DateTime.
Expand All @@ -118,8 +130,3 @@ class Mappr extends $Mappr {
return DateTime.tryParse(input) ?? DateTime(1970);
}
}

extension<T> on T {
@pragma('vm:prefer-inline')
R let<R>(R Function(T v) block) => block(this);
}
18 changes: 17 additions & 1 deletion packages/auto_mappr/test/integration/type_converter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,26 @@ void main() {
});

test('nullable input type with non-nullable type converter input', () {
final userDto = fixture.UserDto('userId', 'accountId');
const userDto = fixture.UserDto('userId', 'accountId');
final user = mappr.convert<fixture.UserDto, fixture.User>(userDto);

expect(user.id, const fixture.UserId('userId'));
expect(user.accountId, const fixture.AccountId('accountId'));
});

test('converter allows subtypes as input', () {
const user = fixture.User(fixture.UserId('userId'), fixture.AccountId('accountId'));
final userDto = mappr.convert<fixture.User, fixture.UserDto>(user);

expect(userDto.id, 'userId');
expect(userDto.accountId, 'accountId');
});

test('whenNull takes precedence', () {
const userDto = fixture.UserDto(null, null);
final user = mappr.convert<fixture.UserDto, fixture.User>(userDto);

expect(user.id, fixture.UserId.defaultUserId);
expect(user.accountId, null);
});
}

0 comments on commit dabc193

Please sign in to comment.