Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Scalar aliases #308

Closed
wants to merge 7 commits into from

Conversation

IvanGoncharov
Copy link
Member

I like that GraphQL allows creating custom scalars. It gives great flexibility.
However, usually, they are used just to have more descriptive type name or to attach additional validation to the type.

Here is the list of custom scalars used by public GitHub API:

  • DateTime
  • URI
  • HTML
  • GitObjectID
  • GitTimestamp
  • X509Certificate

But in the response, they all are represented exactly same as String type and you can't deduce that from introspection. This creates problems for tooling. For example, in code generation, there is no way to reliably map custom scalar to the native type, e.g. issue on apollo ios. The same issue affects tools for mocking GraphQL APIs: issue on graphql-faker.
These are only two examples from the top of my head.

@OlegIlyenko has added a partial solution to this issue in Sangria which allows you to define scalar alias as Scala object:

implicit val UserIdType = ScalarAlias[UserId, String](
  StringType, _.id, id  Right(UserId(id)))

However, these aliases don't appear in introspection so all the semantic is lost for the clients. Also, there is no way to use it in IDL.

In general, full support for custom scalars is pretty complicated because of their flexibility and this PR doesn't try to solve this. However, in the majority of cases, custom scalars can be replaced with scalar aliases.

Note: as far as I understand, no breaking changes are introduced by this PR.

@IvanGoncharov IvanGoncharov changed the title Add support of Scalar aliases Add support for Scalar aliases May 18, 2017
@OlegIlyenko
Copy link
Contributor

OlegIlyenko commented May 18, 2017

I really like the idea. Though, I think, scalar aliases are not the right type of information to expose.

Scalar aliases are extremely helpful for type-safe languages, but this is not necessarily the concept one want to expose. For example, we have a UUID scalar alias for a String scalar in our project which is represented internally with java.util.UUID class. We use it to represent entity IDs. I don't want to tell clients anything about the structure of the ID, for them it should be opaque. On the other hand, I would like to have more type safety in the backend code.

When it comes to scalars, I think it is quite helpful to make a distinction between syntax and semantic elements:

  • Literal is a syntactic concept. GraphQL defines following literals:
    • boolean literal (true or false)
    • number literal (123)
    • string literal ("abcd")
    • and, arguably, an identifier literal (FOO_BAR)
  • Scalar is a semantic concept. So it has particular literal representation in the syntax of the language, but it adds meaning and constraints to it.

For example, 8735463487268275725476825 is a valid number literal, but there is no standard scalar type to represent it. For tools and clients, I think it would more helpful to know the actual literal representation(s) of the scalar type rather than whether this scalar type is based on other scalar type. Something like this:

enum __Literal {
  BOOLEAN
  NUMBER
  STRING
}

extend type __Type {
  literals: [__Literal!]
}

It is a list because scalars like ID should be supported. ID scalar has 2 literal representations: STRING and NUMBER

@leebyron
Copy link
Collaborator

I've worked through an implementation of this idea in graphql/graphql-js#914 and quite like it! Thanks for proposing some spec text! (Though note the base branch should be idl-rfc)

@OlegIlyenko I'd like to dig a bit more into this. I originally came to the same conclusion that you did in that we should consider these in terms of literals, but I was convinced otherwise when presented this same problem by @dschafer as the real value extracted by doing this is to allow clients to understand the serialized response. In that sense, we do actually want to consider this in terms of the semantic scalar values. You could think of the examples from the Github schema which @IvanGoncharov mentioned as constrained scalars. That is, a URI is a String - it is just a String which also conforms to a more specific definition (and therefore holds more semantic info in exchange for that constraint).

These custom scalars defined in terms of existing scalars should also guarantee to play by the same rules that the built-in scalars do.

I think the case where a valid literal that is an invalid scalar (like 8735463487268275725476825) ultimately aren't that useful within GraphQL's existing system. I think the primary value for this would be defining types like BigInt (which could actually be expressed in terms of ID, but that's weird and beside the point) in which we would actually want to define some more fundamental rules about serialization for that sort of thing.

@leebyron
Copy link
Collaborator

I think this idea deserves some bikeshedding for naming. "Alias" implies that the newly defined type is the same as built-in it's based on. I don't think that's quite right. A URI is definitely not the same thing as a String, so "alias" doesn't quite capture what's going on. I'm open to ideas here.

@stubailo
Copy link
Contributor

stubailo commented Jun 16, 2017

Maybe it's a difference between a scalar type "encoding" and the type itself.

Maybe it's a case of extension?

scalar URI implements String

@leebyron
Copy link
Collaborator

Encoding is better name for this.

I'd like to avoid overriding the implements which implies interfaces, or adding additional keywords.

@wincent
Copy link
Contributor

wincent commented Jun 16, 2017

Joining the bikesheding committee:

scalar URI extends String

@OlegIlyenko
Copy link
Contributor

@leebyron agree, I think it is a very good point. There is a lot of value in knowing not only the literal representation but also some semantic information.

I think the only place where it makes sense to define a completely custom scalar values, as you mentioned, is a number. It should be possible to represent BigInt and BigDecimal, I know quite a few users who are using these (in sangria BigInt and BigDecimal are provided out-of-the-box). Though I agree that it can be represented as different concept withing the language. Maybe it can be represented by an absence of a base scalar type. In this case, literal information can be quite useful. I wonder whether both: literal info and base scalar type info can coexist in the introspection API?

@IvanGoncharov IvanGoncharov changed the base branch from master to rfc-idl June 17, 2017 13:56
@leebyron
Copy link
Collaborator

I opened an RFC which adds this (except for the schema language addition) to #326

The reference implementation is at graphql/graphql-js#914 (which currently uses as as the keyword).

Please let me know what you think of these - if there's consensus then I'll get to merging

@IvanGoncharov
Copy link
Member Author

I'll get to merging

@leebyron Yes definitely #326 is perfectly covering use-case I had in mind. So I'm closing this PR in favor of #326

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.

6 participants