Error "Input is incompatible with <customScalar> type" in mutation with custom scalar parameter

We implemented our own DataTime scalar type (based on the one in the bookshelf
demo project). And tried to use it as a parameter type in a mutation.

This results in an error Input is incompatible with "DateTime" type

To demonstrate our issue I gutted the bookshelf demo, and implemented a bare minimum schema with one query that returns a custom scalar type, and one mutation that accepts the same custom scalar type

Schema
type Mutation {
  dateToString(date: DateTime!): String!
}
type Query {
  currentDate(): DateTime!
}
Mutation
mutation {
  dateToString(date: "2025-01-24T10:21:01.325Z")
}
Mutation Result
{
  "errors": [
    {
      "message": "Input is incompatible with \"DateTime\" type",
      "locations": [
        {
          "line": 2,
          "column": 22
        }
      ],
      "extensions": {}
    }
  ]
}

I've attached an example project here: 0001-custom-scalar-as-input.zip (93.5 KB)

We patched the issue with the following change in TGraphQLOperationValidator.IsValueCompatibleToInputType (GraphQL.Validator.Operation.pas):

Diff patch
Index: source/core/GraphQL.Validator.Operation.pas
===================================================================
--- source/core/GraphQL.Validator.Operation.pas	(revision 3575)
+++ source/core/GraphQL.Validator.Operation.pas	(working copy)
@@ -495,6 +495,10 @@
 begin
   Result := False;

+  // accept scalar input values
+  if ASchemaInputType is TSchemaScalarType then
+    Exit(True);
+
   if AValue is TASTNullValue then
   begin
     Result := True;

I don't know if we get the error because we did something wrong here. Or if it's a TMS GraphQL issue or limitation.

Hi @Stefan, welcome to TMS Support Center.

Thank you for the project, it helps a lot. The problem is rather simple, you have declared CurrentDate method as private. Such visibility prevents Delphi from adding RTTI information to it, thus GraphQL can't find it.

If you make the method public it works fine:

  TQuery = class
  public // <-- change visibility
    function CurrentDate: TDateTime;
  end;

Oh, that's an oopsie from my end. Sorry. The query is not the issue. The query should work fine when it's marked public.

Our issue is with the mutation. With the TQuery implementation fixed, could you try the mutation I supplied in my original message?

Since your DateTime accepts string values, you can inherit it from TSchemaStringType and implement it like this:

  TSchemaDateTimeType = class(TSchemaStringType)
  strict protected
    function ParseLiteral(Value: TASTValue): TValue; override;
    function GetName: string; override;
    function GetDisplayName: string; override;
  public
    function ParseValue(const Value: TValue): TValue; override;
    function Serialize(const Value: TValue): TValue; override;
  end;

...

{ TSchemaDateTimeype }

function TSchemaDateTimeType.GetDisplayName: string;
begin
  Result := 'DateTime';
end;

function TSchemaDateTimeType.GetName: string;
begin
  Result := 'DateTime';
end;

function TSchemaDateTimeType.ParseLiteral(Value: TASTValue): TValue;
var
  DateValue: TDateTime;
begin
  if Value is TASTStringValue then
  begin
    if not TryISO8601ToDate(TASTSTringValue(Value).Value, DateValue) then
      raise EGraphQLCoercingParseLiteral.Create(Self.DisplayName, Value);
    Result := TValue.From<TDateTime>(DateValue);
  end
  else
    raise EGraphQLCoercingParseLiteral.Create(Self.DisplayName, Value);
end;

function TSchemaDateTimeType.ParseValue(const Value: TValue): TValue;
begin
  if (Value.TypeInfo =  System.TypeInfo(TDateTime)) or
    (Value.TypeInfo =  System.TypeInfo(TDate)) or
    (Value.TypeInfo =  System.TypeInfo(TTime)) then
    Result := Value
  else
    raise EGraphQLCoercingParseValue.Create(Self.DisplayName, Value);
end;

function TSchemaDateTimeType.Serialize(const Value: TValue): TValue;
begin
  if Value.IsEmpty then
    Exit(TValue.Empty);

  if not Value.TryCast(TypeInfo(TDateTime), Result) then
    raise EGraphQLCoercingSerialize.Create(Self.DisplayName, Value);

  Result := DateToISO8601(Value.AsType<TDateTime>, True);
end;

That should do it.

Thanks!

That seems to resolve our issue.

It'd probably be a good idea to change this in the bookshelf demo as well. because that's where we borrowed the concept from.

And for my own understanding, just to be clear: would it be fair to say that scalar types (besides those for the standard GraphQL scalars) should never directly inherit from TSchemaScalarType? If, for whatever reason, I'd want to define a scalar that is backed by an Int I would inherit from TSchemaIntType?

Yes, that is correct for current version.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.