sanity-io / sanity

Sanity Studio – Rapidly configure content workspaces powered by structured content
https://www.sanity.io
MIT License
5.2k stars 420 forks source link

Provide a means for making fields required in GraphQL schema #1430

Open mshick opened 5 years ago

mshick commented 5 years ago

Is your feature request related to a problem? Please describe. When using TypeScript in development from Sanity content, it's frustrating that all fields (even fields that validate as required) remain optional. This limits the effectiveness of type checking on Sanity data.

Describe the solution you'd like I would like for Sanity to provide a means to flag fields as required for the GraphQL schema, either via rules interpretation or a special property. That way I can generate types that have required fields and use them in my TypeScript codebase.

kmelve commented 5 years ago

Thanks for the feature request @mshick. This seems like a reasonable request and something that we should consider fixing. Stay tuned!

raine commented 4 years ago

Is this on the roadmap?

lorenzodejong commented 4 years ago

I have the exact same use case. TypeScript types generated from the GraphQL schema are now unusable as every property is optional. Would be nice if we can use the current validation rules (i.e.: validation: (Rule) => Rule.required()) for the schema generation.

RobbinHabermehl commented 3 years ago

Unfortunately this still isn't possible, resulting in very unpretty TypeScript that's actually defeating its purpose this way. Could you please prioritize this @kmelve? Thanks!

rexxars commented 3 years ago

Some background as to why it's currently implemented this way:

Currently the validation rules (and the field types) declared in the studio is only a "client side concern" - it doesn't actually enforce the rules on the server side when writing documents through the API. This has implications for type safety.

Consider for instance that you start out with a "product" schema type that has the following shape, where "title" is not required:

{
  _id: 'titleless-product',
  _type: 'product',
  description: 'Who knows what this is'
}

You realize your mistake, and add a required rule:

{
  _id: 'some-product',
  _type: 'product',
  title: 'Vanilla Noir',
}

Down the line, you decide that you want your product titles to be localized, so you change the shape of the title from a string to an object:

{
  _id: 'some-product',
  _type: 'product',
  title: {
    en: 'Vanilla Noir',
    no: 'Vanilje Noir',
    es: 'Vainilla Noir'
  }
}

Since the schemas from the studio are not enforced on the server, you can still have documents without a title, or which has a string as a title. You can also still create documents through the API which doesn't match the expected shape. The question then is what the GraphQL layer should do when encountering these values. In a schema where these values can be nullable, it does so. If they cannot be null, you will get an error.

One option we've talked about is having a flag to opt-in to this behavior.

lorenzodejong commented 3 years ago

Thanks for providing the extra context @rexxars. What i personally was expecting is that deploying the GraphQL schema using sanity graphql deploy (docs) also deploys any relevant validation rules.

I can imagine that enforcing these validation rules is hard, especially while developing locally where you're changing the schema quite often. The ability to opt-in to this "strict" behavior would fulfil my needs, however we don't want this to get in the way of the local development workflow as you've described in your reply.

An option would be to bypass the schema validation in the studio while in development mode and only enforce validation when deployed to production. This would allow you to change the data from the client-side studio without being restricted by any validation. The only downside is that if you want to reflect the changes in the (in this case generated) schema on the consumer side, you'd have to redeploy the schema.

It would be interesting to figure out if it's possible to automatically synchronise the schema while developing. In the current infrastructure this would quickly cause conflicts with other (code) collaborators, so perhaps it's a possibility to allow each developer to have their own deployed version of the schema.

andyrichardson commented 3 years ago

Currently the validation rules (and the field types) declared in the studio is only a "client side concern"

@rexxars am I right in thinking the solution to this is to generate the full GraphQL schema when the CLI arg is called then?

If that was the case, we could access those validation functions - something like this could do the trick.

abhijithvijayan commented 3 years ago

any updates on this? @kmelve

This is causing issues for advanced usages like generating SDL from sanity graphql schema and then using it as type-definitions for graphql server and relying on codegens to generate the types for the frontend

mattddean commented 1 year ago

@lorenzodejong @rexxars @kmelve

An option would be to bypass the schema validation in the studio while in development mode and only enforce validation when deployed to production. This would allow you to change the data from the client-side studio without being restricted by any validation. The only downside is that if you want to reflect the changes in the (in this case generated) schema on the consumer side, you'd have to redeploy the schema.

The idea of conditionally making the fields required is really interesting. I'd almost argue though that I'd rather have the extra validation in development datasets rather than in production ones, simply because of code generation. I could point the code generator, whether that be graphql-zeus, graphql-codegen, or whatever else I want, at the development dataset and build the proper types, then use those same types in production. Even though the optionality of the properties wouldn't be validated by graphql in production, the code that I write would have all the outcomes of non-optional properties in development. This is similar to how we use TypeScript itself. We use types at build time to help us build better code, and then strip them out at runtime.

I also am not convinced that it's totally necessary to conflate Sanity Studio and Content Lake concerns in the configuration. I'd happily maintain validation and schema requirement separately like this:

fields: [
 {
    name: 'title',
    type: 'string',
    title: 'Book title',
    validation: (rule) => rule.required(),
    required: true
  },
],

I think making this optional or always off in production would resolve the concern around documents being schemaless and documents existing that were created before the field was made required. Graphql wouldn't yell when encountering a document without a required field. It would simply return the document without the required field, which is the best we can expect when we've created non-conforming documents.

mikebrotzman commented 8 months ago

Any motion on this? Still a significant annoyance to us. FWIW I do see that in the library sanity-codegen, this appears solved at the Typescript level at least, via the codegen: { required: true } idea: https://www.sanity.io/plugins/sanity-codegen

Another possible solution to this is to provide a means for migrating data over time automatically, perhaps even at the time when the data is fetched. I would think that'd be necessary anyway -- if you DO change a field's type or nullability, any application depending on that data will want Sanity to intelligently migrate old data to the new format before serving it. This is a normal issue with document-oriented stores in my experience: the schema is hard to pin down and can provide an inconsistent experience to clients over time. This issue with GraphQL schemas feels like just a symptom of that larger issue.

A simple way to implement this "migration" idea is to just let users provide a defaultValue field for non-nullable fields. defaultValue would be optional, returning a sensible "empty" (but non-null) value when the backend tries to serve null data to a non-nullable field:

fields: [
 {
    name: 'title',
    type: 'string',
    title: 'Book title',
    validation: (rule) => rule.required(),
    defaultValue: 'Title'  // <-- If I don't specify this, the system falls back on ''
  },
],