mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-23 10:05:10 +00:00
203 lines
5 KiB
Go
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
|
|
}
|