Skip to content

Commit 48c5ba9

Browse files
committed
Merge remote-tracking branch 'origin/develop' into develop
2 parents 50c663b + eaccfdd commit 48c5ba9

2 files changed

Lines changed: 104 additions & 33 deletions

File tree

providers/openapi/schema/parse_xml.go

Lines changed: 84 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"mokapi/schema/json/parser"
8+
"path"
89
"strconv"
910
)
1011

@@ -17,6 +18,7 @@ type node struct {
1718
Attrs []xml.Attr `xml:",any,attr"`
1819
Content []byte `xml:",innerxml"`
1920
Nodes []node `xml:",any"`
21+
Path string
2022
}
2123

2224
func NewXmlParser(s *Schema) *XmlParser {
@@ -39,7 +41,7 @@ func (p *XmlParser) Parse(v any) (any, error) {
3941
if err != nil {
4042
return nil, fmt.Errorf("failed to unmarshal XML: %w", err)
4143
}
42-
data, err := parseXML(n, p.s)
44+
data, err := parseXML(n, p.s, "/"+xmlNameAsString(n.XMLName))
4345
if err != nil {
4446
return nil, fmt.Errorf("failed to parse XML: %w", err)
4547
}
@@ -75,10 +77,10 @@ func UnmarshalXML(r io.Reader, s *Schema) (interface{}, error) {
7577
if err != nil {
7678
return nil, fmt.Errorf("failed to unmarshal xml: %w", err)
7779
}
78-
return parseXML(n, s)
80+
return parseXML(n, s, "/"+xmlNameAsString(n.XMLName))
7981
}
8082

81-
func parseXML(n *node, s *Schema) (any, error) {
83+
func parseXML(n *node, s *Schema, xpath string) (any, error) {
8284
if len(n.Nodes) == 0 && len(n.Attrs) == 0 {
8385
if len(n.Content) == 0 {
8486
if s.Type.IsObject() {
@@ -88,10 +90,10 @@ func parseXML(n *node, s *Schema) (any, error) {
8890
return []any{}, nil
8991
}
9092
}
91-
return parseValue(string(n.Content), s)
93+
return parseValue(string(n.Content), s, xpath)
9294
}
9395

94-
if isArray(n) || (s != nil && s.Type.IsArray()) {
96+
if isArray(n, s) {
9597
var items *Schema
9698
if s != nil {
9799
items = s.Items
@@ -102,7 +104,7 @@ func parseXML(n *node, s *Schema) (any, error) {
102104
}
103105
n = &n.Nodes[0]
104106
}
105-
return getItems(n, items)
107+
return getItems(n, items, xpath)
106108
}
107109

108110
m := map[string]any{}
@@ -113,7 +115,7 @@ func parseXML(n *node, s *Schema) (any, error) {
113115
if prop != nil && prop.Xml != nil && !prop.Xml.Attribute {
114116
continue
115117
}
116-
v, err := parseValue(attr.Value, prop)
118+
v, err := parseValue(attr.Value, prop, fmt.Sprintf("%s[@%s]", xpath, prop))
117119
if err != nil {
118120
return nil, err
119121
}
@@ -123,20 +125,31 @@ func parseXML(n *node, s *Schema) (any, error) {
123125
for _, child := range n.Nodes {
124126
name, prop := getProperty(child.XMLName, s)
125127
if prop != nil && prop.Xml != nil && prop.Xml.Attribute {
128+
if prop.Xml.Attribute && !s.IsFreeForm() {
129+
return nil, fmt.Errorf("property '%s' is expected as XML attribute but received as XML node and additionalProperty is not allowed: %v", name, path.Join(xpath, xmlNameAsString(child.XMLName)))
130+
}
126131
continue
127132
}
128-
v, err := parseXML(&child, prop)
129-
if err != nil {
130-
return nil, err
131-
}
132-
if _, ok := m[name]; ok {
133-
if arr, isArray := m[name].([]any); isArray {
134-
m[name] = append(arr, v)
133+
134+
var v any
135+
var err error
136+
if prop != nil && (prop.Type.IsArray() || prop.Items != nil) {
137+
if _, wrapped := isWrapped(prop); wrapped {
138+
m[name], err = parseWrappedArray(&child, prop.Items, xpath)
135139
} else {
136-
m[name] = []interface{}{m[name], v}
140+
v, err = parseXML(&child, prop.Items, xpath)
141+
m[name], err = appendOrError(m[name], v, err)
137142
}
138143
} else {
139-
m[name] = v
144+
v, err = parseXML(&child, prop, xpath)
145+
if _, ok := m[name]; ok {
146+
m[name], err = appendOrError(m[name], v, err)
147+
} else {
148+
m[name] = v
149+
}
150+
}
151+
if err != nil {
152+
return nil, err
140153
}
141154
}
142155

@@ -159,7 +172,7 @@ func parseXML(n *node, s *Schema) (any, error) {
159172
return m, nil
160173
}
161174

162-
func parseValue(s string, ref *Schema) (interface{}, error) {
175+
func parseValue(s string, ref *Schema, xpath string) (interface{}, error) {
163176
if ref == nil || ref.Type.IsString() {
164177
return s, nil
165178
}
@@ -186,7 +199,7 @@ func parseValue(s string, ref *Schema) (interface{}, error) {
186199
return s == "true", nil
187200
}
188201

189-
return nil, fmt.Errorf("unknown type: %v", ref.Type)
202+
return nil, fmt.Errorf("expected type '%s' for '%s' but got: '%s'", ref.Type, xpath, s)
190203
}
191204

192205
func getProperty(name xml.Name, s *Schema) (string, *Schema) {
@@ -224,10 +237,10 @@ func getProperty(name xml.Name, s *Schema) (string, *Schema) {
224237
return name.Local, nil
225238
}
226239

227-
func getItems(n *node, ref *Schema) ([]interface{}, error) {
240+
func getItems(n *node, items *Schema, xpath string) ([]interface{}, error) {
228241
var r []interface{}
229-
for _, child := range n.Nodes {
230-
v, err := parseXML(&child, ref)
242+
for i, child := range n.Nodes {
243+
v, err := parseXML(&child, items, fmt.Sprintf("%s/%s[%d]", xpath, xmlNameAsString(child.XMLName), i))
231244
if err != nil {
232245
return nil, err
233246
}
@@ -236,17 +249,36 @@ func getItems(n *node, ref *Schema) ([]interface{}, error) {
236249
return r, nil
237250
}
238251

239-
func isArray(n *node) bool {
240-
if len(n.Nodes) <= 1 {
241-
return false
242-
}
243-
name := n.Nodes[0].XMLName.Local
244-
for _, child := range n.Nodes[1:] {
245-
if child.XMLName.Local != name {
252+
func isArray(n *node, s *Schema) bool {
253+
if s == nil {
254+
if len(n.Nodes) <= 1 {
246255
return false
247256
}
257+
name := n.Nodes[0].XMLName.Local
258+
for _, child := range n.Nodes[1:] {
259+
if child.XMLName.Local != name {
260+
return false
261+
}
262+
}
263+
return true
264+
}
265+
266+
if s.Type.IsArray() || s.Items != nil {
267+
return true
268+
}
269+
return false
270+
}
271+
272+
func parseWrappedArray(n *node, items *Schema, xpath string) (any, error) {
273+
var list []any
274+
for _, item := range n.Nodes {
275+
v, err := parseXML(&item, items, path.Join(xpath, xmlNameAsString(n.XMLName)))
276+
if err != nil {
277+
return nil, err
278+
}
279+
list = append(list, v)
248280
}
249-
return true
281+
return list, nil
250282
}
251283

252284
func isWrapped(ref *Schema) (string, bool) {
@@ -274,3 +306,26 @@ func (n *node) hasElement(name string) bool {
274306
}
275307
return false
276308
}
309+
310+
func xmlNameAsString(n xml.Name) string {
311+
if n.Space != "" {
312+
return fmt.Sprintf("%s:%s", n.Space, n.Local)
313+
}
314+
return n.Local
315+
}
316+
317+
func appendOrError(slice any, item any, err error) (any, error) {
318+
if err != nil {
319+
return slice, err
320+
}
321+
var list []any
322+
if slice != nil {
323+
var ok bool
324+
list, ok = slice.([]any)
325+
if !ok {
326+
list = []any{slice}
327+
}
328+
}
329+
list = append(list, item)
330+
return list, nil
331+
}

providers/openapi/schema/parse_xml_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func TestParseXML(t *testing.T) {
2727
),
2828
test: func(t *testing.T, v any, err error) {
2929
require.NoError(t, err)
30-
// id is defined and shouldn't be treated as an additional property
30+
// id is indeed defined as attribute but in the payload as node
31+
// so it shouldn't be treated as an additional property
3132
require.Equal(t, map[string]any{}, v)
3233
},
3334
},
@@ -42,8 +43,7 @@ func TestParseXML(t *testing.T) {
4243
schematest.WithFreeForm(false),
4344
),
4445
test: func(t *testing.T, v any, err error) {
45-
require.NoError(t, err)
46-
require.Equal(t, map[string]any{}, v)
46+
require.EqualError(t, err, "failed to parse XML: property 'id' is expected as XML attribute but received as XML node and additionalProperty is not allowed: /root/id")
4747
},
4848
},
4949
{
@@ -297,7 +297,8 @@ func TestUnmarshalXML(t *testing.T) {
297297
test: func(t *testing.T) {
298298
v, err := schema.UnmarshalXML(strings.NewReader("<person><children><name>bob</name><name>sarah</name></children></person>"),
299299
schematest.New("object", schematest.WithProperty("children", schematest.New("array",
300-
schematest.WithItems("string"),
300+
schematest.WithItems("string", schematest.WithXml(&schema.Xml{Name: "name"})),
301+
schematest.WithXml(&schema.Xml{Wrapped: true}),
301302
))),
302303
)
303304
require.NoError(t, err)
@@ -355,6 +356,21 @@ func TestUnmarshalXML(t *testing.T) {
355356
require.Equal(t, map[string]interface{}{"id": "15", "author": "J K. Rowling", "title": "Harry Potter", "smp": "https://example.com/schema"}, v)
356357
},
357358
},
359+
{
360+
name: "books example from swagger",
361+
test: func(t *testing.T) {
362+
v, err := schema.UnmarshalXML(strings.NewReader(`<root><foo><books>one</books><books>two</books></foo></root>`),
363+
schematest.New("object",
364+
schematest.WithProperty("foo", schematest.New("object",
365+
schematest.WithProperty("books",
366+
schematest.New("array", schematest.WithItems("string"))),
367+
)),
368+
),
369+
)
370+
require.NoError(t, err)
371+
require.Equal(t, map[string]any{"foo": map[string]any{"books": []any{"one", "two"}}}, v)
372+
},
373+
},
358374
}
359375

360376
t.Parallel()

0 commit comments

Comments
 (0)