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!
}
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.
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;
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;
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?