Skip to content

Ferry Generator Bug Report: Incorrect .replace() usage for primitive custom scalar types #654

Description

@li-nkSN

Hello, I employed claude to help me write this bug report. I'm willing to help support the resolution of this issue as well.

Ferry Generator Bug Report: Incorrect .replace() usage for primitive custom scalar types

Summary

When using type_overrides to map a custom GraphQL scalar to a primitive Dart type (e.g., DateTime), the ferry_generator incorrectly generates .replace() method calls in input type builders. Primitive types don't have a .replace() method - that's only for built_value types with builders.

Environment

  • ferry: ^0.16.2-dev.4
  • ferry_generator: ^0.14.0-dev.2
  • built_value: ^8.9.3
  • Dart SDK: 3.x

Configuration

# build.yaml
targets:
  $default:
    builders:
      ferry_generator|graphql_builder:
        enabled: true
        options:
          schema: li_nk_app|lib/graphql/schema.graphql
          type_overrides:
            OffsetDateTime:
              name: DateTime
              import: "dart:core"

      ferry_generator|serializer_builder:
        enabled: true
        options:
          schema: li_nk_app|lib/graphql/schema.graphql
          type_overrides:
            OffsetDateTime:
              name: DateTime
              import: "dart:core"
          custom_serializers:
            - import: "package:li_nk_app/graphql/serializers/offset_date_time_serializer.dart"
              name: OffsetDateTimeSerializer

Custom Serializer

class OffsetDateTimeSerializer implements PrimitiveSerializer<DateTime> {
  @override
  DateTime deserialize(Serializers serializers, Object serialized, {
    FullType specifiedType = FullType.unspecified,
  }) {
    return DateTime.parse(serialized as String);
  }

  @override
  Object serialize(Serializers serializers, DateTime dateTime, {
    FullType specifiedType = FullType.unspecified,
  }) =>
      dateTime.toUtc().toIso8601String();

  @override
  Iterable<Type> get types => [DateTime];

  @override
  String get wireName => 'OffsetDateTime';
}

GraphQL Schema (excerpt)

scalar OffsetDateTime

input CommentInput {
  content: String!
  createdAt: OffsetDateTime
  lastUpdated: OffsetDateTime
}

Incorrect Generated Code

In lib/graphql/__generated__/schema.schema.gql.dart:

// In the GCommentInputSerializer._fromMap() method:
case 'createdAt':
  var _$fieldValue = serializers.deserialize(value,
      specifiedType: const FullType(DateTime)) as DateTime;
  builder.createdAt.replace(_$fieldValue);  // ERROR!
  break;

Error Message

Error: The method 'replace' isn't defined for the type 'DateTime?'.
 - 'DateTime' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'replace'.
          builder.createdAt.replace(_$fieldValue);
                            ^^^^^^^

Expected Generated Code

case 'createdAt':
  var _$fieldValue = serializers.deserialize(value,
      specifiedType: const FullType(DateTime)) as DateTime;
  builder.createdAt = _$fieldValue;  // Direct assignment for primitives
  break;

Root Cause Analysis

The generator appears to assume that any type mapped via type_overrides might be a BuiltValue type (which has builders and .replace() methods). However, when the target type is a primitive Dart type like DateTime, int, or String, the generator should use direct assignment (=) instead of .replace().

The .replace() method is only available on built_value builder fields for nested BuiltValue objects.

Workaround

Manually fix the generated code by replacing .replace() with direct assignment:

sed -i 's/builder\.createdAt\.replace(_\$fieldValue);/builder.createdAt = _\$fieldValue;/g' lib/graphql/__generated__/schema.schema.gql.dart
sed -i 's/builder\.lastUpdated\.replace(_\$fieldValue);/builder.lastUpdated = _\$fieldValue;/g' lib/graphql/__generated__/schema.schema.gql.dart

Affected Fields

Any nullable field in an input type that uses a custom scalar mapped to a primitive type:

  • createdAt: OffsetDateTimeDateTime
  • lastUpdated: OffsetDateTimeDateTime
  • dateOfBirth: OffsetDateTimeDateTime

Suggested Fix

In the generator, when emitting builder assignment code for deserialized values, check if the target type is a primitive (or mapped to a primitive via type_overrides) and use direct assignment instead of .replace().

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions