diff --git a/ALLOF.md b/ALLOF.md index 576c4485..449afdab 100644 --- a/ALLOF.md +++ b/ALLOF.md @@ -20,10 +20,10 @@ diff --side-by-side data/allof/simple.yaml data/allof/revision.yaml In order to reduce such false-positives, oasdiff supports the ability to replace allOf by a merged equivalent before comparing the specs, like this: ``` -oasdiff breaking data/allof/simple.yaml data/allof/revision.yaml --flatten +oasdiff breaking data/allof/simple.yaml data/allof/revision.yaml --flatten-allof ``` In this case no breaking changes are reported, correctly. -The `--flatten` flag is also supported with `diff` and `changelog`. +The `--flatten-allof` flag is also supported with `diff`, `changelog`, `delta` and `summary`. In order to see how oasdiff merges allOf, you can use the dedicated `flatten` command: ``` diff --git a/BREAKING-CHANGES-EXAMPLES.md b/BREAKING-CHANGES-EXAMPLES.md index 6be34dc1..1c86ad48 100644 --- a/BREAKING-CHANGES-EXAMPLES.md +++ b/BREAKING-CHANGES-EXAMPLES.md @@ -1,11 +1,11 @@ # Examples of Breaking and Non-Breaking Changes These examples are automatically generated from unit tests. ## Examples of breaking changes -[adding 'allOf' subschema to the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L718) +[adding 'allOf' subschema to the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L717) [adding a new required property in request body is breaking](checker/checker_breaking_property_test.go?plain=1#L353) -[adding a pattern to a schema is breaking for recursive properties](checker/checker_breaking_test.go?plain=1#L496) -[adding a pattern to a schema is breaking](checker/checker_breaking_test.go?plain=1#L479) -[adding a required request body is breaking](checker/checker_breaking_test.go?plain=1#L67) +[adding a pattern to a schema is breaking for recursive properties](checker/checker_breaking_test.go?plain=1#L495) +[adding a pattern to a schema is breaking](checker/checker_breaking_test.go?plain=1#L478) +[adding a required request body is breaking](checker/checker_breaking_test.go?plain=1#L66) [changing a request body to enum is breaking](checker/checker_breaking_property_test.go?plain=1#L123) [changing a request body type and changing it to enum simultaneously is breaking](checker/checker_breaking_property_test.go?plain=1#L153) [changing a request property to not nullable is breaking](checker/checker_breaking_property_test.go?plain=1#L233) @@ -14,7 +14,7 @@ These examples are automatically generated from unit tests. [changing a response property to nullable is breaking](checker/checker_breaking_property_test.go?plain=1#L249) [changing a response property to optional under AllOf, AnyOf or OneOf is breaking](checker/checker_breaking_property_test.go?plain=1#L644) [changing an embedded response property to nullable is breaking](checker/checker_breaking_property_test.go?plain=1#L265) -[changing an existing header param from optional to required is breaking](checker/checker_breaking_test.go?plain=1#L196) +[changing an existing header param from optional to required is breaking](checker/checker_breaking_test.go?plain=1#L195) [changing an existing header param to enum is breaking](checker/checker_breaking_property_test.go?plain=1#L185) [changing an existing property in request body anyOf to required is breaking](checker/checker_breaking_property_test.go?plain=1#L612) [changing an existing property in request body items to required is breaking](checker/checker_breaking_property_test.go?plain=1#L596) @@ -24,9 +24,9 @@ These examples are automatically generated from unit tests. [changing an existing property in request header to required is breaking](checker/checker_breaking_property_test.go?plain=1#L57) [changing an existing property in response body to optional is breaking](checker/checker_breaking_property_test.go?plain=1#L107) [changing an existing property under another property in request body to required is breaking](checker/checker_breaking_property_test.go?plain=1#L628) -[changing an existing request body from optional to required is breaking](checker/checker_breaking_test.go?plain=1#L85) +[changing an existing request body from optional to required is breaking](checker/checker_breaking_test.go?plain=1#L84) [changing an existing required property in response body to not-write-only is breaking](checker/checker_breaking_property_test.go?plain=1#L561) -[changing an existing response header from required to optional is breaking](checker/checker_breaking_test.go?plain=1#L220) +[changing an existing response header from required to optional is breaking](checker/checker_breaking_test.go?plain=1#L219) [changing max length in request from nil to any value is breaking](checker/checker_breaking_min_max_test.go?plain=1#L110) [changing max length in response from any value to nil is breaking](checker/checker_breaking_min_max_test.go?plain=1#L160) [changing request's body schema type from number to integer is breaking](checker/checker_breaking_request_type_changed_test.go?plain=1#L51) @@ -37,52 +37,52 @@ These examples are automatically generated from unit tests. [changing response's body schema type from number to string is breaking](checker/checker_breaking_response_type_changed_test.go?plain=1#L31) [changing response's body schema type from string to number is breaking](checker/checker_breaking_response_type_changed_test.go?plain=1#L11) [changing response's embedded property schema type from string/none to integer/int32 is breaking](checker/checker_breaking_response_type_changed_test.go?plain=1#L108) -[deleting a media-type from response is breaking](checker/checker_breaking_test.go?plain=1#L448) +[deleting a media-type from response is breaking](checker/checker_breaking_test.go?plain=1#L447) [deleting a non-required non-write-only property in response body is breaking with warning](checker/checker_breaking_property_test.go?plain=1#L512) -[deleting a path is breaking](checker/checker_breaking_test.go?plain=1#L43) +[deleting a path is breaking](checker/checker_breaking_test.go?plain=1#L42) [deleting a path with some operations having sunset date in the future is breaking](checker/checker_deprecation_test.go?plain=1#L281) [deleting a required property in request is breaking with warn](checker/checker_breaking_property_test.go?plain=1#L369) [deleting a required property in response body is breaking](checker/checker_breaking_property_test.go?plain=1#L421) [deleting a required property under AllOf in response body is breaking](checker/checker_breaking_property_test.go?plain=1#L451) [deleting an embedded optional property in request is breaking with warn](checker/checker_breaking_property_test.go?plain=1#L386) -[deleting an enum value is breaking](checker/checker_breaking_test.go?plain=1#L107) +[deleting an enum value is breaking](checker/checker_breaking_test.go?plain=1#L106) [deleting an operation before sunset date is breaking](checker/checker_deprecation_test.go?plain=1#L33) -[deleting an operation is breaking](checker/checker_breaking_test.go?plain=1#L51) +[deleting an operation is breaking](checker/checker_breaking_test.go?plain=1#L50) [deleting an operation without sunset date is breaking](checker/checker_deprecation_test.go?plain=1#L51) [deleting sunset header for a deprecated endpoint is breaking](checker/checker_deprecation_test.go?plain=1#L299) [deprecating an operation with a deprecation policy and sunset date before required deprecation period is breaking](checker/checker_deprecation_test.go?plain=1#L223) [deprecating an operation with a deprecation policy but without specifying sunset date is breaking](checker/checker_deprecation_test.go?plain=1#L84) [increasing max length in response is breaking](checker/checker_breaking_min_max_test.go?plain=1#L93) [increasing min items in request is breaking](checker/checker_breaking_min_max_test.go?plain=1#L236) -[modifying a pattern in a schema is breaking](checker/checker_breaking_test.go?plain=1#L513) -[modifying a pattern in request parameter is breaking](checker/checker_breaking_test.go?plain=1#L530) -[modifying the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L561) +[modifying a pattern in a schema is breaking](checker/checker_breaking_test.go?plain=1#L512) +[modifying a pattern in request parameter is breaking](checker/checker_breaking_test.go?plain=1#L529) +[modifying the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L560) [new header, query and cookie required request default param is breaking](checker/check-new-request-non-path-default-parameter_test.go?plain=1#L12) -[new required header param is breaking](checker/checker_breaking_test.go?plain=1#L179) -[new required path param is breaking](checker/checker_breaking_test.go?plain=1#L162) +[new required header param is breaking](checker/checker_breaking_test.go?plain=1#L178) +[new required path param is breaking](checker/checker_breaking_test.go?plain=1#L161) [new required property in request header is breaking](checker/checker_breaking_property_test.go?plain=1#L18) [reducing max in request is breaking](checker/checker_breaking_min_max_test.go?plain=1#L264) [reducing max length in request is breaking](checker/checker_breaking_min_max_test.go?plain=1#L12) [reducing min items in response is breaking](checker/checker_breaking_min_max_test.go?plain=1#L220) [reducing min length in response is breaking](checker/checker_breaking_min_max_test.go?plain=1#L62) -[removing 'allOf' subschema from the request body or request body property is breaking with warn](checker/checker_breaking_test.go?plain=1#L740) -[removing 'anyOf' schema from the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L675) -[removing 'oneOf' schema from the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L697) -[removing a media type from request body is breaking](checker/checker_breaking_test.go?plain=1#L659) +[removing 'allOf' subschema from the request body or request body property is breaking with warn](checker/checker_breaking_test.go?plain=1#L739) +[removing 'anyOf' schema from the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L674) +[removing 'oneOf' schema from the request body or request body property is breaking](checker/checker_breaking_test.go?plain=1#L696) +[removing a media type from request body is breaking](checker/checker_breaking_test.go?plain=1#L658) [removing a success status is breaking](checker/check-response-status-updated_test.go?plain=1#L87) -[removing an existing optional response header is breaking as warn](checker/checker_breaking_test.go?plain=1#L428) -[removing an existing required response header is breaking as error](checker/checker_breaking_test.go?plain=1#L237) -[removing an existing response with non-successful status is breaking (optional)](checker/checker_breaking_test.go?plain=1#L276) -[removing an existing response with successful status is breaking](checker/checker_breaking_test.go?plain=1#L257) -[removing an schema object from components is breaking (optional)](checker/checker_breaking_test.go?plain=1#L634) -[removing the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L597) +[removing an existing optional response header is breaking as warn](checker/checker_breaking_test.go?plain=1#L427) +[removing an existing required response header is breaking as error](checker/checker_breaking_test.go?plain=1#L236) +[removing an existing response with non-successful status is breaking (optional)](checker/checker_breaking_test.go?plain=1#L275) +[removing an existing response with successful status is breaking](checker/checker_breaking_test.go?plain=1#L256) +[removing an schema object from components is breaking (optional)](checker/checker_breaking_test.go?plain=1#L633) +[removing the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L596) [removing the path without a deprecation policy and without specifying sunset date is breaking if some APIs are not alpha stability level](checker/checker_deprecation_test.go?plain=1#L138) [removing the path without a deprecation policy and without specifying sunset date is breaking if some APIs are not draft stability level](checker/checker_deprecation_test.go?plain=1#L194) -[removing/updating a property enum in response is breaking (optional)](checker/checker_breaking_test.go?plain=1#L339) -[removing/updating a tag is breaking (optional)](checker/checker_breaking_test.go?plain=1#L357) -[removing/updating an enum in request body is breaking (optional)](checker/checker_breaking_test.go?plain=1#L316) -[removing/updating an operation id is breaking (optional)](checker/checker_breaking_test.go?plain=1#L295) -[setting the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L579) +[removing/updating a property enum in response is breaking (optional)](checker/checker_breaking_test.go?plain=1#L338) +[removing/updating a tag is breaking (optional)](checker/checker_breaking_test.go?plain=1#L356) +[removing/updating an enum in request body is breaking (optional)](checker/checker_breaking_test.go?plain=1#L315) +[removing/updating an operation id is breaking (optional)](checker/checker_breaking_test.go?plain=1#L294) +[setting the default value of an optional request parameter is breaking](checker/checker_breaking_test.go?plain=1#L578) ## Examples of non-breaking changes [adding a media-type to response is not breaking](checker/checker_not_breaking_test.go?plain=1#L185) @@ -115,7 +115,7 @@ These examples are automatically generated from unit tests. [changing response's body schema type from number/none to integer/int32 is not breaking](checker/checker_breaking_response_type_changed_test.go?plain=1#L89) [changing servers is not breaking](checker/checker_not_breaking_test.go?plain=1#L253) [deleting a path after sunset date of all contained operations is not breaking](checker/checker_deprecation_test.go?plain=1#L266) -[deleting a pattern from a schema is not breaking](checker/checker_breaking_test.go?plain=1#L465) +[deleting a pattern from a schema is not breaking](checker/checker_breaking_test.go?plain=1#L464) [deleting a required write-only property in response body is not breaking](checker/checker_breaking_property_test.go?plain=1#L495) [deleting a tag is not breaking](checker/checker_not_breaking_test.go?plain=1#L71) [deleting an operation after sunset date is not breaking](checker/checker_deprecation_test.go?plain=1#L69) @@ -127,8 +127,8 @@ These examples are automatically generated from unit tests. [deprecating an operation without a deprecation policy and without specifying sunset date is not breaking](checker/checker_deprecation_test.go?plain=1#L104) [increasing max length in request is not breaking](checker/checker_breaking_min_max_test.go?plain=1#L76) [increasing min items in response is not breaking](checker/checker_breaking_min_max_test.go?plain=1#L250) -[modifying a pattern to ".*" in a schema is not breaking](checker/checker_breaking_test.go?plain=1#L547) -[modifying the default value of a required request parameter is not breaking](checker/checker_breaking_test.go?plain=1#L615) +[modifying a pattern to ".*" in a schema is not breaking](checker/checker_breaking_test.go?plain=1#L546) +[modifying the default value of a required request parameter is not breaking](checker/checker_breaking_test.go?plain=1#L614) [new optional header param is not breaking](checker/checker_not_breaking_test.go?plain=1#L119) [new optional property in request header is not breaking](checker/checker_breaking_property_test.go?plain=1#L39) [new required response header param is not breaking](checker/checker_not_breaking_test.go?plain=1#L153) @@ -137,11 +137,11 @@ These examples are automatically generated from unit tests. [reducing max length in response is not breaking](checker/checker_breaking_min_max_test.go?plain=1#L31) [reducing min items in request is not breaking](checker/checker_breaking_min_max_test.go?plain=1#L206) [reducing min length in request is not breaking](checker/checker_breaking_min_max_test.go?plain=1#L48) -[removing an existing response with error status is not breaking](checker/checker_breaking_test.go?plain=1#L412) -[removing an existing response with unparseable status is not breaking](checker/checker_breaking_test.go?plain=1#L396) +[removing an existing response with error status is not breaking](checker/checker_breaking_test.go?plain=1#L411) +[removing an existing response with unparseable status is not breaking](checker/checker_breaking_test.go?plain=1#L395) [removing the path without a deprecation policy and without specifying sunset date is not breaking for alpha level](checker/checker_deprecation_test.go?plain=1#L119) [removing the path without a deprecation policy and without specifying sunset date is not breaking for draft level](checker/checker_deprecation_test.go?plain=1#L175) -[renaming a path parameter is not breaking](checker/checker_breaking_test.go?plain=1#L142) +[renaming a path parameter is not breaking](checker/checker_breaking_test.go?plain=1#L141) ## Examples of info-level changes for changelog [adding 'allOf' subschema to the request body or request body property](checker/check-request-property-all-of-updated_test.go?plain=1#L12) diff --git a/COMMON-PARAMS.md b/COMMON-PARAMS.md new file mode 100644 index 00000000..15294779 --- /dev/null +++ b/COMMON-PARAMS.md @@ -0,0 +1,32 @@ +## Common Parameters + +### Common Parameters Definition +Parameters shared by all operations of a path can be defined on the path level instead of the operation level. +Path-level parameters are inherited by all operations of that path. +A typical use case are the GET/PUT/PATCH/DELETE operations that manipulate a resource accessed via a path parameter. + +### There are two ways to handle Common Parameters in oasdiff +1. By default, oasdiff compares path parameters and operation parameters separately. +2. The `--flatten-params` merges common parameters from the path level into the operation level before running the diff. + +For example, this command outputs two breaking changes: +``` +oasdiff changelog data/common-params/params_in_path.yaml data/common-params/params_in_op.yaml +``` +Output: +``` +2 changes: 2 error, 0 warning, 0 info +error [new-request-path-parameter] at data/common-params/params_in_op.yaml + in API GET /admin/v0/abc/{id} + added the new path request parameter 'id' + +error [new-required-request-parameter] at data/common-params/params_in_op.yaml + in API GET /admin/v0/abc/{id} + added the new required 'header' request parameter 'tenant-id' +``` + + +Adding the `--flatten-params` eliminates the errors: +``` +oasdiff changelog data/common-params/params_in_path.yaml data/common-params/params_in_op.yaml --flatten-params +``` diff --git a/README.md b/README.md index f07de405..bc068f4b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ docker run --rm -t tufin/oasdiff changelog https://raw.githubusercontent.com/Tuf - [API deprecation](API-DEPRECATION.md) - [Multiple versions of the same endpoint](MATCHING-ENDPOINTS.md) - [Merge allOf schemas](ALLOF.md) +- [Merge common parameters](COMMON-PARAMS.md) - [Path prefix modification](#path-prefix-modification) - [Path parameter renaming](#path-parameter-renaming) - [Excluding certain kinds of changes](#excluding-specific-kinds-of-changes) diff --git a/checker/checker_breaking_min_max_test.go b/checker/checker_breaking_min_max_test.go index fca3f7dc..6227b161 100644 --- a/checker/checker_breaking_min_max_test.go +++ b/checker/checker_breaking_min_max_test.go @@ -20,7 +20,7 @@ func TestBreaking_RequestMaxLengthSmaller(t *testing.T) { maxLengthTo := uint64(11) s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -39,7 +39,7 @@ func TestBreaking_ResponseMaxLengthSmaller(t *testing.T) { maxLengthTo := uint64(11) s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -53,7 +53,7 @@ func TestBreaking_RequestMinLengthSmaller(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinLength = uint64(13) s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinLength = uint64(11) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -67,7 +67,7 @@ func TestBreaking_MinLengthSmaller(t *testing.T) { s1.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinLength = uint64(13) s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinLength = uint64(11) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Equal(t, checker.ResponseBodyMinLengthDecreasedId, errs[0].GetId()) @@ -84,7 +84,7 @@ func TestBreaking_RequestMaxLengthGreater(t *testing.T) { maxLengthTo := uint64(14) s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -101,7 +101,7 @@ func TestBreaking_ResponseMaxLengthGreater(t *testing.T) { maxLengthTo := uint64(14) s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -117,7 +117,7 @@ func TestBreaking_MaxLengthFromNil(t *testing.T) { maxLengthTo := uint64(14) s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -135,7 +135,7 @@ func TestBreaking_ResponseMaxLengthFromNil(t *testing.T) { maxLengthTo := uint64(14) s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = &maxLengthTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -151,7 +151,7 @@ func TestBreaking_RequestMaxLengthToNil(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = nil - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -167,7 +167,7 @@ func TestBreaking_ResponseMaxLengthToNil(t *testing.T) { s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = nil - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -183,7 +183,7 @@ func TestBreaking_MaxLengthBothNil(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = nil s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MaxLength = nil - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -197,7 +197,7 @@ func TestBreaking_ResponseMaxLengthBothNil(t *testing.T) { s1.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = nil s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MaxLength = nil - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -211,7 +211,7 @@ func TestBreaking_RequestMinItemsSmaller(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinItems = 13 s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinItems = 11 - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -225,7 +225,7 @@ func TestBreaking_ResponseMinItemsSmaller(t *testing.T) { s1.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinItems = 13 s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinItems = 11 - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -241,7 +241,7 @@ func TestBreaking_RequeatMinItemsGreater(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinItems = 13 s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.MinItems = 14 - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -255,7 +255,7 @@ func TestBreaking_ResponseMinItemsGreater(t *testing.T) { s1.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinItems = 13 s2.Spec.Paths.Value(securityScorePath).Get.Responses.Value("201").Value.Content["application/xml"].Schema.Value.MinItems = 14 - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -272,7 +272,7 @@ func TestBreaking_MaxSmaller(t *testing.T) { maxTo := float64(11) s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInPath, "domain").Schema.Value.Max = &maxTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -289,7 +289,7 @@ func TestBreaking_MaxSmallerInResponse(t *testing.T) { maxTo := float64(11) s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Schema.Value.Max = &maxTo - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) diff --git a/checker/checker_breaking_property_test.go b/checker/checker_breaking_property_test.go index cf784a1b..48c5cec9 100644 --- a/checker/checker_breaking_property_test.go +++ b/checker/checker_breaking_property_test.go @@ -28,7 +28,7 @@ func TestBreaking_NewRequiredProperty(t *testing.T) { } s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Required = []string{"courseId"} - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -48,7 +48,7 @@ func TestBreaking_NewNonRequiredProperty(t *testing.T) { }, } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -72,7 +72,7 @@ func TestBreaking_PropertyRequiredEnabled(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Properties["courseId"] = &sr s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Required = []string{"courseId"} - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -98,7 +98,7 @@ func TestBreaking_PropertyRequiredDisabled(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Properties["courseId"] = &sr s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Required = []string{} - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) diff --git a/checker/checker_breaking_test.go b/checker/checker_breaking_test.go index 43dccef3..00c4291f 100644 --- a/checker/checker_breaking_test.go +++ b/checker/checker_breaking_test.go @@ -18,19 +18,18 @@ const ( installCommandPath = "/api/{domain}/{project}/install-command" ) -func l(t *testing.T, v int) load.SpecInfo { +func l(t *testing.T, v int) *load.SpecInfo { t.Helper() - loader := openapi3.NewLoader() - oas, err := loader.LoadFromFile(fmt.Sprintf("../data/openapi-test%d.yaml", v)) + specInfo, err := load.NewSpecInfo(openapi3.NewLoader(), load.NewSource(fmt.Sprintf("../data/openapi-test%d.yaml", v))) require.NoError(t, err) - return load.SpecInfo{Spec: oas, Url: fmt.Sprintf("../data/openapi-test%d.yaml", v)} + return specInfo } func d(t *testing.T, config *diff.Config, v1, v2 int) checker.Changes { t.Helper() l1 := l(t, v1) l2 := l(t, v2) - d, osm, err := diff.GetWithOperationsSourcesMap(config, &l1, &l2) + d, osm, err := diff.GetWithOperationsSourcesMap(config, l1, l2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) return errs @@ -55,7 +54,7 @@ func TestBreaking_DeletedOp(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Put = openapi3.NewOperation() - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -73,7 +72,7 @@ func TestBreaking_AddingRequiredRequestBody(t *testing.T) { Value: openapi3.NewRequestBody().WithRequired(true), } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -95,7 +94,7 @@ func TestBreaking_RequestBodyRequiredEnabled(t *testing.T) { Value: openapi3.NewRequestBody().WithRequired(true), } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -167,7 +166,7 @@ func TestBreaking_NewPathParam(t *testing.T) { deleteParam(s1.Spec.Paths.Value(installCommandPath).Get, openapi3.ParameterInPath, "project") // note: path params are always required - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) @@ -184,7 +183,7 @@ func TestBreaking_NewRequiredHeaderParam(t *testing.T) { deleteParam(s1.Spec.Paths.Value(installCommandPath).Get, openapi3.ParameterInHeader, "network-policies") s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -201,7 +200,7 @@ func TestBreaking_HeaderParamRequiredEnabled(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = false s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Len(t, errs, 1) @@ -225,7 +224,7 @@ func TestBreaking_ResponseHeaderParamRequiredDisabled(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Required = true s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Required = false - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.NotEmpty(t, errs) @@ -242,7 +241,7 @@ func TestBreaking_ResponseHeaderRemoved(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Required = true delete(s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers, "X-RateLimit-Limit") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) for _, err := range errs { @@ -261,7 +260,7 @@ func TestBreaking_ResponseSuccessStatusUpdated(t *testing.T) { delete(s2.Spec.Paths.Value(securityScorePath).Get.Responses.Map(), "200") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) for _, err := range errs { @@ -280,7 +279,7 @@ func TestBreaking_ResponseNonSuccessStatusUpdated(t *testing.T) { delete(s2.Spec.Paths.Value(securityScorePath).Get.Responses.Map(), "400") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetChecks(utils.StringList{checker.ResponseNonSuccessStatusRemovedId}), d, osm) for _, err := range errs { @@ -299,7 +298,7 @@ func TestBreaking_OperationIdRemoved(t *testing.T) { s2.Spec.Paths.Value(securityScorePath).Get.OperationID = "newOperationId" - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetChecks(utils.StringList{checker.APIOperationIdRemovedId}), d, osm) @@ -341,7 +340,7 @@ func TestBreaking_ResponsePropertyEnumRemoved(t *testing.T) { s1 := l(t, 704) s2 := l(t, 703) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetChecks(utils.StringList{checker.ResponsePropertyEnumValueRemovedId}), d, osm) @@ -361,7 +360,7 @@ func TestBreaking_TagRemoved(t *testing.T) { s2.Spec.Paths.Value(securityScorePath).Get.Tags[0] = "newTag" - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetChecks(utils.StringList{checker.APITagRemovedId}), d, osm) for _, err := range errs { @@ -400,7 +399,7 @@ func TestBreaking_ResponseUnparseableStatusRemoved(t *testing.T) { delete(s2.Spec.Paths.Value(installCommandPath).Get.Responses.Map(), "default") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) for _, err := range errs { @@ -416,7 +415,7 @@ func TestBreaking_ResponseErrorStatusRemoved(t *testing.T) { delete(s2.Spec.Paths.Value(securityScorePath).Get.Responses.Map(), "400") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) for _, err := range errs { @@ -433,7 +432,7 @@ func TestBreaking_OptionalResponseHeaderRemoved(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Required = false delete(s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers, "X-RateLimit-Limit") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) for _, err := range errs { @@ -568,7 +567,7 @@ func TestBreaking_ModifyRequiredOptionalParamDefaultValue(t *testing.T) { // By default, OpenAPI treats all request parameters as optional - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Len(t, errs, 1) @@ -586,7 +585,7 @@ func TestBreaking_SettingOptionalParamDefaultValue(t *testing.T) { // By default, OpenAPI treats all request parameters as optional - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Len(t, errs, 1) @@ -604,7 +603,7 @@ func TestBreaking_RemovingOptionalParamDefaultValue(t *testing.T) { // By default, OpenAPI treats all request parameters as optional - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Len(t, errs, 1) @@ -625,7 +624,7 @@ func TestBreaking_ModifyRequiredRequiredParamDefaultValue(t *testing.T) { paramRevision.Required = true paramRevision.Schema.Value.Default = "Y" - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -642,7 +641,7 @@ func TestBreaking_SchemaRemoved(t *testing.T) { delete(s2.Spec.Components.Schemas, k) } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) checks := checker.GetChecks(utils.StringList{checker.APISchemasRemovedId}) errs := checker.CheckBackwardCompatibility(checks, d, osm) diff --git a/checker/checker_deprecation_test.go b/checker/checker_deprecation_test.go index 09927615..23fd34f4 100644 --- a/checker/checker_deprecation_test.go +++ b/checker/checker_deprecation_test.go @@ -15,7 +15,7 @@ import ( ) func open(file string) (*load.SpecInfo, error) { - return load.LoadSpecInfo(openapi3.NewLoader(), load.NewSource(file)) + return load.NewSpecInfo(openapi3.NewLoader(), load.NewSource(file)) } func getDeprecationFile(file string) string { diff --git a/checker/checker_not_breaking_test.go b/checker/checker_not_breaking_test.go index c1876abe..b63d9734 100644 --- a/checker/checker_not_breaking_test.go +++ b/checker/checker_not_breaking_test.go @@ -29,7 +29,7 @@ func TestBreaking_Same(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -44,7 +44,7 @@ func TestBreaking_AddingOptionalRequestBody(t *testing.T) { Value: openapi3.NewRequestBody().WithRequired(false), } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -63,7 +63,7 @@ func TestBreaking_RequestBodyRequiredDisabled(t *testing.T) { Value: openapi3.NewRequestBody().WithRequired(false), } - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.RequestBodyBecameOptionalId) } @@ -124,7 +124,7 @@ func TestBreaking_NewOptionalHeaderParam(t *testing.T) { deleteParam(s1.Spec.Paths.Value(installCommandPath).Get, openapi3.ParameterInHeader, "network-policies") s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = false - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -138,7 +138,7 @@ func TestBreaking_HeaderParamRequiredDisabled(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = true s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = false - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) changes := checker.CheckBackwardCompatibilityUntilLevel(checker.GetDefaultChecks(), d, osm, checker.INFO) require.NotEmpty(t, changes) @@ -158,7 +158,7 @@ func TestBreaking_NewRequiredResponseHeader(t *testing.T) { deleteResponseHeader(s1.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value, "X-RateLimit-Limit") s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Required = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -204,7 +204,7 @@ func TestBreaking_DeprecatedOperation(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Deprecated = true s2.Spec.Paths.Value(installCommandPath).Get.Extensions = map[string]interface{}{diff.SunsetExtension: toJson(t, civil.DateOf(time.Now()).AddDays(180).String())} - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibilityUntilLevel(checker.GetDefaultChecks(), d, osm, checker.INFO) require.Len(t, errs, 1) @@ -218,7 +218,7 @@ func TestBreaking_DeprecatedParameter(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Deprecated = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -231,7 +231,7 @@ func TestBreaking_DeprecatedHeader(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Responses.Value("default").Value.Headers["X-RateLimit-Limit"].Value.Deprecated = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -244,7 +244,7 @@ func TestBreaking_DeprecatedSchema(t *testing.T) { s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Schema.Value.Deprecated = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Empty(t, errs) @@ -270,7 +270,7 @@ func TestBreaking_TagAdded(t *testing.T) { s2 := l(t, 1) s2.Spec.Paths.Value(securityScorePath).Get.Tags = append(s2.Spec.Paths.Value(securityScorePath).Get.Tags, "newTag") - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APITagAddedId) } @@ -282,7 +282,7 @@ func TestBreaking_OperationIdAdded(t *testing.T) { s1.Spec.Paths.Value(securityScorePath).Get.OperationID = "" - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APIOperationIdAddId) } diff --git a/checker/checker_request_parameter_required_value_updated_test.go b/checker/checker_request_parameter_required_value_updated_test.go index dc059230..aeed9927 100644 --- a/checker/checker_request_parameter_required_value_updated_test.go +++ b/checker/checker_request_parameter_required_value_updated_test.go @@ -18,7 +18,7 @@ func TestBreaking_HeaderParamBecameRequired(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = false s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = true - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(singleCheckConfig(checker.RequestParameterRequiredValueUpdatedCheck), d, osm) require.Len(t, errs, 1) @@ -40,7 +40,7 @@ func TestBreaking_HeaderParamBecameOptional(t *testing.T) { s1.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = true s2.Spec.Paths.Value(installCommandPath).Get.Parameters.GetByInAndName(openapi3.ParameterInHeader, "network-policies").Required = false - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibilityUntilLevel(singleCheckConfig(checker.RequestParameterRequiredValueUpdatedCheck), d, osm, checker.INFO) require.Len(t, errs, 1) diff --git a/checker/ignore_test.go b/checker/ignore_test.go index 4c80fe46..4f01850a 100644 --- a/checker/ignore_test.go +++ b/checker/ignore_test.go @@ -13,7 +13,7 @@ func TestIgnore(t *testing.T) { s1 := l(t, 1) s2 := l(t, 3) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Equal(t, 6, len(errs)) @@ -27,7 +27,7 @@ func TestIgnoreSubpath(t *testing.T) { s1 := l(t, 6) s2 := l(t, 7) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Equal(t, 3, len(errs)) @@ -41,7 +41,7 @@ func TestIgnoreOnlyIncludedSubpaths(t *testing.T) { s1 := l(t, 8) s2 := l(t, 7) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetDefaultChecks(), d, osm) require.Equal(t, 2, len(errs)) // detect new and newest were deleted @@ -58,7 +58,7 @@ func TestIgnoreComponent(t *testing.T) { s1 := l(t, 1) s2 := l(t, 3) - d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), &s1, &s2) + d, osm, err := diff.GetWithOperationsSourcesMap(getConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(checker.GetChecks(utils.StringList{checker.APISchemasRemovedId}), d, osm) require.Equal(t, 8, len(errs)) diff --git a/data/common-params/no_params.yaml b/data/common-params/no_params.yaml new file mode 100644 index 00000000..6060d026 --- /dev/null +++ b/data/common-params/no_params.yaml @@ -0,0 +1,53 @@ +openapi: 3.0.0 +info: + title: Some API + version: 1.0.0 + description: Some desc + contact: + name: '#onetwothree' + url: 'https://test.com' +servers: + - description: local + url: 'http://localhost:8080' +tags: + - name: One + - name: Two +paths: + '/admin/v0/abc/{id}': + get: + summary: Get abc + tags: + - Two + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/abc' +components: + schemas: + abc: + title: abc + x-stoplight: + id: lzwt7se3t6ab2 + type: object + properties: + details: + type: string + + parameters: + tenant_id: + schema: + type: string + in: header + required: true + name: tenant-id + description: 'Tenant IDs' + id: + name: id + in: path + required: true + schema: + type: string + description: 'The ID' \ No newline at end of file diff --git a/data/common-params/params_in_op.yaml b/data/common-params/params_in_op.yaml new file mode 100644 index 00000000..2eaf502e --- /dev/null +++ b/data/common-params/params_in_op.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Some API + version: 1.0.0 + description: Some desc + contact: + name: '#onetwothree' + url: 'https://test.com' +servers: + - description: local + url: 'http://localhost:8080' +tags: + - name: One + - name: Two +paths: + '/admin/v0/abc/{id}': + get: + parameters: + - $ref: '#/components/parameters/tenant_id' + - $ref: '#/components/parameters/id' + summary: Get abc + tags: + - Two + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/abc' +components: + schemas: + abc: + title: abc + x-stoplight: + id: lzwt7se3t6ab2 + type: object + properties: + details: + type: string + + parameters: + tenant_id: + schema: + type: string + in: header + required: true + name: tenant-id + description: 'Tenant IDs' + id: + name: id + in: path + required: true + schema: + type: string + description: 'The ID' \ No newline at end of file diff --git a/data/common-params/params_in_path.yaml b/data/common-params/params_in_path.yaml new file mode 100644 index 00000000..2fd4779a --- /dev/null +++ b/data/common-params/params_in_path.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Some API + version: 1.0.0 + description: Some desc + contact: + name: '#onetwothree' + url: 'https://test.com' +servers: + - description: local + url: 'http://localhost:8080' +tags: + - name: One + - name: Two +paths: + '/admin/v0/abc/{id}': + parameters: + - $ref: '#/components/parameters/tenant_id' + - $ref: '#/components/parameters/id' + get: + summary: Get abc + tags: + - Two + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/abc' +components: + schemas: + abc: + title: abc + x-stoplight: + id: lzwt7se3t6ab2 + type: object + properties: + details: + type: string + + parameters: + tenant_id: + schema: + type: string + in: header + required: true + name: tenant-id + description: 'Tenant IDs' + id: + name: id + in: path + required: true + schema: + type: string + description: 'The ID' \ No newline at end of file diff --git a/diff/diff_common_params_test.go b/diff/diff_common_params_test.go new file mode 100644 index 00000000..3b304144 --- /dev/null +++ b/diff/diff_common_params_test.go @@ -0,0 +1,52 @@ +package diff_test + +import ( + "testing" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/require" + "github.com/tufin/oasdiff/diff" + "github.com/tufin/oasdiff/load" +) + +func TestDiff_CommonParamsDeleted(t *testing.T) { + loader := openapi3.NewLoader() + + s1, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/params_in_path.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + s2, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/no_params.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) + require.NoError(t, err) + require.NotEmpty(t, d.EndpointsDiff.Modified[diff.Endpoint{Method: "GET", Path: "/admin/v0/abc/{id}"}].ParametersDiff.Deleted) +} + +func TestDiff_CommonParamsMoved(t *testing.T) { + loader := openapi3.NewLoader() + + s1, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/params_in_path.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + s2, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/params_in_op.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) + require.NoError(t, err) + require.Empty(t, d) +} + +func TestDiff_CommonParamsAdded(t *testing.T) { + loader := openapi3.NewLoader() + + s1, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/no_params.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + s2, err := load.NewSpecInfo(loader, load.NewSource("../data/common-params/params_in_path.yaml"), load.WithFlattenParams()) + require.NoError(t, err) + + d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) + require.NoError(t, err) + require.NotEmpty(t, d.EndpointsDiff.Modified[diff.Endpoint{Method: "GET", Path: "/admin/v0/abc/{id}"}].ParametersDiff.Added) +} diff --git a/diff/diff_test.go b/diff/diff_test.go index 7fe7b8ee..32be3376 100644 --- a/diff/diff_test.go +++ b/diff/diff_test.go @@ -886,35 +886,19 @@ func TestDiff_DifferentComponentModifiedParam(t *testing.T) { } func TestDiff_DifferentComponentSameSchema(t *testing.T) { - loader := openapi3.NewLoader() - - s1, err := loader.LoadFromFile("../data/different_component_same_schema.yaml") + s1, err := load.NewSpecInfo(openapi3.NewLoader(), load.NewSource("../data/different_component_same_schema.yaml")) require.NoError(t, err) - d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), - &load.SpecInfo{ - Spec: s1, - }, - &load.SpecInfo{ - Spec: s1, - }) + d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s1) require.NoError(t, err) require.Empty(t, d) } func TestDiff_DifferentComponentSameHeader(t *testing.T) { - loader := openapi3.NewLoader() - - s1, err := loader.LoadFromFile("../data/different_component_same_header.yaml") + s1, err := load.NewSpecInfo(openapi3.NewLoader(), load.NewSource("../data/different_component_same_header.yaml")) require.NoError(t, err) - d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), - &load.SpecInfo{ - Spec: s1, - }, - &load.SpecInfo{ - Spec: s1, - }) + d, _, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s1) require.NoError(t, err) require.Empty(t, d) } diff --git a/diff/example_test.go b/diff/example_test.go index 9fa9daf0..b04eae8c 100644 --- a/diff/example_test.go +++ b/diff/example_test.go @@ -64,13 +64,13 @@ func ExampleGetPathsDiff() { loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true - s1, err := load.LoadSpecInfo(loader, load.NewSource("../data/openapi-test1.yaml")) + s1, err := load.NewSpecInfo(loader, load.NewSource("../data/openapi-test1.yaml")) if err != nil { fmt.Fprintf(os.Stderr, "failed to load spec with %v", err) return } - s2, err := load.LoadSpecInfo(loader, load.NewSource("../data/openapi-test3.yaml")) + s2, err := load.NewSpecInfo(loader, load.NewSource("../data/openapi-test3.yaml")) if err != nil { fmt.Fprintf(os.Stderr, "failed to load spec with %v", err) return diff --git a/flatten/allof/doc.go b/flatten/allof/doc.go new file mode 100644 index 00000000..ceed5019 --- /dev/null +++ b/flatten/allof/doc.go @@ -0,0 +1,5 @@ +/* +Package allof replaces allOf by a merged equivalent +This is helpful to improve breaking changes accuracy +*/ +package allof diff --git a/flatten/merge_allof.go b/flatten/allof/merge_allof.go similarity index 99% rename from flatten/merge_allof.go rename to flatten/allof/merge_allof.go index 6d20fda6..0bdac0fe 100644 --- a/flatten/merge_allof.go +++ b/flatten/allof/merge_allof.go @@ -1,4 +1,4 @@ -package flatten +package allof import ( "errors" diff --git a/flatten/merge_allof_spec.go b/flatten/allof/merge_allof_spec.go similarity index 99% rename from flatten/merge_allof_spec.go rename to flatten/allof/merge_allof_spec.go index fd1a357c..70b3fea9 100644 --- a/flatten/merge_allof_spec.go +++ b/flatten/allof/merge_allof_spec.go @@ -1,4 +1,4 @@ -package flatten +package allof import ( "github.com/getkin/kin-openapi/openapi3" diff --git a/flatten/merge_allof_spec_test.go b/flatten/allof/merge_allof_spec_test.go similarity index 78% rename from flatten/merge_allof_spec_test.go rename to flatten/allof/merge_allof_spec_test.go index 2d984204..4b0bd787 100644 --- a/flatten/merge_allof_spec_test.go +++ b/flatten/allof/merge_allof_spec_test.go @@ -1,18 +1,17 @@ -package flatten_test +package allof_test import ( "testing" "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/require" - "github.com/tufin/oasdiff/flatten" + "github.com/tufin/oasdiff/load" ) func Test_MergeSpecOK(t *testing.T) { - loader := openapi3.NewLoader() - s, err := loader.LoadFromFile("../data/allof/simple.yaml") + spec, err := load.NewSpecInfo(openapi3.NewLoader(), load.NewSource("../../data/allof/simple.yaml"), load.WithFlattenAllOf()) require.NoError(t, err) - merged, err := flatten.MergeSpec(s) + merged := spec.Spec require.NoError(t, err) require.Equal(t, "string", merged.Components.Schemas["GroupView"].Value.Properties["created"].Value.Type) require.Equal(t, "string", merged.Components.Parameters["groupId"].Value.Schema.Value.Properties["prop1"].Value.Type) @@ -24,10 +23,6 @@ func Test_MergeSpecOK(t *testing.T) { } func Test_MergeSpecInvalid(t *testing.T) { - loader := openapi3.NewLoader() - s, err := loader.LoadFromFile("../data/allof/invalid.yaml") - require.NoError(t, err) - - _, err = flatten.MergeSpec(s) + _, err := load.NewSpecInfo(openapi3.NewLoader(), load.NewSource("../../data/allof/invalid.yaml"), load.WithFlattenAllOf()) require.EqualError(t, err, "unable to resolve Type conflict: all Type values must be identical") } diff --git a/flatten/merge_allof_test.go b/flatten/allof/merge_allof_test.go similarity index 92% rename from flatten/merge_allof_test.go rename to flatten/allof/merge_allof_test.go index 2eb45e1c..41cf2248 100644 --- a/flatten/merge_allof_test.go +++ b/flatten/allof/merge_allof_test.go @@ -1,10 +1,10 @@ -package flatten_test +package allof_test import ( "context" "testing" - "github.com/tufin/oasdiff/flatten" + "github.com/tufin/oasdiff/flatten/allof" "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/require" @@ -12,7 +12,7 @@ import ( // identical Default fields are merged successfully func TestMerge_Default(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Default: 10, @@ -22,7 +22,7 @@ func TestMerge_Default(t *testing.T) { require.NoError(t, err) require.Equal(t, 10, merged.Default) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -39,7 +39,7 @@ func TestMerge_Default(t *testing.T) { require.Nil(t, merged.AllOf) require.Equal(t, nil, merged.Default) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -57,7 +57,7 @@ func TestMerge_Default(t *testing.T) { require.Nil(t, merged.AllOf) require.Equal(t, 10, merged.Default) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -81,7 +81,7 @@ func TestMerge_Default(t *testing.T) { require.Nil(t, merged.AllOf) require.Equal(t, 10, merged.Default) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -108,7 +108,7 @@ func TestMerge_Default(t *testing.T) { // Conflicting Default values cannot be resolved func TestMerge_DefaultFailure(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -128,12 +128,12 @@ func TestMerge_DefaultFailure(t *testing.T) { }, }) - require.EqualError(t, err, flatten.DefaultErrorMessage) + require.EqualError(t, err, allof.DefaultErrorMessage) } // verify that if all ReadOnly fields are set to false, then the ReadOnly field in the merged schema is false. func TestMerge_ReadOnlyIsSetToFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -160,7 +160,7 @@ func TestMerge_ReadOnlyIsSetToFalse(t *testing.T) { // verify that if there exists a ReadOnly field which is true, then the ReadOnly field in the merged schema is true. func TestMerge_ReadOnlyIsSetToTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -185,7 +185,7 @@ func TestMerge_ReadOnlyIsSetToTrue(t *testing.T) { // verify that if all WriteOnly fields are set to false, then the WriteOnly field in the merged schema is false. func TestMerge_WriteOnlyIsSetToFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -210,7 +210,7 @@ func TestMerge_WriteOnlyIsSetToFalse(t *testing.T) { // verify that if there exists a WriteOnly field which is true, then the WriteOnly field in the merged schema is true. func TestMerge_WriteOnlyIsSetToTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -235,7 +235,7 @@ func TestMerge_WriteOnlyIsSetToTrue(t *testing.T) { // verify that if all nullable fields are set to true, then the nullable field in the merged schema is true. func TestMerge_NullableIsSetToTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -261,7 +261,7 @@ func TestMerge_NullableIsSetToTrue(t *testing.T) { // verify that if there exists a nullable field which is false, then the nullable field in the merged schema is false. func TestMerge_NullableIsSetToFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -286,7 +286,7 @@ func TestMerge_NullableIsSetToFalse(t *testing.T) { } func TestMerge_NestedAllOfInProperties(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Properties: openapi3.Schemas{ @@ -320,7 +320,7 @@ func TestMerge_NestedAllOfInProperties(t *testing.T) { } func TestMerge_NestedAllOfInNot(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Not: &openapi3.SchemaRef{ @@ -352,7 +352,7 @@ func TestMerge_NestedAllOfInNot(t *testing.T) { } func TestMerge_NestedAllOfInOneOf(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ OneOf: openapi3.SchemaRefs{ @@ -387,7 +387,7 @@ func TestMerge_NestedAllOfInOneOf(t *testing.T) { // AllOf is empty in base schema, but there is nested non-empty AllOf in base schema. func TestMerge_NestedAllOfInAnyOf(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AnyOf: openapi3.SchemaRefs{ @@ -422,7 +422,7 @@ func TestMerge_NestedAllOfInAnyOf(t *testing.T) { // identical numeric types are merged successfully func TestMerge_TypeNumeric(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -455,7 +455,7 @@ func TestMerge_TypeNumeric(t *testing.T) { require.NoError(t, err) require.Equal(t, "number", merged.Properties["prop1"].Value.Type) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -491,7 +491,7 @@ func TestMerge_TypeNumeric(t *testing.T) { // Conflicting numeric types are merged successfully func TestMerge_TypeNumericConflictResolved(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -527,7 +527,7 @@ func TestMerge_TypeNumericConflictResolved(t *testing.T) { // Conflicting types cannot be resolved func TestMerge_TypeFailure(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -558,12 +558,12 @@ func TestMerge_TypeFailure(t *testing.T) { }, }}) - require.EqualError(t, err, flatten.TypeErrorMessage) + require.EqualError(t, err, allof.TypeErrorMessage) } // if ExclusiveMax is true on the minimum Max value, then ExclusiveMax is true in the merged schema. func TestMerge_ExclusiveMaxIsTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -589,7 +589,7 @@ func TestMerge_ExclusiveMaxIsTrue(t *testing.T) { // if ExclusiveMax is false on the minimum Max value, then ExclusiveMax is false in the merged schema. func TestMerge_ExclusiveMaxIsFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -615,7 +615,7 @@ func TestMerge_ExclusiveMaxIsFalse(t *testing.T) { // if ExclusiveMin is false on the highest Min value, then ExclusiveMin is false in the merged schema. func TestMerge_ExclusiveMinIsFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -641,7 +641,7 @@ func TestMerge_ExclusiveMinIsFalse(t *testing.T) { // if ExclusiveMin is true on the highest Min value, then ExclusiveMin is true in the merged schema. func TestMerge_ExclusiveMinIsTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -667,7 +667,7 @@ func TestMerge_ExclusiveMinIsTrue(t *testing.T) { // merge multiple Not inside AllOf func TestMerge_Not(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -701,7 +701,7 @@ func TestMerge_Not(t *testing.T) { // merge multiple OneOf inside AllOf func TestMerge_OneOf(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -746,7 +746,7 @@ func TestMerge_OneOf(t *testing.T) { // merge multiple AnyOf inside AllOf func TestMerge_AnyOf(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -790,7 +790,7 @@ func TestMerge_AnyOf(t *testing.T) { // conflicting uniqueItems values are merged successfully func TestMerge_UniqueItemsTrue(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -814,7 +814,7 @@ func TestMerge_UniqueItemsTrue(t *testing.T) { // non-conflicting uniqueItems values are merged successfully func TestMerge_UniqueItemsFalse(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -838,7 +838,7 @@ func TestMerge_UniqueItemsFalse(t *testing.T) { // Item merge fails due to conflicting item types. func TestMerge_Items_Failure(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -878,12 +878,12 @@ func TestMerge_Items_Failure(t *testing.T) { }, }, }}) - require.EqualError(t, err, flatten.TypeErrorMessage) + require.EqualError(t, err, allof.TypeErrorMessage) } // items are merged successfully when there are no conflicts func TestMerge_Items(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -932,7 +932,7 @@ func TestMerge_Items(t *testing.T) { func TestMerge_MultipleOfContained(t *testing.T) { //todo - more tests - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -955,7 +955,7 @@ func TestMerge_MultipleOfContained(t *testing.T) { } func TestMerge_MultipleOfDecimal(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -978,7 +978,7 @@ func TestMerge_MultipleOfDecimal(t *testing.T) { } func TestMerge_EnumContained(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1002,7 +1002,7 @@ func TestMerge_EnumContained(t *testing.T) { // enum merge fails if the intersection of enum values is empty. func TestMerge_EnumNoIntersection(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1025,7 +1025,7 @@ func TestMerge_EnumNoIntersection(t *testing.T) { // Properties range is the most restrictive func TestMerge_RangeProperties(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1053,7 +1053,7 @@ func TestMerge_RangeProperties(t *testing.T) { // Items range is the most restrictive func TestMerge_RangeItems(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1079,7 +1079,7 @@ func TestMerge_RangeItems(t *testing.T) { } func TestMerge_Range(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1105,7 +1105,7 @@ func TestMerge_Range(t *testing.T) { } func TestMerge_MaxLength(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1128,7 +1128,7 @@ func TestMerge_MaxLength(t *testing.T) { } func TestMerge_MinLength(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1151,7 +1151,7 @@ func TestMerge_MinLength(t *testing.T) { } func TestMerge_Description(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Description: "desc0", @@ -1173,7 +1173,7 @@ func TestMerge_Description(t *testing.T) { require.NoError(t, err) require.Equal(t, "desc0", merged.Description) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1197,7 +1197,7 @@ func TestMerge_Description(t *testing.T) { // non-conflicting types are merged successfully func TestMerge_NonConflictingType(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1219,7 +1219,7 @@ func TestMerge_NonConflictingType(t *testing.T) { // schema cannot be merged if types are conflicting func TestMerge_FailsOnConflictingTypes(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1253,7 +1253,7 @@ func TestMerge_FailsOnConflictingTypes(t *testing.T) { } func TestMerge_Title(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Title: "base schema", @@ -1275,7 +1275,7 @@ func TestMerge_Title(t *testing.T) { require.NoError(t, err) require.Equal(t, "base schema", merged.Title) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1299,7 +1299,7 @@ func TestMerge_Title(t *testing.T) { // merge conflicting integer formats func TestMerge_FormatInteger(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1308,7 +1308,7 @@ func TestMerge_FormatInteger(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatInt32, + Format: allof.FormatInt32, }, }, }, @@ -1320,7 +1320,7 @@ func TestMerge_FormatInteger(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatInt64, + Format: allof.FormatInt64, }, }, }, @@ -1330,12 +1330,12 @@ func TestMerge_FormatInteger(t *testing.T) { }, }}) require.NoError(t, err) - require.Equal(t, flatten.FormatInt32, merged.Properties["prop1"].Value.Format) + require.Equal(t, allof.FormatInt32, merged.Properties["prop1"].Value.Format) } // merge conflicting float formats func TestMerge_FormatFloat(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1344,7 +1344,7 @@ func TestMerge_FormatFloat(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatFloat, + Format: allof.FormatFloat, }, }, }, @@ -1356,7 +1356,7 @@ func TestMerge_FormatFloat(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatDouble, + Format: allof.FormatDouble, }, }, }, @@ -1366,12 +1366,12 @@ func TestMerge_FormatFloat(t *testing.T) { }, }}) require.NoError(t, err) - require.Equal(t, flatten.FormatFloat, merged.Properties["prop1"].Value.Format) + require.Equal(t, allof.FormatFloat, merged.Properties["prop1"].Value.Format) } // merge conflicting integer and float formats func TestMerge_NumericFormat(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1380,7 +1380,7 @@ func TestMerge_NumericFormat(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatFloat, + Format: allof.FormatFloat, }, }, }, @@ -1392,7 +1392,7 @@ func TestMerge_NumericFormat(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatDouble, + Format: allof.FormatDouble, }, }, }, @@ -1404,7 +1404,7 @@ func TestMerge_NumericFormat(t *testing.T) { Properties: openapi3.Schemas{ "prop1": &openapi3.SchemaRef{ Value: &openapi3.Schema{ - Format: flatten.FormatInt32, + Format: allof.FormatInt32, }, }, }, @@ -1414,11 +1414,11 @@ func TestMerge_NumericFormat(t *testing.T) { }, }}) require.NoError(t, err) - require.Equal(t, flatten.FormatInt32, merged.Properties["prop1"].Value.Format) + require.Equal(t, allof.FormatInt32, merged.Properties["prop1"].Value.Format) } func TestMerge_Format(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1441,7 +1441,7 @@ func TestMerge_Format(t *testing.T) { } func TestMerge_Format_Failure(t *testing.T) { - _, err := flatten.Merge( + _, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1459,14 +1459,14 @@ func TestMerge_Format_Failure(t *testing.T) { }, }, }}) - require.EqualError(t, err, flatten.FormatErrorMessage) + require.EqualError(t, err, allof.FormatErrorMessage) } func TestMerge_EmptySchema(t *testing.T) { schema := openapi3.SchemaRef{ Value: &openapi3.Schema{}, } - merged, err := flatten.Merge(schema) + merged, err := allof.Merge(schema) require.NoError(t, err) require.Equal(t, schema.Value, merged) } @@ -1476,7 +1476,7 @@ func TestMerge_NoAllOf(t *testing.T) { Value: &openapi3.Schema{ Title: "test", }} - merged, err := flatten.Merge(schema) + merged, err := allof.Merge(schema) require.NoError(t, err) require.Equal(t, schema.Value, merged) } @@ -1517,7 +1517,7 @@ func TestMerge_TwoObjects(t *testing.T) { }, }} - merged, err := flatten.Merge(schema) + merged, err := allof.Merge(schema) require.NoError(t, err) require.Len(t, merged.AllOf, 0) require.Len(t, merged.Properties, 2) @@ -1547,7 +1547,7 @@ func TestMerge_OneObjectOneProp(t *testing.T) { }, }} - merged, err := flatten.Merge(schema) + merged, err := allof.Merge(schema) require.NoError(t, err) require.Len(t, merged.Properties, 1) require.Equal(t, object["description"].Value.Type, merged.Properties["description"].Value.Type) @@ -1555,7 +1555,7 @@ func TestMerge_OneObjectOneProp(t *testing.T) { func TestMerge_OneObjectNoProps(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1606,7 +1606,7 @@ func TestMerge_OverlappingProps(t *testing.T) { }, }, }} - merged, err := flatten.Merge(schema) + merged, err := allof.Merge(schema) require.NoError(t, err) require.Len(t, merged.AllOf, 0) require.Len(t, merged.Properties, 1) @@ -1626,7 +1626,7 @@ func TestMerge_AdditionalProperties_False(t *testing.T) { secondPropEnum = append(secondPropEnum, "1", "8", "7") thirdPropEnum = append(thirdPropEnum, "3", "8", "5") - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1697,7 +1697,7 @@ func TestMerge_AdditionalProperties_True(t *testing.T) { secondPropEnum = append(secondPropEnum, "1", "8", "7") thirdPropEnum = append(thirdPropEnum, "3", "8", "5") - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1757,7 +1757,7 @@ func TestMerge_AdditionalProperties_True(t *testing.T) { } func TestMergeAllOf_Pattern(t *testing.T) { - merged, err := flatten.Merge( + merged, err := allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ Pattern: "abc", @@ -1765,7 +1765,7 @@ func TestMergeAllOf_Pattern(t *testing.T) { require.NoError(t, err) require.Equal(t, "abc", merged.Pattern) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1780,7 +1780,7 @@ func TestMergeAllOf_Pattern(t *testing.T) { require.NoError(t, err) require.Equal(t, "abc", merged.Pattern) - merged, err = flatten.Merge( + merged, err = allof.Merge( openapi3.SchemaRef{ Value: &openapi3.Schema{ AllOf: openapi3.SchemaRefs{ @@ -1820,7 +1820,7 @@ func TestMerge_Required(t *testing.T) { require.NoError(t, err, "loading test file") err = doc.Validate(ctx) require.NoError(t, err, "validating spec") - merged, err := flatten.Merge(*doc.Paths.Value("/products").Get.Responses.Value("200").Value.Content["application/json"].Schema) + merged, err := allof.Merge(*doc.Paths.Value("/products").Get.Responses.Value("200").Value.Content["application/json"].Schema) require.NoError(t, err) props := merged.Properties @@ -1839,7 +1839,7 @@ func TestMerge_Required(t *testing.T) { func TestMerge_CircularAllOf(t *testing.T) { doc := loadSpec(t, "testdata/circular1.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["AWSEnvironmentSettings"]) + merged, err := allof.Merge(*doc.Components.Schemas["AWSEnvironmentSettings"]) require.NoError(t, err) require.Empty(t, merged.AllOf) require.Empty(t, merged.OneOf) @@ -1851,7 +1851,7 @@ func TestMerge_CircularAllOf(t *testing.T) { // A single OneOf field is pruned if it references it's parent schema func TestMerge_OneOfIsPruned(t *testing.T) { doc := loadSpec(t, "testdata/circular2.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["OneOf_Is_Pruned_B"]) + merged, err := allof.Merge(*doc.Components.Schemas["OneOf_Is_Pruned_B"]) require.NoError(t, err) require.Empty(t, merged.AllOf) require.Empty(t, merged.OneOf) @@ -1860,7 +1860,7 @@ func TestMerge_OneOfIsPruned(t *testing.T) { // A single OneOf field is not pruned if it does not reference it's parent schema func TestMerge_OneOfIsNotPruned(t *testing.T) { doc := loadSpec(t, "testdata/circular2.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["OneOf_Is_Not_Pruned_B"]) + merged, err := allof.Merge(*doc.Components.Schemas["OneOf_Is_Not_Pruned_B"]) require.NoError(t, err) require.Empty(t, merged.AllOf) require.NotEmpty(t, merged.OneOf) @@ -1869,7 +1869,7 @@ func TestMerge_OneOfIsNotPruned(t *testing.T) { // A single AnyOf field is pruned if it references it's parent schema func TestMerge_AnyOfIsPruned(t *testing.T) { doc := loadSpec(t, "testdata/circular2.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["AnyOf_Is_Pruned_B"]) + merged, err := allof.Merge(*doc.Components.Schemas["AnyOf_Is_Pruned_B"]) require.NoError(t, err) require.Empty(t, merged.AllOf) require.Empty(t, merged.AnyOf) @@ -1878,7 +1878,7 @@ func TestMerge_AnyOfIsPruned(t *testing.T) { // A single AnyOf field is not pruned if it does not reference it's parent schema func TestMerge_AnyOfIsNotPruned(t *testing.T) { doc := loadSpec(t, "testdata/circular2.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["AnyOf_Is_Not_Pruned_B"]) + merged, err := allof.Merge(*doc.Components.Schemas["AnyOf_Is_Not_Pruned_B"]) require.NoError(t, err) require.Empty(t, merged.AllOf) require.NotEmpty(t, merged.AnyOf) @@ -1886,24 +1886,24 @@ func TestMerge_AnyOfIsNotPruned(t *testing.T) { func TestMerge_ComplexOneOfIsPruned(t *testing.T) { doc := loadSpec(t, "testdata/prune-oneof.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["SchemaWithWithoutOneOf"]) + merged, err := allof.Merge(*doc.Components.Schemas["SchemaWithWithoutOneOf"]) require.NoError(t, err) require.Empty(t, merged.OneOf) } func TestMerge_ComplexOneOfIsNotPruned(t *testing.T) { doc := loadSpec(t, "testdata/prune-oneof.yaml") - merged, err := flatten.Merge(*doc.Components.Schemas["ThirdSchema"]) + merged, err := allof.Merge(*doc.Components.Schemas["ThirdSchema"]) require.NoError(t, err) require.NotEmpty(t, merged.OneOf) require.Len(t, merged.OneOf, 2) - merged, err = flatten.Merge(*doc.Components.Schemas["ComplexSchema"]) + merged, err = allof.Merge(*doc.Components.Schemas["ComplexSchema"]) require.NoError(t, err) require.NotEmpty(t, merged.OneOf) require.Len(t, merged.OneOf, 2) - merged, err = flatten.Merge(*doc.Components.Schemas["SchemaWithOneOf"]) + merged, err = allof.Merge(*doc.Components.Schemas["SchemaWithOneOf"]) require.NoError(t, err) require.NotEmpty(t, merged.OneOf) require.Len(t, merged.OneOf, 2) diff --git a/flatten/merge_allof_validation_test.go b/flatten/allof/merge_allof_validation_test.go similarity index 99% rename from flatten/merge_allof_validation_test.go rename to flatten/allof/merge_allof_validation_test.go index 70652937..c8d675ce 100644 --- a/flatten/merge_allof_validation_test.go +++ b/flatten/allof/merge_allof_validation_test.go @@ -1,4 +1,4 @@ -package flatten_test +package allof_test import ( "bytes" @@ -6,7 +6,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tufin/oasdiff/flatten" + "github.com/tufin/oasdiff/flatten/allof" "github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3filter" @@ -1300,7 +1300,7 @@ func runTests(t *testing.T, spec string, tests []Test, shouldMerge bool) []error require.NoError(t, err) if shouldMerge { - doc, err = flatten.MergeSpec(doc) + doc, err = allof.MergeSpec(doc) require.NoError(t, err) } diff --git a/flatten/testdata/circular1.yaml b/flatten/allof/testdata/circular1.yaml similarity index 100% rename from flatten/testdata/circular1.yaml rename to flatten/allof/testdata/circular1.yaml diff --git a/flatten/testdata/circular2.yaml b/flatten/allof/testdata/circular2.yaml similarity index 100% rename from flatten/testdata/circular2.yaml rename to flatten/allof/testdata/circular2.yaml diff --git a/flatten/testdata/properties.yml b/flatten/allof/testdata/properties.yml similarity index 100% rename from flatten/testdata/properties.yml rename to flatten/allof/testdata/properties.yml diff --git a/flatten/testdata/prune-oneof.yaml b/flatten/allof/testdata/prune-oneof.yaml similarity index 100% rename from flatten/testdata/prune-oneof.yaml rename to flatten/allof/testdata/prune-oneof.yaml diff --git a/flatten/commonparams/doc.go b/flatten/commonparams/doc.go new file mode 100644 index 00000000..2287fcde --- /dev/null +++ b/flatten/commonparams/doc.go @@ -0,0 +1,6 @@ +/* +Package commonparams moves common parameters to the operations under it +This is helpful to improve breaking changes accuracy +See here for Common Parameters definition: https://swagger.io/docs/specification/describing-parameters/ +*/ +package commonparams diff --git a/flatten/commonparams/params.go b/flatten/commonparams/params.go new file mode 100644 index 00000000..92082db3 --- /dev/null +++ b/flatten/commonparams/params.go @@ -0,0 +1,32 @@ +package commonparams + +import "github.com/getkin/kin-openapi/openapi3" + +// Move moves common parameters to the operations under the path +func Move(spec *openapi3.T) { + moveParams(spec) +} + +func moveParams(spec *openapi3.T) { + for _, path := range spec.Paths.Map() { + for _, op := range path.Operations() { + addParams(op, path.Parameters) + } + path.Parameters = nil + } +} + +func addParams(op *openapi3.Operation, pathParams openapi3.Parameters) { + for _, pathParam := range pathParams { + op.Parameters = addParam(op.Parameters, pathParam.Value) + } +} + +func addParam(opParams openapi3.Parameters, pathParam *openapi3.Parameter) openapi3.Parameters { + if opParams.GetByInAndName(pathParam.In, pathParam.Name) == nil { + opParams = append(opParams, &openapi3.ParameterRef{ + Value: pathParam, + }) + } + return opParams +} diff --git a/formatters/interface.go b/formatters/interface.go index d67773e5..b165b244 100644 --- a/formatters/interface.go +++ b/formatters/interface.go @@ -2,6 +2,7 @@ package formatters import ( "fmt" + "sort" "github.com/getkin/kin-openapi/openapi3" "github.com/tufin/oasdiff/checker" @@ -64,6 +65,7 @@ func SupportedFormatsByContentType(output Output) []string { formats = append(formats, string(k)) } } + sort.Strings(formats) return formats } diff --git a/internal/breaking_changes.go b/internal/breaking_changes.go index d3f791fe..0af912b7 100644 --- a/internal/breaking_changes.go +++ b/internal/breaking_changes.go @@ -6,8 +6,6 @@ import ( "github.com/spf13/cobra" "github.com/tufin/oasdiff/checker" - "github.com/tufin/oasdiff/checker/localizations" - "github.com/tufin/oasdiff/diff" "github.com/tufin/oasdiff/formatters" ) @@ -23,26 +21,10 @@ func getBreakingChangesCmd() *cobra.Command { RunE: getRun(&flags, runBreakingChanges), } - cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") + addCommonDiffFlags(&cmd, &flags) + addCommonBreakingFlags(&cmd, &flags) enumWithOptions(&cmd, newEnumValue(formatters.SupportedFormatsByContentType(formatters.OutputBreaking), string(formatters.FormatText), &flags.format), "format", "f", "output format") - enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude") - cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression") - cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") - cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") - cmd.PersistentFlags().StringVarP(&flags.prefixBase, "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.prefixRevision, "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") - cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching") - cmd.PersistentFlags().BoolVarP(&flags.flatten, "flatten", "", false, "merge subschemas under allOf before diff") enumWithOptions(&cmd, newEnumValue([]string{LevelErr, LevelWarn}, "", &flags.failOn), "fail-on", "o", "exit with return code 1 when output includes errors with this level or higher") - enumWithOptions(&cmd, newEnumValue(localizations.GetSupportedLanguages(), localizations.LangDefault, &flags.lang), "lang", "l", "language for localized output") - cmd.PersistentFlags().StringVarP(&flags.errIgnoreFile, "err-ignore", "", "", "configuration file for ignoring errors") - cmd.PersistentFlags().StringVarP(&flags.warnIgnoreFile, "warn-ignore", "", "", "configuration file for ignoring warnings") - cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks (run 'oasdiff checks --required false' to see options)") - cmd.PersistentFlags().IntVarP(&flags.deprecationDaysBeta, "deprecation-days-beta", "", checker.BetaDeprecationDays, "min days required between deprecating a beta resource and removing it") - cmd.PersistentFlags().IntVarP(&flags.deprecationDaysStable, "deprecation-days-stable", "", checker.StableDeprecationDays, "min days required between deprecating a stable resource and removing it") - enumWithOptions(&cmd, newEnumValue([]string{"auto", "always", "never"}, "auto", &flags.color), "color", "", "when to colorize textual output") return &cmd } diff --git a/internal/changelog.go b/internal/changelog.go index b625c96e..4d5f3a61 100644 --- a/internal/changelog.go +++ b/internal/changelog.go @@ -7,8 +7,6 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/spf13/cobra" "github.com/tufin/oasdiff/checker" - "github.com/tufin/oasdiff/checker/localizations" - "github.com/tufin/oasdiff/diff" "github.com/tufin/oasdiff/formatters" "github.com/tufin/oasdiff/load" ) @@ -25,25 +23,9 @@ func getChangelogCmd() *cobra.Command { RunE: getRun(&flags, runChangelog), } - cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") + addCommonDiffFlags(&cmd, &flags) + addCommonBreakingFlags(&cmd, &flags) enumWithOptions(&cmd, newEnumValue(formatters.SupportedFormatsByContentType(formatters.OutputChangelog), string(formatters.FormatText), &flags.format), "format", "f", "output format") - enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude") - cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression") - cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") - cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") - cmd.PersistentFlags().StringVarP(&flags.prefixBase, "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.prefixRevision, "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") - cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching") - cmd.PersistentFlags().BoolVarP(&flags.flatten, "flatten", "", false, "merge subschemas under allOf before diff") - enumWithOptions(&cmd, newEnumValue(localizations.GetSupportedLanguages(), localizations.LangDefault, &flags.lang), "lang", "l", "language for localized output") - cmd.PersistentFlags().StringVarP(&flags.errIgnoreFile, "err-ignore", "", "", "configuration file for ignoring errors") - cmd.PersistentFlags().StringVarP(&flags.warnIgnoreFile, "warn-ignore", "", "", "configuration file for ignoring warnings") - cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, &flags.includeChecks), "include-checks", "i", "comma-separated list of optional checks (run 'oasdiff checks --required false' to see options)") - cmd.PersistentFlags().IntVarP(&flags.deprecationDaysBeta, "deprecation-days-beta", "", checker.BetaDeprecationDays, "min days required between deprecating a beta resource and removing it") - cmd.PersistentFlags().IntVarP(&flags.deprecationDaysStable, "deprecation-days-stable", "", checker.StableDeprecationDays, "min days required between deprecating a stable resource and removing it") - enumWithOptions(&cmd, newEnumValue([]string{"auto", "always", "never"}, "auto", &flags.color), "color", "", "when to colorize textual output") return &cmd } diff --git a/internal/changelog_flags.go b/internal/changelog_flags.go index 05775bd5..96bc88ea 100644 --- a/internal/changelog_flags.go +++ b/internal/changelog_flags.go @@ -21,7 +21,8 @@ type ChangelogFlags struct { excludeElements []string includeChecks []string failOn string - flatten bool + flattenAllOf bool + flattenParams bool lang string errIgnoreFile string warnIgnoreFile string @@ -55,8 +56,12 @@ func (flags *ChangelogFlags) getRevision() *load.Source { return flags.revision } -func (flags *ChangelogFlags) getFlatten() bool { - return flags.flatten +func (flags *ChangelogFlags) getFlattenAllOf() bool { + return flags.flattenAllOf +} + +func (flags *ChangelogFlags) getFlattenParams() bool { + return flags.flattenParams } func (flags *ChangelogFlags) getCircularReferenceCounter() int { @@ -118,3 +123,79 @@ func (flags *ChangelogFlags) setRevision(source *load.Source) { func (flags *ChangelogFlags) addExcludeElements(element string) { flags.excludeElements = append(flags.excludeElements, element) } + +func (flags *ChangelogFlags) refComposed() *bool { + return &flags.composed +} + +func (flags *ChangelogFlags) refExcludeElements() *[]string { + return &flags.excludeElements +} + +func (flags *ChangelogFlags) refMatchPath() *string { + return &flags.matchPath +} + +func (flags *ChangelogFlags) refFilterExtension() *string { + return &flags.filterExtension +} + +func (flags *ChangelogFlags) refCircularReferenceCounter() *int { + return &flags.circularReferenceCounter +} + +func (flags *ChangelogFlags) refPrefixBase() *string { + return &flags.prefixBase +} + +func (flags *ChangelogFlags) refPrefixRevision() *string { + return &flags.prefixRevision +} + +func (flags *ChangelogFlags) refStripPrefixBase() *string { + return &flags.stripPrefixBase +} + +func (flags *ChangelogFlags) refStripPrefixRevision() *string { + return &flags.stripPrefixRevision +} + +func (flags *ChangelogFlags) refIncludePathParams() *bool { + return &flags.includePathParams +} + +func (flags *ChangelogFlags) refFlattenAllOf() *bool { + return &flags.flattenAllOf +} + +func (flags *ChangelogFlags) refFlattenParams() *bool { + return &flags.flattenParams +} + +func (flags *ChangelogFlags) refLang() *string { + return &flags.lang +} + +func (flags *ChangelogFlags) refErrIgnoreFile() *string { + return &flags.errIgnoreFile +} + +func (flags *ChangelogFlags) refWarnIgnoreFile() *string { + return &flags.warnIgnoreFile +} + +func (flags *ChangelogFlags) refIncludeChecks() *[]string { + return &flags.includeChecks +} + +func (flags *ChangelogFlags) refDeprecationDaysBeta() *int { + return &flags.deprecationDaysBeta +} + +func (flags *ChangelogFlags) refDeprecationDaysStable() *int { + return &flags.deprecationDaysStable +} + +func (flags *ChangelogFlags) refColor() *string { + return &flags.color +} diff --git a/internal/cmd_flags.go b/internal/cmd_flags.go new file mode 100644 index 00000000..4daa53fb --- /dev/null +++ b/internal/cmd_flags.go @@ -0,0 +1,49 @@ +package internal + +import ( + "github.com/spf13/cobra" + "github.com/tufin/oasdiff/checker" + "github.com/tufin/oasdiff/checker/localizations" + "github.com/tufin/oasdiff/diff" +) + +func addCommonDiffFlags(cmd *cobra.Command, flags Flags) { + cmd.PersistentFlags().BoolVarP(flags.refComposed(), "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") + enumWithOptions(cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, flags.refExcludeElements()), "exclude-elements", "e", "comma-separated list of elements to exclude") + cmd.PersistentFlags().StringVarP(flags.refMatchPath(), "match-path", "p", "", "include only paths that match this regular expression") + cmd.PersistentFlags().StringVarP(flags.refFilterExtension(), "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") + cmd.PersistentFlags().IntVarP(flags.refCircularReferenceCounter(), "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") + cmd.PersistentFlags().StringVarP(flags.refPrefixBase(), "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") + cmd.PersistentFlags().StringVarP(flags.refPrefixRevision(), "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") + cmd.PersistentFlags().StringVarP(flags.refStripPrefixBase(), "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") + cmd.PersistentFlags().StringVarP(flags.refStripPrefixRevision(), "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") + cmd.PersistentFlags().BoolVarP(flags.refIncludePathParams(), "include-path-params", "", false, "include path parameter names in endpoint matching") + cmd.PersistentFlags().BoolVarP(flags.refFlattenAllOf(), "flatten-allof", "", false, "merge subschemas under allOf before diff") + cmd.PersistentFlags().BoolVarP(flags.refFlattenParams(), "flatten-params", "", false, "merge common parameters at path level with operation parameters") + addDeprecatedFlattenFlag(cmd, flags) +} + +func addDeprecatedFlattenFlag(cmd *cobra.Command, flags Flags) { + const flag = "flatten" + + // add this flag for backward compatibility + cmd.PersistentFlags().BoolVarP(flags.refFlattenAllOf(), flag, "", false, "merge subschemas under allOf before diff") + + // ideally we'd like to mark '--flatten' as deprecated but this causes cobra to write an error message to stdout when the flag is used + // this messes up the json and yaml output + // so instead we just hide the flag + if err := cmd.PersistentFlags().MarkHidden(flag); err != nil { + // we can ignore this error safely + _ = err + } +} + +func addCommonBreakingFlags(cmd *cobra.Command, flags Flags) { + enumWithOptions(cmd, newEnumValue(localizations.GetSupportedLanguages(), localizations.LangDefault, flags.refLang()), "lang", "l", "language for localized output") + cmd.PersistentFlags().StringVarP(flags.refErrIgnoreFile(), "err-ignore", "", "", "configuration file for ignoring errors") + cmd.PersistentFlags().StringVarP(flags.refWarnIgnoreFile(), "warn-ignore", "", "", "configuration file for ignoring warnings") + cmd.PersistentFlags().VarP(newEnumSliceValue(checker.GetOptionalChecks(), nil, flags.refIncludeChecks()), "include-checks", "i", "comma-separated list of optional checks (run 'oasdiff checks --required false' to see options)") + cmd.PersistentFlags().IntVarP(flags.refDeprecationDaysBeta(), "deprecation-days-beta", "", checker.BetaDeprecationDays, "min days required between deprecating a beta resource and removing it") + cmd.PersistentFlags().IntVarP(flags.refDeprecationDaysStable(), "deprecation-days-stable", "", checker.StableDeprecationDays, "min days required between deprecating a stable resource and removing it") + enumWithOptions(cmd, newEnumValue([]string{"auto", "always", "never"}, "auto", flags.refColor()), "color", "", "when to colorize textual output") +} diff --git a/internal/delta.go b/internal/delta.go index 5a86afd4..9e980bef 100644 --- a/internal/delta.go +++ b/internal/delta.go @@ -7,7 +7,6 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/spf13/cobra" "github.com/tufin/oasdiff/delta" - "github.com/tufin/oasdiff/diff" ) func getDeltaCmd() *cobra.Command { @@ -22,17 +21,7 @@ func getDeltaCmd() *cobra.Command { RunE: getRun(&flags, runDelta), } - cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") - enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude") - cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression") - cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") - cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") - cmd.PersistentFlags().StringVarP(&flags.prefixBase, "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.prefixRevision, "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") - cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching") - cmd.PersistentFlags().BoolVarP(&flags.flatten, "flatten", "", false, "merge subschemas under allOf before diff") + addCommonDiffFlags(&cmd, &flags) cmd.PersistentFlags().BoolVarP(&flags.asymmetric, "asymmetric", "", false, "perform asymmetric diff (only elements of base that are missing in revision)") return &cmd diff --git a/internal/delta_flags.go b/internal/delta_flags.go index 7d63d6ab..e7ee0f58 100644 --- a/internal/delta_flags.go +++ b/internal/delta_flags.go @@ -8,3 +8,51 @@ type DeltaFlags struct { func (flags *DeltaFlags) getAsymmetric() bool { return flags.asymmetric } + +func (flags *DeltaFlags) refComposed() *bool { + return &flags.composed +} + +func (flags *DeltaFlags) refExcludeElements() *[]string { + return &flags.excludeElements +} + +func (flags *DeltaFlags) refMatchPath() *string { + return &flags.matchPath +} + +func (flags *DeltaFlags) refFilterExtension() *string { + return &flags.filterExtension +} + +func (flags *DeltaFlags) refCircularReferenceCounter() *int { + return &flags.circularReferenceCounter +} + +func (flags *DeltaFlags) refPrefixBase() *string { + return &flags.prefixBase +} + +func (flags *DeltaFlags) refPrefixRevision() *string { + return &flags.prefixRevision +} + +func (flags *DeltaFlags) refStripPrefixBase() *string { + return &flags.stripPrefixBase +} + +func (flags *DeltaFlags) refStripPrefixRevision() *string { + return &flags.stripPrefixRevision +} + +func (flags *DeltaFlags) refIncludePathParams() *bool { + return &flags.includePathParams +} + +func (flags *DeltaFlags) refFlattenAllOf() *bool { + return &flags.flattenAllOf +} + +func (flags *DeltaFlags) refFlattenParams() *bool { + return &flags.flattenParams +} diff --git a/internal/diff.go b/internal/diff.go index a925cfa5..5c996f67 100644 --- a/internal/diff.go +++ b/internal/diff.go @@ -7,7 +7,6 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/spf13/cobra" "github.com/tufin/oasdiff/diff" - "github.com/tufin/oasdiff/flatten" "github.com/tufin/oasdiff/formatters" "github.com/tufin/oasdiff/load" ) @@ -24,18 +23,8 @@ func getDiffCmd() *cobra.Command { RunE: getRun(&flags, runDiff), } - cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") + addCommonDiffFlags(&cmd, &flags) enumWithOptions(&cmd, newEnumValue(formatters.SupportedFormatsByContentType(formatters.OutputDiff), string(formatters.FormatYAML), &flags.format), "format", "f", "output format") - enumWithOptions(&cmd, newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude") - cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression") - cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") - cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") - cmd.PersistentFlags().StringVarP(&flags.prefixBase, "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.prefixRevision, "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") - cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching") - cmd.PersistentFlags().BoolVarP(&flags.flatten, "flatten", "", false, "merge subschemas under allOf before diff") cmd.PersistentFlags().BoolVarP(&flags.failOnDiff, "fail-on-diff", "o", false, "exit with return code 1 when any change is found") return &cmd @@ -107,12 +96,16 @@ func newDiffResult(d *diff.Diff, o *diff.OperationsSourcesMap, s *load.SpecInfoP } func normalDiff(loader load.Loader, flags Flags) (*diffResult, *ReturnError) { - s1, err := load.LoadSpecInfo(loader, flags.getBase()) + + flattenAllOf := load.GetOption(load.WithFlattenAllOf(), flags.getFlattenAllOf()) + flattenParams := load.GetOption(load.WithFlattenParams(), flags.getFlattenParams()) + + s1, err := load.NewSpecInfo(loader, flags.getBase(), flattenAllOf, flattenParams) if err != nil { return nil, getErrFailedToLoadSpec("base", flags.getBase(), err) } - s2, err := load.LoadSpecInfo(loader, flags.getRevision()) + s2, err := load.NewSpecInfo(loader, flags.getRevision(), flattenAllOf, flattenParams) if err != nil { return nil, getErrFailedToLoadSpec("revision", flags.getRevision(), err) } @@ -122,16 +115,6 @@ func normalDiff(loader load.Loader, flags Flags) (*diffResult, *ReturnError) { s2.Spec = s1.Spec } - if flags.getFlatten() { - if err := mergeAllOf("base", []*load.SpecInfo{s1}, flags.getBase()); err != nil { - return nil, err - } - - if err := mergeAllOf("revision", []*load.SpecInfo{s2}, flags.getRevision()); err != nil { - return nil, err - } - } - diffReport, operationsSources, err := diff.GetWithOperationsSourcesMap(flags.toConfig(), s1, s2) if err != nil { return nil, getErrDiffFailed(err) @@ -141,26 +124,20 @@ func normalDiff(loader load.Loader, flags Flags) (*diffResult, *ReturnError) { } func composedDiff(loader load.Loader, flags Flags) (*diffResult, *ReturnError) { - s1, err := load.FromGlob(loader, flags.getBase().Path) + + flattenAllOf := load.GetOption(load.WithFlattenAllOf(), flags.getFlattenAllOf()) + flattenParams := load.GetOption(load.WithFlattenParams(), flags.getFlattenParams()) + + s1, err := load.NewSpecInfoFromGlob(loader, flags.getBase().Path, flattenAllOf, flattenParams) if err != nil { return nil, getErrFailedToLoadSpecs("base", flags.getBase().Path, err) } - s2, err := load.FromGlob(loader, flags.getRevision().Path) + s2, err := load.NewSpecInfoFromGlob(loader, flags.getRevision().Path, flattenAllOf, flattenParams) if err != nil { return nil, getErrFailedToLoadSpecs("revision", flags.getRevision().Path, err) } - if flags.getFlatten() { - if err := mergeAllOf("base", s1, flags.getBase()); err != nil { - return nil, err - } - - if err := mergeAllOf("revision", s2, flags.getRevision()); err != nil { - return nil, err - } - } - diffReport, operationsSources, err := diff.GetPathsDiff(flags.toConfig(), s1, s2) if err != nil { return nil, getErrDiffFailed(err) @@ -168,16 +145,3 @@ func composedDiff(loader load.Loader, flags Flags) (*diffResult, *ReturnError) { return newDiffResult(diffReport, operationsSources, nil), nil } - -func mergeAllOf(title string, specInfos []*load.SpecInfo, source *load.Source) *ReturnError { - - var err error - - for _, specInfo := range specInfos { - if specInfo.Spec, err = flatten.MergeSpec(specInfo.Spec); err != nil { - return getErrFailedToFlattenSpec(title, source, err) - } - } - - return nil -} diff --git a/internal/diff_flags.go b/internal/diff_flags.go index 541ba9dd..a93f9907 100644 --- a/internal/diff_flags.go +++ b/internal/diff_flags.go @@ -17,7 +17,8 @@ type DiffFlags struct { filterExtension string format string failOnDiff bool - flatten bool + flattenAllOf bool + flattenParams bool circularReferenceCounter int includePathParams bool excludeElements []string @@ -48,8 +49,12 @@ func (flags *DiffFlags) getRevision() *load.Source { return flags.revision } -func (flags *DiffFlags) getFlatten() bool { - return flags.flatten +func (flags *DiffFlags) getFlattenAllOf() bool { + return flags.flattenAllOf +} + +func (flags *DiffFlags) getFlattenParams() bool { + return flags.flattenParams } func (flags *DiffFlags) getCircularReferenceCounter() int { @@ -111,3 +116,79 @@ func (flags *DiffFlags) setRevision(source *load.Source) { func (flags *DiffFlags) addExcludeElements(element string) { flags.excludeElements = append(flags.excludeElements, element) } + +func (flags *DiffFlags) refComposed() *bool { + return &flags.composed +} + +func (flags *DiffFlags) refExcludeElements() *[]string { + return &flags.excludeElements +} + +func (flags *DiffFlags) refMatchPath() *string { + return &flags.matchPath +} + +func (flags *DiffFlags) refFilterExtension() *string { + return &flags.filterExtension +} + +func (flags *DiffFlags) refCircularReferenceCounter() *int { + return &flags.circularReferenceCounter +} + +func (flags *DiffFlags) refPrefixBase() *string { + return &flags.prefixBase +} + +func (flags *DiffFlags) refPrefixRevision() *string { + return &flags.prefixRevision +} + +func (flags *DiffFlags) refStripPrefixBase() *string { + return &flags.stripPrefixBase +} + +func (flags *DiffFlags) refStripPrefixRevision() *string { + return &flags.stripPrefixRevision +} + +func (flags *DiffFlags) refIncludePathParams() *bool { + return &flags.includePathParams +} + +func (flags *DiffFlags) refFlattenAllOf() *bool { + return &flags.flattenAllOf +} + +func (flags *DiffFlags) refFlattenParams() *bool { + return &flags.flattenParams +} + +func (flags *DiffFlags) refLang() *string { + return nil +} + +func (flags *DiffFlags) refErrIgnoreFile() *string { + return nil +} + +func (flags *DiffFlags) refWarnIgnoreFile() *string { + return nil +} + +func (flags *DiffFlags) refIncludeChecks() *[]string { + return nil +} + +func (flags *DiffFlags) refDeprecationDaysBeta() *int { + return nil +} + +func (flags *DiffFlags) refDeprecationDaysStable() *int { + return nil +} + +func (flags *DiffFlags) refColor() *string { + return nil +} diff --git a/internal/errors.go b/internal/errors.go index 2482b53c..0d0c5e4a 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -25,13 +25,6 @@ func getErrFailedToLoadSpec(what string, source *load.Source, err error) *Return } } -func getErrFailedToFlattenSpec(what string, source *load.Source, err error) *ReturnError { - return &ReturnError{ - error: fmt.Errorf("failed to flatten %s spec from %s with %v", what, source.Out(), err), - Code: 102, - } -} - func getErrFailedToLoadSpecs(what string, path string, err error) *ReturnError { return &ReturnError{ error: fmt.Errorf("failed to load %s specs from glob %q with %v", what, path, err), diff --git a/internal/flags.go b/internal/flags.go index ff1be7f1..9e92d755 100644 --- a/internal/flags.go +++ b/internal/flags.go @@ -11,7 +11,8 @@ type Flags interface { getComposed() bool getBase() *load.Source getRevision() *load.Source - getFlatten() bool + getFlattenAllOf() bool + getFlattenParams() bool getCircularReferenceCounter() int getIncludeChecks() []string getDeprecationDaysBeta() int @@ -29,4 +30,24 @@ type Flags interface { setRevision(source *load.Source) addExcludeElements(string) + + refComposed() *bool + refExcludeElements() *[]string + refMatchPath() *string + refFilterExtension() *string + refCircularReferenceCounter() *int + refPrefixBase() *string + refPrefixRevision() *string + refStripPrefixBase() *string + refStripPrefixRevision() *string + refIncludePathParams() *bool + refFlattenAllOf() *bool + refFlattenParams() *bool + refLang() *string + refErrIgnoreFile() *string + refWarnIgnoreFile() *string + refIncludeChecks() *[]string + refDeprecationDaysBeta() *int + refDeprecationDaysStable() *int + refColor() *string } diff --git a/internal/flatten.go b/internal/flatten.go index e3297dbd..97eac09b 100644 --- a/internal/flatten.go +++ b/internal/flatten.go @@ -6,7 +6,6 @@ import ( "github.com/getkin/kin-openapi/openapi3" "github.com/spf13/cobra" - "github.com/tufin/oasdiff/flatten" "github.com/tufin/oasdiff/formatters" "github.com/tufin/oasdiff/load" ) @@ -51,7 +50,7 @@ func runFlatten(flags *FlattenFlags, stdout io.Writer) *ReturnError { loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true - spec, err := load.LoadSpecInfo(loader, flags.spec) + spec, err := load.NewSpecInfo(loader, flags.spec, load.WithFlattenAllOf()) if err != nil { return getErrFailedToLoadSpec("original", flags.spec, err) } @@ -59,12 +58,7 @@ func runFlatten(flags *FlattenFlags, stdout io.Writer) *ReturnError { // TODO: get the original format of the spec format := flags.format - flatSpec, err := flatten.MergeSpec(spec.Spec) - if err != nil { - return getErrFailedToFlattenSpec("original", flags.spec, err) - } - - if returnErr := outputFlattenedSpec(stdout, flatSpec, format); returnErr != nil { + if returnErr := outputFlattenedSpec(stdout, spec.Spec, format); returnErr != nil { return returnErr } diff --git a/internal/run_test.go b/internal/run_test.go index b4389dc4..c165ee26 100644 --- a/internal/run_test.go +++ b/internal/run_test.go @@ -29,6 +29,7 @@ func TestMain(m *testing.M) { func setup() { os.Setenv(model.EnvNoTelemetry, "1") } + func Test_InvalidCmd(t *testing.T) { require.Equal(t, 100, internal.Run(cmdToArgs("oasdiff invalid"), io.Discard, io.Discard)) } @@ -261,18 +262,26 @@ func Test_BreakingChangesChangelogOptionalCheckersAreErrorLevelWhenSpecified(t * } } -func Test_BreakingChangesFlatten(t *testing.T) { +func Test_BreakingChangesFlattenDeprecated(t *testing.T) { require.Zero(t, internal.Run(cmdToArgs("oasdiff breaking ../data/allof/simple.yaml ../data/allof/revision.yaml --flatten --fail-on ERR"), io.Discard, io.Discard)) } -func Test_FlattenOK(t *testing.T) { +func Test_BreakingChangesFlattenAllOf(t *testing.T) { + require.Zero(t, internal.Run(cmdToArgs("oasdiff breaking ../data/allof/simple.yaml ../data/allof/revision.yaml --flatten-allof --fail-on ERR"), io.Discard, io.Discard)) +} + +func Test_BreakingChangesFlattenCommonParams(t *testing.T) { + require.Zero(t, internal.Run(cmdToArgs("oasdiff breaking ../data/common-params/params_in_path.yaml ../data/common-params/params_in_op.yaml --flatten-params --fail-on ERR"), io.Discard, io.Discard)) +} + +func Test_FlattenCmdOK(t *testing.T) { require.Zero(t, internal.Run(cmdToArgs("oasdiff flatten ../data/allof/simple.yaml"), io.Discard, io.Discard)) } -func Test_FlattenInvalid(t *testing.T) { +func Test_FlattenCmdInvalid(t *testing.T) { var stderr bytes.Buffer require.Equal(t, 102, internal.Run(cmdToArgs("oasdiff flatten ../data/allof/invalid.yaml"), io.Discard, &stderr)) - require.Equal(t, `Error: failed to flatten original spec from "../data/allof/invalid.yaml" with unable to resolve Type conflict: all Type values must be identical + require.Equal(t, `Error: failed to load original spec from "../data/allof/invalid.yaml" with unable to resolve Type conflict: all Type values must be identical `, stderr.String()) } diff --git a/internal/summary.go b/internal/summary.go index cdeb3589..60c497b2 100644 --- a/internal/summary.go +++ b/internal/summary.go @@ -21,17 +21,8 @@ func getSummaryCmd() *cobra.Command { RunE: getRun(&flags, runSummary), } - cmd.PersistentFlags().BoolVarP(&flags.composed, "composed", "c", false, "work in 'composed' mode, compare paths in all specs matching base and revision globs") + addCommonDiffFlags(&cmd, &flags) enumWithOptions(&cmd, newEnumValue(formatters.SupportedFormatsByContentType(formatters.OutputSummary), string(formatters.FormatYAML), &flags.format), "format", "f", "output format") - cmd.PersistentFlags().VarP(newEnumSliceValue(diff.ExcludeDiffOptions, nil, &flags.excludeElements), "exclude-elements", "e", "comma-separated list of elements to exclude") - cmd.PersistentFlags().StringVarP(&flags.matchPath, "match-path", "p", "", "include only paths that match this regular expression") - cmd.PersistentFlags().StringVarP(&flags.filterExtension, "filter-extension", "", "", "exclude paths and operations with an OpenAPI Extension matching this regular expression") - cmd.PersistentFlags().IntVarP(&flags.circularReferenceCounter, "max-circular-dep", "", 5, "maximum allowed number of circular dependencies between objects in OpenAPI specs") - cmd.PersistentFlags().StringVarP(&flags.prefixBase, "prefix-base", "", "", "add this prefix to paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.prefixRevision, "prefix-revision", "", "", "add this prefix to paths in revised-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixBase, "strip-prefix-base", "", "", "strip this prefix from paths in base-spec before comparison") - cmd.PersistentFlags().StringVarP(&flags.stripPrefixRevision, "strip-prefix-revision", "", "", "strip this prefix from paths in revised-spec before comparison") - cmd.PersistentFlags().BoolVarP(&flags.includePathParams, "include-path-params", "", false, "include path parameter names in endpoint matching") cmd.PersistentFlags().BoolVarP(&flags.failOnDiff, "fail-on-diff", "", false, "exit with return code 1 when any change is found") return &cmd diff --git a/load/doc.go b/load/doc.go index 53032eab..6ddef303 100644 --- a/load/doc.go +++ b/load/doc.go @@ -1,2 +1,3 @@ -// Package load provides a function to load an OpenAPI spec from a URL or a Path. +// Package load loads OpenAPI specs from different sources like URLs, paths, globs and stdin +// Optionally, specs can be preprocessed after loading package load diff --git a/load/load.go b/load/load.go index 768e6df9..7d52d7ee 100644 --- a/load/load.go +++ b/load/load.go @@ -14,8 +14,8 @@ type Loader interface { LoadFromStdin() (*openapi3.T, error) } -// From is a convenience function that opens an OpenAPI spec from a URL or a local path based on the format of the path parameter -func From(loader Loader, source *Source) (*openapi3.T, error) { +// from is a convenience function that opens an OpenAPI spec from a URL or a local path based on the format of the path parameter +func from(loader Loader, source *Source) (*openapi3.T, error) { switch source.Type { case SourceTypeStdin: diff --git a/load/load_test.go b/load/load_test.go deleted file mode 100644 index 43b7ccf0..00000000 --- a/load/load_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package load_test - -import ( - "log" - "net/url" - "os" - "testing" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/stretchr/testify/require" - "github.com/tufin/oasdiff/load" -) - -func (mockLoader MockLoader) LoadFromFile(path string) (*openapi3.T, error) { - return openapi3.NewLoader().LoadFromFile(path) -} - -func (mockLoader MockLoader) LoadFromURI(location *url.URL) (*openapi3.T, error) { - return openapi3.NewLoader().LoadFromFile(".." + location.Path) -} - -func (mockLoader MockLoader) LoadFromStdin() (*openapi3.T, error) { - return openapi3.NewLoader().LoadFromStdin() -} - -type MockLoader struct{} - -func TestLoad_File(t *testing.T) { - _, err := load.From(MockLoader{}, load.NewSource("../data/openapi-test1.yaml")) - require.NoError(t, err) -} - -func TestLoad_FileWindows(t *testing.T) { - _, err := load.From(MockLoader{}, load.NewSource(`C:\dev\OpenApi\spec2.yaml`)) - require.Condition(t, func() (success bool) { - return err.Error() == "open C:\\dev\\OpenApi\\spec2.yaml: no such file or directory" || - err.Error() == "open C:/dev/OpenApi/spec2.yaml: The system cannot find the path specified." - }) -} - -func TestLoad_URI(t *testing.T) { - _, err := load.From(MockLoader{}, load.NewSource("https://localhost/data/openapi-test1.yaml")) - require.NoError(t, err) -} - -func TestLoad_URIError(t *testing.T) { - _, err := load.From(MockLoader{}, load.NewSource("http://localhost/null")) - require.Condition(t, func() (success bool) { - return err.Error() == "open ../null: no such file or directory" || - err.Error() == "open ../null: The system cannot find the file specified." - }) -} - -func TestLoad_URIBadScheme(t *testing.T) { - _, err := load.From(MockLoader{}, load.NewSource("ftp://localhost/null")) - require.Condition(t, func() (success bool) { - return err.Error() == "open ftp://localhost/null: no such file or directory" || - err.Error() == "open ftp://localhost/null: The filename, directory name, or volume label syntax is incorrect." - }) -} - -func TestLoad_Stdin(t *testing.T) { - content := []byte(`openapi: 3.0.1 -info: - title: Test API - version: v1 -paths: - /partner-api/test/some-method: - get: - responses: - "200": - description: Success -`) - - tmpfile, err := os.CreateTemp("", "example") - if err != nil { - log.Fatal(err) - } - - defer os.Remove(tmpfile.Name()) // clean up - - if _, err := tmpfile.Write(content); err != nil { - log.Fatal(err) - } - - if _, err := tmpfile.Seek(0, 0); err != nil { - log.Fatal(err) - } - - oldStdin := os.Stdin - defer func() { os.Stdin = oldStdin }() // Restore original Stdin - - os.Stdin = tmpfile - _, err = load.From(MockLoader{}, load.NewSource("-")) - require.NoError(t, err) -} diff --git a/load/mock_loader_test.go b/load/mock_loader_test.go new file mode 100644 index 00000000..884cd32e --- /dev/null +++ b/load/mock_loader_test.go @@ -0,0 +1,21 @@ +package load_test + +import ( + "net/url" + + "github.com/getkin/kin-openapi/openapi3" +) + +func (mockLoader MockLoader) LoadFromFile(path string) (*openapi3.T, error) { + return openapi3.NewLoader().LoadFromFile(path) +} + +func (mockLoader MockLoader) LoadFromURI(location *url.URL) (*openapi3.T, error) { + return openapi3.NewLoader().LoadFromFile(".." + location.Path) +} + +func (mockLoader MockLoader) LoadFromStdin() (*openapi3.T, error) { + return openapi3.NewLoader().LoadFromStdin() +} + +type MockLoader struct{} diff --git a/load/option.go b/load/option.go new file mode 100644 index 00000000..d31fab7f --- /dev/null +++ b/load/option.go @@ -0,0 +1,48 @@ +package load + +import ( + "github.com/tufin/oasdiff/flatten/allof" + "github.com/tufin/oasdiff/flatten/commonparams" +) + +// option functions can be used to preprocess specs after loading them +type Option func(Loader, []*SpecInfo) ([]*SpecInfo, error) + +// WithIdentity returns the original SpecInfos +func WithIdentity() Option { + return func(loader Loader, specInfos []*SpecInfo) ([]*SpecInfo, error) { + return specInfos, nil + } +} + +// GetOption returns the requested option or the identity option +func GetOption(option Option, enable bool) Option { + if !enable { + return WithIdentity() + } + return option +} + +// WithFlattenAllOf returns SpecInfos with flattened allOf +func WithFlattenAllOf() Option { + return func(loader Loader, specInfos []*SpecInfo) ([]*SpecInfo, error) { + var err error + for _, specInfo := range specInfos { + if specInfo.Spec, err = allof.MergeSpec(specInfo.Spec); err != nil { + return nil, err + } + } + return specInfos, nil + } +} + +// WithFlattenParams returns SpecInfos with Common Parameters combined into operation parameters +// See here for Common Parameters definition: https://swagger.io/docs/specification/describing-parameters/ +func WithFlattenParams() Option { + return func(loader Loader, specInfos []*SpecInfo) ([]*SpecInfo, error) { + for _, specInfo := range specInfos { + commonparams.Move(specInfo.Spec) + } + return specInfos, nil + } +} diff --git a/load/spec_info.go b/load/spec_info.go index e59c1a1e..f1f57b58 100644 --- a/load/spec_info.go +++ b/load/spec_info.go @@ -30,52 +30,54 @@ func newSpecInfo(spec *openapi3.T, path string) *SpecInfo { } } -type SpecInfoPair struct { - Base *SpecInfo - Revision *SpecInfo -} - -func (specInfoPair *SpecInfoPair) GetBaseVersion() string { - if specInfoPair == nil { - return "n/a" +func getVersion(spec *openapi3.T) string { + if spec == nil || spec.Info == nil { + return "" } - return specInfoPair.Base.GetVersion() + + return spec.Info.Version } -func (specInfoPair *SpecInfoPair) GetRevisionVersion() string { - if specInfoPair == nil { - return "n/a" +// NewSpecInfo creates a SpecInfo from a local file path, a URL, or stdin +func NewSpecInfo(loader Loader, source *Source, options ...Option) (*SpecInfo, error) { + specInfo, err := loadSpecInfo(loader, source) + if err != nil { + return nil, err } + specInfos := []*SpecInfo{specInfo} - return specInfoPair.Revision.GetVersion() -} - -func NewSpecInfoPair(specInfo1, specInfo2 *SpecInfo) *SpecInfoPair { - return &SpecInfoPair{ - Base: specInfo1, - Revision: specInfo2, + for _, option := range options { + if specInfos, err = option(loader, specInfos); err != nil { + return nil, err + } } + return specInfos[0], nil } -func getVersion(spec *openapi3.T) string { - if spec == nil || spec.Info == nil { - return "" +// NewSpecInfoFromGlob creates SpecInfos from local files matching the specified glob parameter +func NewSpecInfoFromGlob(loader Loader, glob string, options ...Option) ([]*SpecInfo, error) { + specInfos, err := fromGlob(loader, glob) + if err != nil { + return nil, err } - return spec.Info.Version + for _, option := range options { + if specInfos, err = option(loader, specInfos); err != nil { + return nil, err + } + } + return specInfos, nil } -// LoadSpecInfo creates a SpecInfo from a local file path, a URL, or stdin -func LoadSpecInfo(loader Loader, source *Source) (*SpecInfo, error) { - s, err := From(loader, source) +func loadSpecInfo(loader Loader, source *Source) (*SpecInfo, error) { + s, err := from(loader, source) if err != nil { return nil, err } return newSpecInfo(s, source.Path), nil } -// FromGlob creates SpecInfo specs from local files matching the specified glob parameter -func FromGlob(loader Loader, glob string) ([]*SpecInfo, error) { +func fromGlob(loader Loader, glob string) ([]*SpecInfo, error) { files, err := filepathx.Glob(glob) if err != nil { return nil, err diff --git a/load/spec_info_pair.go b/load/spec_info_pair.go new file mode 100644 index 00000000..5e9de9de --- /dev/null +++ b/load/spec_info_pair.go @@ -0,0 +1,28 @@ +package load + +type SpecInfoPair struct { + Base *SpecInfo + Revision *SpecInfo +} + +func (specInfoPair *SpecInfoPair) GetBaseVersion() string { + if specInfoPair == nil { + return "n/a" + } + return specInfoPair.Base.GetVersion() +} + +func (specInfoPair *SpecInfoPair) GetRevisionVersion() string { + if specInfoPair == nil { + return "n/a" + } + + return specInfoPair.Revision.GetVersion() +} + +func NewSpecInfoPair(specInfo1, specInfo2 *SpecInfo) *SpecInfoPair { + return &SpecInfoPair{ + Base: specInfo1, + Revision: specInfo2, + } +} diff --git a/load/spec_info_pair_test.go b/load/spec_info_pair_test.go new file mode 100644 index 00000000..b3ca3d8e --- /dev/null +++ b/load/spec_info_pair_test.go @@ -0,0 +1,31 @@ +package load_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tufin/oasdiff/load" +) + +func TestSpecInfoPair(t *testing.T) { + spec, err := load.NewSpecInfo(MockLoader{}, load.NewSource("../data/openapi-test1.yaml")) + require.NoError(t, err) + + pair := load.NewSpecInfoPair(spec, spec) + require.Equal(t, "1.0.0", pair.GetBaseVersion()) + require.Equal(t, "1.0.0", pair.GetRevisionVersion()) +} + +func TestSpecInfoPair_NA(t *testing.T) { + var pair *load.SpecInfoPair + require.Equal(t, "n/a", pair.GetBaseVersion()) + require.Equal(t, "n/a", pair.GetRevisionVersion()) +} + +func TestSpecInfoPair_Nil(t *testing.T) { + var spec *load.SpecInfo + pair := load.NewSpecInfoPair(spec, spec) + + require.Equal(t, "n/a", pair.GetBaseVersion()) + require.Equal(t, "n/a", pair.GetRevisionVersion()) +} diff --git a/load/spec_info_test.go b/load/spec_info_test.go index 05ed6a31..de7e90c0 100644 --- a/load/spec_info_test.go +++ b/load/spec_info_test.go @@ -1,61 +1,140 @@ package load_test import ( + "os" "testing" "github.com/stretchr/testify/require" "github.com/tufin/oasdiff/load" ) -func TestSpecInfo(t *testing.T) { - _, err := load.LoadSpecInfo(MockLoader{}, load.NewSource("../data/openapi-test1.yaml")) +func TestSpecInfo_File(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource("../data/openapi-test1.yaml")) require.NoError(t, err) } +func TestLoadInfo_FileWindows(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource(`C:\dev\OpenApi\spec2.yaml`)) + require.Condition(t, func() (success bool) { + return err.Error() == "open C:\\dev\\OpenApi\\spec2.yaml: no such file or directory" || + err.Error() == "open C:/dev/OpenApi/spec2.yaml: The system cannot find the path specified." + }) +} + +func TestLoadInfo_URI(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource("https://localhost/data/openapi-test1.yaml")) + require.NoError(t, err) +} + +func TestLoadInfo_UriInvalid(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource("http://localhost/null")) + require.Condition(t, func() (success bool) { + return err.Error() == "open ../null: no such file or directory" || + err.Error() == "open ../null: The system cannot find the file specified." + }) +} + +func TestLoadInfo_UriBadScheme(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource("ftp://localhost/null")) + require.Condition(t, func() (success bool) { + return err.Error() == "open ftp://localhost/null: no such file or directory" || + err.Error() == "open ftp://localhost/null: The filename, directory name, or volume label syntax is incorrect." + }) +} + +func TestLoadInfo_Stdin(t *testing.T) { + content := []byte(`openapi: 3.0.1 +info: + title: Test API + version: v1 +paths: + /partner-api/test/some-method: + get: + responses: + "200": + description: Success +`) + + tmpfile, err := os.CreateTemp("", "example") + require.NoError(t, err) + + defer os.Remove(tmpfile.Name()) // clean up + + _, err = tmpfile.Write(content) + require.NoError(t, err) + + _, err = tmpfile.Seek(0, 0) + require.NoError(t, err) + + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() // Restore original Stdin + + os.Stdin = tmpfile + _, err = load.NewSpecInfo(MockLoader{}, load.NewSource("-")) + require.NoError(t, err) +} + +func TestLoadInfo_NoVersion(t *testing.T) { + content := []byte(`openapi: 3.0.1 +paths: + /partner-api/test/some-method: + get: + responses: + "200": + description: Success +`) + + tmpfile, err := os.CreateTemp("", "example") + require.NoError(t, err) + + defer os.Remove(tmpfile.Name()) // clean up + + _, err = tmpfile.Write(content) + require.NoError(t, err) + + _, err = tmpfile.Seek(0, 0) + require.NoError(t, err) + + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() // Restore original Stdin + + os.Stdin = tmpfile + specInfo, err := load.NewSpecInfo(MockLoader{}, load.NewSource("-")) + require.NoError(t, err) + require.Empty(t, specInfo.Version) +} + func TestSpecInfo_GlobOK(t *testing.T) { - _, err := load.FromGlob(MockLoader{}, "../data/*.yaml") + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "../data/*.yaml") require.NoError(t, err) } func TestSpecInfo_InvalidSpec(t *testing.T) { - _, err := load.FromGlob(MockLoader{}, "../data/ignore-err-example.txt") + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "../data/ignore-err-example.txt") require.EqualError(t, err, "error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type openapi3.TBis") } func TestSpecInfo_InvalidGlob(t *testing.T) { - _, err := load.FromGlob(MockLoader{}, "[*") + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "[*") require.EqualError(t, err, "syntax error in pattern") } func TestSpecInfo_URL(t *testing.T) { - _, err := load.FromGlob(MockLoader{}, "http://localhost/openapi-test1.yaml") + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "http://localhost/openapi-test1.yaml") require.EqualError(t, err, "no matching files (should be a glob, not a URL)") } func TestSpecInfo_GlobNoFiles(t *testing.T) { - _, err := load.FromGlob(MockLoader{}, "../data/*.xxx") + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "../data/*.xxx") require.EqualError(t, err, "no matching files") } -func TestSpecInfoPair(t *testing.T) { - spec, err := load.LoadSpecInfo(MockLoader{}, load.NewSource("../data/openapi-test1.yaml")) +func TestSpecInfo_Options(t *testing.T) { + _, err := load.NewSpecInfo(MockLoader{}, load.NewSource("../data/openapi-test1.yaml"), load.GetOption(load.WithFlattenAllOf(), false), load.GetOption(load.WithFlattenAllOf(), true), load.WithFlattenParams()) require.NoError(t, err) - - pair := load.NewSpecInfoPair(spec, spec) - require.Equal(t, "1.0.0", pair.GetBaseVersion()) - require.Equal(t, "1.0.0", pair.GetRevisionVersion()) } -func TestSpecInfoPair_NA(t *testing.T) { - var pair *load.SpecInfoPair - require.Equal(t, "n/a", pair.GetBaseVersion()) - require.Equal(t, "n/a", pair.GetRevisionVersion()) -} - -func TestSpecInfoPair_Nil(t *testing.T) { - var spec *load.SpecInfo - pair := load.NewSpecInfoPair(spec, spec) - - require.Equal(t, "n/a", pair.GetBaseVersion()) - require.Equal(t, "n/a", pair.GetRevisionVersion()) +func TestSpecInfo_GlobOptions(t *testing.T) { + _, err := load.NewSpecInfoFromGlob(MockLoader{}, "../data/*.yaml", load.WithIdentity(), load.WithFlattenAllOf(), load.WithFlattenParams()) + require.NoError(t, err) } diff --git a/report/report.go b/report/report.go index ce082798..19a91cb7 100644 --- a/report/report.go +++ b/report/report.go @@ -52,7 +52,7 @@ func (r *report) output(d *diff.Diff) { } if d.EndpointsDiff.Empty() { - r.print("No endpoint changes") + r.print("No endpoint changes, but there are some other changes") } else { r.printEndpoints(d.EndpointsDiff) } diff --git a/report/text_test.go b/report/text_test.go index da2b7de9..e9c777db 100644 --- a/report/text_test.go +++ b/report/text_test.go @@ -44,7 +44,7 @@ func Test_NoEndpointChanges(t *testing.T) { dd, err := diff.Get(diff.NewConfig(), &s1, &s2) require.NoError(t, err) - require.Equal(t, report.GetTextReportAsString(dd), "No endpoint changes\n") + require.Equal(t, report.GetTextReportAsString(dd), "No endpoint changes, but there are some other changes\n") } func TestText1(t *testing.T) {