How to reference a specific ENUM value in GraphQL type? - graphql

I have the following schema:
enum PaymentTypeName {
PAYMENT_CARD
PAYMENT_CARD_TOKEN
}
interface Payment {
id: ID!
type: PaymentTypeName!
}
type PaymentCardPayment implements Payment {
id: ID!
type: PaymentTypeName!
card: PaymentCard!
}
type PaymentCardTokenPayment implements Payment {
id: ID!
type: PaymentTypeName!
card: PaymentCard!
}
When Payment is PaymentCardPayment or PaymentCardTokenPayment is determined by the value of type, i.e. it is either PAYMENT_CARD or PAYMENT_CARD_TOKEN.
How do I signify in the interface, that PaymentCardPayment/ PaymentCardTokenPayment inherit a specific value of PaymentTypeName?
I have tried various combinations of:
type PaymentCardPayment implements Payment {
id: ID!
type: PaymentTypeName.PAYMENT_CARD!
card: PaymentCard!
}
and:
type PaymentCardPayment implements Payment {
id: ID!
type: PaymentTypeName[PAYMENT_CARD]!
card: PaymentCard!
}
but all of these prompt a syntax error and I was unable to find the relevant documentation.

What you're trying to do is not supported in GraphQL. If a field's type is declared to be PaymentTypeName, and PAYMENT_CARD and PAYMENT_CARD_TOKEN are both valid values of PaymentTypeName, then they must also be valid values for that field. There is no way to take an existing type9 whether it's an enum, scalar, or object type) and conditionally create a subset of possible values from the set of possible values already defined by the type.
That said, if PaymentCardPayment.type will always resolve to PAYMENT_CARD and PaymentCardTokenPayment.type will always resolve to PAYMENT_CARD_TOKEN, then it doesn't really make sense to use an enum here at all. In fact, in this specific case, we can omit the type field entirely. After all, the only purpose of the field in this case is to allow the client to distinguish between the possible types that Payment may resolve to. However, GraphQL already provides us with a __typename field that does just that by resolving to the name of the resolved type.
So in this specific instance, it's sufficient to just do:
interface Payment {
id: ID!
}
type PaymentCardPayment implements Payment {
id: ID!
card: PaymentCard!
}
type PaymentCardTokenPayment implements Payment {
id: ID!
card: PaymentCard!
}
and query a field whose type is Payment like this:
{
payments {
id
__typename
... on PaymentCardPayment {
card {
# ...
}
}
... on PaymentCardTokenPayment {
card {
# ...
}
}
}
}

You are trying to declare the field value in your type schema, which is not what a schema is meant for. You should only be declaring your field type within your schema, in this case it is just type: PaymentTypeName. You have it correct in your first code block.
Your PaymentCardPayment's type resolver function should return the value of the enum, in your case, PAYMENT_CARD.
Your PaymentCardTokenPayment's type resolver function should return the value of PAYMENT_CARD_TOKEN.

Edit: This only works on input types.
enum PaymentTypeName {
PAYMENT_CARD
WAD_OF_CASH
}
input BuySomethingInput {
method: PaymentTypeName! = WAD_OF_CASH
price: Number
}
and then use the input type in a Mutation.
The following is not going to work
Would using a default value work for you?
type PaymentCardPayment implements Payment {
id: ID!
type: PaymentTypeName! = PAYMENT_CARD
card: PaymentCard!
}
It won't prevent you from overwriting the value but at least it should be set correctly.

Related

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

Alias types in GraphQL Schema Definition Language

