Passing nested JSON as variable in Machinebox GraphQL mutation using golang - go

Hi there Golang experts,
I am using the Machinebox "github.com/machinebox/graphql" library in golang as client for my GraphQL server.
Mutations with single layer JSON variables work just fine
I am, however, at a loss as to how to pass a nested JSON as a variable
With a single layer JSON I simply create a map[string]string type and pass into the Var method. This in turn populates my graphql $data variable
The machinebox (graphql.Request).Var method takes an empty interface{} as value so the map[string]string works fine. But embedded json simply throws an error.
code:
func Mutate(data map[string]string, mutation string) interface{} {
client := GQLClient()
graphqlRequest := graphql.NewRequest(mutation)
graphqlRequest.Var("data", data)
var graphqlResponse interface{}
if err := client.Run(context.Background(), graphqlRequest, &graphqlResponse); err != nil {
panic(err)
}
return graphqlResponse
}
Mutation:
mutation createWfLog($data: WfLogCreateInput)
{
createWfLog (data: $data){
taskGUID {
id
status
notes
}
event
log
createdBy
}
}
data variable shape:
{
"data": {
"event": "Task Create",
"taskGUID": {
"connect": {"id": "606f46cdbbe767001a3b4707"}
},
"log": "my log and information",
"createdBy": "calvin cheng"
}
}
As mentioned, the embedded json (value of taskGUID) presents the problem. If value was simple string type, it's not an issue.
Have tried using a struct to define every nesting, passed in struct.
Have tried unmarshaling a struct to json. Same error.
Any help appreciated
Calvin

I have figured it out... and it is a case of my noobness with Golang.
I didn't need to do all this conversion of data or any such crazy type conversions. For some reason I got in my head everything HAD to be a map for the machinebox Var(key, value) to work
thanks to xarantolus's referenced site I was able to construct a proper strut. I populated the strut with my variable data (which was a nested json) and the mutation ran perfectly!
thanks!

Related

too many parameters returned by Resolver.ModuleName

I am working on breaking the monolithic architecture into Microservice architecture.
I did that but when I am building the code in my current repository I am getting this error.
We use graphql-gophers library
panic: too many parameters returned by (Resolver).Dummy
Has anyone ever seen this error in golang using graphql for querying?
Tried so many things but nothing has worked.
Any help would be appreciated
The error message comes from graph-gophers/graphql-go internal/exec/resolvable/resolvable.go#makeFieldExec
It is called when you parse a schema which does not match the field of an existing struct.
The one illustrated in example/customerrors/starwars.go does match every field and would not trigger the error message:
var Schema = `
schema {
query: Query
}
type Query {
droid(id: ID!): Droid!
}
# An autonomous mechanical character in the Star Wars universe
type Droid {
# The ID of the droid
id: ID!
# What others call this droid
name: String!
}
`
type droid struct {
ID graphql.ID
Name string
}
Its resolver does use the right parameters:
type Resolver struct{}
func (r *Resolver) Droid(args struct{ ID graphql.ID }) (*droidResolver, error) {
if d := droidData[args.ID]; d != nil {
return &droidResolver{d: d}, nil
}
return nil, &droidNotFoundError{Code: "NotFound", Message: "This is not the droid you are looking for"}
}
Try and use that example to check it does work, then modify it to transition to your own code.

UUID field within Go Pact consumer test

I'm currently looking at adding Pact testing into my Go code, and i'm getting stuck on how to deal with field types of UUID.
I have the following struct, which I use to deserialise a response from an API to
import (
"github.com/google/uuid"
)
type Foo struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
Now, when I try and write my consumer test, it looks something like this
pact.
AddInteraction().
Given("A result exists").
UponReceiving("A request to get all results").
WithRequest(dsl.Request{
Method: "get",
Path: dsl.String("/v1"),
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
Body: dsl.Match(&Foo{}),
})
The problem now, is the mocked response comes through as below, where it tries to put an array of bytes in the "id" field, I'm assuming since behind the scenes that is what the google/uuid library stores it as.
{
"id": [
1
],
"name": "string",
"description": "string"
}
Has anyone encountered this? And what would be the best way forward - the only solution I can see is changing my model to be a string, and then manually convert to a UUID within my code.
You currently can't use the Match function this way, as it recurses non primitive structures, albeit it should be possible to override this behaviour with struct tags. Could you please raise a feature request?
The simplest approach is to not use the Match method, and just manually express the contract details in the usual way.
The internal representation of uuid.UUID is type UUID [16]byte but json representation of UUID is string
var u uuid.UUID
u, _ = uuid.Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
foo1 := Foo{u, "n", "d"}
res, _ := json.Marshal(foo1)
fmt.Println(string(res))
{
"id": "f47ac10b-58cc-0372-8567-0e02b2c3d479",
"name": "n",
"description": "d"
}
and then load marshaled []byte
var foo Foo
json.Unmarshal(res, &foo)

