Skip to content

Commit 86a762b

Browse files
ySnoopyDogydaveshanley
authored andcommitted
only convert values that needs to
also added a validation for XML namespaces and prefix
1 parent 4f92af0 commit 86a762b

12 files changed

Lines changed: 719 additions & 133 deletions

errors/parameters_howtofix.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,27 @@ const (
1212
HowToFixParamInvalidBoolean string = "Convert the value '%s' into a true/false value"
1313
HowToFixParamInvalidEnum string = "Instead of '%s', use one of the allowed values: '%s'"
1414
HowToFixParamInvalidFormEncode string = "Use a form style encoding for parameter values, for example: '%s'"
15+
HowToFixInvalidXml string = "Ensure xml is well-formed and matches schema structure"
16+
HowToFixXmlPrefix string = "Make sure to prepend the correct prefix '%s' to the declared fields"
17+
HowToFixXmlNamespace string = "Make sure to declare the 'xmlns:%s' with the correct namespace URI"
1518
HowToFixInvalidSchema string = "Ensure that the object being submitted, matches the schema correctly"
1619
HowToFixParamInvalidSpaceDelimitedObjectExplode string = "When using 'explode' with space delimited parameters, " +
1720
"they should be separated by spaces. For example: '%s'"
1821
HowToFixParamInvalidPipeDelimitedObjectExplode string = "When using 'explode' with pipe delimited parameters, " +
1922
"they should be separated by pipes '|'. For example: '%s'"
2023
HowToFixParamInvalidDeepObjectMultipleValues string = "There can only be a single value per property name, " +
2124
"deepObject parameters should contain the property key in square brackets next to the parameter name. For example: '%s'"
22-
HowToFixInvalidJSON string = "The JSON submitted is invalid, please check the syntax"
23-
HowToFixDecodingError = "The object can't be decoded, so make sure it's being encoded correctly according to the spec."
24-
HowToFixInvalidContentType = "The content type is invalid, Use one of the %d supported types for this operation: %s"
25-
HowToFixInvalidResponseCode = "The service is responding with a code that is not defined in the spec, fix the service or add the code to the specification"
26-
HowToFixInvalidEncoding = "Ensure the correct encoding has been used on the object"
27-
HowToFixMissingValue = "Ensure the value has been set"
28-
HowToFixPath = "Check the path is correct, and check that the correct HTTP method has been used (e.g. GET, POST, PUT, DELETE)"
29-
HowToFixPathMethod = "Add the missing operation to the contract for the path"
30-
HowToFixInvalidMaxItems = "Reduce the number of items in the array to %d or less"
31-
HowToFixInvalidMinItems = "Increase the number of items in the array to %d or more"
32-
HowToFixMissingHeader = "Make sure the service responding sets the required headers with this response code"
25+
HowToFixInvalidJSON string = "The JSON submitted is invalid, please check the syntax"
26+
HowToFixDecodingError string = "The object can't be decoded, so make sure it's being encoded correctly according to the spec."
27+
HowToFixInvalidContentType string = "The content type is invalid, Use one of the %d supported types for this operation: %s"
28+
HowToFixInvalidResponseCode string = "The service is responding with a code that is not defined in the spec, fix the service or add the code to the specification"
29+
HowToFixInvalidEncoding string = "Ensure the correct encoding has been used on the object"
30+
HowToFixMissingValue string = "Ensure the value has been set"
31+
HowToFixPath string = "Check the path is correct, and check that the correct HTTP method has been used (e.g. GET, POST, PUT, DELETE)"
32+
HowToFixPathMethod string = "Add the missing operation to the contract for the path"
33+
HowToFixInvalidMaxItems string = "Reduce the number of items in the array to %d or less"
34+
HowToFixInvalidMinItems string = "Increase the number of items in the array to %d or more"
35+
HowToFixMissingHeader string = "Make sure the service responding sets the required headers with this response code"
36+
HowToFixInvalidRenderedSchema string = "Check the request schema for circular references or invalid structures"
37+
HowToFixInvalidJsonSchema string = "Check the request schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs"
3338
)

