Make Input Schema optional #1845
Replies: 36 comments
-
you need to use
|
Beta Was this translation helpful? Give feedback.
-
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Beta Was this translation helpful? Give feedback.
-
The problem is there is no difference between undefined and null value in code.
And you can use it in three different ways:
In cases 2 and 3 you will get |
Beta Was this translation helpful? Give feedback.
-
if you do some params not required then it will be a pointer otherwise it will be a value. |
Beta Was this translation helpful? Give feedback.
-
That's the problem. You can't set required field to null, but you need it to reset the value. So it needs to be optional. |
Beta Was this translation helpful? Give feedback.
-
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Beta Was this translation helpful? Give feedback.
-
Nope @Stale... |
Beta Was this translation helpful? Give feedback.
-
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Beta Was this translation helpful? Give feedback.
-
Nope @Stale... |
Beta Was this translation helpful? Give feedback.
-
I have to fall back to map[string]interface{} in such cases. Is there a better way to write it at the moment?
|
Beta Was this translation helpful? Give feedback.
-
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Beta Was this translation helpful? Give feedback.
-
Nope, stale. |
Beta Was this translation helpful? Give feedback.
-
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Beta Was this translation helpful? Give feedback.
-
A workaround is to use an empty string as null value, so there is no need to check whether the input value is null. |
Beta Was this translation helpful? Give feedback.
-
gqlgen should provide a better way than use a |
Beta Was this translation helpful? Give feedback.
-
@frederikhors I thought you want to use Usage example input UpdateTodoInput {
id: ID!
deadline: Time @goField(nullable: true)
}
type Mutation {
updateTodo(input: UpdateTodoInput!) Boolean
} expected resolver type Time = time.Time
type NullTime struct {
Time Time
IsNull bool
}
type UpdateTodoInput struct {
ID string
Deadline *NullTime
}
func (r *mutationResolver) updateTodo(ctx context.Context, input UpdateTodoInput) (*bool, error) {
if input.Deadline == nil {
print("input value is undefined")
} else if input.Deadline.IsNull {
print("input value is null")
} else {
print(input.Deadline.Time)
}
panic("not implemented")
} For now we can also check ArgumentMap from context, mentioned by #977 (comment) |
Beta Was this translation helpful? Give feedback.
-
Thank you so much. These are invaluable advice. Can I ask you to explain Thanks again. |
Beta Was this translation helpful? Give feedback.
-
Because if null field in a array, like: input UpdateInputTodoInputData! {
id: ID!
deadline: Time
}
input UpdateTodoInput {
data: [UpdateInputTodoInputData!]
} Then current index in array is required to check whether |
Beta Was this translation helpful? Give feedback.
-
Ok, just to understand if I get it. Today we can handle the
I'll briefly share what I'm using today to figure out if I'm SERIOUSLY wrong:
type Player {
name: String!
notes: String
}
input PlayerInput {
name: String
notes: String
}
---
models:
PlayerInput:
model: "map[string]interface{}"
type Player struct {
Name string `validate:"required"`
Notes *string
}
func (r *mutationResolver) PlayerCreate(ctx context.Context, input map[string]interface{}) (*entities.Player, error) {
return r.Controllers.Player.Create(ctx, input)
}
func (r *mutationResolver) PlayerUpdate(ctx context.Context, id int, input map[string]interface{}) (*entities.Player, error) {
return r.Controllers.Player.Update(ctx, id, input)
}
func (controller *controller) Create(ctx context.Context, input map[string]interface{}) (*entities.Player, error) {
var finalPlayer entities.Player
mergeInputToFinalPlayer(input, &finalPlayer)
validateFinalPlayer(&finalPlayer)
businessLogicBeforeCreate(&finalPlayer)
controller.Create(ctx, &finalPlayer)
return &finalPlayer, nil
} As you can see, all the validation and business logic is therefore on the final types ( And hence my question: what do you think could happen so badly? I know that I lose type safety, but having GraphQL as a contract for API users I think it can be fine (ex: if there are more fields on the wrong request a "field not existing" error is issued). Am I missing something? Is it a good-enough way in your opinion? Again, thanks for your precious time. 😄 |
Beta Was this translation helpful? Give feedback.
-
@frederikhors nullable types is not implemented by gqlgen, just a idea to handle this problem. type safety is why i like gqlgen, map solution is not good enough for me as:
|
Beta Was this translation helpful? Give feedback.
-
I wrote a util function to check value IsNull import (
"context"
"github.com/99designs/gqlgen/graphql"
)
type argumentSelector = func(v interface{}) (ret interface{}, ok bool)
// ArgumentsQuery to check whether arg value is null
type ArgumentsQuery struct {
args map[string]interface{}
selectors []argumentSelector
}
func (a ArgumentsQuery) selected() (ret interface{}, ok bool) {
ret, ok = a.args, true
for _, fn := range a.selectors {
ret, ok = fn(ret)
if !ok {
break
}
}
return
}
// IsNull return whether selected field value is null.
func (a ArgumentsQuery) IsNull() bool {
v, ok := a.selected()
return ok && v == nil
}
func (a ArgumentsQuery) child(fn argumentSelector) ArgumentsQuery {
var selectors = make([]argumentSelector, 0, len(a.selectors)+1)
selectors = append(selectors, a.selectors...)
selectors = append(selectors, fn)
return ArgumentsQuery{
args: a.args,
selectors: selectors,
}
}
// Field select field by name, returns a new query.
func (a ArgumentsQuery) Field(name string) ArgumentsQuery {
return a.child(func(v interface{}) (ret interface{}, ok bool) {
var m map[string]interface{}
if m, ok = v.(map[string]interface{}); ok {
ret, ok = m[name]
}
return
})
}
// Index select field by array index, returns a new query.
func (a ArgumentsQuery) Index(index int) ArgumentsQuery {
return a.child(func(v interface{}) (ret interface{}, ok bool) {
if index < 0 {
return
}
var a []interface{}
if a, ok = v.([]interface{}); ok {
if index > len(a)-1 {
ok = false
return
}
ret = a[index]
}
return
})
}
// Arguments query to check whether args value is null.
// https://github.com/99designs/gqlgen/issues/866
func Arguments(ctx context.Context) (ret ArgumentsQuery) {
fc := graphql.GetFieldContext(ctx)
oc := graphql.GetOperationContext(ctx)
if fc == nil || oc == nil {
return
}
ret.args = fc.Field.ArgumentMap(oc.Variables)
return
} Usage: func (r *mutationResolver) updateTodo(ctx context.Context, input UpdateTodoInput) (*bool, error) {
inputArgs = Arguments(ctx).Field("input")
for index, d := range input.Data {
dataArgs = inputArgs.Field("data").Index(index)
if dataArgs.Field("deadline").IsNull() {
print("input value is null")
} else if d.Deadline == nil {
print("input value is undefined")
} else {
print(d.Deadline)
}
}
panic("not implemented")
}
`` |
Beta Was this translation helpful? Give feedback.
-
@NateScarlet, your work here is amazing! What I'm afraid of is writing all this code manually: func (r *mutationResolver) updateTodo(ctx context.Context, input UpdateTodoInput) (*bool, error) {
inputArgs = Arguments(ctx).Field("input")
for index, d := range input.Data {
dataArgs = inputArgs.Field("data").Index(index)
if dataArgs.Field("deadline").IsNull() {
print("input value is null")
} else if d.Deadline == nil {
print("input value is undefined")
} else {
print(d.Deadline)
}
}
panic("not implemented")
} In this code there are multiple strings that don't help in IDE or at build time. Or am I wrong?
And I have to write this code for each field of my structs! Hard to think you can write it by hand, right? Were you thinking about automatic code generation? |
Beta Was this translation helpful? Give feedback.
-
Yes, this is the limitation for ArgumentMap solution. when you have typo on field name, it just returns false. Auto generated null type by gqlgen still a better way to solve this problem. For now, i am using https://github.com/NateScarlet/gotmpl to generate repeative code. String path can be converted from field name using |
Beta Was this translation helpful? Give feedback.
-
@NateScarlet I'm taking advantage of your patience again. And I thank you in advance. ❤️ I'm trying your code and what I'm using right now is only this: type PlayerInput struct {
Username *string
GamingDetail *GamingDetail
}
type GamingDetail struct {
Towns []TownInput
Phones []PhoneInput
}
type TownInput struct {
Name *string
People *string
}
type PhoneInput struct {
Name *string
Phone *string
}
func (r *mutationResolver) PlayerUpdate(ctx context.Context, id int, input PlayerInput) (*Player, error) {
var finalPlayer *Player
DB.GetPlayer(finalPlayer)
inputArgs := gqlgen.Arguments(ctx).Field("input")
if inputArgs.Field("username").IsNull() {
print("username is null")
} else if input.Username == nil {
print("username is undefined")
finalPlayer.Username = input.Username
} else {
print(input.Username)
finalPlayer.Username = input.Username
}
if inputArgs.Field("gamingDetail").IsNull() {
print("gamingDetail is null")
} else if input.GamingDetail == nil {
print("gamingDetail is undefined")
finalPlayer.GamingDetail = input.GamingDetail
} else {
if inputArgs.Field("gamingDetail").Field("towns").IsNull() {
print("towns is null")
} else if input.GamingDetail.Towns == nil {
print("towns is undefined")
finalPlayer.GamingDetail.Towns = input.GamingDetail.Towns
} else {
print(input.GamingDetail.Towns)
finalPlayer.GamingDetail.Towns = input.GamingDetail.Towns
}
}
r.Controllers.Player.Update(id, finalPlayer)
return finalPlayer, nil
} and it works.
Thank you again. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@NateScarlet, what do you think about this simpler code from #505 (comment)? func getInputFromContext(ctx context.Context, key string) map[string]interface{} {
requestContext := graphql.GetRequestContext(ctx)
m, ok := requestContext.Variables[key].(map[string]interface{})
if !ok {
fmt.Println("can not get input from context")
}
return m
} |
Beta Was this translation helpful? Give feedback.
-
I think this implementation is not support literal variable inside query gql, or variable names that not same as argument name defined in schema. |
Beta Was this translation helpful? Give feedback.
-
@NateScarlet I was looking for a solution that uses nullable types, as you also suggested, I came across this thread where @jszwedko says they don't solve the problem anyway. It's true? What do you think about it? |
Beta Was this translation helpful? Give feedback.
-
@frederikhors null type need gqlgen support, you can't just write a custom null type as model. Because gqlgen not call unmarshall when input value is null. |
Beta Was this translation helpful? Give feedback.
-
This issue has been be solved by #2585 |
Beta Was this translation helpful? Give feedback.
-
What happened?
I have some minimal apps using gqlgen that need to update the User Model, updating the using
But when regenerate gqlgen it should use pointers for the password where the password are not inputed from user and then causing (nil pointer)
What did you expect?
So when updating the User , the user can optionally input password or not.
Minimal graphql.schema and models to reproduce
versions
gqlgen version
? V.0.9.3go version
? 1.12.9Beta Was this translation helpful? Give feedback.
All reactions