huma/openapi.go

164 lines
4.7 KiB
Go

package huma
import (
"fmt"
"reflect"
"github.com/danielgtaylor/huma/schema"
)
// oaContact describes contact information for this API.
type oaContact struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Email string `json:"email,omitempty"`
}
// oaServer describes an OpenAPI 3 API server location
type oaServer struct {
URL string `json:"url"`
Description string `json:"description,omitempty"`
}
// paramLocation describes where in the HTTP request the parameter comes from.
type paramLocation string
// Parameter locations supported by OpenAPI 3
const (
inPath paramLocation = "path"
inQuery paramLocation = "query"
inHeader paramLocation = "header"
)
// oaParam describes an OpenAPI 3 parameter
type oaParam struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
In paramLocation `json:"in"`
Required bool `json:"required,omitempty"`
Schema *schema.Schema `json:"schema,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
Explode *bool `json:"explode,omitempty"`
CLIName string `json:"x-cli-name,omitempty"`
// Internal params are excluded from the OpenAPI document and can set up
// params sent between a load balander / proxy and the service internally.
Internal bool `json:"-"`
typ reflect.Type
}
type oaComponents struct {
Schemas map[string]*schema.Schema `json:"schemas,omitempty"`
SecuritySchemes map[string]oaSecurityScheme `json:"securitySchemes,omitempty"`
}
// AddSchema creates and adds a new schema from a type.
func (c *oaComponents) AddSchema(t reflect.Type, mode schema.Mode, hint string, generateSchemaField bool) string {
return c.addSchema(t, mode, hint, generateSchemaField)
}
func (c *oaComponents) addSchema(t reflect.Type, mode schema.Mode, hint string, generateSchemaField bool) string {
// Try to determine the type's name.
name := t.Name()
if name == "" && t.Kind() == reflect.Ptr {
// Take the name of the pointed-to type.
name = t.Elem().Name()
}
if name == "" && t.Kind() == reflect.Slice {
// Take the name of the type in the array and append "List" to it.
tmp := t.Elem()
if tmp.Kind() == reflect.Ptr {
tmp = tmp.Elem()
}
name = tmp.Name()
if name != "" {
name += "List"
}
}
if name == "" {
// No luck, fall back to the passed-in hint. Better than nothing.
name = hint
}
var s *schema.Schema
if t.Kind() == reflect.Slice {
// We actually want to create two models: one for the container slice
// and one for the items within it.
ref := c.addSchema(t.Elem(), mode, name+"Item", false)
s = &schema.Schema{
Type: schema.TypeArray,
Items: &schema.Schema{
Ref: ref,
},
}
} else {
var err error
if s, err = schema.GenerateWithMode(t, mode, nil); err != nil {
panic(err)
}
}
return c.addExistingSchema(s, name, generateSchemaField)
}
//AddExistingSchema adds an existing schema instance under the given name.
func (c *oaComponents) AddExistingSchema(s *schema.Schema, name string, generateSchemaField bool) string {
return c.addExistingSchema(s, name, generateSchemaField)
}
func (c *oaComponents) addExistingSchema(s *schema.Schema, name string, generateSchemaField bool) string {
if generateSchemaField && s.Type == schema.TypeObject && s.Properties != nil {
if s.Properties["$schema"] == nil {
// Some editors allow you to place a $schema key which gives you rich
// validation and code completion support. Let's enable that by allowing
// a field here if it doesn't already exist in the model.
s.Properties["$schema"] = &schema.Schema{
Type: schema.TypeString,
Format: "uri",
Description: "An optional URL to a JSON Schema document describing this resource",
}
}
}
orig := name
num := 1
for {
if c.Schemas[name] == nil {
// No existing schema, we are the first!
break
}
if reflect.DeepEqual(c.Schemas[name], s) {
// Existing schema matches!
break
}
// If we are here, then an existing schema doesn't match and this is a new
// type. So we will rename it in a deterministic fashion.
num++
name = fmt.Sprintf("%s%d", orig, num)
}
c.Schemas[name] = s
return "#/components/schemas/" + name
}
type oaFlow struct {
AuthorizationURL string `json:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
Scopes map[string]string `json:"scopes,omitempty"`
}
type oaFlows struct {
ClientCredentials *oaFlow `json:"clientCredentials,omitempty"`
AuthorizationCode *oaFlow `json:"authorizationCode,omitempty"`
}
type oaSecurityScheme struct {
Type string `json:"type"`
Scheme string `json:"scheme,omitempty"`
Flows oaFlows `json:"flows,omitempty"`
}