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.
Related
I have ECHO server on GO and assigned on it Swagger.
If I'm using the structure which is on the same package (file) then example values are working well, ex:
type Test struct {
TestField string `json:"test_field" example:"testfield"`
type MaintenanceConfigPage struct {
ConfigFile string `json:"config_file" example:"configfile"`
Test TestField `json:"test"`
}
// maintenance godoc
// #Accept json
// #Produce json
// #Param data body MaintenanceConfigPage true "Config configuration"
// #Success 200 {object} string
// #Failure 400 {object} string
// #Router /maintenance [post]
func maintenanceSetupPost(echo_context echo.Context) error {
return echo_context.JSON(http.StatusOK, "test")
}
So, that on Swagger example value I'm getting correct value:
{ "config_file": "configfile", "test": {
"test_field": "testfield" } }
But issue is coming when I'm trying to import the struct from another package, like:
type MaintenanceConfigPage struct {
ConfigFile string `json:"config_file" example:"configfile"`
Test anotherPackage.TestField `json:"test"`
}
The everything is working, but swagger on example value providing just a
{}
If someone also receive before such problems and found the solution, please share
Technologies used
Go
Gorm
PostgreSQL 14.5 (In Docker container)
OpenAPI
oapi-codegen package v1.11.0
I am building an API for CRUD operations on Personas from the Shin Megami Tensei Persona spin-off series of games. I have an issue when trying to fetch data from my database using the technologies above.
Error message
sql: Scan error on column index 0, name "arcana_id": unsupported Scan, storing driver.Value type string into type *api.ArcanaID
I think the issue is that when retrieving the data it is trying to store a string inside of a *api.ArcanaID.
How can I adjust my data model so that I can pull a UUID from my DB?
I have looked at this question and it did not solve my issue because it is dealing with nil values.
I have tried changing the type of the ArcanaID from string to uuid.UUID with no success. Same error message.
Data Model - openapi.yaml
components:
schemas:
P5Arcana:
type: object
required:
- ArcanaID
properties:
ArcanaID:
$ref: "#/components/schemas/ArcanaID"
ArcanaID:
description: A universally unique identifier for identifying one of the 22 Major Arcana.
type: string
x-go-type: uuid.UUID
x-go-type-import:
path: github.com/google/uuid
x-oapi-codegen-extra-tags:
gorm: "primaryKey;unique;type:uuid;default:uuid_generate_v4()"
interface interface.go
packages databases
import (
"context"
"github.com/bradleyGamiMarques/PersonaGrimoire/api
)
type PersonaGrimoire interface {
GetPersona5ArcanaByUUID(ctx context.Context, arcanaUUID api.ArcanaID) (arcana api.P5Arcana, err error)
}
interfaceimpl interfaceimpl.go
packages databases
import (
"context"
"errors"
"fmt"
"github.com/bradleyGamiMarques/PersonaGrimoire/api"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type PersonaGrimoireImpl struct {
Gorm *gorm.DB
Logger *logrus.Logger
}
func (p *PersonaGrimoireImpl) GetPersona5ArcanaByUUID(ctx context.Context, arcanaUUID api.ArcanaID) (arcana api.P5Arcana, err error) {
err = p.Gorm.WithContext(ctx).Model(&api.P5Arcana{ArcanaID: arcana.ArcanaID}).Where(&api.P5Arcana{ArcanaID: arcanaUUID}).First(&arcana).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
p.Logger.Warnf("Attempted to get Persona 5 Arcana by ID that does not exist. Error: %s", err.Error())
return api.P5Arcana{}, fmt.Errorf("attempted to get Persona 5 Arcana by ID that does not exist Error: %w", err)
}
}
return arcana, nil
}
Implementation code
// Check if ID exists
// Calls GetPersona5ArcanaByUUID()
// Return result
Thank you to Jamie Tanna at https://www.jvt.me/posts/2022/07/12/go-openapi-server/.
Their solution involved not using the github.com/google/uuid package and instead used the openapi_types.UUID type.
This was done by defining the schema as such.
ArcanaID:
description: A universally unique identifier for identifying one of the 22 Major Arcana.
type: string
format: uuid
pattern: "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}"
x-oapi-codegen-extra-tags:
gorm: "type:uuid;primaryKey"
This results in generated code that looks like
// ArcanaID A universally unique identifier for identifying one of the 22 Major Arcana.
type ArcanaID = openapi_types.UUID
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?
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.
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,
}
}