github.com/gogf/gf/v2@v2.7.4/net/goai/goai_shema.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package goai
     8  
     9  import (
    10  	"reflect"
    11  
    12  	"github.com/gogf/gf/v2/container/gmap"
    13  	"github.com/gogf/gf/v2/container/gset"
    14  	"github.com/gogf/gf/v2/errors/gerror"
    15  	"github.com/gogf/gf/v2/internal/json"
    16  	"github.com/gogf/gf/v2/internal/utils"
    17  	"github.com/gogf/gf/v2/os/gstructs"
    18  	"github.com/gogf/gf/v2/text/gstr"
    19  	"github.com/gogf/gf/v2/util/gconv"
    20  	"github.com/gogf/gf/v2/util/gmeta"
    21  	"github.com/gogf/gf/v2/util/gvalid"
    22  )
    23  
    24  // Schema is specified by OpenAPI/Swagger 3.0 standard.
    25  type Schema struct {
    26  	OneOf                SchemaRefs     `json:"oneOf,omitempty"`
    27  	AnyOf                SchemaRefs     `json:"anyOf,omitempty"`
    28  	AllOf                SchemaRefs     `json:"allOf,omitempty"`
    29  	Not                  *SchemaRef     `json:"not,omitempty"`
    30  	Type                 string         `json:"type,omitempty"`
    31  	Title                string         `json:"title,omitempty"`
    32  	Format               string         `json:"format,omitempty"`
    33  	Description          string         `json:"description,omitempty"`
    34  	Enum                 []interface{}  `json:"enum,omitempty"`
    35  	Default              interface{}    `json:"default,omitempty"`
    36  	Example              interface{}    `json:"example,omitempty"`
    37  	ExternalDocs         *ExternalDocs  `json:"externalDocs,omitempty"`
    38  	UniqueItems          bool           `json:"uniqueItems,omitempty"`
    39  	ExclusiveMin         bool           `json:"exclusiveMinimum,omitempty"`
    40  	ExclusiveMax         bool           `json:"exclusiveMaximum,omitempty"`
    41  	Nullable             bool           `json:"nullable,omitempty"`
    42  	ReadOnly             bool           `json:"readOnly,omitempty"`
    43  	WriteOnly            bool           `json:"writeOnly,omitempty"`
    44  	AllowEmptyValue      bool           `json:"allowEmptyValue,omitempty"`
    45  	XML                  interface{}    `json:"xml,omitempty"`
    46  	Deprecated           bool           `json:"deprecated,omitempty"`
    47  	Min                  *float64       `json:"minimum,omitempty"`
    48  	Max                  *float64       `json:"maximum,omitempty"`
    49  	MultipleOf           *float64       `json:"multipleOf,omitempty"`
    50  	MinLength            uint64         `json:"minLength,omitempty"`
    51  	MaxLength            *uint64        `json:"maxLength,omitempty"`
    52  	Pattern              string         `json:"pattern,omitempty"`
    53  	MinItems             uint64         `json:"minItems,omitempty"`
    54  	MaxItems             *uint64        `json:"maxItems,omitempty"`
    55  	Items                *SchemaRef     `json:"items,omitempty"`
    56  	Required             []string       `json:"required,omitempty"`
    57  	Properties           *Schemas       `json:"properties,omitempty"`
    58  	MinProps             uint64         `json:"minProperties,omitempty"`
    59  	MaxProps             *uint64        `json:"maxProperties,omitempty"`
    60  	AdditionalProperties *SchemaRef     `json:"additionalProperties,omitempty"`
    61  	Discriminator        *Discriminator `json:"discriminator,omitempty"`
    62  	XExtensions          XExtensions    `json:"-"`
    63  	ValidationRules      string         `json:"-"`
    64  }
    65  
    66  // Clone only clones necessary attributes.
    67  // TODO clone all attributes, or improve package deepcopy.
    68  func (s *Schema) Clone() *Schema {
    69  	newSchema := *s
    70  	newSchema.Required = make([]string, len(s.Required))
    71  	copy(newSchema.Required, s.Required)
    72  	newSchema.Properties = s.Properties.Clone()
    73  	return &newSchema
    74  }
    75  
    76  func (s Schema) MarshalJSON() ([]byte, error) {
    77  	var (
    78  		b   []byte
    79  		m   map[string]json.RawMessage
    80  		err error
    81  	)
    82  	type tempSchema Schema // To prevent JSON marshal recursion error.
    83  	if b, err = json.Marshal(tempSchema(s)); err != nil {
    84  		return nil, err
    85  	}
    86  	if err = json.Unmarshal(b, &m); err != nil {
    87  		return nil, err
    88  	}
    89  	for k, v := range s.XExtensions {
    90  		if b, err = json.Marshal(v); err != nil {
    91  			return nil, err
    92  		}
    93  		m[k] = b
    94  	}
    95  	return json.Marshal(m)
    96  }
    97  
    98  // Discriminator is specified by OpenAPI/Swagger standard version 3.0.
    99  type Discriminator struct {
   100  	PropertyName string            `json:"propertyName"`
   101  	Mapping      map[string]string `json:"mapping,omitempty"`
   102  }
   103  
   104  // addSchema creates schemas with objects.
   105  // Note that the `object` can be array alias like: `type Res []Item`.
   106  func (oai *OpenApiV3) addSchema(object ...interface{}) error {
   107  	for _, v := range object {
   108  		if err := oai.doAddSchemaSingle(v); err != nil {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func (oai *OpenApiV3) doAddSchemaSingle(object interface{}) error {
   116  	if oai.Components.Schemas.refs == nil {
   117  		oai.Components.Schemas.refs = gmap.NewListMap()
   118  	}
   119  
   120  	var (
   121  		reflectType    = reflect.TypeOf(object)
   122  		structTypeName = oai.golangTypeToSchemaName(reflectType)
   123  	)
   124  
   125  	// Already added.
   126  	if oai.Components.Schemas.Get(structTypeName) != nil {
   127  		return nil
   128  	}
   129  	// Take the holder first.
   130  	oai.Components.Schemas.Set(structTypeName, SchemaRef{})
   131  
   132  	schema, err := oai.structToSchema(object)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	oai.Components.Schemas.Set(structTypeName, SchemaRef{
   138  		Ref:   "",
   139  		Value: schema,
   140  	})
   141  	return nil
   142  }
   143  
   144  // structToSchema converts and returns given struct object as Schema.
   145  func (oai *OpenApiV3) structToSchema(object interface{}) (*Schema, error) {
   146  	var (
   147  		tagMap = gmeta.Data(object)
   148  		schema = &Schema{
   149  			Properties:  createSchemas(),
   150  			XExtensions: make(XExtensions),
   151  		}
   152  		ignoreProperties []interface{}
   153  	)
   154  	if len(tagMap) > 0 {
   155  		if err := oai.tagMapToSchema(tagMap, schema); err != nil {
   156  			return nil, err
   157  		}
   158  	}
   159  	if schema.Type != "" && schema.Type != TypeObject {
   160  		return schema, nil
   161  	}
   162  	// []struct.
   163  	if utils.IsArray(object) {
   164  		schema.Type = TypeArray
   165  		subSchemaRef, err := oai.newSchemaRefWithGolangType(reflect.TypeOf(object).Elem(), nil)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		schema.Items = subSchemaRef
   170  		if len(schema.Enum) > 0 {
   171  			schema.Items.Value.Enum = schema.Enum
   172  			schema.Enum = nil
   173  		}
   174  		return schema, nil
   175  	}
   176  	// struct.
   177  	structFields, _ := gstructs.Fields(gstructs.FieldsInput{
   178  		Pointer:         object,
   179  		RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
   180  	})
   181  	schema.Type = TypeObject
   182  	for _, structField := range structFields {
   183  		if !gstr.IsLetterUpper(structField.Name()[0]) {
   184  			continue
   185  		}
   186  		var fieldName = structField.TagPriorityName()
   187  		fieldName = gstr.Split(gstr.Trim(fieldName), ",")[0]
   188  		if fieldName == "" {
   189  			fieldName = structField.Name()
   190  		}
   191  		schemaRef, err := oai.newSchemaRefWithGolangType(
   192  			structField.Type().Type,
   193  			structField.TagMap(),
   194  		)
   195  		if err != nil {
   196  			return nil, err
   197  		}
   198  		schema.Properties.Set(fieldName, *schemaRef)
   199  	}
   200  
   201  	schema.Properties.Iterator(func(key string, ref SchemaRef) bool {
   202  		if ref.Value != nil && ref.Value.ValidationRules != "" {
   203  			validationRuleSet := gset.NewStrSetFrom(gstr.Split(ref.Value.ValidationRules, "|"))
   204  			if validationRuleSet.Contains(validationRuleKeyForRequired) {
   205  				schema.Required = append(schema.Required, key)
   206  			}
   207  		}
   208  		if !isValidParameterName(key) {
   209  			ignoreProperties = append(ignoreProperties, key)
   210  		}
   211  		return true
   212  	})
   213  
   214  	if len(ignoreProperties) > 0 {
   215  		schema.Properties.Removes(ignoreProperties)
   216  	}
   217  
   218  	return schema, nil
   219  }
   220  
   221  func (oai *OpenApiV3) tagMapToSchema(tagMap map[string]string, schema *Schema) error {
   222  	var mergedTagMap = oai.fillMapWithShortTags(tagMap)
   223  	if err := gconv.Struct(mergedTagMap, schema); err != nil {
   224  		return gerror.Wrap(err, `mapping struct tags to Schema failed`)
   225  	}
   226  	oai.tagMapToXExtensions(mergedTagMap, schema.XExtensions)
   227  	// Validation info to OpenAPI schema pattern.
   228  	for _, tag := range gvalid.GetTags() {
   229  		if validationTagValue, ok := tagMap[tag]; ok {
   230  			_, validationRules, _ := gvalid.ParseTagValue(validationTagValue)
   231  			schema.ValidationRules = validationRules
   232  			// Enum checks.
   233  			if len(schema.Enum) == 0 {
   234  				for _, rule := range gstr.SplitAndTrim(validationRules, "|") {
   235  					if gstr.HasPrefix(rule, validationRuleKeyForIn) {
   236  						var (
   237  							isAllEnumNumber = true
   238  							enumArray       = gstr.SplitAndTrim(rule[len(validationRuleKeyForIn):], ",")
   239  						)
   240  						for _, enum := range enumArray {
   241  							if !gstr.IsNumeric(enum) {
   242  								isAllEnumNumber = false
   243  								break
   244  							}
   245  						}
   246  						if isAllEnumNumber {
   247  							schema.Enum = gconv.Interfaces(gconv.Int64s(enumArray))
   248  						} else {
   249  							schema.Enum = gconv.Interfaces(enumArray)
   250  						}
   251  					}
   252  				}
   253  			}
   254  			break
   255  		}
   256  	}
   257  	return nil
   258  }