fabric-samples/asset-transfer-basic/chaincode-go/vendor/github.com/hyperledger/fabric-contract-api-go/metadata/schema.go
2022-09-29 14:18:01 -07:00

203 lines
5 KiB
Go

// Copyright the Hyperledger Fabric contributors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package metadata
import (
"fmt"
"reflect"
"strings"
"unicode"
"github.com/go-openapi/spec"
"github.com/hyperledger/fabric-contract-api-go/internal/types"
)
// GetSchema returns the open api spec schema for a given type. For struct types the property
// name used in the generated schema will be the name of the property unless a metadata or json
// tag exists for the property. Metadata tags take precedence over json tags. Private properties
// without a metadata tag will be ignored. Json tags are not used for private properties. Components
// will be added to component metadata if the field is a struct type. The schema will then reference
// this component
func GetSchema(field reflect.Type, components *ComponentMetadata) (*spec.Schema, error) {
return getSchema(field, components, false)
}
func getSchema(field reflect.Type, components *ComponentMetadata, nested bool) (*spec.Schema, error) {
var schema *spec.Schema
var err error
if bt, ok := types.BasicTypes[field.Kind()]; !ok {
if field == types.TimeType {
schema = spec.DateTimeProperty()
} else if field.Kind() == reflect.Array {
schema, err = buildArraySchema(reflect.New(field).Elem(), components, nested)
} else if field.Kind() == reflect.Slice {
schema, err = buildSliceSchema(reflect.MakeSlice(field, 1, 1), components, nested)
} else if field.Kind() == reflect.Map {
schema, err = buildMapSchema(reflect.MakeMap(field), components, nested)
} else if field.Kind() == reflect.Struct || (field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct) {
schema, err = buildStructSchema(field, components, nested)
} else {
return nil, fmt.Errorf("%s was not a valid type", field.String())
}
} else {
return bt.GetSchema(), nil
}
if err != nil {
return nil, err
}
return schema, nil
}
func buildArraySchema(array reflect.Value, components *ComponentMetadata, nested bool) (*spec.Schema, error) {
if array.Len() < 1 {
return nil, fmt.Errorf("Arrays must have length greater than 0")
}
lowerSchema, err := getSchema(array.Index(0).Type(), components, nested)
if err != nil {
return nil, err
}
return spec.ArrayProperty(lowerSchema), nil
}
func buildSliceSchema(slice reflect.Value, components *ComponentMetadata, nested bool) (*spec.Schema, error) {
if slice.Len() < 1 {
slice = reflect.MakeSlice(slice.Type(), 1, 10)
}
lowerSchema, err := getSchema(slice.Index(0).Type(), components, nested)
if err != nil {
return nil, err
}
return spec.ArrayProperty(lowerSchema), nil
}
func buildMapSchema(rmap reflect.Value, components *ComponentMetadata, nested bool) (*spec.Schema, error) {
lowerSchema, err := getSchema(rmap.Type().Elem(), components, nested)
if err != nil {
return nil, err
}
return spec.MapProperty(lowerSchema), nil
}
func addComponentIfNotExists(obj reflect.Type, components *ComponentMetadata) error {
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
if _, ok := components.Schemas[obj.Name()]; ok {
return nil
}
schema := ObjectMetadata{}
schema.ID = obj.Name()
schema.Required = []string{}
schema.Properties = make(map[string]spec.Schema)
schema.AdditionalProperties = false
if components.Schemas == nil {
components.Schemas = make(map[string]ObjectMetadata)
}
components.Schemas[obj.Name()] = schema // lock up slot for cyclic
for i := 0; i < obj.NumField(); i++ {
err := getField(obj.Field(i), &schema, components)
if err != nil {
delete(components.Schemas, obj.Name())
return err
}
}
components.Schemas[obj.Name()] = schema // include changes
return nil
}
func getField(field reflect.StructField, schema *ObjectMetadata, components *ComponentMetadata) error {
if field.Anonymous {
if field.Type.Kind() == reflect.Struct {
for i := 0; i < field.Type.NumField(); i++ {
err := getField(field.Type.Field(i), schema, components)
if err != nil {
return err
}
}
}
return nil
}
name := field.Tag.Get("metadata")
required := true
if strings.Contains(name, ",") {
spl := strings.Split(name, ",")
name = spl[0]
for _, val := range spl[1:] {
if strings.TrimSpace(val) == "optional" {
required = false
}
}
}
if (unicode.IsLower([]rune(field.Name)[0]) && name == "") || name == "-" {
return nil
} else if name == "" {
name = field.Tag.Get("json")
}
if name == "" || name == "-" {
name = field.Name
}
var err error
propSchema, err := getSchema(field.Type, components, true)
if err != nil {
return err
}
if required {
schema.Required = append(schema.Required, name)
}
schema.Properties[name] = *propSchema
return nil
}
func buildStructSchema(obj reflect.Type, components *ComponentMetadata, nested bool) (*spec.Schema, error) {
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
err := addComponentIfNotExists(obj, components)
if err != nil {
return nil, err
}
refPath := "#/components/schemas/"
if nested {
refPath = ""
}
return spec.RefSchema(refPath + obj.Name()), nil
}