Determine if POST data value matches struct field type - go

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,
}
}

Related

What is strfmt.Registry in go-swagger

I have a error model that looked like this
Error:
type: object
properties:
code:
type: string
example: missing_field
description: This field contains a string succinctly identifying the problem.
message:
type: string
example: The `first_name` field is required.
description: This field contain a plainly-written, developer-oriented explanation of the solution to the problem in complete, well-formed sentences.
more_info:
type: string
format: url
example: https://docs.api.example.com/v2/users/create_user#first_name
description: This field SHOULD contain a publicly-accessible URL where information about the error can be read in a web browser.
target:
$ref: '#/definitions/Error_target'
description: error model
I used go-swagger to generate server stub. In models/error.go file
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// swagger:model Error
type Error struct {
// This field contains a string succinctly identifying the problem.
// Example: missing_field
Code string `json:"code,omitempty"`
// This field contain a plainly-written, developer-oriented explanation of the solution to the problem in complete, well-formed sentences.
// Example: The `first_name` field is required.
Message string `json:"message,omitempty"`
// This field SHOULD contain a publicly-accessible URL where information about the error can be read in a web browser.
// Example: https://docs.api.example.com/v2/users/create_user#first_name
MoreInfo string `json:"more_info,omitempty"`
// target
Target *ErrorTarget `json:"target,omitempty"`
}
// Validate validates this error
func (m *Error) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateTarget(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Error) validateTarget(formats strfmt.Registry) error {
...
}
The function Validate(formats strfmt.Registry) takes in a parameter called formats of type strfmt.Registry. Not sure, how and where to use this validate function and what to pass a argument for formats. Any example is greatly appreciated.

Pattern for writing graphql schema with gorm decorator?

I am writing graphql schema that need to work with gorm, my understanding is that graphql can't inherently use gorm decorator, so to make it work, I wrote a separate Output type:
Let's say I have this table ORM:
type Character struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"unique" json:"name"`
CliqueType string `json:"clique"`
IsHero bool `json:"hero"`
}
This is hardcoded and put into a model.go file. The gorm decorator works here as this is native go file.
In the schema.graphql I have this instead:
type CharacterOutput {
id: ID!
name: String!
isHero: Boolean!
cliqueType: CliqueType!
}
input CharacterInput {
name: String!
isHero: Boolean
cliqueType: CliqueType
}
As you can see, I have both a input and output graphql schema definition. This allow graphql to generate the required type without conflicting with gorm.
The downside of this is that I need to write a separate manual mapper between the 2 type, which kind of defeat the purpose of a ORM:
func mapChar2Output(character *model.Character) *model.CharacterOutput {
output := model.CharacterOutput{
ID: strconv.FormatUint(uint64(character.ID), 10),
Name: character.Name,
CliqueType: model.CliqueType(character.CliqueType),
IsHero: character.IsHero,
}
return &output
}
func mapInputToChar(CharacterInput *model.CharacterInput, Character *model.Character) {
Character.Name = CharacterInput.Name
Character.IsHero = *CharacterInput.IsHero
Character.CliqueType = CharacterInput.CliqueType.String()
}
My graphql resolver look like this, notice I call the mapper after getting the result from repo:
// CreateCharacter is the resolver for the createCharacter field.
func (r *mutationResolver) CreateCharacter(ctx context.Context, input model.CharacterInput) (*model.CharacterOutput, error) {
result, err := r.Resolver.CharacterRepository.CreateCharacter(&input)
output := mapChar2Output(result)
return output, err
}
My database CRUD function call the input mapper:
func (b *CharacterService) CreateCharacter(CharacterInput *model.CharacterInput) (*model.Character, error) {
character := &model.Character{}
mapInputToChar(CharacterInput, character)
err := b.Db.Create(&character).Error
return character, err
}
What is a better way of designing this schema so graphql can work with gorm without needing to write any extra mapper?

Passing nested JSON as variable in Machinebox GraphQL mutation using golang

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!

My struct is not encoding correctly and is missing a property

type ApiResponse struct {
Success bool `json:"success"`
Errors []string `json:"errors"`
}
type NewSessionResponse struct {
ApiResponse `json:"apiResponse"`
authToken string `json:"authToken"`
}
In my handler I am doing this:
resp := NewSessionResponse{ApiResponse{true, []string{}}, "auth123"}
json.NewEncoder(w).Encode(resp)
The response I am seeing is this:
{
apiResponse: {
success: true,
errors: [ ]
}
}
Why isn't my authToken property in the JSON result?
authToken filed is an unexported field. Json library does not have the power to view fields using reflect unless they are exported. A package can only view the unexported fields of types within its own package.
You can export the filed to get this working
type NewSessionResponse struct {
ApiResponse `json:"apiResponse"`
AuthToken string `json:"authToken"`
}
FYI: Exported identifiers https://golang.org/ref/spec#Exported_identifiers

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.

Resources