Skip to content

Commit e868dd6

Browse files
committed
Refactored out XML validation into a new interface.
XMLValidator is now it’s own interface. no mixing things up with the SchemaValidator.
1 parent 9e79377 commit e868dd6

File tree

4 files changed

+84
-48
lines changed

4 files changed

+84
-48
lines changed

schema_validation/validate_schema.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,14 @@ import (
2929
)
3030

3131
// SchemaValidator is an interface that defines the methods for validating a *base.Schema (V3+ Only) object.
32-
// There are 8 methods for validating a schema:
32+
// There are 6 methods for validating a schema:
3333
//
3434
// ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
3535
// ValidateSchemaObject accepts a schema object to validate against, and an object, created from unmarshalled JSON/YAML.
3636
// ValidateSchemaBytes accepts a schema object to validate against, and a JSON/YAML blob that is defined as a byte array.
3737
// ValidateSchemaStringWithVersion - version-aware validation that allows OpenAPI 3.0 keywords when version is specified.
3838
// ValidateSchemaObjectWithVersion - version-aware validation that allows OpenAPI 3.0 keywords when version is specified.
3939
// ValidateSchemaBytesWithVersion - version-aware validation that allows OpenAPI 3.0 keywords when version is specified.
40-
// ValidateXMLString - validates XML string against schema, applying OpenAPI xml object transformations.
41-
// ValidateXMLStringWithVersion - version-aware XML validation.
4240
type SchemaValidator interface {
4341
// ValidateSchemaString accepts a schema object to validate against, and a JSON/YAML blob that is defined as a string.
4442
// Uses OpenAPI 3.1+ validation by default (strict JSON Schema compliance).
@@ -67,14 +65,6 @@ type SchemaValidator interface {
6765
// When version is 3.0, OpenAPI 3.0-specific keywords like 'nullable' are allowed and processed.
6866
// When version is 3.1+, OpenAPI 3.0-specific keywords like 'nullable' will cause validation to fail.
6967
ValidateSchemaBytesWithVersion(schema *base.Schema, payload []byte, version float32) (bool, []*liberrors.ValidationError)
70-
71-
// ValidateXMLString validates an XML string against an OpenAPI schema, applying xml object transformations.
72-
// Uses OpenAPI 3.1+ validation by default.
73-
ValidateXMLString(schema *base.Schema, xmlString string) (bool, []*liberrors.ValidationError)
74-
75-
// ValidateXMLStringWithVersion validates an XML string with version-specific rules.
76-
// When version is 3.0, OpenAPI 3.0-specific keywords like 'nullable' are allowed.
77-
ValidateXMLStringWithVersion(schema *base.Schema, xmlString string, version float32) (bool, []*liberrors.ValidationError)
7868
}
7969

8070
var instanceLocationRegex = regexp.MustCompile(`^/(\d+)`)

schema_validation/validate_xml.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,7 @@ import (
1616
"github.com/pb33f/libopenapi-validator/helpers"
1717
)
1818

19-
// ValidateXMLString validates an XML string against an OpenAPI schema,
20-
// applying xml object transformations before validation.
21-
// uses openapi 3.1+ validation by default.
22-
func (s *schemaValidator) ValidateXMLString(schema *base.Schema, xmlString string) (bool, []*liberrors.ValidationError) {
23-
return s.validateXMLWithVersion(schema, xmlString, s.logger, 3.1)
24-
}
25-
26-
// ValidateXMLStringWithVersion validates an XML string with version-specific rules.
27-
// when version is 3.0, openapi 3.0-specific keywords like 'nullable' are allowed.
28-
func (s *schemaValidator) ValidateXMLStringWithVersion(schema *base.Schema, xmlString string, version float32) (bool, []*liberrors.ValidationError) {
29-
return s.validateXMLWithVersion(schema, xmlString, s.logger, version)
30-
}
31-
32-
func (s *schemaValidator) validateXMLWithVersion(schema *base.Schema, xmlString string, log *slog.Logger, version float32) (bool, []*liberrors.ValidationError) {
19+
func (x *xmlValidator) validateXMLWithVersion(schema *base.Schema, xmlString string, log *slog.Logger, version float32) (bool, []*liberrors.ValidationError) {
3320
var validationErrors []*liberrors.ValidationError
3421

3522
if schema == nil {
@@ -58,7 +45,7 @@ func (s *schemaValidator) validateXMLWithVersion(schema *base.Schema, xmlString
5845
}
5946

6047
// validate transformed json against schema using existing validator
61-
return s.validateSchemaWithVersion(schema, nil, transformedJSON, log, version)
48+
return x.schemaValidator.validateSchemaWithVersion(schema, nil, transformedJSON, log, version)
6249
}
6350

6451
// transformXMLToSchemaJSON converts xml to json structure matching openapi schema.

schema_validation/validate_xml_test.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ paths:
4242
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
4343
Content.GetOrZero("application/xml").Schema.Schema()
4444

45-
validator := NewSchemaValidator()
45+
validator := NewXMLValidator()
4646
valid, validationErrors := validator.ValidateXMLString(schema, "<Cat><nice>true</nice></Cat>")
4747

4848
assert.True(t, valid)
@@ -72,7 +72,7 @@ paths:
7272
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
7373
Content.GetOrZero("application/xml").Schema.Schema()
7474

75-
validator := NewSchemaValidator()
75+
validator := NewXMLValidator()
7676

7777
// empty xml should fail
7878
valid, validationErrors := validator.ValidateXMLString(schema, "")
@@ -112,7 +112,7 @@ paths:
112112
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
113113
Content.GetOrZero("application/xml").Schema.Schema()
114114

115-
validator := NewSchemaValidator()
115+
validator := NewXMLValidator()
116116
valid, validationErrors := validator.ValidateXMLString(schema, `<Cat id="123"><name>Fluffy</name></Cat>`)
117117

118118
assert.True(t, valid)
@@ -145,7 +145,7 @@ paths:
145145
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
146146
Content.GetOrZero("application/xml").Schema.Schema()
147147

148-
validator := NewSchemaValidator()
148+
validator := NewXMLValidator()
149149

150150
// valid integer
151151
valid, validationErrors := validator.ValidateXMLString(schema, "<Cat><age>5</age></Cat>")
@@ -195,7 +195,7 @@ paths:
195195
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/pets").Get.Responses.Codes.GetOrZero("200").
196196
Content.GetOrZero("application/xml").Schema.Schema()
197197

198-
validator := NewSchemaValidator()
198+
validator := NewXMLValidator()
199199

200200
// valid wrapped array
201201
validXML := `<Pets><pets><pet><name>Fluffy</name><age>3</age></pet><pet><name>Spot</name><age>5</age></pet></pets></Pets>`
@@ -246,7 +246,7 @@ paths:
246246
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/user").Get.Responses.Codes.GetOrZero("200").
247247
Content.GetOrZero("application/xml").Schema.Schema()
248248

249-
validator := NewSchemaValidator()
249+
validator := NewXMLValidator()
250250

251251
// valid xml with custom element names
252252
validXML := `<User><id>42</id><username>johndoe</username><email>[email protected]</email></User>`
@@ -293,7 +293,7 @@ paths:
293293
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/book").Get.Responses.Codes.GetOrZero("200").
294294
Content.GetOrZero("application/xml").Schema.Schema()
295295

296-
validator := NewSchemaValidator()
296+
validator := NewXMLValidator()
297297

298298
// valid xml with both attributes and elements
299299
validXML := `<Book id="1" isbn="978-3-16-148410-0"><title>Go Programming</title><author>John Doe</author><price>29.99</price></Book>`
@@ -340,7 +340,7 @@ paths:
340340
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/order").Get.Responses.Codes.GetOrZero("200").
341341
Content.GetOrZero("application/xml").Schema.Schema()
342342

343-
validator := NewSchemaValidator()
343+
validator := NewXMLValidator()
344344

345345
// valid nested xml
346346
validXML := `<Order><orderId>123</orderId><customer><name>Jane Doe</name><address><street>123 Main St</street><city>Springfield</city></address></customer></Order>`
@@ -381,7 +381,7 @@ paths:
381381
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/data").Get.Responses.Codes.GetOrZero("200").
382382
Content.GetOrZero("application/xml").Schema.Schema()
383383

384-
validator := NewSchemaValidator()
384+
validator := NewXMLValidator()
385385

386386
// goxml2json should coerce numeric strings to numbers
387387
validXML := `<Data><intValue>42</intValue><floatValue>3.14</floatValue><stringValue>hello</stringValue><boolValue>true</boolValue></Data>`
@@ -423,7 +423,7 @@ paths:
423423
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/product").Get.Responses.Codes.GetOrZero("200").
424424
Content.GetOrZero("application/xml").Schema.Schema()
425425

426-
validator := NewSchemaValidator()
426+
validator := NewXMLValidator()
427427

428428
// missing required property 'name'
429429
invalidXML := `<Product><productId>123</productId></Product>`
@@ -481,7 +481,7 @@ paths:
481481
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/api").Post.Responses.Codes.GetOrZero("200").
482482
Content.GetOrZero("application/soap+xml").Schema.Schema()
483483

484-
validator := NewSchemaValidator()
484+
validator := NewXMLValidator()
485485

486486
// valid soap-like xml
487487
validXML := `<Response requestId="req-12345"><status>success</status><timestamp>1699372800</timestamp><data><value>result</value></data></Response>`
@@ -516,7 +516,7 @@ paths:
516516
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/test").Get.Responses.Codes.GetOrZero("200").
517517
Content.GetOrZero("application/xml").Schema.Schema()
518518

519-
validator := NewSchemaValidator()
519+
validator := NewXMLValidator()
520520

521521
// valid xml with whitespace
522522
validXML := `<Test>
@@ -561,7 +561,7 @@ paths:
561561
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/message").Get.Responses.Codes.GetOrZero("200").
562562
Content.GetOrZero("application/xml").Schema.Schema()
563563

564-
validator := NewSchemaValidator()
564+
validator := NewXMLValidator()
565565

566566
// valid xml with namespace (goxml2json strips namespace prefixes)
567567
validXML := `<msg:Message xmlns:msg="http://example.com/message"><msg:subject>Hello</msg:subject><msg:body>World</msg:body></msg:Message>`
@@ -601,7 +601,7 @@ paths:
601601
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/config").Get.Responses.Codes.GetOrZero("200").
602602
Content.GetOrZero("application/xml").Schema.Schema()
603603

604-
validator := NewSchemaValidator()
604+
validator := NewXMLValidator()
605605

606606
// xml has wrong element names (should be 'enabled' and 'maxRetries')
607607
// this should fail because required properties are missing
@@ -645,7 +645,7 @@ paths:
645645
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/item").Get.Responses.Codes.GetOrZero("200").
646646
Content.GetOrZero("application/xml").Schema.Schema()
647647

648-
validator := NewSchemaValidator()
648+
validator := NewXMLValidator()
649649

650650
// valid - attributes are integers
651651
validXML := `<Item id="123" quantity="5"><name>Widget</name></Item>`
@@ -690,7 +690,7 @@ paths:
690690
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/measurement").Get.Responses.Codes.GetOrZero("200").
691691
Content.GetOrZero("application/xml").Schema.Schema()
692692

693-
validator := NewSchemaValidator()
693+
validator := NewXMLValidator()
694694

695695
// valid xml with float values
696696
validXML := `<Measurement><temperature>23.456</temperature><humidity>65.2</humidity><pressure>1013.25</pressure></Measurement>`
@@ -732,7 +732,7 @@ paths:
732732
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/item").Get.Responses.Codes.GetOrZero("200").
733733
Content.GetOrZero("application/xml").Schema.Schema()
734734

735-
validator := NewSchemaValidator()
735+
validator := NewXMLValidator()
736736

737737
// test with version 3.0 - should allow nullable keyword
738738
valid, validationErrors := validator.ValidateXMLStringWithVersion(schema, "<Item><value>test</value></Item>", 3.0)
@@ -741,7 +741,7 @@ paths:
741741
}
742742

743743
func TestValidateXML_NilSchema(t *testing.T) {
744-
validator := NewSchemaValidator()
744+
validator := NewXMLValidator()
745745

746746
// test with nil schema - should return false with empty errors
747747
valid, validationErrors := validator.ValidateXMLString(nil, "<Test>value</Test>")
@@ -798,7 +798,7 @@ paths:
798798
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/empty").Get.Responses.Codes.GetOrZero("200").
799799
Content.GetOrZero("application/xml").Schema.Schema()
800800

801-
validator := NewSchemaValidator()
801+
validator := NewXMLValidator()
802802

803803
// schema with no properties should still validate
804804
valid, validationErrors := validator.ValidateXMLString(schema, "<Empty><anything>value</anything></Empty>")
@@ -829,7 +829,7 @@ paths:
829829
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/simple").Get.Responses.Codes.GetOrZero("200").
830830
Content.GetOrZero("application/xml").Schema.Schema()
831831

832-
validator := NewSchemaValidator()
832+
validator := NewXMLValidator()
833833

834834
// primitive value (non-object) should work
835835
valid, validationErrors := validator.ValidateXMLString(schema, "<Value>hello world</Value>")
@@ -865,7 +865,7 @@ paths:
865865
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/items").Get.Responses.Codes.GetOrZero("200").
866866
Content.GetOrZero("application/xml").Schema.Schema()
867867

868-
validator := NewSchemaValidator()
868+
validator := NewXMLValidator()
869869

870870
// array without wrapped - items are direct siblings
871871
validXML := `<Items><items>one</items><items>two</items><items>three</items></Items>`
@@ -909,7 +909,7 @@ paths:
909909
schema := v3Doc.Model.Paths.PathItems.GetOrZero("/collection").Get.Responses.Codes.GetOrZero("200").
910910
Content.GetOrZero("application/xml").Schema.Schema()
911911

912-
validator := NewSchemaValidator()
912+
validator := NewXMLValidator()
913913

914914
// wrapper contains items with wrong name (item instead of record)
915915
// this tests the fallback path where unwrapped element is not found

schema_validation/xml_validator.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
2+
// SPDX-License-Identifier: MIT
3+
4+
package schema_validation
5+
6+
import (
7+
"log/slog"
8+
"os"
9+
10+
"github.com/pb33f/libopenapi/datamodel/high/base"
11+
12+
"github.com/pb33f/libopenapi-validator/config"
13+
liberrors "github.com/pb33f/libopenapi-validator/errors"
14+
)
15+
16+
// XMLValidator is an interface that defines methods for validating XML against OpenAPI schemas.
17+
// There are 2 methods for validating XML:
18+
//
19+
// ValidateXMLString validates an XML string against a schema, applying OpenAPI xml object transformations.
20+
// ValidateXMLStringWithVersion - version-aware XML validation that allows OpenAPI 3.0 keywords when version is specified.
21+
type XMLValidator interface {
22+
// ValidateXMLString validates an XML string against an OpenAPI schema, applying xml object transformations.
23+
// Uses OpenAPI 3.1+ validation by default (strict JSON Schema compliance).
24+
ValidateXMLString(schema *base.Schema, xmlString string) (bool, []*liberrors.ValidationError)
25+
26+
// ValidateXMLStringWithVersion validates an XML string with version-specific rules.
27+
// When version is 3.0, OpenAPI 3.0-specific keywords like 'nullable' are allowed and processed.
28+
// When version is 3.1+, OpenAPI 3.0-specific keywords like 'nullable' will cause validation to fail.
29+
ValidateXMLStringWithVersion(schema *base.Schema, xmlString string, version float32) (bool, []*liberrors.ValidationError)
30+
}
31+
32+
type xmlValidator struct {
33+
schemaValidator *schemaValidator
34+
logger *slog.Logger
35+
}
36+
37+
// NewXMLValidatorWithLogger creates a new XMLValidator instance with a custom logger.
38+
func NewXMLValidatorWithLogger(logger *slog.Logger, opts ...config.Option) XMLValidator {
39+
options := config.NewValidationOptions(opts...)
40+
// Create an internal schema validator for JSON validation after XML transformation
41+
sv := &schemaValidator{options: options, logger: logger}
42+
return &xmlValidator{schemaValidator: sv, logger: logger}
43+
}
44+
45+
// NewXMLValidator creates a new XMLValidator instance with default logging configuration.
46+
func NewXMLValidator(opts ...config.Option) XMLValidator {
47+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
48+
Level: slog.LevelError,
49+
}))
50+
return NewXMLValidatorWithLogger(logger, opts...)
51+
}
52+
53+
func (x *xmlValidator) ValidateXMLString(schema *base.Schema, xmlString string) (bool, []*liberrors.ValidationError) {
54+
return x.validateXMLWithVersion(schema, xmlString, x.logger, 3.1)
55+
}
56+
57+
func (x *xmlValidator) ValidateXMLStringWithVersion(schema *base.Schema, xmlString string, version float32) (bool, []*liberrors.ValidationError) {
58+
return x.validateXMLWithVersion(schema, xmlString, x.logger, version)
59+
}

0 commit comments

Comments
 (0)