Skip to content

Enum as scalar type #496

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

Open
thebergamo opened this issue Aug 10, 2018 · 7 comments
Open

Enum as scalar type #496

thebergamo opened this issue Aug 10, 2018 · 7 comments
Labels
💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)

Comments

@thebergamo
Copy link

Sometimes, it's useful for declaring an interface that expect to receive an Enum, but you don't want to specify it on interface level, but in the implementation level.

Consider the example:

enum TemperatureUnit {
  C
  F
}

enum WeightUnit {
  G
  OZ
}

interface MeasurableValue {
  value: Float
  unit: Enum
}

type WeightValue implements MeasurableValue {
  value: Float
  unit: WeightUnit
}

type TemperatureValue implements MeasurableValue {
  value: Float
  unit: TemperatureUnit
}

type TemperatureTask {
   type: TaskType!
   value: TemperatureValue
}

I tried to replicate this example as minimum as possible, if needed I can provide a more complex one.

But in this case, today if we want to achieve a behavior like this, we need to keep unit out of our interface, because we can't specify a generic type and override it in the implementation of the interface.

@leebyron leebyron added the 💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md) label Oct 2, 2018
@leebyron
Copy link
Collaborator

leebyron commented Oct 2, 2018

Unfortunately unit: Enum can't tell us much. The point of these fields on an interface is that we can query them and know what to expect - but I can't think of what query-aware tooling would attempt to do with Enum as a type.

My suggestion would be to simply remove unit: Enum from the MeasurableValue, since its type cannot be known until a specific subtype is known.

I'm leaving this issue open as "Strawman" in case I missed something about your motivating use case

@thebergamo
Copy link
Author

I agree with you, in my case I just create a generic Enum with all values supported. But ideally would be nice to enforce a specific enum in the implementation.

As well, when I tried to leave the interface without unit and just enforce the proper values in the specific types, was not possible as well, because as it can be used for different types that implement the interface with same value name as prop.

@jlouis
Copy link

jlouis commented Oct 17, 2018

You may need an extension which allows you to map types to types:

interface MeasurableValue<T> {
    value: Float
    unit: T
}

Where MeasurableValue<.> : * -> * is a "function" from types to types[0]. This allows

type WeightValue implements MeasurableValue<WeightUnit> {
  # Omitted
  ..
}

An implementation can take two paths:

  • Expand the type by generating a new one. This is workable, but requires you to keep track of a map from where the type came so you can get proper error reporting with a type the user/developer knows about.
  • Add types of this kind into the type system, and expand as needed, especially under execution. More involved, but it does make error handling easier.

The above is sometimes called a higher kinded type.

[0] The * here is used in the usual type theory as a "kind" which is a type system one-level above the normal type system. * is the type of types. See e.g., https://en.wikipedia.org/wiki/Kind_(type_theory)

@thebergamo
Copy link
Author

thebergamo commented Oct 23, 2018

I didn't know that we can use it in a GraphQL file. Will try it today. Looks good.

Could you link any graphql doc or source related to this?

@ivawzh
Copy link

ivawzh commented Apr 12, 2020

Hi, I am a GraphQL newbie here. Please let me know if any of my statement is wrong or there are better ways to do things.

I am planning out the schema for a new project. Meanwhile, I try to find a good practice for error handling.

Without the ability to define a generic enum field in an interface, it makes the query of union very verbose.

For example

type Mutation {
  greetThemAll(userId: string!, dogId: String!, catId: String!): GreetThemAllResult
}

interface Error {
  message: String!
  code: Enum!
  field: Enum!
}

type UserError implements Error {
  message: String!
  code: ErrorCodeEnum! # e.g. RECORD_NOT_FOUND, INVALID_MESSAGE
  field: UserFieldEnum! # e.g. ID, AGE
}

type DogError implements Error {
  message: String!
  code: ErrorCodeEnum!
  field: DogFieldEnum!
}

type CatError implements Error {
  message: String!
  code: ErrorCodeEnum!
  field: CatFieldEnum!
}

union GreetThemAllResult = Success | UserError | DogError | CatError | ...more

Ideally, I want a simple query

mutation {
  greetThemAll(userId: "1", dogId: "2", catId: "3") {
    ... on Error {
      message
      code
      field
    }
  }
}

Now because there is no generic enum. I have to

# schema 
interface Error {
  message: String!
  # code: Enum!
  # field: Enum!
}

# client query
mutation {
  greetThemAll(userId: "1", dogId: "2", catId: "3") {
    ... on Error {
      message
    }
    ... on UserError {
      code
      field
    }
    ... on DogError {
      code
      field
    }
    ... on CatError {
      code
      field
    }
    # ... and more if I have more types under the union
  }
}

More importantly, the verbose version also makes the client query much less future-proof. As the lean version can support any future added error types as long as they are under the correct interface. The verbose version has the risks of missing new error types in response.

@mjmahone
Copy link
Contributor

Generic enums feels like more a case for first-class generics rather than creating another form of type hierarchy. And I agree: the Generic Error use case is the most obvious reason to support generics.

Basically you want to define something like:

interface Error<T: enum> {
  message: String!
  code: T!
}

@ivawzh
Copy link

ivawzh commented Apr 13, 2020

Yes supporting full feature generic type would certainly be an awesome thing. I'd even hope Graphql type system to be turing completed. However, if there will be a huge amount of effort and delay implementing that. I think it wouldn't hurt to take baby steps to support generic Enum first. In future, generic Enum like code: Enum can easily migrate to code: (T:Enum) as you said.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💭 Strawman (RFC 0) RFC Stage 0 (See CONTRIBUTING.md)
Projects
None yet
Development

No branches or pull requests

5 participants