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

Scaffold input type inheritance #23

Closed
chillu opened this issue Dec 15, 2016 · 5 comments
Closed

Scaffold input type inheritance #23

chillu opened this issue Dec 15, 2016 · 5 comments

Comments

@chillu
Copy link
Member

chillu commented Dec 15, 2016

Related to Scaffold object type inheritance

Overview

Object types in queries should represent the SilverStripe inheritance structure, in order to make it clear which fields a developer can expect to be returned from a query, and to which part of the PHP class inheritance chain they belong (e.g. Page vs. RedirectorPage).

The same rationale applies to "input types" used for mutations. But as opposed to "object types", they don't support polymorphism through unions. While there's a graphql-js plugin we could port to graphql-php for this, we'd be going outside of the GraphQL spec for this. It's unlikely that this would be accepted into the graphql-php project.

Option 1: One mutation, separate fields for input types

Input types can be nested. One solution would be creating a field for each possible subclass, and then create a specific input type for it. This allows us to retain strong typeing, e.g. making fields mandatory on certain subclasses. One downside here is that we can't enforce that any of these fields is passed through GraphQL, and have to raise an exception in our own resolvers instead.

type SiteTree {
    id
    created
    title
    content
}

type Page {
    id
    created
    title
    content
    myField
}

type RedirectorPage {
    id
    created
    title
    content
    myField
    redirectionType
    externalUrl
}

union SiteTreeTypes = SiteTree | Page | RedirectorPage

input SiteTreeInputType {
    created
    title
    content
}

input PageInputType {
    created
    title
    content
    myField
}

input RedirectorPageInputType {
    created
    title
    content
    myField
    redirectionType
    externalUrl
}

input AllSiteTreeInputTypes {
    SiteTree: SiteTreeInputType
    Page: PageInputType
    RedirectorPage: RedirectorPageInputType
}

mutation createSiteTree(ID:ID!, AllSiteTreeInputTypes) {
  SiteTreeTypes
}

Option 2: One mutation, flattened input type

Alternatively, we could flatten the structure, and indicate the desired class through a className field. This isn't ideal because we can't enforce non-null fields through GraphQL, and have to use exceptions in our own logic instead. It also makes it less clear which fields belong to which subclass to developers navigating the GraphQL API surface. Given that we split up object types by subclass, that's a relevant concern for this target group.

input SiteTreeInputType {
    className
    created
    title
    content
    myField
    redirectionType
    externalUrl
}

mutation createSiteTree(ID:ID!, SiteTreeInputType) {
  SiteTreeTypes
}

Option 3: Multiple mutations, each with one input type

Using the same types as above, we could create specialised mutations for each subclass. This increases the API surface, but makes the input and return arguments much clearer (1:1 mapping with types).

mutation createSiteTree(ID:ID!, SiteTreeInputType) {
  SiteTreeType
}

mutation createPage(ID:ID!, PageInputType) {
  PageType
}

mutation createRedirectorPage(ID:ID!, RedirectorPageInputType) {
  RedirectorPageType
}
@sminnee
Copy link
Member

sminnee commented Dec 15, 2016

Why would createSiteTree() take a RedirectorPage as an input?
Wouldn’t it be createRedirectorPage()?

If you're expecting createSiteTree() to create an instance of any one of its subclasses, why not just make it createDataObject()?

@sminnee
Copy link
Member

sminnee commented Dec 15, 2016

More generally, it seems like a mutation would either be designed for specific subclass, or if it were generic it would only make use of the fields provided by a parent class.

For operations that aren't "create" or "update" — e.g. "delete or publish" — I'd expect that we use something narrower, like and ID or a BaseClass + ID pair, to identify objects.

@sminnee
Copy link
Member

sminnee commented Dec 15, 2016

If you wanted something truly generic I would probably just admit the type weakness and go for something of the form:

{
  Fields: [
    { Name: "Title", Value: "Sam" },
    { Name: "Content", Value: "blarg" }
  ]
} 

@chillu
Copy link
Member Author

chillu commented Dec 18, 2016

I've added @sminnee feedback as "option 3", and agree that this is the best way forward.

chillu added a commit to open-sausages/silverstripe-asset-admin that referenced this issue Dec 22, 2016
At the moment this is limited to "read files"
and "create folder". The remaining API endpoints will be
successively moved over to GraphQL.

The GraphQL type creators are likely temporary,
to be replaced with a shorter scaffolding-based version:
silverstripe/silverstripe-graphql#9
silverstripe/silverstripe-graphql#22
silverstripe/silverstripe-graphql#23

RFC at silverstripe/silverstripe-framework#6245
@chillu
Copy link
Member Author

chillu commented Feb 2, 2017

Aaron's done this in #20 now, hooray!

@chillu chillu closed this as completed Feb 2, 2017
unclecheese pushed a commit to open-sausages/silverstripe-graphql that referenced this issue Jan 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants