this repo has no description
at master 550 lines 12 kB view raw
1// Copyright 2025 The CUE Authors 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 koala_test 16 17import ( 18 "strings" 19 "testing" 20 21 "github.com/go-quicktest/qt" 22 23 "cuelang.org/go/cue" 24 "cuelang.org/go/cue/ast/astutil" 25 "cuelang.org/go/cue/cuecontext" 26 "cuelang.org/go/cue/errors" 27 "cuelang.org/go/cue/format" 28 "cuelang.org/go/encoding/xml/koala" 29) 30 31func TestErrorReporting(t *testing.T) { 32 t.Parallel() 33 tests := []struct { 34 name string 35 inputXML string 36 cueConstraints string 37 expectedError string 38 }{{ 39 name: "Element Text Content Constraint Error", 40 inputXML: `<?xml version="1.0" encoding="UTF-8"?> 41 <test v="v2.1"> 42 <edge n="2.65" o="3.65"/> 43 <container id="555"/> 44 <container id="777"/> 45 <container id="888" > 46 <l attr="x"/> 47 <l attr="y"/> 48 </container> 49 <text>content</text> 50 </test>`, 51 cueConstraints: `test: { 52 $v: string 53 edge: { 54 $n: string 55 $o: string 56 } 57 container: [...{ 58 $id: string 59 l: [...{ 60 $attr: string 61 }] 62 }] 63 text: { 64 $$: int 65 } 66 }`, 67 expectedError: "test.text.$$: conflicting values int and \"content\" (mismatched types int and string):\n input.xml:10:10\n schema.cue:14:8\n", 68 }, { 69 name: "Attribute Constraint Error", 70 inputXML: `<?xml version="1.0" encoding="UTF-8"?> 71 <test v="v2.1"> 72 <edge n="2.65" o="3.65"/> 73 <container id="555"/> 74 <container id="777"/> 75 <container id="888" > 76 <l attr="x"/> 77 <l attr="y"/> 78 </container> 79 <text>content</text> 80 </test>`, 81 cueConstraints: `test: { 82 $v: int 83 edge: { 84 $n: string 85 $o: string 86 } 87 container: [...{ 88 $id: string 89 l: [...{ 90 $attr: string 91 }] 92 }] 93 text: { 94 $$: string 95 } 96 }`, 97 expectedError: "test.$v: conflicting values int and \"v2.1\" (mismatched types int and string):\n input.xml:2:3\n schema.cue:2:7\n", 98 }, 99 { 100 name: "Attribute Constraint Error on self-closing element", 101 inputXML: `<?xml version="1.0" encoding="UTF-8"?> 102 <test v="v2.1"> 103 <edge n="2.65" o="3.65"/> 104 <container id="555"/> 105 <container id="777"/> 106 <container id="888" > 107 <l attr="x"/> 108 <l attr="y"/> 109 </container> 110 <text>content</text> 111 </test>`, 112 cueConstraints: `test: { 113 $v: string 114 edge: { 115 $n: int 116 $o: string 117 } 118 container: [...{ 119 $id: string 120 l: [...{ 121 $attr: string 122 }] 123 }] 124 text: { 125 $$: string 126 } 127 }`, 128 expectedError: "test.edge.$n: conflicting values int and \"2.65\" (mismatched types int and string):\n input.xml:3:4\n schema.cue:4:8\n", 129 }, 130 } 131 132 for _, test := range tests { 133 t.Run(test.name, func(t *testing.T) { 134 t.Parallel() 135 136 fileName := "input.xml" 137 dec := koala.NewDecoder(fileName, strings.NewReader(test.inputXML)) 138 139 cueExpr, err := dec.Decode() 140 141 qt.Assert(t, qt.IsNil(err)) 142 143 rootCueFile, err := astutil.ToFile(cueExpr) 144 qt.Assert(t, qt.IsNil(err)) 145 146 c := cuecontext.New() 147 rootCueVal := c.BuildFile(rootCueFile, cue.Filename(fileName)) 148 149 // compile some CUE into a Value 150 compiledSchema := c.CompileString(test.cueConstraints, cue.Filename("schema.cue")) 151 152 //unify the compiledSchema against the formattedConfig 153 unified := compiledSchema.Unify(rootCueVal) 154 155 actualError := "" 156 if err := unified.Validate(cue.Concrete(true)); err != nil { 157 actualError = errors.Details(err, nil) 158 } 159 160 qt.Assert(t, qt.Equals(actualError, test.expectedError)) 161 }) 162 } 163} 164 165func TestElementDecoding(t *testing.T) { 166 t.Parallel() 167 168 tests := []struct { 169 name string 170 inputXML string 171 wantCUE string 172 }{{ 173 name: "Simple Elements", 174 inputXML: `<note> 175 <to> </to> 176 <from>Jani</from> 177 <heading>Reminder</heading> 178 <body>Don't forget me this weekend!</body> 179</note>`, 180 wantCUE: `note: { 181 to: $$: " " 182 from: $$: "Jani" 183 heading: $$: "Reminder" 184 body: $$: "Don't forget me this weekend!" 185} 186`, 187 }, 188 { 189 name: "Simple self-closing element", 190 inputXML: `<note> 191 <to/> 192 <from>Jani</from> 193 <heading>Reminder</heading> 194 <body>Don't forget me this weekend!</body> 195</note>`, 196 wantCUE: `note: { 197 to: {} 198 from: $$: "Jani" 199 heading: $$: "Reminder" 200 body: $$: "Don't forget me this weekend!" 201} 202`, 203 }, 204 { 205 name: "Attribute", 206 inputXML: `<note alpha="abcd"> 207 <to>Tove</to> 208 <from>Jani</from> 209 <heading>Reminder</heading> 210 <body>Don't forget me this weekend!</body> 211</note>`, 212 wantCUE: `note: { 213 $alpha: "abcd" 214 to: $$: "Tove" 215 from: $$: "Jani" 216 heading: $$: "Reminder" 217 body: $$: "Don't forget me this weekend!" 218} 219`, 220 }, 221 { 222 name: "Attribute and Element with the same name", 223 inputXML: `<note alpha="abcd"> 224 <to>Tove</to> 225 <from>Jani</from> 226 <heading>Reminder</heading> 227 <body>Don't forget me this weekend!</body> 228 <alpha>efgh</alpha> 229</note>`, 230 wantCUE: `note: { 231 $alpha: "abcd" 232 to: $$: "Tove" 233 from: $$: "Jani" 234 heading: $$: "Reminder" 235 body: $$: "Don't forget me this weekend!" 236 alpha: $$: "efgh" 237} 238`, 239 }, 240 { 241 name: "Mapping for content when an attribute exists", 242 inputXML: `<note alpha="abcd"> 243 hello 244</note>`, 245 wantCUE: `note: { 246 $alpha: "abcd" 247 $$: "\n\thello\n" 248} 249`, 250 }, 251 { 252 name: "Nested Element", 253 inputXML: `<notes> 254 <note alpha="abcd">hello</note> 255</notes>`, 256 wantCUE: `notes: note: { 257 $alpha: "abcd" 258 $$: "hello" 259} 260`, 261 }, 262 { 263 name: "Collections", 264 inputXML: `<notes> 265 <note alpha="abcd">hello</note> 266 <note alpha="abcdef">goodbye</note> 267</notes>`, 268 wantCUE: `notes: note: [{ 269 $alpha: "abcd" 270 $$: "hello" 271}, { 272 $alpha: "abcdef" 273 $$: "goodbye" 274}] 275`, 276 }, 277 { 278 name: "Interleaving Element Types", 279 inputXML: `<notes> 280 <note alpha="abcd">hello</note> 281 <note alpha="abcdef">goodbye</note> 282 <book>mybook</book> 283 <note alpha="ab">goodbye</note> 284 <note>direct</note> 285</notes>`, 286 wantCUE: `notes: { 287 note: [{ 288 $alpha: "abcd" 289 $$: "hello" 290 }, { 291 $alpha: "abcdef" 292 $$: "goodbye" 293 }, { 294 $alpha: "ab" 295 $$: "goodbye" 296 }, { 297 $$: "direct" 298 }] 299 book: $$: "mybook" 300} 301`, 302 }, 303 { 304 name: "Namespaces", 305 inputXML: `<h:table xmlns:h="http://www.w3.org/TR/html4/"> 306 <h:tr> 307 <h:td>Apples</h:td> 308 <h:td>Bananas</h:td> 309 </h:tr> 310</h:table>`, 311 wantCUE: `"h:table": { 312 "$xmlns:h": "http://www.w3.org/TR/html4/" 313 "h:tr": "h:td": [{ 314 $$: "Apples" 315 }, { 316 $$: "Bananas" 317 }] 318} 319`, 320 }, 321 { 322 name: "Attribute namespace prefix", 323 inputXML: `<h:table xmlns:h="http://www.w3.org/TR/html4/" xmlns:f="http://www.w3.org/TR/html5/"> 324 <h:tr> 325 <h:td f:type="fruit">Apples</h:td> 326 <h:td>Bananas</h:td> 327 </h:tr> 328</h:table>`, 329 wantCUE: `"h:table": { 330 "$xmlns:h": "http://www.w3.org/TR/html4/" 331 "$xmlns:f": "http://www.w3.org/TR/html5/" 332 "h:tr": "h:td": [{ 333 "$f:type": "fruit" 334 $$: "Apples" 335 }, { 336 $$: "Bananas" 337 }] 338} 339`, 340 }, 341 { 342 name: "Mixed Namespaces", 343 inputXML: `<h:table xmlns:h="http://www.w3.org/TR/html4/" xmlns:r="d"> 344 <h:tr> 345 <h:td>Apples</h:td> 346 <h:td>Bananas</h:td> 347 <r:blah>e3r</r:blah> 348 </h:tr> 349</h:table>`, 350 wantCUE: `"h:table": { 351 "$xmlns:h": "http://www.w3.org/TR/html4/" 352 "$xmlns:r": "d" 353 "h:tr": { 354 "h:td": [{ 355 $$: "Apples" 356 }, { 357 $$: "Bananas" 358 }] 359 "r:blah": $$: "e3r" 360 } 361} 362`, 363 }, 364 { 365 name: "Elements with same name but different namespaces", 366 inputXML: `<h:table xmlns:h="http://www.w3.org/TR/html4/" xmlns:r="d"> 367 <h:tr> 368 <h:td>Apples</h:td> 369 <h:td>Bananas</h:td> 370 <r:td>e3r</r:td> 371 </h:tr> 372</h:table>`, 373 wantCUE: `"h:table": { 374 "$xmlns:h": "http://www.w3.org/TR/html4/" 375 "$xmlns:r": "d" 376 "h:tr": { 377 "h:td": [{ 378 $$: "Apples" 379 }, { 380 $$: "Bananas" 381 }] 382 "r:td": $$: "e3r" 383 } 384} 385`, 386 }, 387 { 388 name: "Collection of elements, where elements have optional properties", 389 inputXML: `<books> 390 <book> 391 <title>title</title> 392 <author>John Doe</author> 393 </book> 394 <book> 395 <title>title2</title> 396 <author>Jane Doe</author> 397 </book> 398 <book> 399 <title>Lord of the rings</title> 400 <author>JRR Tolkien</author> 401 <volume> 402 <title>Fellowship</title> 403 <author>JRR Tolkien</author> 404 </volume> 405 <volume> 406 <title>Two Towers</title> 407 <author>JRR Tolkien</author> 408 </volume> 409 <volume> 410 <title>Return of the King</title> 411 <author>JRR Tolkien</author> 412 </volume> 413 </book> 414</books>`, 415 wantCUE: `books: book: [{ 416 title: $$: "title" 417 author: $$: "John Doe" 418}, { 419 title: $$: "title2" 420 author: $$: "Jane Doe" 421}, { 422 title: $$: "Lord of the rings" 423 author: $$: "JRR Tolkien" 424 volume: [{ 425 title: $$: "Fellowship" 426 author: $$: "JRR Tolkien" 427 }, { 428 title: $$: "Two Towers" 429 author: $$: "JRR Tolkien" 430 }, { 431 title: $$: "Return of the King" 432 author: $$: "JRR Tolkien" 433 }] 434}] 435`, 436 }, 437 { 438 name: "Carriage Return Filter Test", 439 inputXML: "<node>\r\nhello\r\n</node>", 440 wantCUE: `node: $$: "\nhello\n" 441`, 442 }, 443 { 444 name: "Spacing either side of xml (including new lines before and after root node)", 445 inputXML: ` 446 447 <root> 448 <message>Hello World!</message> 449 <nested> 450 <a1>one level</a1> 451 <a2> 452 <b>two levels</b> 453 </a2> 454 </nested> 455 </root> 456 457 `, 458 wantCUE: `root: { 459 message: $$: "Hello World!" 460 nested: { 461 a1: $$: "one level" 462 a2: b: $$: "two levels" 463 } 464} 465`, 466 }, 467 } 468 469 for _, test := range tests { 470 t.Run(test.name, func(t *testing.T) { 471 t.Parallel() 472 473 dec := koala.NewDecoder("input.xml", strings.NewReader(test.inputXML)) 474 cueExpr, err := dec.Decode() 475 476 qt.Assert(t, qt.IsNil(err)) 477 478 rootCueFile, err := astutil.ToFile(cueExpr) 479 qt.Assert(t, qt.IsNil(err)) 480 481 actualCue, err := format.Node(rootCueFile) 482 483 qt.Assert(t, qt.IsNil(err)) 484 qt.Assert(t, qt.Equals(string(actualCue), test.wantCUE)) 485 }) 486 } 487} 488 489func TestErrors(t *testing.T) { 490 t.Parallel() 491 492 tests := []struct { 493 name string 494 inputXML string 495 expectedError string 496 }{ 497 { 498 name: "Text after root node followed by subelements", 499 inputXML: `<note> 500 mixed 501 <from>Jani</from> 502 <heading>Reminder</heading> 503 <body>Don't forget me this weekend!</body> 504 </note>`, 505 expectedError: `text content within an XML element that has sub-elements is not supported`, 506 }, 507 { 508 name: "Text in middle of subelements", 509 inputXML: `<note> 510 <to/> 511 mixed 512 <from>Jani</from> 513 <heading>Reminder</heading> 514 <body>Don't forget me this weekend!</body> 515 </note>`, 516 expectedError: `text content within an XML element that has sub-elements is not supported`, 517 }, 518 { 519 name: "Nested mixed content", 520 inputXML: `<note> 521 <to/> 522 <from>Jani <subElement/></from> 523 <heading>Reminder</heading> 524 <body>Don't forget me this weekend!</body> 525 </note>`, 526 expectedError: `text content within an XML element that has sub-elements is not supported`, 527 }, 528 { 529 name: "Text before end of root element", 530 inputXML: `<note> 531 <to/> 532 <from></from> 533 <heading>Reminder</heading> 534 myText 535 </note>`, 536 expectedError: `text content within an XML element that has sub-elements is not supported`, 537 }, 538 } 539 540 for _, test := range tests { 541 t.Run(test.name, func(t *testing.T) { 542 t.Parallel() 543 544 dec := koala.NewDecoder("input.xml", strings.NewReader(test.inputXML)) 545 _, err := dec.Decode() 546 547 qt.Assert(t, qt.ErrorMatches(err, test.expectedError)) 548 }) 549 } 550}