forked from ebhomengo/niki
482 lines
12 KiB
Go
482 lines
12 KiB
Go
|
// Copyright 2015 go-swagger maintainers
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package swag
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"path/filepath"
|
||
|
"reflect"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/mailru/easyjson/jlexer"
|
||
|
"github.com/mailru/easyjson/jwriter"
|
||
|
yaml "gopkg.in/yaml.v3"
|
||
|
)
|
||
|
|
||
|
// YAMLMatcher matches yaml
|
||
|
func YAMLMatcher(path string) bool {
|
||
|
ext := filepath.Ext(path)
|
||
|
return ext == ".yaml" || ext == ".yml"
|
||
|
}
|
||
|
|
||
|
// YAMLToJSON converts YAML unmarshaled data into json compatible data
|
||
|
func YAMLToJSON(data interface{}) (json.RawMessage, error) {
|
||
|
jm, err := transformData(data)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
b, err := WriteJSON(jm)
|
||
|
return json.RawMessage(b), err
|
||
|
}
|
||
|
|
||
|
// BytesToYAMLDoc converts a byte slice into a YAML document
|
||
|
func BytesToYAMLDoc(data []byte) (interface{}, error) {
|
||
|
var document yaml.Node // preserve order that is present in the document
|
||
|
if err := yaml.Unmarshal(data, &document); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if document.Kind != yaml.DocumentNode || len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
|
||
|
return nil, errors.New("only YAML documents that are objects are supported")
|
||
|
}
|
||
|
return &document, nil
|
||
|
}
|
||
|
|
||
|
func yamlNode(root *yaml.Node) (interface{}, error) {
|
||
|
switch root.Kind {
|
||
|
case yaml.DocumentNode:
|
||
|
return yamlDocument(root)
|
||
|
case yaml.SequenceNode:
|
||
|
return yamlSequence(root)
|
||
|
case yaml.MappingNode:
|
||
|
return yamlMapping(root)
|
||
|
case yaml.ScalarNode:
|
||
|
return yamlScalar(root)
|
||
|
case yaml.AliasNode:
|
||
|
return yamlNode(root.Alias)
|
||
|
default:
|
||
|
return nil, fmt.Errorf("unsupported YAML node type: %v", root.Kind)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func yamlDocument(node *yaml.Node) (interface{}, error) {
|
||
|
if len(node.Content) != 1 {
|
||
|
return nil, fmt.Errorf("unexpected YAML Document node content length: %d", len(node.Content))
|
||
|
}
|
||
|
return yamlNode(node.Content[0])
|
||
|
}
|
||
|
|
||
|
func yamlMapping(node *yaml.Node) (interface{}, error) {
|
||
|
m := make(JSONMapSlice, len(node.Content)/2)
|
||
|
|
||
|
var j int
|
||
|
for i := 0; i < len(node.Content); i += 2 {
|
||
|
var nmi JSONMapItem
|
||
|
k, err := yamlStringScalarC(node.Content[i])
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to decode YAML map key: %w", err)
|
||
|
}
|
||
|
nmi.Key = k
|
||
|
v, err := yamlNode(node.Content[i+1])
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to process YAML map value for key %q: %w", k, err)
|
||
|
}
|
||
|
nmi.Value = v
|
||
|
m[j] = nmi
|
||
|
j++
|
||
|
}
|
||
|
return m, nil
|
||
|
}
|
||
|
|
||
|
func yamlSequence(node *yaml.Node) (interface{}, error) {
|
||
|
s := make([]interface{}, 0)
|
||
|
|
||
|
for i := 0; i < len(node.Content); i++ {
|
||
|
|
||
|
v, err := yamlNode(node.Content[i])
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to decode YAML sequence value: %w", err)
|
||
|
}
|
||
|
s = append(s, v)
|
||
|
}
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
const ( // See https://yaml.org/type/
|
||
|
yamlStringScalar = "tag:yaml.org,2002:str"
|
||
|
yamlIntScalar = "tag:yaml.org,2002:int"
|
||
|
yamlBoolScalar = "tag:yaml.org,2002:bool"
|
||
|
yamlFloatScalar = "tag:yaml.org,2002:float"
|
||
|
yamlTimestamp = "tag:yaml.org,2002:timestamp"
|
||
|
yamlNull = "tag:yaml.org,2002:null"
|
||
|
)
|
||
|
|
||
|
func yamlScalar(node *yaml.Node) (interface{}, error) {
|
||
|
switch node.LongTag() {
|
||
|
case yamlStringScalar:
|
||
|
return node.Value, nil
|
||
|
case yamlBoolScalar:
|
||
|
b, err := strconv.ParseBool(node.Value)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting bool content: %w", node.Value, err)
|
||
|
}
|
||
|
return b, nil
|
||
|
case yamlIntScalar:
|
||
|
i, err := strconv.ParseInt(node.Value, 10, 64)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting integer content: %w", node.Value, err)
|
||
|
}
|
||
|
return i, nil
|
||
|
case yamlFloatScalar:
|
||
|
f, err := strconv.ParseFloat(node.Value, 64)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("unable to process scalar node. Got %q. Expecting float content: %w", node.Value, err)
|
||
|
}
|
||
|
return f, nil
|
||
|
case yamlTimestamp:
|
||
|
return node.Value, nil
|
||
|
case yamlNull:
|
||
|
return nil, nil //nolint:nilnil
|
||
|
default:
|
||
|
return nil, fmt.Errorf("YAML tag %q is not supported", node.LongTag())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func yamlStringScalarC(node *yaml.Node) (string, error) {
|
||
|
if node.Kind != yaml.ScalarNode {
|
||
|
return "", fmt.Errorf("expecting a string scalar but got %q", node.Kind)
|
||
|
}
|
||
|
switch node.LongTag() {
|
||
|
case yamlStringScalar, yamlIntScalar, yamlFloatScalar:
|
||
|
return node.Value, nil
|
||
|
default:
|
||
|
return "", fmt.Errorf("YAML tag %q is not supported as map key", node.LongTag())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// JSONMapSlice represent a JSON object, with the order of keys maintained
|
||
|
type JSONMapSlice []JSONMapItem
|
||
|
|
||
|
// MarshalJSON renders a JSONMapSlice as JSON
|
||
|
func (s JSONMapSlice) MarshalJSON() ([]byte, error) {
|
||
|
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
|
||
|
s.MarshalEasyJSON(w)
|
||
|
return w.BuildBytes()
|
||
|
}
|
||
|
|
||
|
// MarshalEasyJSON renders a JSONMapSlice as JSON, using easyJSON
|
||
|
func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) {
|
||
|
w.RawByte('{')
|
||
|
|
||
|
ln := len(s)
|
||
|
last := ln - 1
|
||
|
for i := 0; i < ln; i++ {
|
||
|
s[i].MarshalEasyJSON(w)
|
||
|
if i != last { // last item
|
||
|
w.RawByte(',')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
w.RawByte('}')
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON makes a JSONMapSlice from JSON
|
||
|
func (s *JSONMapSlice) UnmarshalJSON(data []byte) error {
|
||
|
l := jlexer.Lexer{Data: data}
|
||
|
s.UnmarshalEasyJSON(&l)
|
||
|
return l.Error()
|
||
|
}
|
||
|
|
||
|
// UnmarshalEasyJSON makes a JSONMapSlice from JSON, using easyJSON
|
||
|
func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
||
|
if in.IsNull() {
|
||
|
in.Skip()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var result JSONMapSlice
|
||
|
in.Delim('{')
|
||
|
for !in.IsDelim('}') {
|
||
|
var mi JSONMapItem
|
||
|
mi.UnmarshalEasyJSON(in)
|
||
|
result = append(result, mi)
|
||
|
}
|
||
|
*s = result
|
||
|
}
|
||
|
|
||
|
func (s JSONMapSlice) MarshalYAML() (interface{}, error) {
|
||
|
var n yaml.Node
|
||
|
n.Kind = yaml.DocumentNode
|
||
|
var nodes []*yaml.Node
|
||
|
for _, item := range s {
|
||
|
nn, err := json2yaml(item.Value)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ns := []*yaml.Node{
|
||
|
{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlStringScalar,
|
||
|
Value: item.Key,
|
||
|
},
|
||
|
nn,
|
||
|
}
|
||
|
nodes = append(nodes, ns...)
|
||
|
}
|
||
|
|
||
|
n.Content = []*yaml.Node{
|
||
|
{
|
||
|
Kind: yaml.MappingNode,
|
||
|
Content: nodes,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
return yaml.Marshal(&n)
|
||
|
}
|
||
|
|
||
|
func isNil(input interface{}) bool {
|
||
|
if input == nil {
|
||
|
return true
|
||
|
}
|
||
|
kind := reflect.TypeOf(input).Kind()
|
||
|
switch kind { //nolint:exhaustive
|
||
|
case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Chan:
|
||
|
return reflect.ValueOf(input).IsNil()
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func json2yaml(item interface{}) (*yaml.Node, error) {
|
||
|
if isNil(item) {
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Value: "null",
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
switch val := item.(type) {
|
||
|
case JSONMapSlice:
|
||
|
var n yaml.Node
|
||
|
n.Kind = yaml.MappingNode
|
||
|
for i := range val {
|
||
|
childNode, err := json2yaml(&val[i].Value)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
n.Content = append(n.Content, &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlStringScalar,
|
||
|
Value: val[i].Key,
|
||
|
}, childNode)
|
||
|
}
|
||
|
return &n, nil
|
||
|
case map[string]interface{}:
|
||
|
var n yaml.Node
|
||
|
n.Kind = yaml.MappingNode
|
||
|
keys := make([]string, 0, len(val))
|
||
|
for k := range val {
|
||
|
keys = append(keys, k)
|
||
|
}
|
||
|
sort.Strings(keys)
|
||
|
|
||
|
for _, k := range keys {
|
||
|
v := val[k]
|
||
|
childNode, err := json2yaml(v)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
n.Content = append(n.Content, &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlStringScalar,
|
||
|
Value: k,
|
||
|
}, childNode)
|
||
|
}
|
||
|
return &n, nil
|
||
|
case []interface{}:
|
||
|
var n yaml.Node
|
||
|
n.Kind = yaml.SequenceNode
|
||
|
for i := range val {
|
||
|
childNode, err := json2yaml(val[i])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
n.Content = append(n.Content, childNode)
|
||
|
}
|
||
|
return &n, nil
|
||
|
case string:
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlStringScalar,
|
||
|
Value: val,
|
||
|
}, nil
|
||
|
case float64:
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlFloatScalar,
|
||
|
Value: strconv.FormatFloat(val, 'f', -1, 64),
|
||
|
}, nil
|
||
|
case int64:
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlIntScalar,
|
||
|
Value: strconv.FormatInt(val, 10),
|
||
|
}, nil
|
||
|
case uint64:
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlIntScalar,
|
||
|
Value: strconv.FormatUint(val, 10),
|
||
|
}, nil
|
||
|
case bool:
|
||
|
return &yaml.Node{
|
||
|
Kind: yaml.ScalarNode,
|
||
|
Tag: yamlBoolScalar,
|
||
|
Value: strconv.FormatBool(val),
|
||
|
}, nil
|
||
|
default:
|
||
|
return nil, fmt.Errorf("unhandled type: %T", val)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
|
||
|
type JSONMapItem struct {
|
||
|
Key string
|
||
|
Value interface{}
|
||
|
}
|
||
|
|
||
|
// MarshalJSON renders a JSONMapItem as JSON
|
||
|
func (s JSONMapItem) MarshalJSON() ([]byte, error) {
|
||
|
w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
|
||
|
s.MarshalEasyJSON(w)
|
||
|
return w.BuildBytes()
|
||
|
}
|
||
|
|
||
|
// MarshalEasyJSON renders a JSONMapItem as JSON, using easyJSON
|
||
|
func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) {
|
||
|
w.String(s.Key)
|
||
|
w.RawByte(':')
|
||
|
w.Raw(WriteJSON(s.Value))
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON makes a JSONMapItem from JSON
|
||
|
func (s *JSONMapItem) UnmarshalJSON(data []byte) error {
|
||
|
l := jlexer.Lexer{Data: data}
|
||
|
s.UnmarshalEasyJSON(&l)
|
||
|
return l.Error()
|
||
|
}
|
||
|
|
||
|
// UnmarshalEasyJSON makes a JSONMapItem from JSON, using easyJSON
|
||
|
func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) {
|
||
|
key := in.UnsafeString()
|
||
|
in.WantColon()
|
||
|
value := in.Interface()
|
||
|
in.WantComma()
|
||
|
s.Key = key
|
||
|
s.Value = value
|
||
|
}
|
||
|
|
||
|
func transformData(input interface{}) (out interface{}, err error) {
|
||
|
format := func(t interface{}) (string, error) {
|
||
|
switch k := t.(type) {
|
||
|
case string:
|
||
|
return k, nil
|
||
|
case uint:
|
||
|
return strconv.FormatUint(uint64(k), 10), nil
|
||
|
case uint8:
|
||
|
return strconv.FormatUint(uint64(k), 10), nil
|
||
|
case uint16:
|
||
|
return strconv.FormatUint(uint64(k), 10), nil
|
||
|
case uint32:
|
||
|
return strconv.FormatUint(uint64(k), 10), nil
|
||
|
case uint64:
|
||
|
return strconv.FormatUint(k, 10), nil
|
||
|
case int:
|
||
|
return strconv.Itoa(k), nil
|
||
|
case int8:
|
||
|
return strconv.FormatInt(int64(k), 10), nil
|
||
|
case int16:
|
||
|
return strconv.FormatInt(int64(k), 10), nil
|
||
|
case int32:
|
||
|
return strconv.FormatInt(int64(k), 10), nil
|
||
|
case int64:
|
||
|
return strconv.FormatInt(k, 10), nil
|
||
|
default:
|
||
|
return "", fmt.Errorf("unexpected map key type, got: %T", k)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch in := input.(type) {
|
||
|
case yaml.Node:
|
||
|
return yamlNode(&in)
|
||
|
case *yaml.Node:
|
||
|
return yamlNode(in)
|
||
|
case map[interface{}]interface{}:
|
||
|
o := make(JSONMapSlice, 0, len(in))
|
||
|
for ke, va := range in {
|
||
|
var nmi JSONMapItem
|
||
|
if nmi.Key, err = format(ke); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
v, ert := transformData(va)
|
||
|
if ert != nil {
|
||
|
return nil, ert
|
||
|
}
|
||
|
nmi.Value = v
|
||
|
o = append(o, nmi)
|
||
|
}
|
||
|
return o, nil
|
||
|
case []interface{}:
|
||
|
len1 := len(in)
|
||
|
o := make([]interface{}, len1)
|
||
|
for i := 0; i < len1; i++ {
|
||
|
o[i], err = transformData(in[i])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
return o, nil
|
||
|
}
|
||
|
return input, nil
|
||
|
}
|
||
|
|
||
|
// YAMLDoc loads a yaml document from either http or a file and converts it to json
|
||
|
func YAMLDoc(path string) (json.RawMessage, error) {
|
||
|
yamlDoc, err := YAMLData(path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
data, err := YAMLToJSON(yamlDoc)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return data, nil
|
||
|
}
|
||
|
|
||
|
// YAMLData loads a yaml document from either http or a file
|
||
|
func YAMLData(path string) (interface{}, error) {
|
||
|
data, err := LoadFromFileOrHTTP(path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return BytesToYAMLDoc(data)
|
||
|
}
|