errors/xml_errors.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package errors
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/pb33f/libopenapi-validator/helpers"
7+
"github.com/pb33f/libopenapi/datamodel/high/base"
8+
)
9+
10+
func MissingPrefix(schema *base.Schema, prefix string) *ValidationError {
11+
line := 1
12+
col := 0
13+
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
14+
line = low.Type.KeyNode.Line
15+
col = low.Type.KeyNode.Column
16+
}
17+
18+
return &ValidationError{
19+
ValidationType: helpers.XmlValidation,
20+
ValidationSubType: helpers.XmlValidationPrefix,
21+
Message: fmt.Sprintf("The prefix '%s' is defined in the schema, however it's missing from the xml", prefix),
22+
Reason: fmt.Sprintf("The prefix '%s' is defined in the schema, however it's missing from the xml content", prefix),
23+
SpecLine: line,
24+
SpecCol: col,
25+
Context: schema,
26+
HowToFix: fmt.Sprintf(HowToFixXmlPrefix, prefix),
27+
}
28+
}
29+
30+
func InvalidPrefix(schema *base.Schema, prefix string) *ValidationError {
31+
line := 1
32+
col := 0
33+
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
34+
line = low.Type.KeyNode.Line
35+
col = low.Type.KeyNode.Column
36+
}
37+
38+
return &ValidationError{
39+
ValidationType: helpers.XmlValidation,
40+
ValidationSubType: helpers.XmlValidationPrefix,
41+
Message: fmt.Sprintf("The prefix '%s' defined in the schema differs from the xml", prefix),
42+
Reason: fmt.Sprintf("The prefix '%s' is defined in the schema, however the xml sent and invalid prefix", prefix),
43+
SpecCol: col,
44+
SpecLine: line,
45+
Context: schema,
46+
HowToFix: fmt.Sprintf(HowToFixXmlPrefix, prefix),
47+
}
48+
}
49+
50+
func MissingNamespace(schema *base.Schema, namespace string) *ValidationError {
51+
line := 1
52+
col := 0
53+
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
54+
line = low.Type.KeyNode.Line
55+
col = low.Type.KeyNode.Column
56+
}
57+
58+
return &ValidationError{
59+
ValidationType: helpers.XmlValidation,
60+
ValidationSubType: helpers.XmlValidationNamespace,
61+
Message: fmt.Sprintf("The namespace '%s' is defined in the schema, however it's missing from the xml", namespace),
62+
Reason: fmt.Sprintf("The namespace '%s' is defined in the schema, however it's missing from the xml content", namespace),
63+
SpecLine: line,
64+
SpecCol: col,
65+
Context: schema,
66+
HowToFix: fmt.Sprintf(HowToFixXmlNamespace, namespace),
67+
}
68+
}
69+
70+
func InvalidNamespace(schema *base.Schema, namespace, expectedNamespace, prefix string) *ValidationError {
71+
line := 1
72+
col := 0
73+
if low := schema.GoLow(); low != nil && low.Type.KeyNode != nil {
74+
line = low.Type.KeyNode.Line
75+
col = low.Type.KeyNode.Column
76+
}
77+
78+
return &ValidationError{
79+
ValidationType: helpers.XmlValidation,
80+
ValidationSubType: helpers.XmlValidationNamespace,
81+
Message: fmt.Sprintf("The namespace from prefix '%s' differs from the xml", prefix),
82+
Reason: fmt.Sprintf("The namespace from prefix '%s' is declared as '%s' in the schema, however in xml is declared as '%s'",
83+
prefix, expectedNamespace, namespace),
84+
SpecLine: line,
85+
SpecCol: col,
86+
Context: schema,
87+
HowToFix: fmt.Sprintf(HowToFixXmlNamespace, namespace),
88+
}
89+
}
90+
91+
func InvalidXmlParsing(reason, referenceObject string) *ValidationError {
92+
return &ValidationError{
93+
ValidationType: helpers.XmlValidation,
94+
ValidationSubType: helpers.Schema,
95+
Message: "xml example is malformed",
96+
Reason: fmt.Sprintf("failed to parse xml: %s", reason),
97+
SchemaValidationErrors: []*SchemaValidationFailure{{
98+
Reason: reason,
99+
Location: "xml parsing",
100+
ReferenceSchema: "",
101+
ReferenceObject: referenceObject,
102+
}},
103+
HowToFix: HowToFixInvalidXml,
104+
}
105+
}

