github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/gf. 6 7 package goai 8 9 import ( 10 "reflect" 11 12 "github.com/wangyougui/gf/v2/container/gmap" 13 "github.com/wangyougui/gf/v2/container/gset" 14 "github.com/wangyougui/gf/v2/errors/gerror" 15 "github.com/wangyougui/gf/v2/internal/json" 16 "github.com/wangyougui/gf/v2/internal/utils" 17 "github.com/wangyougui/gf/v2/os/gstructs" 18 "github.com/wangyougui/gf/v2/text/gstr" 19 "github.com/wangyougui/gf/v2/util/gconv" 20 "github.com/wangyougui/gf/v2/util/gmeta" 21 "github.com/wangyougui/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 }