Generate input object helpers
gampleman opened this issue · comments
At work we have an InputObject.Extra
module, that looks like this:
withTags : List String -> { a | tags : OptionalArgument (List String) } -> { a | tags : OptionalArgument (List String) }
withTags tags inp =
{ inp | tags = Present tags }
withName : String -> { a | name : OptionalArgument String } -> { a | name : OptionalArgument String }
withName name inp =
{ inp | name = Present name }
withVisibility : List Visibility -> { a | withVisibility : OptionalArgument (List Visibility) } -> { a | withVisibility : OptionalArgument (List Visibility) }
withVisibility vis inp =
{ inp | withVisibility = Present vis }
withSetData : (SetDataInAnalysisInput -> SetDataInAnalysisInput) -> { a | setData : OptionalArgument SetDataInAnalysisInput } -> { a | setData : OptionalArgument SetDataInAnalysisInput }
withSetData fn inp =
{ inp | setData = Present (InputObject.buildSetDataInAnalysisInput fn) }
and so on. This makes simple queries nicer:
Mutation.updateProject projectId (InputObject.withTags ["some-tag"]) Project.projectId
-- vs
Mutation.updateProject projectId (\inp -> { inp | tags = Present ["some-tag" }) Project.projectId
More complex queries are quite nice also with function composition:
InputObject.buildProjectsInput (InputObject.withVisibility [ Public ] >> InputObject.withTags [ "tag" ])
-- vs
InputObject.buildProjectsInput (\inp -> { inp | visibility = Present [ Public ], tags = Public [ "tag" ] })
Complex nested queries can also be simplified:
InputObject.buildAnalysisInput (InputObject.withSetData (InputObject.withTags ["tag"]))
-- vs
InputObject.buildAnalysisInput (\inp ->
{inp |
setData = Present (InputObject.buildSetDataInAnalysisInput (\sdiaInp ->
{ sdiaInp | tags = Present ["tag"] }
)
}
)
The idea here is to generate these helpers automatically, so this more compact form of input can be used by anyone.
I'm definitely open to a pull request for some functionality like this if you're interested in working on it. It seems like a good idea overall.
One thing to consider is that I think it would need to be more flexible to allow for name collisions:
withVisibility : List Visibility -> { a | withVisibility : OptionalArgument (List Visibility) } -> { a | withVisibility : OptionalArgument (List Visibility) }
So something like this instead, since you could have fields in different types of Input Objects that have the same name but different types of values:
withVisibility : visibility -> { a | withVisibility : OptionalArgument visibility } -> { a | withVisibility : OptionalArgument visibility }
Also, I think Matt Griffith and Ryan Haskell-Glatz explored some different approaches to building input object values at some point, so it might be worth comparing notes with them. Ryan wrote a post about a few ideas: https://rhg.dev/blog/exploring-graphql-input/. And I think Matt ended up with a slightly different approach.