123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- // 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"
- "fmt"
- "path/filepath"
- "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, fmt.Errorf("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
- 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 json2yaml(item interface{}) (*yaml.Node, error) {
- 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
- for k, v := range val {
- 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
- }
- return nil, nil
- }
- // 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)
- }
|