go.mod: Update oapi-codegen and kin-openapi

This commit is contained in:
sanne 2022-01-11 19:00:14 +01:00 committed by Sanne Raymaekers
parent add17bba45
commit a83cf95d5b
156 changed files with 29663 additions and 2248 deletions

View file

@ -13,6 +13,8 @@ type Schema struct {
GoType string // The Go type needed to represent the schema
RefType string // If the type has a type name, this is set
ArrayType *Schema // The schema of array element
EnumValues map[string]string // Enum values
Properties []Property // For an object, the fields with names
@ -21,6 +23,11 @@ type Schema struct {
AdditionalTypes []TypeDefinition // We may need to generate auxiliary helper types, stored here
SkipOptionalPointer bool // Some types don't need a * in front when they're optional
Description string // The description of the element
// The original OpenAPIv3 Schema.
OAPISchema *openapi3.Schema
}
func (s Schema) IsRef() bool {
@ -55,11 +62,12 @@ func (s Schema) GetAdditionalTypeDefs() []TypeDefinition {
}
type Property struct {
Description string
JsonFieldName string
Schema Schema
Required bool
Nullable bool
Description string
JsonFieldName string
Schema Schema
Required bool
Nullable bool
ExtensionProps *openapi3.ExtensionProps
}
func (p Property) GoFieldName() string {
@ -74,11 +82,56 @@ func (p Property) GoTypeDef() string {
return typeDef
}
type TypeDefinition struct {
TypeName string
JsonName string
ResponseName string
// EnumDefinition holds type information for enum
type EnumDefinition struct {
Schema Schema
TypeName string
ValueWrapper string
}
type Constants struct {
// SecuritySchemeProviderNames holds all provider names for security schemes.
SecuritySchemeProviderNames []string
// EnumDefinitions holds type and value information for all enums
EnumDefinitions []EnumDefinition
}
// TypeDefinition describes a Go type definition in generated code.
//
// Let's use this example schema:
// components:
// schemas:
// Person:
// type: object
// properties:
// name:
// type: string
type TypeDefinition struct {
// The name of the type, eg, type <...> Person
TypeName string
// The name of the corresponding JSON description, as it will sometimes
// differ due to invalid characters.
JsonName string
// This is the Schema wrapper is used to populate the type description
Schema Schema
}
// ResponseTypeDefinition is an extension of TypeDefinition, specifically for
// response unmarshaling in ClientWithResponses.
type ResponseTypeDefinition struct {
TypeDefinition
// The content type name where this is used, eg, application/json
ContentTypeName string
// The type name of a response model.
ResponseName string
}
func (t *TypeDefinition) CanAlias() bool {
return t.Schema.IsRef() || /* actual reference */
(t.Schema.ArrayType != nil && t.Schema.ArrayType.IsRef()) /* array to ref */
}
func PropertiesEqual(a, b Property) bool {
@ -86,39 +139,44 @@ func PropertiesEqual(a, b Property) bool {
}
func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
// If Ref is set on the SchemaRef, it means that this type is actually a reference to
// another type. We're not de-referencing, so simply use the referenced type.
var refType string
// Add a fallback value in case the sref is nil.
// i.e. the parent schema defines a type:array, but the array has
// no items defined. Therefore we have at least valid Go-Code.
if sref == nil {
return Schema{GoType: "interface{}", RefType: refType}, nil
return Schema{GoType: "interface{}"}, nil
}
schema := sref.Value
if sref.Ref != "" {
var err error
// If Ref is set on the SchemaRef, it means that this type is actually a reference to
// another type. We're not de-referencing, so simply use the referenced type.
if IsGoTypeReference(sref.Ref) {
// Convert the reference path to Go type
refType, err = RefPathToGoType(sref.Ref)
refType, err := RefPathToGoType(sref.Ref)
if err != nil {
return Schema{}, fmt.Errorf("error turning reference (%s) into a Go type: %s",
sref.Ref, err)
}
return Schema{
GoType: refType,
GoType: refType,
Description: StringToGoComment(schema.Description),
}, nil
}
outSchema := Schema{
Description: StringToGoComment(schema.Description),
OAPISchema: schema,
}
// We can't support this in any meaningful way
if schema.AnyOf != nil {
return Schema{GoType: "interface{}", RefType: refType}, nil
outSchema.GoType = "interface{}"
return outSchema, nil
}
// We can't support this in any meaningful way
if schema.OneOf != nil {
return Schema{GoType: "interface{}", RefType: refType}, nil
outSchema.GoType = "interface{}"
return outSchema, nil
}
// AllOf is interesting, and useful. It's the union of a number of other
@ -130,14 +188,10 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
if err != nil {
return Schema{}, errors.Wrap(err, "error merging schemas")
}
mergedSchema.RefType = refType
mergedSchema.OAPISchema = schema
return mergedSchema, nil
}
outSchema := Schema{
RefType: refType,
}
// Check for custom Go type extension
if extension, ok := schema.Extensions[extPropGoType]; ok {
typeName, err := extTypeName(extension)
@ -200,11 +254,12 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
description = p.Value.Description
}
prop := Property{
JsonFieldName: pName,
Schema: pSchema,
Required: required,
Description: description,
Nullable: p.Value.Nullable,
JsonFieldName: pName,
Schema: pSchema,
Required: required,
Description: description,
Nullable: p.Value.Nullable,
ExtensionProps: &p.Value.ExtensionProps,
}
outSchema.Properties = append(outSchema.Properties, prop)
}
@ -224,76 +279,133 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
outSchema.GoType = GenStructFromSchema(outSchema)
}
return outSchema, nil
} else if len(schema.Enum) > 0 {
err := resolveType(schema, path, &outSchema)
if err != nil {
return Schema{}, errors.Wrap(err, "error resolving primitive type")
}
enumValues := make([]string, len(schema.Enum))
for i, enumValue := range schema.Enum {
enumValues[i] = fmt.Sprintf("%v", enumValue)
}
sanitizedValues := SanitizeEnumNames(enumValues)
outSchema.EnumValues = make(map[string]string, len(sanitizedValues))
var constNamePath []string
for k, v := range sanitizedValues {
if v == "" {
constNamePath = append(path, "Empty")
} else {
constNamePath = append(path, k)
}
outSchema.EnumValues[SchemaNameToTypeName(PathToTypeName(constNamePath))] = v
}
if len(path) > 1 { // handle additional type only on non-toplevel types
typeName := SchemaNameToTypeName(PathToTypeName(path))
typeDef := TypeDefinition{
TypeName: typeName,
JsonName: strings.Join(path, "."),
Schema: outSchema,
}
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, typeDef)
outSchema.RefType = typeName
}
//outSchema.RefType = typeName
} else {
f := schema.Format
switch t {
case "array":
// For arrays, we'll get the type of the Items and throw a
// [] in front of it.
arrayType, err := GenerateGoSchema(schema.Items, path)
if err != nil {
return Schema{}, errors.Wrap(err, "error generating type for array")
}
outSchema.GoType = "[]" + arrayType.TypeDecl()
outSchema.Properties = arrayType.Properties
case "integer":
// We default to int if format doesn't ask for something else.
if f == "int64" {
outSchema.GoType = "int64"
} else if f == "int32" {
outSchema.GoType = "int32"
} else if f == "" {
outSchema.GoType = "int"
} else {
return Schema{}, fmt.Errorf("invalid integer format: %s", f)
}
case "number":
// We default to float for "number"
if f == "double" {
outSchema.GoType = "float64"
} else if f == "float" || f == "" {
outSchema.GoType = "float32"
} else {
return Schema{}, fmt.Errorf("invalid number format: %s", f)
}
case "boolean":
if f != "" {
return Schema{}, fmt.Errorf("invalid format (%s) for boolean", f)
}
outSchema.GoType = "bool"
case "string":
enumValues := make([]string, len(schema.Enum))
for i, enumValue := range schema.Enum {
enumValues[i] = enumValue.(string)
}
outSchema.EnumValues = SanitizeEnumNames(enumValues)
// Special case string formats here.
switch f {
case "byte":
outSchema.GoType = "[]byte"
case "email":
outSchema.GoType = "openapi_types.Email"
case "date":
outSchema.GoType = "openapi_types.Date"
case "date-time":
outSchema.GoType = "time.Time"
case "json":
outSchema.GoType = "json.RawMessage"
outSchema.SkipOptionalPointer = true
default:
// All unrecognized formats are simply a regular string.
outSchema.GoType = "string"
}
default:
return Schema{}, fmt.Errorf("unhandled Schema type: %s", t)
err := resolveType(schema, path, &outSchema)
if err != nil {
return Schema{}, errors.Wrap(err, "error resolving primitive type")
}
}
return outSchema, nil
}
// resolveType resolves primitive type or array for schema
func resolveType(schema *openapi3.Schema, path []string, outSchema *Schema) error {
f := schema.Format
t := schema.Type
switch t {
case "array":
// For arrays, we'll get the type of the Items and throw a
// [] in front of it.
arrayType, err := GenerateGoSchema(schema.Items, path)
if err != nil {
return errors.Wrap(err, "error generating type for array")
}
outSchema.ArrayType = &arrayType
outSchema.GoType = "[]" + arrayType.TypeDecl()
additionalTypes := arrayType.GetAdditionalTypeDefs()
// Check also types defined in array item
if len(additionalTypes) > 0 {
outSchema.AdditionalTypes = append(outSchema.AdditionalTypes, additionalTypes...)
}
outSchema.Properties = arrayType.Properties
case "integer":
// We default to int if format doesn't ask for something else.
if f == "int64" {
outSchema.GoType = "int64"
} else if f == "int32" {
outSchema.GoType = "int32"
} else if f == "int16" {
outSchema.GoType = "int16"
} else if f == "int8" {
outSchema.GoType = "int8"
} else if f == "int" {
outSchema.GoType = "int"
} else if f == "uint64" {
outSchema.GoType = "uint64"
} else if f == "uint32" {
outSchema.GoType = "uint32"
} else if f == "uint16" {
outSchema.GoType = "uint16"
} else if f == "uint8" {
outSchema.GoType = "uint8"
} else if f == "uint" {
outSchema.GoType = "uint"
} else if f == "" {
outSchema.GoType = "int"
} else {
return fmt.Errorf("invalid integer format: %s", f)
}
case "number":
// We default to float for "number"
if f == "double" {
outSchema.GoType = "float64"
} else if f == "float" || f == "" {
outSchema.GoType = "float32"
} else {
return fmt.Errorf("invalid number format: %s", f)
}
case "boolean":
if f != "" {
return fmt.Errorf("invalid format (%s) for boolean", f)
}
outSchema.GoType = "bool"
case "string":
// Special case string formats here.
switch f {
case "byte":
outSchema.GoType = "[]byte"
case "email":
outSchema.GoType = "openapi_types.Email"
case "date":
outSchema.GoType = "openapi_types.Date"
case "date-time":
outSchema.GoType = "time.Time"
case "json":
outSchema.GoType = "json.RawMessage"
outSchema.SkipOptionalPointer = true
default:
// All unrecognized formats are simply a regular string.
outSchema.GoType = "string"
}
default:
return fmt.Errorf("unhandled Schema type: %s", t)
}
return nil
}
// This describes a Schema, a type definition.
type SchemaDescriptor struct {
Fields []FieldDescriptor
@ -313,20 +425,49 @@ type FieldDescriptor struct {
// JSON annotations
func GenFieldsFromProperties(props []Property) []string {
var fields []string
for _, p := range props {
for i, p := range props {
field := ""
// Add a comment to a field in case we have one, otherwise skip.
if p.Description != "" {
// Separate the comment from a previous-defined, unrelated field.
// Make sure the actual field is separated by a newline.
field += fmt.Sprintf("\n%s\n", StringToGoComment(p.Description))
if i != 0 {
field += "\n"
}
field += fmt.Sprintf("%s\n", StringToGoComment(p.Description))
}
field += fmt.Sprintf(" %s %s", p.GoFieldName(), p.GoTypeDef())
if p.Required || p.Nullable {
field += fmt.Sprintf(" `json:\"%s\"`", p.JsonFieldName)
} else {
field += fmt.Sprintf(" `json:\"%s,omitempty\"`", p.JsonFieldName)
// Support x-omitempty
omitEmpty := true
if _, ok := p.ExtensionProps.Extensions[extPropOmitEmpty]; ok {
if extOmitEmpty, err := extParseOmitEmpty(p.ExtensionProps.Extensions[extPropOmitEmpty]); err == nil {
omitEmpty = extOmitEmpty
}
}
fieldTags := make(map[string]string)
if p.Required || p.Nullable || !omitEmpty {
fieldTags["json"] = p.JsonFieldName
} else {
fieldTags["json"] = p.JsonFieldName + ",omitempty"
}
if extension, ok := p.ExtensionProps.Extensions[extPropExtraTags]; ok {
if tags, err := extExtraTags(extension); err == nil {
keys := SortedStringKeys(tags)
for _, k := range keys {
fieldTags[k] = tags[k]
}
}
}
// Convert the fieldTags map into Go field annotations.
keys := SortedStringKeys(fieldTags)
tags := make([]string, len(keys))
for i, k := range keys {
tags[i] = fmt.Sprintf(`%s:"%s"`, k, fieldTags[k])
}
field += "`" + strings.Join(tags, " ") + "`"
fields = append(fields, field)
}
return fields
@ -359,7 +500,7 @@ func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
var refType string
var err error
if ref != "" {
if IsGoTypeReference(ref) {
refType, err = RefPathToGoType(ref)
if err != nil {
return Schema{}, errors.Wrap(err, "error converting reference path to a go type")
@ -412,7 +553,7 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err
objectParts := []string{"struct {"}
for _, schemaOrRef := range allOf {
ref := schemaOrRef.Ref
if ref != "" {
if IsGoTypeReference(ref) {
// We have a referenced type, we will generate an inlined struct
// member.
// struct {
@ -426,7 +567,7 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err
objectParts = append(objectParts,
fmt.Sprintf(" // Embedded struct due to allOf(%s)", ref))
objectParts = append(objectParts,
fmt.Sprintf(" %s", goType))
fmt.Sprintf(" %s `yaml:\",inline\"`", goType))
} else {
// Inline all the fields from the schema into the output struct,
// just like in the simple case of generating an object.
@ -471,7 +612,8 @@ func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) {
// so we'll return the parameter as a string, not bothering to decode it.
if len(param.Content) > 1 {
return Schema{
GoType: "string",
GoType: "string",
Description: StringToGoComment(param.Description),
}, nil
}
@ -480,7 +622,8 @@ func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) {
if !found {
// If we don't have json, it's a string
return Schema{
GoType: "string",
GoType: "string",
Description: StringToGoComment(param.Description),
}, nil
}