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

Support for Null Values in mutations #210

Closed
Hagbarth opened this issue May 15, 2018 · 11 comments
Closed

Support for Null Values in mutations #210

Hagbarth opened this issue May 15, 2018 · 11 comments

Comments

@Hagbarth
Copy link

According to the GraphQL spec, there should be a distinction between explicitly set nullvalues and just plain omitted values (spec here: http://facebook.github.io/graphql/draft/#sec-Null-Value).

It can be used to distinguish between ignoring a field and deleting the field value in a mutation fx.

Is there support for this already and I'm just missing it?

@derekpitt
Copy link

I am interested this as well.

It would be nice to support json.RawMessage for nullable fields in the schema and not require it to be a pointer..

or maybe use a struct tag to skip the runtime type checks against the schema?

I'll poke around and maybe work on a PR.

@neelance or @tonyghita any thoughts on this?

@Hagbarth
Copy link
Author

@derekpitt we had a pretty severe bug in our system, so I ended up forking and providing a pretty crude solution that enables you to add another argument to your resolver functions, that can tell you if a field was provided or not.

@rinatio
Copy link

rinatio commented Jan 18, 2019

I'm also interested in this. An example would be an update mutation that updates only fields passed in the query (and some of them could be null). There seems to be no way to identify if particular argument was passed as null or simply omitted. Could you please suggest any workarounds?

@desdic
Copy link

desdic commented Mar 13, 2019

I second a solution on this. I have tried 2 different graphql projects written on go now and they both do not support null vs attribute not there. It would be really nice since this library has a pretty nice interface.

@pavelnikolov
Copy link
Member

pavelnikolov commented Nov 11, 2019

This issue is not something that I am actively looking at. Indeed it is mentioned in the spec that null and a missing value should be treated differently. PRs are welcome.

@pavelnikolov
Copy link
Member

#430 resolved this

@Slessi
Copy link
Contributor

Slessi commented Nov 29, 2023

Following off of #430 if I wanted to handle the null vs omitted case for an array, e.g. [String!], do I need the below? Or am I overlooking a more straight forward approach?

type NullSliceString struct {
	Value *[]string
	Set   bool
}

func (NullSliceString) ImplementsGraphQLType(name string) bool {
	return name == "[String!]"
}

func (s *NullSliceString) UnmarshalGraphQL(input any) error {
	s.Set = true

	if input == nil {
		return nil
	}

	switch v := input.(type) {
	case []any:
		val := make([]string, len(v))
		for i := range v {
			val[i] = v[i].(string)
		}
		s.Value = &val
		return nil
	default:
		return fmt.Errorf("wrong type for [String!]: %T", v)
	}
}

func (s *NullSliceString) Nullable() {}

@pavelnikolov
Copy link
Member

pavelnikolov commented Nov 30, 2023

Hi @Slessi,
you don't need this since it's only for scalar types. In your case you could just have a pointer to an array and have the same effect:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	graphql "github.com/graph-gophers/graphql-go"
	"github.com/graph-gophers/graphql-go/relay"
)

type query struct{}

func (query) Test(args struct{ Vals *[]string }) string {
	if args.Vals == nil {
		return "Got <nil> input"
	}

	res := fmt.Sprintf("Got a non-nil array: [%v]", strings.Join(*args.Vals, ", "))
	return res
}

func main() {
	s := `
        type Query {
		test(vals: [String!]): String!
        }
    	`
	schema := graphql.MustParseSchema(s, &query{})
	http.Handle("/query", &relay.Handler{Schema: schema})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Then after running go run main.go:

➜  curl -XPOST -d '{"query": "{ test1:test(vals: null) test2:test(vals: [\"val1\",\"val2\"]) }"}' localhost:8080/query | jq
{
  "data": {
    "test1": "Got <nil> input",
    "test2": "Got a non-nil array: [val1, val2]"
  }
}

@Slessi
Copy link
Contributor

Slessi commented Nov 30, 2023

@pavelnikolov That doesn't address the key information I'm looking for though, namely 3 states, not 2 states:

  • Input not specified
  • Input specified, nil
  • Input specified, non-nil

with *[]string there is no way to distinguish between the first two right?

@pavelnikolov
Copy link
Member

pavelnikolov commented Nov 30, 2023

Ah, I'm sorry, you are right! In that case you need to implement the NullUnmarshaller interface.

type NullUnmarshaller interface {
	Unmarshaler
	Nullable()
}

Therefore your example above works fine:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	graphql "github.com/graph-gophers/graphql-go"
	"github.com/graph-gophers/graphql-go/relay"
)

type query struct{}

func (query) Test(args struct{ Val NullSliceString }) string {
	if !args.Val.Set {
		return "Val is not set"
	}

	if args.Val.Value == nil {
		return "Got <nil> input"
	}

	res := fmt.Sprintf("Got a non-nil array: [%v]", strings.Join(*args.Val.Value, ", "))
	return res
}

type NullSliceString struct {
	Value *[]string
	Set   bool
}

func (NullSliceString) ImplementsGraphQLType(name string) bool {
	return name == "[String!]"
}

func (s *NullSliceString) UnmarshalGraphQL(input any) error {
	s.Set = true

	if input == nil {
		return nil
	}

	switch v := input.(type) {
	case []any:
		val := make([]string, len(v))
		for i := range v {
			val[i] = v[i].(string)
		}
		s.Value = &val
		return nil
	default:
		return fmt.Errorf("wrong type for [String!]: %T", v)
	}
}

func (s *NullSliceString) Nullable() {}

func main() {
	s := `
        type Query {
		test(val: [String!]): String!
	}
    	`
	schema := graphql.MustParseSchema(s, &query{})
	http.Handle("/query", &relay.Handler{Schema: schema})
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Then when I invoke that I got:

➜ curl -XPOST -d '{"query": "{ test1:test(val: null) test2:test(val: [\"val1\",\"val2\"]) test3:test }"}' localhost:8080/query | jq
{
  "data": {
    "test1": "Got <nil> input",
    "test2": "Got a non-nil array: [val1, val2]",
    "test3": "Val is not set"
  }
}

I'm not aware of a shorter way to implement this behavior.

@Slessi
Copy link
Contributor

Slessi commented Nov 30, 2023

Ok cool, I'm new to go and wanted to make sure I'm on the right track - thanks a lot!

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

No branches or pull requests

6 participants