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

238 lines
5.2 KiB
Go
Raw Normal View History

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