| // 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) |
| } |