1// Copyright 2015 go-swagger maintainers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package swag
16
17import (
18	"encoding/json"
19	"fmt"
20	"path/filepath"
21	"strconv"
22
23	"github.com/mailru/easyjson/jlexer"
24	"github.com/mailru/easyjson/jwriter"
25	yaml "gopkg.in/yaml.v2"
26)
27
28// YAMLMatcher matches yaml
29func YAMLMatcher(path string) bool {
30	ext := filepath.Ext(path)
31	return ext == ".yaml" || ext == ".yml"
32}
33
34// YAMLToJSON converts YAML unmarshaled data into json compatible data
35func YAMLToJSON(data interface{}) (json.RawMessage, error) {
36	jm, err := transformData(data)
37	if err != nil {
38		return nil, err
39	}
40	b, err := WriteJSON(jm)
41	return json.RawMessage(b), err
42}
43
44// BytesToYAMLDoc converts a byte slice into a YAML document
45func BytesToYAMLDoc(data []byte) (interface{}, error) {
46	var canary map[interface{}]interface{} // validate this is an object and not a different type
47	if err := yaml.Unmarshal(data, &canary); err != nil {
48		return nil, err
49	}
50
51	var document yaml.MapSlice // preserve order that is present in the document
52	if err := yaml.Unmarshal(data, &document); err != nil {
53		return nil, err
54	}
55	return document, nil
56}
57
58// JSONMapSlice represent a JSON object, with the order of keys maintained
59type JSONMapSlice []JSONMapItem
60
61// MarshalJSON renders a JSONMapSlice as JSON
62func (s JSONMapSlice) MarshalJSON() ([]byte, error) {
63	w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
64	s.MarshalEasyJSON(w)
65	return w.BuildBytes()
66}
67
68// MarshalEasyJSON renders a JSONMapSlice as JSON, using easyJSON
69func (s JSONMapSlice) MarshalEasyJSON(w *jwriter.Writer) {
70	w.RawByte('{')
71
72	ln := len(s)
73	last := ln - 1
74	for i := 0; i < ln; i++ {
75		s[i].MarshalEasyJSON(w)
76		if i != last { // last item
77			w.RawByte(',')
78		}
79	}
80
81	w.RawByte('}')
82}
83
84// UnmarshalJSON makes a JSONMapSlice from JSON
85func (s *JSONMapSlice) UnmarshalJSON(data []byte) error {
86	l := jlexer.Lexer{Data: data}
87	s.UnmarshalEasyJSON(&l)
88	return l.Error()
89}
90
91// UnmarshalEasyJSON makes a JSONMapSlice from JSON, using easyJSON
92func (s *JSONMapSlice) UnmarshalEasyJSON(in *jlexer.Lexer) {
93	if in.IsNull() {
94		in.Skip()
95		return
96	}
97
98	var result JSONMapSlice
99	in.Delim('{')
100	for !in.IsDelim('}') {
101		var mi JSONMapItem
102		mi.UnmarshalEasyJSON(in)
103		result = append(result, mi)
104	}
105	*s = result
106}
107
108// JSONMapItem represents the value of a key in a JSON object held by JSONMapSlice
109type JSONMapItem struct {
110	Key   string
111	Value interface{}
112}
113
114// MarshalJSON renders a JSONMapItem as JSON
115func (s JSONMapItem) MarshalJSON() ([]byte, error) {
116	w := &jwriter.Writer{Flags: jwriter.NilMapAsEmpty | jwriter.NilSliceAsEmpty}
117	s.MarshalEasyJSON(w)
118	return w.BuildBytes()
119}
120
121// MarshalEasyJSON renders a JSONMapItem as JSON, using easyJSON
122func (s JSONMapItem) MarshalEasyJSON(w *jwriter.Writer) {
123	w.String(s.Key)
124	w.RawByte(':')
125	w.Raw(WriteJSON(s.Value))
126}
127
128// UnmarshalJSON makes a JSONMapItem from JSON
129func (s *JSONMapItem) UnmarshalJSON(data []byte) error {
130	l := jlexer.Lexer{Data: data}
131	s.UnmarshalEasyJSON(&l)
132	return l.Error()
133}
134
135// UnmarshalEasyJSON makes a JSONMapItem from JSON, using easyJSON
136func (s *JSONMapItem) UnmarshalEasyJSON(in *jlexer.Lexer) {
137	key := in.UnsafeString()
138	in.WantColon()
139	value := in.Interface()
140	in.WantComma()
141	s.Key = key
142	s.Value = value
143}
144
145func transformData(input interface{}) (out interface{}, err error) {
146	format := func(t interface{}) (string, error) {
147		switch k := t.(type) {
148		case string:
149			return k, nil
150		case uint:
151			return strconv.FormatUint(uint64(k), 10), nil
152		case uint8:
153			return strconv.FormatUint(uint64(k), 10), nil
154		case uint16:
155			return strconv.FormatUint(uint64(k), 10), nil
156		case uint32:
157			return strconv.FormatUint(uint64(k), 10), nil
158		case uint64:
159			return strconv.FormatUint(k, 10), nil
160		case int:
161			return strconv.Itoa(k), nil
162		case int8:
163			return strconv.FormatInt(int64(k), 10), nil
164		case int16:
165			return strconv.FormatInt(int64(k), 10), nil
166		case int32:
167			return strconv.FormatInt(int64(k), 10), nil
168		case int64:
169			return strconv.FormatInt(k, 10), nil
170		default:
171			return "", fmt.Errorf("unexpected map key type, got: %T", k)
172		}
173	}
174
175	switch in := input.(type) {
176	case yaml.MapSlice:
177
178		o := make(JSONMapSlice, len(in))
179		for i, mi := range in {
180			var nmi JSONMapItem
181			if nmi.Key, err = format(mi.Key); err != nil {
182				return nil, err
183			}
184
185			v, ert := transformData(mi.Value)
186			if ert != nil {
187				return nil, ert
188			}
189			nmi.Value = v
190			o[i] = nmi
191		}
192		return o, nil
193	case map[interface{}]interface{}:
194		o := make(JSONMapSlice, 0, len(in))
195		for ke, va := range in {
196			var nmi JSONMapItem
197			if nmi.Key, err = format(ke); err != nil {
198				return nil, err
199			}
200
201			v, ert := transformData(va)
202			if ert != nil {
203				return nil, ert
204			}
205			nmi.Value = v
206			o = append(o, nmi)
207		}
208		return o, nil
209	case []interface{}:
210		len1 := len(in)
211		o := make([]interface{}, len1)
212		for i := 0; i < len1; i++ {
213			o[i], err = transformData(in[i])
214			if err != nil {
215				return nil, err
216			}
217		}
218		return o, nil
219	}
220	return input, nil
221}
222
223// YAMLDoc loads a yaml document from either http or a file and converts it to json
224func YAMLDoc(path string) (json.RawMessage, error) {
225	yamlDoc, err := YAMLData(path)
226	if err != nil {
227		return nil, err
228	}
229
230	data, err := YAMLToJSON(yamlDoc)
231	if err != nil {
232		return nil, err
233	}
234
235	return data, nil
236}
237
238// YAMLData loads a yaml document from either http or a file
239func YAMLData(path string) (interface{}, error) {
240	data, err := LoadFromFileOrHTTP(path)
241	if err != nil {
242		return nil, err
243	}
244
245	return BytesToYAMLDoc(data)
246}
247