wI2L / fizz

:lemon: Gin wrapper with OpenAPI 3 spec generation
https://pkg.go.dev/github.com/wI2L/fizz
MIT License
214 stars 52 forks source link

Providing Examples for Custom Types #72

Closed nikicc closed 2 years ago

nikicc commented 2 years ago

Question

Is there a way to provide example values for fields with custom types? I've implemented a custom type to change the format of a time.Time field and spotted that example value became {}. Is there a workaround for this?

Details

For the purposes of modifying the format in which we return time.time field (e.g. let's say I don't want to return the timezone information), I've implemented a custom type:

type MyTime time.Time

func (t MyTime) MarshalJSON() ([]byte, error) {
    stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format("2006-01-02T15:04:05"))
    return []byte(stamp), nil

When I use the this type on my struct:

type Item struct {
    ID      int64         `gorm:"primaryKey,column:id" json:"-"`
    DateOld time.Time     `gorm:"column:item_timestamp" json:"dateOld" example:"2021-07-12T12:20:48Z" description:"The time of the item"`
    DateNew MyTime        `gorm:"column:item_timestamp" json:"dateNew" example:"2021-07-12T12:20:48" description:"The time of the item"`
}

When I look into rendered open api spec I see this:

    Item:
      type: object
      properties:
        dateNew:
          $ref: '#/components/schemas/MyTime'
        dateOld:
          type: string
          description: The time of the item
          format: date-time

components:
  schemas:
    MyTime:
      type: object
      description: The time of the item

And consequently also the Elements library we use to rended the docs shows empty dictionary {} instead of an actual example value:

Screenshot 2022-02-07 at 15 31 51

I assume the issue for this is that custom objects are not handled in the parseExampleValue function, which cannot handle my type.

Is there any known workaround for providing examples when using custom types? If not, I'm happy to try to work on this. Any ideas how to approach this? Should we perhaps introduce an interface which custom structs would need to implement in order to provide examples?

wI2L commented 2 years ago

I think you got it. parseExampleValue doesn't work for Struct kind, so a time.Time custom value isn't handled.

I think one simple option would be to simply add a new case that check for the Struct kind and return the value parameter as-is, unparsed && untouched, but it would means that we assume all custom types are parsed from a string value. In case we'd like to use a custom type such as type Foobar int64 with tag example:"6", the spec would show a default value that is a string, not an int. This is because the schema.Example field is an interface{} and as such is marshaled to JSON accordingly to it's concret underlying type. So in the end, not very accurate.

Based on the above, we could instead add an interface OpenAPIExample (example name) that custom types would implement, such as:

type OpenAPIExample interface {
   ParseExample(v string) interface{}
}

But to be able to call it, we'd need the concrete reflect.Value, or create a new one with reflect.New, because the caller of the parseExampleValue works with a reflect.StructField.

This way, we leave the "complexity" to the custom type implementation to parse its default value from the string representation supplied by the tag, and return that.