Skip to content

Conversation

@martinbonnin
Copy link
Contributor

@martinbonnin martinbonnin commented Dec 2, 2025

Follow up from #26 (comment)

I find the Robustness principle to be generally harmful for the reasons outlined in this RFC.

It also complexifies the handling of scalars because it breaks the symmetry between output and input wire formats.

For an example, a lot of specifications right now have different examples for input vs. output. That duplicates a lot of information.

GraphQL being generally typesafe and the number of GraphQL servers being limited, I think we can be strict about what we accept.

jakobmerrild added a commit to jakobmerrild/graphql-scalars that referenced this pull request Dec 2, 2025
There's a separate suggestion to update the guide lines to not
mention being liberal in what is accepted for inputs.
See: graphql#40
@martinbonnin martinbonnin requested a review from a team December 17, 2025 14:30
Copy link
Member

@benjie benjie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is removing this a breaking change?

🤔

I don’t think so.

Copy link
Collaborator

@andimarek andimarek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote that paragraph and still think it is how Scalars should work.

I don't think this breaks any symmetry as there is no symmetry to begin with: I can have an runtime input value that is some language/code dependent format and then have an output in JSON. I still believe the output should be more restricted and produce always the same value regardless of the format produced.

It would also put unreasonable expectation onto Scalar implementations: for example an ID can take 1234 as input but it always produces a String value "1234" regardless of the input being "1234" or 1234. If you want to try to preserve not only the semantics of the value but the exact format you would have to consider that in the implementation which would make everything much more complicated.

@martinbonnin
Copy link
Contributor Author

Is removing this a breaking change?

@benjie I don't think it is.

there is no symmetry to begin with

@andimarek I think there is. For JSON, there is serializeToJson() (for output coercion) and deserializeFromJson() (for variables). I think there is some value in having the function be symmetrical and produce/expect the same format.

Or do you disagree there?

an ID can take 1234 as input but it always produces a String value "1234" regardless of the input being "1234" or 1234. If you want to try to preserve not only the semantics of the value but the exact format you would have to consider that in the implementation which would make everything much more complicated.

Not sure I get it. Do you have some sample code of that? I'm operating under the assumption that the Coercion will do "the good thing" regardless of the internal (Java/JS) value.

@BoD
Copy link

BoD commented Dec 18, 2025

To me the term "liberal" feels a bit out of place in a specification. Liberal to what extent? I find this opens more questions than it answers. It would probably be more useful to let the specifications for individual scalars precisely define what their acceptable input/output formats are.

martinbonnin added a commit that referenced this pull request Dec 18, 2025
* Add spec for `Long` custom scalar type

The `Long` data type is commonly used in many applications because
the largest value that can be held in an `Int` value is `2^31-1`.
See: graphql/graphql-spec#73

* Update scalars/contributed/jakobmerrild/long.md

Co-authored-by: Martin Bonnin <[email protected]>

* Update scalars/contributed/jakobmerrild/long.md

Co-authored-by: Martin Bonnin <[email protected]>

* Update wording of Long spec

This makes the wording more consistent and removes all references
to JSON.

* Update Long spec to not allow strings for input

There's a separate suggestion to update the guide lines to not
mention being liberal in what is accepted for inputs.
See: #40

* Rewrite Long spec in terms of String encoding

Ultimately, not every language can easily parse arbitrarily large
JSON numbers. As such we are better off using a string serialization
scheme which then allows the adaptors to use whichever methods they
see fit to turn the string into a different representation after
coercion, e.g. Long or BigInt

* Update date of spec

The recent changes represent a significant departure from the original
spec, so we update the date :)

* Fix formatting and spelling

---------

Co-authored-by: Martin Bonnin <[email protected]>
@andimarek
Copy link
Collaborator

@martinbonnin for example in GraphQL JS: https://github.com/graphql/graphql-js/blob/16.x.x/src/type/scalars.ts#L219

ID is always returned as String but it accepts also numbers. Same logic we have in GraphQL Java.

@benjie
Copy link
Member

benjie commented Dec 19, 2025

201020022002 could mean 20th October 2002 at 8:02pm, or it could be May 15th 1976 at 2:53pm as a unix timestamp in milliseconds. Or it could be invalid 2010-20-02T20:02 (there’s no 20th month), or it could be 2010.20.02 in the old Kazakhstan yyyy.dd.mm format.

Accepting broad input and applying our own heuristics to it is likely to result in subtle bugs in software, and doesn’t bring much value: why would someone want to format a date in that way? What problem are we trying to solve here? I think it’s much more likely that someone would submit a Unix timestamp than an all numbers formatted date with no separators.

Ambiguity should error.

ID is not ambiguous: you simply stringify the number and you’re done. Not so with dates.

@martinbonnin
Copy link
Contributor Author

@andimarek thanks for providing that example.

  parseValue(inputValue) {
    if (typeof inputValue === 'string') {
      return inputValue;
    }
    if (typeof inputValue === 'number' && Number.isInteger(inputValue)) {
      return inputValue.toString();
    }
    throw new GraphQLError(`ID cannot represent value: ${inspect(inputValue)}`);
  },

This is in line with what the GraphQL spec says:

When expected as an input type, any string (such as "4") or integer (such as 4 or -4) input 
value should be coerced to ID as appropriate for the ID formats a given GraphQL service expects.

This is pretty much required by the spec in itself but if you ask me, that is a mistake and makes ID more complex than if ID were always a String. It was certainly the good choice in 2012 but I wouldn't apply the same design choices to new scalars being added in 2025.

