-
Notifications
You must be signed in to change notification settings - Fork 1.2k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Make Input Schema optional #866
Comments
you need to use
|
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. |
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 |
if you do some params not required then it will be a pointer otherwise it will be a value. |
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. |
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. |
Nope @Stale... |
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. |
Nope @Stale... |
I have to fall back to map[string]interface{} in such cases. Is there a better way to write it at the moment?
|
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. |
Nope, stale. |
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. |
A workaround is to use an empty string as null value, so there is no need to check whether the input value is null. |
gqlgen should provide a better way than use a |
What's the problem with using something like this: https://gqlgen.com/reference/changesets/ ? |
Line 10 in 622316e
|
I'm using I use Is there a way (today) to not handwrite the following code for each field? if input.PhoneNumber != nil {
output.PhoneNumber = *input.PhoneNumber
}
if input.Password != nil {
output.Password = *input.Password
} |
map[string]interface{} has no build time type check |
Set each field explicitly looks good to me.
|
Why are you suggesting to transform the struct into a models:
UserChanges:
model: "map[string]interface{}" I too would like to avoid I am stuck like others people at the point where I don't know if in the coming input object the fields are Can I ask you to write a usage example with types like There is no other way to accomplish this besides Thanks for your help, really. |
@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) |
Thank you so much. These are invaluable advice. Can I ask you to explain Thanks again. |
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 |
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. 😄 |
@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:
|
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")
}
`` |
@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? |
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 |
@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. |
|
@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
} |
I think this implementation is not support literal variable inside query gql, or variable names that not same as argument name defined in schema. |
@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? |
@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. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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.9The text was updated successfully, but these errors were encountered: