]>
Commit | Line | Data |
---|---|---|
863486a6 AG |
1 | // Package yaml can marshal and unmarshal cty values in YAML format. |
2 | package yaml | |
3 | ||
4 | import ( | |
5 | "errors" | |
6 | "fmt" | |
7 | "reflect" | |
8 | "strings" | |
9 | "sync" | |
10 | ||
11 | "github.com/zclconf/go-cty/cty" | |
12 | ) | |
13 | ||
14 | // Unmarshal reads the document found within the given source buffer | |
15 | // and attempts to convert it into a value conforming to the given type | |
16 | // constraint. | |
17 | // | |
18 | // This is an alias for Unmarshal on the predefined Converter in "Standard". | |
19 | // | |
20 | // An error is returned if the given source contains any YAML document | |
21 | // delimiters. | |
22 | func Unmarshal(src []byte, ty cty.Type) (cty.Value, error) { | |
23 | return Standard.Unmarshal(src, ty) | |
24 | } | |
25 | ||
26 | // Marshal serializes the given value into a YAML document, using a fixed | |
27 | // mapping from cty types to YAML constructs. | |
28 | // | |
29 | // This is an alias for Marshal on the predefined Converter in "Standard". | |
30 | // | |
31 | // Note that unlike the function of the same name in the cty JSON package, | |
32 | // this does not take a type constraint and therefore the YAML serialization | |
33 | // cannot preserve late-bound type information in the serialization to be | |
34 | // recovered from Unmarshal. Instead, any cty.DynamicPseudoType in the type | |
35 | // constraint given to Unmarshal will be decoded as if the corresponding portion | |
36 | // of the input were processed with ImpliedType to find a target type. | |
37 | func Marshal(v cty.Value) ([]byte, error) { | |
38 | return Standard.Marshal(v) | |
39 | } | |
40 | ||
41 | // ImpliedType analyzes the given source code and returns a suitable type that | |
42 | // it could be decoded into. | |
43 | // | |
44 | // For a converter that is using standard YAML rather than cty-specific custom | |
45 | // tags, only a subset of cty types can be produced: strings, numbers, bools, | |
46 | // tuple types, and object types. | |
47 | // | |
48 | // This is an alias for ImpliedType on the predefined Converter in "Standard". | |
49 | func ImpliedType(src []byte) (cty.Type, error) { | |
50 | return Standard.ImpliedType(src) | |
51 | } | |
52 | ||
53 | func handleErr(err *error) { | |
54 | if v := recover(); v != nil { | |
55 | if e, ok := v.(yamlError); ok { | |
56 | *err = e.err | |
57 | } else { | |
58 | panic(v) | |
59 | } | |
60 | } | |
61 | } | |
62 | ||
63 | type yamlError struct { | |
64 | err error | |
65 | } | |
66 | ||
67 | func fail(err error) { | |
68 | panic(yamlError{err}) | |
69 | } | |
70 | ||
71 | func failf(format string, args ...interface{}) { | |
72 | panic(yamlError{fmt.Errorf("yaml: "+format, args...)}) | |
73 | } | |
74 | ||
75 | // -------------------------------------------------------------------------- | |
76 | // Maintain a mapping of keys to structure field indexes | |
77 | ||
78 | // The code in this section was copied from mgo/bson. | |
79 | ||
80 | // structInfo holds details for the serialization of fields of | |
81 | // a given struct. | |
82 | type structInfo struct { | |
83 | FieldsMap map[string]fieldInfo | |
84 | FieldsList []fieldInfo | |
85 | ||
86 | // InlineMap is the number of the field in the struct that | |
87 | // contains an ,inline map, or -1 if there's none. | |
88 | InlineMap int | |
89 | } | |
90 | ||
91 | type fieldInfo struct { | |
92 | Key string | |
93 | Num int | |
94 | OmitEmpty bool | |
95 | Flow bool | |
96 | // Id holds the unique field identifier, so we can cheaply | |
97 | // check for field duplicates without maintaining an extra map. | |
98 | Id int | |
99 | ||
100 | // Inline holds the field index if the field is part of an inlined struct. | |
101 | Inline []int | |
102 | } | |
103 | ||
104 | var structMap = make(map[reflect.Type]*structInfo) | |
105 | var fieldMapMutex sync.RWMutex | |
106 | ||
107 | func getStructInfo(st reflect.Type) (*structInfo, error) { | |
108 | fieldMapMutex.RLock() | |
109 | sinfo, found := structMap[st] | |
110 | fieldMapMutex.RUnlock() | |
111 | if found { | |
112 | return sinfo, nil | |
113 | } | |
114 | ||
115 | n := st.NumField() | |
116 | fieldsMap := make(map[string]fieldInfo) | |
117 | fieldsList := make([]fieldInfo, 0, n) | |
118 | inlineMap := -1 | |
119 | for i := 0; i != n; i++ { | |
120 | field := st.Field(i) | |
121 | if field.PkgPath != "" && !field.Anonymous { | |
122 | continue // Private field | |
123 | } | |
124 | ||
125 | info := fieldInfo{Num: i} | |
126 | ||
127 | tag := field.Tag.Get("yaml") | |
128 | if tag == "" && strings.Index(string(field.Tag), ":") < 0 { | |
129 | tag = string(field.Tag) | |
130 | } | |
131 | if tag == "-" { | |
132 | continue | |
133 | } | |
134 | ||
135 | inline := false | |
136 | fields := strings.Split(tag, ",") | |
137 | if len(fields) > 1 { | |
138 | for _, flag := range fields[1:] { | |
139 | switch flag { | |
140 | case "omitempty": | |
141 | info.OmitEmpty = true | |
142 | case "flow": | |
143 | info.Flow = true | |
144 | case "inline": | |
145 | inline = true | |
146 | default: | |
147 | return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)) | |
148 | } | |
149 | } | |
150 | tag = fields[0] | |
151 | } | |
152 | ||
153 | if inline { | |
154 | switch field.Type.Kind() { | |
155 | case reflect.Map: | |
156 | if inlineMap >= 0 { | |
157 | return nil, errors.New("Multiple ,inline maps in struct " + st.String()) | |
158 | } | |
159 | if field.Type.Key() != reflect.TypeOf("") { | |
160 | return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String()) | |
161 | } | |
162 | inlineMap = info.Num | |
163 | case reflect.Struct: | |
164 | sinfo, err := getStructInfo(field.Type) | |
165 | if err != nil { | |
166 | return nil, err | |
167 | } | |
168 | for _, finfo := range sinfo.FieldsList { | |
169 | if _, found := fieldsMap[finfo.Key]; found { | |
170 | msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String() | |
171 | return nil, errors.New(msg) | |
172 | } | |
173 | if finfo.Inline == nil { | |
174 | finfo.Inline = []int{i, finfo.Num} | |
175 | } else { | |
176 | finfo.Inline = append([]int{i}, finfo.Inline...) | |
177 | } | |
178 | finfo.Id = len(fieldsList) | |
179 | fieldsMap[finfo.Key] = finfo | |
180 | fieldsList = append(fieldsList, finfo) | |
181 | } | |
182 | default: | |
183 | //return nil, errors.New("Option ,inline needs a struct value or map field") | |
184 | return nil, errors.New("Option ,inline needs a struct value field") | |
185 | } | |
186 | continue | |
187 | } | |
188 | ||
189 | if tag != "" { | |
190 | info.Key = tag | |
191 | } else { | |
192 | info.Key = strings.ToLower(field.Name) | |
193 | } | |
194 | ||
195 | if _, found = fieldsMap[info.Key]; found { | |
196 | msg := "Duplicated key '" + info.Key + "' in struct " + st.String() | |
197 | return nil, errors.New(msg) | |
198 | } | |
199 | ||
200 | info.Id = len(fieldsList) | |
201 | fieldsList = append(fieldsList, info) | |
202 | fieldsMap[info.Key] = info | |
203 | } | |
204 | ||
205 | sinfo = &structInfo{ | |
206 | FieldsMap: fieldsMap, | |
207 | FieldsList: fieldsList, | |
208 | InlineMap: inlineMap, | |
209 | } | |
210 | ||
211 | fieldMapMutex.Lock() | |
212 | structMap[st] = sinfo | |
213 | fieldMapMutex.Unlock() | |
214 | return sinfo, nil | |
215 | } |