errors/xml_errors_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
2+
// https://pb33f.io
3+
4+
package errors
5+
6+
import (
7+
"testing"
8+
9+
"github.com/pb33f/libopenapi"
10+
"github.com/pb33f/libopenapi-validator/helpers"
11+
"github.com/pb33f/libopenapi/datamodel/high/base"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func getTestSchema() *base.Schema {
16+
spec := `openapi: 3.0.0
17+
paths:
18+
/pet:
19+
get:
20+
responses:
21+
'200':
22+
content:
23+
application/xml:
24+
schema:
25+
type: object
26+
properties:
27+
age:
28+
type: integer
29+
xml:
30+
name: Cat`
31+
32+
doc, _ := libopenapi.NewDocument([]byte(spec))
33+
v3Doc, _ := doc.BuildV3Model()
34+
35+
return v3Doc.Model.Paths.PathItems.GetOrZero("/pet").Get.Responses.Codes.GetOrZero("200").
36+
Content.GetOrZero("application/xml").Schema.Schema()
37+
}
38+
39+
func TestMissingPrefixError(t *testing.T) {
40+
schema := getTestSchema()
41+
err := MissingPrefix(schema, "prx")
42+
43+
assert.NotNil(t, *err)
44+
assert.Equal(t, helpers.XmlValidationPrefix, (*err).ValidationSubType)
45+
}
46+
47+
func TestMissingNamespaceError(t *testing.T) {
48+
schema := getTestSchema()
49+
err := MissingNamespace(schema, "http://ex.c")
50+
51+
assert.NotNil(t, *err)
52+
assert.Equal(t, helpers.XmlValidationNamespace, (*err).ValidationSubType)
53+
}
54+
55+
func TestInvalidPrefixError(t *testing.T) {
56+
schema := getTestSchema()
57+
err := InvalidPrefix(schema, "prx")
58+
59+
assert.NotNil(t, *err)
60+
assert.Equal(t, helpers.XmlValidationPrefix, (*err).ValidationSubType)
61+
}
62+
63+
func TestInvalidNamespaceError(t *testing.T) {
64+
schema := getTestSchema()
65+
err := InvalidNamespace(schema, "other", "http://ex.c", "prx")
66+
67+
assert.NotNil(t, *err)
68+
assert.Equal(t, helpers.XmlValidationNamespace, (*err).ValidationSubType)
69+
}
70+
71+
func TestInvalidParsing(t *testing.T) {
72+
err := InvalidXmlParsing("no data sent", "invalid-xml")
73+
74+
assert.NotNil(t, (*err))
75+
assert.Equal(t, (*err).SchemaValidationErrors[0].Location, "xml parsing")
76+
assert.Equal(t, helpers.Schema, (*err).ValidationSubType)
77+
}

helpers/constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const (
1111
ParameterValidationCookie = "cookie"
1212
RequestValidation = "request"
1313
RequestBodyValidation = "requestBody"
14+
XmlValidation = "xmlValidation"
15+
XmlValidationPrefix = "prefix"
16+
XmlValidationNamespace = "namespace"
17+
GolangMapType = "map[string]interface {}"
18+
GolangArrayType = "[]interface {}"
1419
Schema = "schema"
1520
ResponseBodyValidation = "response"
1621
RequestBodyContentType = "contentType"

requests/validate_body.go

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,6 @@ func (v *requestBodyValidator) ValidateRequestBody(request *http.Request) (bool,
2828
return v.ValidateRequestBodyWithPathItem(request, pathItem, foundPath)
2929
}
3030

31-
func generateXmlValidationError(err error, referenceObject string) []*errors.ValidationError {
32-
return []*errors.ValidationError{{
33-
ValidationType: helpers.RequestBodyValidation,
34-
ValidationSubType: helpers.Schema,
35-
Message: "xml example is malformed",
36-
Reason: fmt.Sprintf("failed to parse xml: %s", err.Error()),
37-
SchemaValidationErrors: []*errors.SchemaValidationFailure{{
38-
Reason: err.Error(),
39-
Location: "xml parsing",
40-
ReferenceSchema: "",
41-
ReferenceObject: referenceObject,
42-
}},
43-
HowToFix: "ensure xml is well-formed and matches schema structure",
44-
}}
45-
}
46-
4731
func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
4832
if pathItem == nil {
4933
return false, []*errors.ValidationError{{
@@ -106,14 +90,14 @@ func (v *requestBodyValidator) ValidateRequestBodyWithPathItem(request *http.Req
10690
_ = request.Body.Close()
10791

10892
stringedBody := string(requestBody)
109-
jsonBody, err := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
110-
if err != nil {
111-
return false, generateXmlValidationError(err, stringedBody)
93+
jsonBody, prevalidationErrors := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
94+
if len(prevalidationErrors) > 0 {
95+
return false, prevalidationErrors
11296
}
11397

11498
transformedBytes, err := json.Marshal(jsonBody)
11599
if err != nil {
116-
return false, generateXmlValidationError(err, stringedBody)
100+
return false, []*errors.ValidationError{errors.InvalidXmlParsing(err.Error(), stringedBody)}
117101
}
118102

119103
request.Body = io.NopCloser(bytes.NewBuffer(transformedBytes))

requests/validate_body_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1652,7 +1652,7 @@ paths:
16521652
assert.Len(t, errors, 1)
16531653

16541654
err := errors[0]
1655-
assert.Equal(t, helpers.RequestBodyValidation, err.ValidationType)
1655+
assert.Equal(t, helpers.XmlValidation, err.ValidationType)
16561656
assert.Contains(t, err.Reason, "failed to parse xml")
16571657
}
16581658

requests/validate_request.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func ValidateRequestSchema(input *ValidateRequestSchemaInput) (bool, []*errors.V
100100
SpecLine: 1,
101101
SpecCol: 0,
102102
SchemaValidationErrors: []*errors.SchemaValidationFailure{violation},
103-
HowToFix: "check the request schema for circular references or invalid structures",
103+
HowToFix: errors.HowToFixInvalidRenderedSchema,
104104
Context: referenceSchema,
105105
})
106106
return false, validationErrors

responses/validate_body.go

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,22 +127,6 @@ func (v *responseBodyValidator) ValidateResponseBodyWithPathItem(request *http.R
127127
return true, nil
128128
}
129129

130-
func generateXmlValidationError(err error, referenceObject string) []*errors.ValidationError {
131-
return []*errors.ValidationError{{
132-
ValidationType: helpers.RequestBodyValidation,
133-
ValidationSubType: helpers.Schema,
134-
Message: "xml response is malformed",
135-
Reason: fmt.Sprintf("failed to parse xml: %s", err.Error()),
136-
SchemaValidationErrors: []*errors.SchemaValidationFailure{{
137-
Reason: err.Error(),
138-
Location: "xml parsing",
139-
ReferenceSchema: "",
140-
ReferenceObject: referenceObject,
141-
}},
142-
HowToFix: "ensure xml is well-formed and matches schema structure",
143-
}}
144-
}
145-
146130
func (v *responseBodyValidator) checkResponseSchema(
147131
request *http.Request,
148132
response *http.Response,
@@ -173,14 +157,15 @@ func (v *responseBodyValidator) checkResponseSchema(
173157
_ = response.Body.Close()
174158

175159
stringedBody := string(responseBody)
176-
jsonBody, err := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
177-
if err != nil {
178-
return generateXmlValidationError(err, stringedBody)
160+
jsonBody, prevalidationErrors := schema_validation.TransformXMLToSchemaJSON(stringedBody, schema)
161+
162+
if len(prevalidationErrors) > 0 {
163+
return prevalidationErrors
179164
}
180165

181166
transformedBytes, err := json.Marshal(jsonBody)
182167
if err != nil {
183-
return generateXmlValidationError(err, stringedBody)
168+
return []*errors.ValidationError{errors.InvalidXmlParsing(err.Error(), stringedBody)}
184169
}
185170

186171
response.Body = io.NopCloser(bytes.NewBuffer(transformedBytes))

responses/validate_body_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ paths:
15941594

15951595
assert.False(t, valid)
15961596
assert.Len(t, errors, 1)
1597-
assert.Len(t, errors[0].SchemaValidationErrors, 2)
1597+
assert.Len(t, errors[0].SchemaValidationErrors, 1)
15981598
}
15991599

16001600
func TestValidateBody_IgnoreXmlValidation(t *testing.T) {
@@ -1697,7 +1697,7 @@ paths:
16971697

16981698
assert.False(t, valid)
16991699
assert.Len(t, errors, 1)
1700-
assert.Equal(t, errors[0].Message, "xml response is malformed")
1700+
assert.Equal(t, "xml example is malformed", errors[0].Message)
17011701
}
17021702

17031703
type errorReader struct{}

schema_validation/validate_schema.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
22
// SPDX-License-Identifier: MIT
3-
43
package schema_validation
54

65
import (

0 commit comments

Comments
 (0)