I have the following graphql schema definition in production today:
type BasketPrice {
amount: Int!
currency: String!
}
type BasketItem {
id: ID!
price: BasketPrice!
}
type Basket {
id: ID!
items: [BasketItem!]!
total: BasketPrice!
}
type Query {
basket(id: String!): Basket!
}
I'd like to rename BasketPrice to just Price, however doing so would be a breaking change to the schema because clients may be referencing it in a fragment, e.g.
fragment Price on BasketPrice {
amount
currency
}
query Basket {
basket(id: "123") {
items {
price {
...Price
}
}
total {
...Price
}
}
}
I had hoped it would be possible to alias it for backwards compatibility, e.g.
type Price {
amount: Int!
currency: String!
}
# Remove after next release.
type alias BasketPrice = Price;
type BasketPrice {
amount: Int!
currency: String!
}
type BasketItem {
id: ID!
price: BasketPrice!
}
type Basket {
id: ID!
items: [BasketItem!]!
total: BasketPrice!
}
type Query {
basket(id: String!): Basket!
}
But this doesn't appear to be a feature. Is there a recommended way to safely rename a type in graphql without causing a breaking change?
There's no way to rename a type without it being a breaking change for the reasons you already specified. Renaming a type is a superficial change, not a functional one, so there's no practical reason to do this.
The best way to handle any breaking change to a schema is to expose the new schema on a different endpoint and then transition the clients to using the new endpoint, effectively implementing versioning for your API.
The only other way I can think of getting around this issue is to create new fields for any fields that utilize the old type, for example:
type BasketItem {
id: ID!
price: BasketPrice! # deprecated(reason: "Use itemPrice instead")
itemPrice: Price!
}
type Basket {
id: ID!
items: [BasketItem!]!
total: BasketPrice! # deprecated(reason: "Use basketTotal instead")
basketTotal: Price!
}
I want this too, and apparently we can't have it. Making sure names reflect actual semantics over time is very important for ongoing projects -- it's a very important part of documentation!
The best way I've found to do this is multi-step, and fairly labor intensive, but at least can keep compatibility until a later time. It involves making input fields optional at the protocol level, and enforcing the application-level needs of having "one of them" at the application level. (Because we don't have unions.)
input OldThing {
thingId: ID!
}
input Referee {
oldThing: OldThing!
}
Change it to something like this:
input OldThing {
thingId: ID!
}
input NewThing {
newId: ID!
}
input Referee {
oldThing: OldThing # deprecated(reason: "Use newThing instead")
newThing: NewThing
}
In practice, all old clients will keep working. You can update your handler code to always generate a NewThing, and then use a procedural field resolver to copy it into oldThing if asked-for (depending on which framework you're using.) On input, you can update the handler to always translate old to new on receipt, and only use the new one in the code. You'll also have to return an error manually if neither of the elements are present.
At some point, clients will all be updated, and you can remove the deprecated version.

How to combine multiple field in GraphQL Object

I am trying my first Graphql Schema design. Is it possible that single field inside object type refer to a complex object?
enum KeyPrefix {
WS
WK
SI
ENT
}
input generalKey {
keyPrefix:KeyPrefix!
key:Int!
}
type Item
{
pk: generalKey!
data: String!
name: String!
}
It gives me error as below.
The type of Item.pk must be Output Type but got: generalKey!
input is a keyword reserved for describing input to a GraphQL query. Your schema should look like this:
enum KeyPrefix {
(your values)
}
type PrimaryKey {
prefix: KeyPrefix!
key: Int!
}
type Item {
pk: PrimaryKey!
data: String!
name: String!
}
When you define a Query in your GraphQL schema, you will want to use an input, like so:
input PrimaryKeyInput {
prefix: KeyPrefix!
key: Int!
}
type Query {
getItemByPrimaryKey(input: PrimaryKeyInput!): Item
}
This will allow a client to ask for an Item using the same fields you have on PrimaryKey.

Connections, edges, node: how to use a subtype of a node

I'm following the Connection, Edges, Node, concept of relay in Apollo.
I use a Node { id: ID! } interface. I have a collection of entities that are all kind of 'groups', with small differences. I would like to model these as a single Connection, but not really sure how to do that:
# simplified for example
interface Group {
id: ID!
type: String!
}
type WallGroup implements Node & Group {
id: ID!
type: String!
}
type Event implements Node & Group {
id: ID!
type: String!
dteStart: DateTime!
dteEnd: DateTime!
}
type GroupEdge implements Edge {
cursor: String!
node: Group!
}
type GroupConnection implements Connection {
edges: [GroupEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
This will generate an error because GroupEdge expects node to a Node. It is, but the Group interface is not and it therefore fails.
Another thing I tried was
union AllGroup = WallGroup | Event
type GroupEdge implements Edge {
cursor: String!
node: AllGroup!
}
But this leads to the same issue. Apparently, the union loses notion of the Node interface implemented on WallGroup and Event.
Any ideas on hwo to model this or should I just duplicate everything?
The Relay specification only requires that your schema include a Node interface -- when creating a Relay-compliant, normally you don't create interfaces for Connection and Edge.
The reason Relay requires a Node interface is to allow us to query for any Node by id. However, typically there's no need for a field that returns one of many edges or one of many connections. Therefore, typically there's no need to need to make an interface for edges or connections. This is what you would normally do:
interface Node {
id: ID!
}
interface Group {
id: ID!
type: String!
}
type GroupA implements Node & Group {
id: ID!
type: String!
# other fields
}
type GroupA implements Node & Group {
id: ID!
type: String!
# other fields
}
type GroupEdge {
cursor: String!
node: Group!
}
type GroupConnection {
edges: [GroupEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
According to the spec, "the [implementing] object type must include a field of the same name for every field defined in an interface." So, if an interface specifies that a field is of the type Foo, an implementing type cannot have the field be of type FooBar even if Foo is an interface and FooBar implements it. Unfortunately, that means it's not really possible to use interfaces like you're trying to do.
If you would like to utilize interfaces to provide a safety-net of sorts and ensure that the implementing types are consistent, you can do something like this:
interface Connection {
pageInfo: PageInfo!
# don't specify edges
}
interface Edge {
cursor: String!
# don't specify node
}
This way you can ensure that fields like pageInfo or cursor are present in the implementing types, have the correct type and are non-null. But this kind of schema-level validation is really the only benefit you get out adding and implementing these two interfaces.

Can a GraphQL input type inherit from another type or interface?

Is it possible to use inheritance with GraphQL input types?
Something like that (this, of course, doesn't work with input types):
interface UserInputInterface {
firstName: String
lastName: String
}
input UserInput implements UserInputInterface {
password: String!
}
input UserChangesInput implements UserInputInterface {
id: ID!
password: String
}
No, the spec does not allow input types to implement interfaces. And GraphQL type system in general does not define any form of inheritance (the extends keyword adds fields to an existing type, and isn't for inheritance). The spec is intentionally constrained to stay simple. This means that you're stuck repeating fields across input types.
That said, depending on the way you construct your schema, you could build some kind of type transformer that appends the common fields programmatically based on some meta-data, e.g. a directive. Here's one such implementation.
Better yet, you might be able to solve your problem via composition (always keep composition over inheritance in mind).
E.g.
input Name {
firstName: String
lastName: String
}
input UserInput {
name: Name
password: String!
}
input UserChangesInput {
name: Name
id: ID!
password: String
}
The client now has to send an object a level deeper, but that doesn't sound like much of a price for avoiding big repeating chunks. It might actually be good for the client as well, as they can now have common logic for building names, regardless of the query/mutation using them.
In this example, where it's only 2 simple fields, this approach is an overkill, but in general - I'd say it's the way to go.
Starting with the June2018 stable version of the GraphQL spec, an Input Object type can extend another Input Object type:
Input object type extensions are used to represent an input object type which has been extended from some original input object type.
This isn't inheritance per se; you can only extend the base type, not create new types based on it:
extend input MyInput {
NewField: String
}
Note there is no name for the new type; the existing MyInput type is extended.
The JavaScript reference implementation has implemented Input Object extensions in GraphQL.js v14 (June 2018), though it's unclear how to actually pass the extended input fields to a query without getting an error.
For actual type inheritance, see the graphql-s2s library.
It's doable using a custom directive.
Code Summary
const typeDefs = gql`
directive #inherits(type: String!) on OBJECT
type Car {
manufacturer: String
color: String
}
type Tesla #inherits(type: "Car") {
manufacturer: String
papa: String
model: String
}
type Query {
tesla: Tesla
}
`;
const resolvers = {
Query: {
tesla: () => ({ model: 'S' }),
},
Car: {
manufacturer: () => 'Ford',
color: () => 'Orange',
},
Tesla: {
manufacturer: () => 'Tesla, Inc',
papa: () => 'Elon',
},
};
class InheritsDirective extends SchemaDirectiveVisitor {
visitObject(type) {
const fields = type.getFields();
const baseType = this.schema.getTypeMap()[this.args.type];
Object.entries(baseType.getFields()).forEach(([name, field]) => {
if (fields[name] === undefined) {
fields[name] = { ...field };
}
});
}
}
const schemaDirectives = {
inherits: InheritsDirective,
};
Query:
query {
tesla {
manufacturer
papa
color
model
}
}
Output:
{
"data": {
"tesla": {
"manufacturer": "Tesla, Inc",
"papa": "Elon",
"color": "Orange",
"model": "S",
}
}
}
Working example at https://github.com/jeanbmar/graphql-inherits.
If you came here looking for an explanation for the "implements", keyword, here it is:
An object type must be a super‐set of all interfaces it implements. The object type must include a field of the same name for every field defined in an interface.
(Excerpt taken from the June 2018 GraphQL spec.)
Here's an example
interface Foo {
id: ID!
foo: Int!
}
type Bar implements Foo #entity {
id: ID!;
foo: Int!;
bar: Int!;
}
So the Bar type doesn't inherit from the Foo interface, but it implements it. The former must include all the fields that are listed in the latter.
I think that this is a nice way to annotate types that should be like other types.

Resources