k3s/vendor/github.com/rancher/wrangler/pkg/schemas/openapi/generate.go

238 lines
5.2 KiB
Go

package openapi
import (
"encoding/json"
"fmt"
"sort"
"strings"
types "github.com/rancher/wrangler/pkg/schemas"
"github.com/rancher/wrangler/pkg/schemas/definition"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
func MustGenerate(obj interface{}) *v1.JSONSchemaProps {
if obj == nil {
return nil
}
result, err := ToOpenAPIFromStruct(obj)
if err != nil {
panic(err)
}
return result
}
func ToOpenAPIFromStruct(obj interface{}) (*v1.JSONSchemaProps, error) {
schemas := types.EmptySchemas()
schema, err := schemas.Import(obj)
if err != nil {
return nil, err
}
return ToOpenAPI(schema.ID, schemas)
}
func ToOpenAPI(name string, schemas *types.Schemas) (*v1.JSONSchemaProps, error) {
schema := schemas.Schema(name)
if schema == nil {
return nil, fmt.Errorf("failed to find schema: %s", name)
}
newSchema := schema.DeepCopy()
if newSchema.InternalSchema != nil {
newSchema = newSchema.InternalSchema.DeepCopy()
}
delete(newSchema.ResourceFields, "kind")
delete(newSchema.ResourceFields, "apiVersion")
delete(newSchema.ResourceFields, "metadata")
return schemaToProps(newSchema, schemas, map[string]bool{})
}
func populateField(fieldJSP *v1.JSONSchemaProps, f *types.Field) error {
fieldJSP.Description = f.Description
// don't reset this to not nullable
if f.Nullable {
fieldJSP.Nullable = f.Nullable
}
fieldJSP.MinLength = f.MinLength
fieldJSP.MaxLength = f.MaxLength
if f.Type == "string" && len(f.Options) > 0 {
for _, opt := range append(f.Options, "") {
bytes, err := json.Marshal(&opt)
if err != nil {
return err
}
fieldJSP.Enum = append(fieldJSP.Enum, v1.JSON{
Raw: bytes,
})
}
}
if len(f.InvalidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[^%s]*$", f.InvalidChars)
}
if len(f.ValidChars) > 0 {
fieldJSP.Pattern = fmt.Sprintf("^[%s]*$", f.ValidChars)
}
if f.Min != nil {
fl := float64(*f.Min)
fieldJSP.Minimum = &fl
}
if f.Max != nil {
fl := float64(*f.Max)
fieldJSP.Maximum = &fl
}
if f.Default != nil {
bytes, err := json.Marshal(f.Default)
if err != nil {
return err
}
fieldJSP.Default = &v1.JSON{
Raw: bytes,
}
}
return nil
}
func typeToProps(typeName string, schemas *types.Schemas, inflight map[string]bool) (*v1.JSONSchemaProps, error) {
t, subType, schema, err := typeAndSchema(typeName, schemas)
if err != nil {
return nil, err
}
if schema != nil {
return schemaToProps(schema, schemas, inflight)
}
jsp := &v1.JSONSchemaProps{}
switch t {
case "map":
additionalProps, err := typeToProps(subType, schemas, inflight)
if err != nil {
return nil, err
}
jsp.Type = "object"
jsp.Nullable = true
if subType != "json" {
jsp.AdditionalProperties = &v1.JSONSchemaPropsOrBool{
Allows: true,
Schema: additionalProps,
}
}
case "array":
items, err := typeToProps(subType, schemas, inflight)
if err != nil {
return nil, err
}
jsp.Type = "array"
jsp.Nullable = true
jsp.Items = &v1.JSONSchemaPropsOrArray{
Schema: items,
}
case "string":
jsp.Type = t
jsp.Nullable = true
default:
jsp.Type = t
}
if jsp.Type == "object" && jsp.AdditionalProperties == nil {
jsp.XPreserveUnknownFields = &[]bool{true}[0]
}
return jsp, nil
}
func schemaToProps(schema *types.Schema, schemas *types.Schemas, inflight map[string]bool) (*v1.JSONSchemaProps, error) {
jsp := &v1.JSONSchemaProps{
Description: schema.Description,
Type: "object",
}
if inflight[schema.ID] {
return jsp, nil
}
inflight[schema.ID] = true
defer delete(inflight, schema.ID)
jsp.Properties = map[string]v1.JSONSchemaProps{}
for name, f := range schema.ResourceFields {
fieldJSP, err := typeToProps(f.Type, schemas, inflight)
if err != nil {
return nil, err
}
if err := populateField(fieldJSP, &f); err != nil {
return nil, err
}
if f.Required {
jsp.Required = append(jsp.Required, name)
}
jsp.Properties[name] = *fieldJSP
}
sort.Strings(jsp.Required)
if len(jsp.Properties) == 0 && strings.HasSuffix(strings.ToLower(schema.ID), "map") {
jsp.XPreserveUnknownFields = &[]bool{true}[0]
}
return jsp, nil
}
func typeAndSchema(typeName string, schemas *types.Schemas) (string, string, *types.Schema, error) {
if definition.IsReferenceType(typeName) {
return "string", "", nil, nil
}
if definition.IsArrayType(typeName) {
return "array", definition.SubType(typeName), nil, nil
}
if definition.IsMapType(typeName) {
return "map", definition.SubType(typeName), nil, nil
}
switch typeName {
// TODO: in v1 set the x- header for this
case "intOrString":
return "string", "", nil, nil
case "int":
return "integer", "", nil, nil
case "float":
return "number", "", nil, nil
case "string":
return "string", "", nil, nil
case "date":
return "string", "", nil, nil
case "enum":
return "string", "", nil, nil
case "base64":
return "string", "", nil, nil
case "password":
return "string", "", nil, nil
case "hostname":
return "string", "", nil, nil
case "boolean":
return "boolean", "", nil, nil
case "json":
return "object", "", nil, nil
}
schema := schemas.Schema(typeName)
if schema == nil {
return "", "", nil, fmt.Errorf("failed to find schema %s", typeName)
}
if schema.InternalSchema != nil {
return "", "", schema.InternalSchema, nil
}
return "", "", schema, nil
}