Is there a better way to declare json variable

Declaring a variable of type map[string]map[string]... is not ideal, is there a better way
snaps := map[string]map[string]map[string]map[string]string{
"distros": {
"aws": {
"eu-west-1": {
"snap-0": "/dev/sdm",
},
"eu-west-2": {
"snap-1": "/dev/sdm",
},
},
},
}
fmt.Println(snaps["distros"]["aws"]["eu-west-1"])
The simplest way would be to use the type map[string]interface{}. Since the empty interface, interface{}, refers to any type and therefore handles the arbitrarily nested nature of JSON.
To do this you'll have to write your literal data as a string first and then parse the string into a Go map.
With that in mind here is a refactor of your example:
first: import "encoding/json", then
snapsStr := `{
"distros": {
"aws": {
"eu-west-1" : {
"snap-0": "/dev/sdm"
},
"eu-west-2": {
"snap-1": "/dev/sdm"
}
}
}
}`
var snaps map[string]interface{}
json.Unmarshal([]byte(snapsStr), &snaps)
And now snaps is as desired.
This is the most generic format for JSON data in Go and is one of the ways that the Go JSON library handles types for JSON. See these docs: https://golang.org/pkg/encoding/json/#Unmarshal

Go-gin intercepting a request body

I am using go-gin as server and trying to decode the request body. When I send request which has both the strings
{
"name": "abc"
}
The following code decodes it correctly:
var decodedBody map[string]string
err = json.NewDecoder(c.Request.Body).Decode(&decodedBody)
But if I send
{
"id": 1
}
The following code gives me a blank map
var decodedBody map[string]int
err = json.NewDecoder(c.Request.Body).Decode(&decodedBody)
Not sure what am I missing here. Any pointers?
because you set the decodeBody's data type with string,if your value is not the string value, it will not decode the correct value,{"id":1},it's value's type is int,not the string.

Determine if POST data value matches struct field type

Using the gin framework I am trying to determine if POST'ed data does not match the struct field type and inform API user of their error.
type CreateApp struct {
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" valid:"string,omitempty"`
ApiVersion int64 `db:"api_version" json:"api_version" valid:"int,omitempty"`
}
...
func CreateApps(c *gin.Context) {
var json models.CreateApp
c.Bind(&json)
So when I POST
curl -H "Content-Type: application/json" -d '{"learn_more_image":"someimage.jpg","api_version":"somestring"}' "http://127.0.0.1:8080/v1.0/apps"
I would like to determine whether the POST'ed data for field 'api_version' (passed as string) does not match the struct field it is being binded to (int). If the data doesnt match I'd like to send a message back to the user. Its for this reason I was hoping I could loop through the gin contexts data and check it.
The gin function 'c.Bind()' seems to omit invalid data and all subsequent data fields with it.
Gin has a built-in validation engine: https://github.com/bluesuncorp/validator/blob/v5/baked_in.go
but you can use your own or disable it completely.
The validator does not validate the wire data (json string), instead it validates the binded struct:
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" binding:"required"`
ApiVersion int64 `db:"api_version" json:"api_version" binding:"required,min=1"`
Notice this: binding:"required,min=1"
Then:
err := c.Bind(&json)
or use a middleware and read c.Errors.
UPDATED:
Three workarounds:
Validate the json string your own (it can not be done with enconding/json)
Validate if integer is > 0 binding:"min=1"
Use a map[string]interface{} instead of a Struct, then validate the type.
func endpoint(c *gin.Context) {
var json map[string]interface{}
c.Bind(&json)
struct, ok := validateCreateApp(json)
if ok { /** DO SOMETHING */ }
}
func validateCreateApp(json map[string]interface{}) (CreateApp, bool) {
learn_more_image, ok := json["learn_more_image"].(string)
if !ok {
return CreateApp{}, false
}
api_version, ok = json["api_version"].(int)
if !ok {
return CreateApp{}, false
}
return CreateApp{
learn_more_image, api_version,
}
}

Resources