@andimarek
Copy link
Collaborator

andimarek commented Dec 23, 2025

@benjie @martinbonnin let me try to explain more what my intention was behind this section. (and yes I agree the current section how it is worded is not very helpful and the example provided is not good either)

The idea is to provide convenience to the user, while not be ambiguous.
For example @benjie: I full agree that allowing 201020022002 as input for DateTime is confusing and not helpful.

But the ID example is very much on point: it allows 123 as int literal out of convenience for the user as they can now use 123 or "123". I would apply the same for example for a Long one.

But it goes further than just literals, a Scalar also needs to convert language specific values at runtime and here the Scalar should also be designed to be flexible for the users convenience. For example in Java you should accept a String or an OffsetDateTime for DateTime.

Again same in JS and ID: it takes a number and a String in parseValue because, why not?

But at the same time the output value of a Scalar should not produce different kind of values (for example sometimes a String or Integer for ID) , because this again puts burden on the users to handle different values, without any benefit.

This is background of this section: trying to capture this asymmetry between input (literal and runtime values) and output by being flexible for the input for the user experiences sake, but being restricted for the output (again for the users experience sake).

I hope that helps.

@martinbonnin
Copy link
Contributor Author

@andimarek I disagree that input flexibility gives a better user experience.

I personally dread having a choice. I curse at Gradle dozens of times a day just because there are so many ways to do the same thing. I value consistency and predictability.

As the python folks say:

There should be one-- and preferably only one --obvious way to do it.

Same for scalars. Do I need this Date to be a String or a java.util.Date or a java.time.LocalDateTime? I don't care. Just tell me which one, and preferrably just one, it should be. Anecdotally, ID accepting both Int and String has been a source of confusion for me for many many years.

Strong typing also makes it easier to navigate a code base. And overall gives me better predictability and lower cognitive load.

I get where you're coming from but I think there is value in strict scalars.

It's 100% fine if some people want to have them lenient though. I would just not make it a recommendation.

@andimarek
Copy link
Collaborator

@martinbonnin I argue you should care, because you are designing a scalar/library for the users.

The reality of users for example is that a Date in Java/JS/etc. is represented in multiple ways and therefore you should absorb this complexity (as long as you can do it in a unambiguous, not other problems introducing way). That is the whole goal of a Scalar/Library/etc: to make the life of the users easier.

An ID is not always a String but often an integer too, therefore just take it and make the life of the users easier.

The python quote is not a bad or wrong per se, but we are not designing a closed system, but we are interfacing with other systems which we can't control or change.

@martinbonnin
Copy link
Contributor Author

@andimarek I think we have to agree to disagree.

As a library author, some of my users want lenient, others want strict. What audience to optimize for is a tradeoff and I don't think we (graphql community) should make this tradeoff for the implementers.

@benjie
Copy link
Member

benjie commented Dec 25, 2025

I agree generally with Andi’s opinions but this is a specification and the paragraph is extremely non-specific. Deleting it removes ambiguity without forbidding handling of other values: it leaves it at the discretion of the server rather than making a vague recommendation that clients can’t rely on and servers may implement differently. If nothing else, the “should” should be changed to a “may” to indicate to clients that support for other input values is very much server dependent and may not be accepted at all, and the examples should be removed and replaced with more compelling examples such as using language or transport specific date types. But removing the paragraph is the cheapest fix IMO.

@andimarek
Copy link
Collaborator

@martinbonnin @benjie I created a short alternative section based on our section. Let me know what you think

Comment on lines +163 to +169
Most Scalars adhere to a semantic symmetry between input and output: every valid
input value has at least one corresponding output value and vice versa.

Additionally, it is common to allow different input values, which are
semantically identical but not technically the same. For example, the ID Scalar
accepts Int and String literals, while the output value is always String. This
can improve user experience by allowing flexible inputs.
Copy link
Contributor Author

@martinbonnin martinbonnin Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Most Scalars adhere to a semantic symmetry between input and output: every valid
input value has at least one corresponding output value and vice versa.
Additionally, it is common to allow different input values, which are
semantically identical but not technically the same. For example, the ID Scalar
accepts Int and String literals, while the output value is always String. This
can improve user experience by allowing flexible inputs.
For some scalars, input coercion may be more flexible than result coercion.
For example, the ID Scalar accepts both int and strings literals (for both GraphQL
and JSON literals), while the result is always a string. This can improve user experience by
allowing flexible inputs.

Copy link
Contributor Author

@martinbonnin martinbonnin Dec 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benjie @andimarek I stand by my position that flexibility isn't necessarily improving the user experience.

That being said, we also have onError: null and a bunch of other things to move in 2026 so I'm fine letting this one go. Plus this PR didn't get much traction outside our small group so we can revisit if that topic resurfaces later.

@andimarek I've merged your proposal as I like it better that the current text. I've also suggested a shorter alternative above. Feel free to merge it or dismiss it, your call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Off topic, but you’ve just demonstrated my concern with onError: "NULL" by specifying it as the null literal. There are many servers that don’t distinguish between “present and null” versus “not present”, so it must be string value to be meaningful and backwards compatible.

Copy link
Contributor Author

@martinbonnin martinbonnin Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine. I'm just lazy when writing comments on Christmas day. Implementations will be much better.

Any other value I wouldn't have remembered and I think everyone understands what I meant there.
A server wouldn't but it wasn't the audience of this comment.

@martinbonnin
Copy link
Contributor Author

@andimarek I'm leaving this in your hands. Feel free to:

On Jan 19th, I'll close by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants