diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ca87603e..2d099083 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,6 +3,7 @@ on: push: branches: - 'main' + - 'origin' tags: - 'v*' jobs: @@ -50,7 +51,7 @@ jobs: platforms: linux/amd64,linux/arm64 context: ./ file: ./Dockerfile - tags: tufin/oasdiff:${{ github.ref_name }}, tufin/oasdiff:stable, tufin/oasdiff:latest + tags: tufin/oasdiff:${{ github.ref_name }} labels: ${{ steps.meta.outputs.labels }} if: github.ref != 'refs/heads/main' && github.event_name != 'pull_request' diff --git a/checker/api_change.go b/checker/api_change.go index fe4e9e50..70233ce5 100644 --- a/checker/api_change.go +++ b/checker/api_change.go @@ -31,6 +31,7 @@ type ApiChange struct { } func NewApiChange(id string, config *Config, args []any, comment string, operationsSources *diff.OperationsSourcesMap, operation *openapi3.Operation, method, path string) ApiChange { + return ApiChange{ Id: id, Level: config.getLogLevel(id), @@ -43,7 +44,39 @@ func NewApiChange(id string, config *Config, args []any, comment string, operati CommonChange: CommonChange{ Attributes: getAttributes(config, operation), }, + SourceLine: getOriginLine(operation), + SourceColumn: getOriginCol(operation), + } +} + +func (c ApiChange) WithLocation(origin *openapi3.Origin, field string) ApiChange { + if origin == nil { + return c + } + location, ok := origin.Fields[field] + if !ok { + return c + } + + c.SourceLine = location.Line + c.SourceColumn = location.Column + return c +} + +func getOriginLine(operation *openapi3.Operation) int { + if operation == nil || operation.Origin == nil { + return 0 } + + return operation.Origin.Key.Line +} + +func getOriginCol(operation *openapi3.Operation) int { + if operation == nil || operation.Origin == nil { + return 0 + } + + return operation.Origin.Key.Column } func getAttributes(config *Config, operation *openapi3.Operation) map[string]any { diff --git a/checker/api_change_test.go b/checker/api_change_test.go index 4d35ead6..49078a05 100644 --- a/checker/api_change_test.go +++ b/checker/api_change_test.go @@ -3,6 +3,7 @@ package checker_test import ( "testing" + "github.com/getkin/kin-openapi/openapi3" "github.com/stretchr/testify/require" "github.com/tufin/oasdiff/checker" "github.com/tufin/oasdiff/load" @@ -83,3 +84,13 @@ func TestApiChange_SourceUrl(t *testing.T) { require.Equal(t, "", apiChangeSourceFile.GetSourceFile()) } + +func TestApiChange_WithLocation(t *testing.T) { + apiChangeSourceFile := apiChange.WithLocation(&openapi3.Origin{ + Fields: map[string]openapi3.Location{"field": { + Line: 1, + Column: 2, + }}}, "field") + require.Equal(t, 1, apiChangeSourceFile.SourceLine) + require.Equal(t, 2, apiChangeSourceFile.SourceColumn) +} diff --git a/checker/check_added_required_request_body.go b/checker/check_added_required_request_body.go index 14e23aa9..a681e607 100644 --- a/checker/check_added_required_request_body.go +++ b/checker/check_added_required_request_body.go @@ -38,7 +38,7 @@ func AddedRequestBodyCheck(diffReport *diff.Diff, operationsSources *diff.Operat operationItem.Revision, operation, path, - )) + ).WithLocation(operationItem.Revision.RequestBody.Value.Origin, "required")) } } } diff --git a/checker/check_api_deprecation_test.go b/checker/check_api_deprecation_test.go index 5acf8dbd..79d122f5 100644 --- a/checker/check_api_deprecation_test.go +++ b/checker/check_api_deprecation_test.go @@ -18,6 +18,12 @@ func open(file string) (*load.SpecInfo, error) { return load.NewSpecInfo(openapi3.NewLoader(), load.NewSource(file)) } +func openWithLocation(file string) (*load.SpecInfo, error) { + loader := openapi3.NewLoader() + loader.IncludeOrigin = true + return load.NewSpecInfo(loader, load.NewSource(file)) +} + func getDeprecationFile(file string) string { return fmt.Sprintf("../data/deprecation/%s", file) } diff --git a/checker/check_request_body_required_value_updated.go b/checker/check_request_body_required_value_updated.go index 7f1b037b..22c1f2e2 100644 --- a/checker/check_request_body_required_value_updated.go +++ b/checker/check_request_body_required_value_updated.go @@ -41,7 +41,7 @@ func RequestBodyRequiredUpdatedCheck(diffReport *diff.Diff, operationsSources *d operationItem.Revision, operation, path, - )) + ).WithLocation(operationItem.Revision.RequestBody.Value.Origin, "required")) } } return result diff --git a/checker/check_request_body_required_value_updated_test.go b/checker/check_request_body_required_value_updated_test.go index 67950e66..a09b1ed8 100644 --- a/checker/check_request_body_required_value_updated_test.go +++ b/checker/check_request_body_required_value_updated_test.go @@ -11,46 +11,69 @@ import ( // CL: changing request's body to required is breaking func TestRequestBodyBecameRequired(t *testing.T) { - s1, err := open("../data/checker/request_body_became_required_base.yaml") + s1, err := openWithLocation("../data/checker/request_body_became_required_base.yaml") require.NoError(t, err) - s2, err := open("../data/checker/request_body_became_required_base.yaml") + s2, err := openWithLocation("../data/checker/request_body_became_required_revision.yaml") require.NoError(t, err) - s2.Spec.Paths.Value("/api/v1.0/groups").Post.RequestBody.Value.Required = true - d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibility(singleCheckConfig(checker.RequestBodyRequiredUpdatedCheck), d, osm) require.Len(t, errs, 1) require.Equal(t, checker.ApiChange{ - Id: checker.RequestBodyBecameRequiredId, - Level: checker.ERR, - Operation: "POST", - Path: "/api/v1.0/groups", - Source: load.NewSource("../data/checker/request_body_became_required_base.yaml"), - OperationId: "createOneGroup", + Id: checker.RequestBodyBecameRequiredId, + Level: checker.ERR, + Operation: "POST", + Path: "/api/v1.0/groups", + Source: load.NewSource("../data/checker/request_body_became_required_revision.yaml"), + OperationId: "createOneGroup", + SourceLine: 19, + SourceColumn: 9, }, errs[0]) } // CL: changing request's body to optional func TestRequestBodyBecameOptional(t *testing.T) { - s1, err := open("../data/checker/request_body_became_optional_base.yaml") + s1, err := openWithLocation("../data/checker/request_body_became_optional_base.yaml") require.NoError(t, err) - s2, err := open("../data/checker/request_body_became_optional_base.yaml") + s2, err := openWithLocation("../data/checker/request_body_became_optional_revision.yaml") require.NoError(t, err) - s2.Spec.Paths.Value("/api/v1.0/groups").Post.RequestBody.Value.Required = false + d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) + require.NoError(t, err) + errs := checker.CheckBackwardCompatibilityUntilLevel(singleCheckConfig(checker.RequestBodyRequiredUpdatedCheck), d, osm, checker.INFO) + require.Len(t, errs, 1) + require.Equal(t, checker.ApiChange{ + Id: checker.RequestBodyBecameOptionalId, + Level: checker.INFO, + Operation: "POST", + Path: "/api/v1.0/groups", + Source: load.NewSource("../data/checker/request_body_became_optional_revision.yaml"), + OperationId: "createOneGroup", + SourceLine: 19, + SourceColumn: 9, + }, errs[0]) +} + +// CL: changing request's body to optional by deletion +func TestRequestBodyBecameOptionalDeleted(t *testing.T) { + s1, err := openWithLocation("../data/checker/request_body_became_optional_base.yaml") + require.NoError(t, err) + s2, err := openWithLocation("../data/checker/request_body_became_optional_deleted.yaml") + require.NoError(t, err) d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) errs := checker.CheckBackwardCompatibilityUntilLevel(singleCheckConfig(checker.RequestBodyRequiredUpdatedCheck), d, osm, checker.INFO) require.Len(t, errs, 1) require.Equal(t, checker.ApiChange{ - Id: checker.RequestBodyBecameOptionalId, - Level: checker.INFO, - Operation: "POST", - Path: "/api/v1.0/groups", - Source: load.NewSource("../data/checker/request_body_became_optional_base.yaml"), - OperationId: "createOneGroup", + Id: checker.RequestBodyBecameOptionalId, + Level: checker.INFO, + Operation: "POST", + Path: "/api/v1.0/groups", + Source: load.NewSource("../data/checker/request_body_became_optional_deleted.yaml"), + OperationId: "createOneGroup", + SourceLine: 9, + SourceColumn: 5, }, errs[0]) } diff --git a/data/checker/request_body_became_optional_deleted.yaml b/data/checker/request_body_became_optional_deleted.yaml new file mode 100644 index 00000000..42044a0b --- /dev/null +++ b/data/checker/request_body_became_optional_deleted.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.1 +info: + title: Tufin + version: "2.0" +servers: +- url: https://localhost:9080 +paths: + /api/v1.0/groups: + post: + tags: + - Group + operationId: createOneGroup + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Creates one project. + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: OK + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Conflict + summary: Create One Project +components: + parameters: + groupId: + in: path + name: groupId + required: true + schema: + type: string + schemas: + GroupView: + type: object + properties: + data: + type: object + properties: + created: + type: string + format: date-time + readOnly: true + pattern: "^[a-z]+$" + id: + type: string + readOnly: true + name: + type: string + required: + - name \ No newline at end of file diff --git a/data/checker/request_body_became_optional_revision.yaml b/data/checker/request_body_became_optional_revision.yaml new file mode 100644 index 00000000..5dddd5ed --- /dev/null +++ b/data/checker/request_body_became_optional_revision.yaml @@ -0,0 +1,60 @@ +openapi: 3.0.1 +info: + title: Tufin + version: "2.0" +servers: +- url: https://localhost:9080 +paths: + /api/v1.0/groups: + post: + tags: + - Group + operationId: createOneGroup + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Creates one project. + required: false + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: OK + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Conflict + summary: Create One Project +components: + parameters: + groupId: + in: path + name: groupId + required: true + schema: + type: string + schemas: + GroupView: + type: object + properties: + data: + type: object + properties: + created: + type: string + format: date-time + readOnly: true + pattern: "^[a-z]+$" + id: + type: string + readOnly: true + name: + type: string + required: + - name \ No newline at end of file diff --git a/data/checker/request_body_became_required_revision.yaml b/data/checker/request_body_became_required_revision.yaml new file mode 100644 index 00000000..5835dd48 --- /dev/null +++ b/data/checker/request_body_became_required_revision.yaml @@ -0,0 +1,60 @@ +openapi: 3.0.1 +info: + title: Tufin + version: "2.0" +servers: +- url: https://localhost:9080 +paths: + /api/v1.0/groups: + post: + tags: + - Group + operationId: createOneGroup + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Creates one project. + required: true + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: OK + "409": + content: + application/json: + schema: + $ref: '#/components/schemas/GroupView' + description: Conflict + summary: Create One Project +components: + parameters: + groupId: + in: path + name: groupId + required: true + schema: + type: string + schemas: + GroupView: + type: object + properties: + data: + type: object + properties: + created: + type: string + format: date-time + readOnly: true + pattern: "^[a-z]+$" + id: + type: string + readOnly: true + name: + type: string + required: + - name \ No newline at end of file diff --git a/diff/discriminator_diff.go b/diff/discriminator_diff.go index c0d24b29..acbb466f 100644 --- a/diff/discriminator_diff.go +++ b/diff/discriminator_diff.go @@ -2,6 +2,7 @@ package diff import ( "github.com/getkin/kin-openapi/openapi3" + "github.com/tufin/oasdiff/utils" ) // DiscriminatorDiff describes the changes between a pair of discriminator objects: https://swagger.io/specification/#discriminator-object @@ -60,7 +61,7 @@ func getDiscriminatorDiffInternal(config *Config, state *state, discriminator1, return nil, err } result.PropertyNameDiff = getValueDiff(discriminator1.PropertyName, discriminator2.PropertyName) - result.MappingDiff = getStringMapDiff(discriminator1.Mapping, discriminator2.Mapping) + result.MappingDiff = getStringMapDiff(utils.StringMap(discriminator1.Mapping), utils.StringMap(discriminator2.Mapping)) return result, nil } diff --git a/diff/oauth_flow.go b/diff/oauth_flow.go index 4befb6a7..a6ee119f 100644 --- a/diff/oauth_flow.go +++ b/diff/oauth_flow.go @@ -60,7 +60,7 @@ func getOAuthFlowDiffInternal(config *Config, state *state, flow1, flow2 *openap result.AuthorizationURLDiff = getValueDiff(flow1.AuthorizationURL, flow2.AuthorizationURL) result.TokenURLDiff = getValueDiff(flow1.TokenURL, flow2.TokenURL) result.RefreshURLDiff = getValueDiff(flow1.RefreshURL, flow2.RefreshURL) - result.ScopesDiff = getStringMapDiff(flow1.Scopes, flow2.Scopes) + result.ScopesDiff = getScopesDiff(flow1.Scopes, flow2.Scopes) return &result, nil } diff --git a/diff/scopes.go b/diff/scopes.go new file mode 100644 index 00000000..6ceab60f --- /dev/null +++ b/diff/scopes.go @@ -0,0 +1,16 @@ +package diff + +import ( + "github.com/getkin/kin-openapi/openapi3" + "github.com/tufin/oasdiff/utils" +) + +func getScopesDiff(scopes1, scopes2 openapi3.StringMap) *StringMapDiff { + diff := getStringMapDiff(utils.StringMap(scopes1), utils.StringMap(scopes2)) + + if diff.Empty() { + return nil + } + + return diff +} diff --git a/docs/BREAKING-CHANGES-EXAMPLES.md b/docs/BREAKING-CHANGES-EXAMPLES.md index 8862f666..5afe33f2 100644 --- a/docs/BREAKING-CHANGES-EXAMPLES.md +++ b/docs/BREAKING-CHANGES-EXAMPLES.md @@ -56,10 +56,10 @@ These examples are automatically generated from unit tests. [deleting an operation before sunset date is breaking](../checker/check_api_removed_test.go?plain=1#L11) [deleting an operation is breaking](../checker/check_breaking_test.go?plain=1#L45) [deleting sunset header for a deprecated endpoint is breaking](../checker/check_api_sunset_changed_test.go?plain=1#L11) -[deprecating an operation with a deprecation policy and an invalid stability level is breaking](../checker/check_api_deprecation_test.go?plain=1#L67) -[deprecating an operation with a deprecation policy and an invalid sunset date is breaking](../checker/check_api_deprecation_test.go?plain=1#L48) -[deprecating an operation with a deprecation policy and sunset date before required deprecation period is breaking](../checker/check_api_deprecation_test.go?plain=1#L214) -[deprecating an operation with a deprecation policy but without specifying sunset date is breaking](../checker/check_api_deprecation_test.go?plain=1#L103) +[deprecating an operation with a deprecation policy and an invalid stability level is breaking](../checker/check_api_deprecation_test.go?plain=1#L73) +[deprecating an operation with a deprecation policy and an invalid sunset date is breaking](../checker/check_api_deprecation_test.go?plain=1#L54) +[deprecating an operation with a deprecation policy and sunset date before required deprecation period is breaking](../checker/check_api_deprecation_test.go?plain=1#L220) +[deprecating an operation with a deprecation policy but without specifying sunset date is breaking](../checker/check_api_deprecation_test.go?plain=1#L109) [inclreasing request body min items is breaking](../checker/check_request_property_min_items_increased_test.go?plain=1#L12) [increasing max length in response is breaking](../checker/check_breaking_min_max_test.go?plain=1#L93) [increasing min items in request is breaking](../checker/check_breaking_min_max_test.go?plain=1#L236) @@ -129,21 +129,21 @@ These examples are automatically generated from unit tests. [changing response's body schema type from number to integer is not breaking](../checker/check_breaking_response_type_changed_test.go?plain=1#L52) [changing response's body schema type from number/none to integer/int32 is not breaking](../checker/check_breaking_response_type_changed_test.go?plain=1#L90) [changing servers is not breaking](../checker/check_not_breaking_test.go?plain=1#L252) -[deleting a path after sunset date of all contained operations is not breaking](../checker/check_api_deprecation_test.go?plain=1#L255) +[deleting a path after sunset date of all contained operations is not breaking](../checker/check_api_deprecation_test.go?plain=1#L261) [deleting a pattern from a schema is not breaking](../checker/check_breaking_test.go?plain=1#L459) [deleting a required write-only property in response body is not breaking](../checker/check_breaking_property_test.go?plain=1#L495) [deleting a tag is not breaking](../checker/check_not_breaking_test.go?plain=1#L71) -[deleting an operation after sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L33) +[deleting an operation after sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L39) [deleting an operation without sunset date is not breaking](../checker/check_api_removed_test.go?plain=1#L29) [deleting other extension (not sunset) header for a deprecated endpoint is not breaking](../checker/check_api_sunset_changed_test.go?plain=1#L84) [deprecating a header is not breaking](../checker/check_not_breaking_test.go?plain=1#L226) [deprecating a parameter is not breaking](../checker/check_not_breaking_test.go?plain=1#L213) [deprecating a schema is not breaking](../checker/check_not_breaking_test.go?plain=1#L239) -[deprecating an operation with a default deprecation policy but without specifying sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L121) -[deprecating an operation with a deprecation policy and sunset date after required deprecation period is not breaking](../checker/check_api_deprecation_test.go?plain=1#L234) -[deprecating an operation without a deprecation policy and without specifying sunset date is not breaking for alpha level](../checker/check_api_deprecation_test.go?plain=1#L137) -[deprecating an operation without a deprecation policy and without specifying sunset date is not breaking for draft level](../checker/check_api_deprecation_test.go?plain=1#L171) -[deprecating an operation without a deprecation policy but without specifying sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L87) +[deprecating an operation with a default deprecation policy but without specifying sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L127) +[deprecating an operation with a deprecation policy and sunset date after required deprecation period is not breaking](../checker/check_api_deprecation_test.go?plain=1#L240) +[deprecating an operation without a deprecation policy and without specifying sunset date is not breaking for alpha level](../checker/check_api_deprecation_test.go?plain=1#L143) +[deprecating an operation without a deprecation policy and without specifying sunset date is not breaking for draft level](../checker/check_api_deprecation_test.go?plain=1#L177) +[deprecating an operation without a deprecation policy but without specifying sunset date is not breaking](../checker/check_api_deprecation_test.go?plain=1#L93) [descreasing request body min items is not breaking](../checker/check_request_property_min_items_increased_test.go?plain=1#L35) [increasing max length in request is not breaking](../checker/check_breaking_min_max_test.go?plain=1#L76) [increasing min items in response is not breaking](../checker/check_breaking_min_max_test.go?plain=1#L250) @@ -162,8 +162,8 @@ These examples are automatically generated from unit tests. [reducing min length in request is not breaking](../checker/check_breaking_min_max_test.go?plain=1#L48) [removing an existing response with error status is not breaking](../checker/check_breaking_test.go?plain=1#L406) [removing an existing response with unparseable status is not breaking](../checker/check_breaking_test.go?plain=1#L390) -[removing the path without a deprecation policy and without specifying sunset date is not breaking for alpha level](../checker/check_api_deprecation_test.go?plain=1#L152) -[removing the path without a deprecation policy and without specifying sunset date is not breaking for draft level](../checker/check_api_deprecation_test.go?plain=1#L188) +[removing the path without a deprecation policy and without specifying sunset date is not breaking for alpha level](../checker/check_api_deprecation_test.go?plain=1#L158) +[removing the path without a deprecation policy and without specifying sunset date is not breaking for draft level](../checker/check_api_deprecation_test.go?plain=1#L194) [renaming a path parameter is not breaking](../checker/check_breaking_test.go?plain=1#L136) ## Examples of info-level changes for changelog @@ -251,6 +251,7 @@ These examples are automatically generated from unit tests. [changing request property type](../checker/check_request_property_type_changed_test.go?plain=1#L64) [changing request query parameter format](../checker/check_request_parameters_type_changed_test.go?plain=1#L110) [changing request query parameter type](../checker/check_request_parameters_type_changed_test.go?plain=1#L38) +[changing request's body to optional by deletion](../checker/check_request_body_required_value_updated_test.go?plain=1#L58) [changing request's body to optional](../checker/check_request_body_required_value_updated_test.go?plain=1#L35) [changing request's body to required is breaking](../checker/check_request_body_required_value_updated_test.go?plain=1#L12) [changing required request property to not read-only](../checker/check_request_property_write_only_read_only_test.go?plain=1#L187) @@ -306,8 +307,8 @@ These examples are automatically generated from unit tests. [making request property required, while also giving it a default value](../checker/check_request_property_required_updated_test.go?plain=1#L58) [new header, query and cookie request params](../checker/check_new_request_non_path_parameter_test.go?plain=1#L11) [new paths or path operations](../checker/check_api_added_test.go?plain=1#L11) -[path operations that became deprecated](../checker/check_api_deprecation_test.go?plain=1#L270) -[path operations that were re-activated](../checker/check_api_deprecation_test.go?plain=1#L293) +[path operations that became deprecated](../checker/check_api_deprecation_test.go?plain=1#L276) +[path operations that were re-activated](../checker/check_api_deprecation_test.go?plain=1#L299) [removing 'allOf' subschema from the request body or request body property](../checker/check_request_property_all_of_updated_test.go?plain=1#L46) [removing 'allOf' subschema from the response body or response body property](../checker/check_response_property_all_of_updated_test.go?plain=1#L46) [removing 'anyOf' schema from the request body or request body property](../checker/check_request_property_any_of_updated_test.go?plain=1#L46) diff --git a/formatters/changes.go b/formatters/changes.go index 0230646a..158cf8c4 100644 --- a/formatters/changes.go +++ b/formatters/changes.go @@ -5,17 +5,19 @@ import ( ) type Change struct { - Id string `json:"id,omitempty" yaml:"id,omitempty"` - Text string `json:"text,omitempty" yaml:"text,omitempty"` - Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` - Level checker.Level `json:"level" yaml:"level"` - Operation string `json:"operation,omitempty" yaml:"operation,omitempty"` - OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` - Path string `json:"path,omitempty" yaml:"path,omitempty"` - Source string `json:"source,omitempty" yaml:"source,omitempty"` - Section string `json:"section,omitempty" yaml:"section,omitempty"` - IsBreaking bool `json:"-" yaml:"-"` - Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"` + Id string `json:"id,omitempty" yaml:"id,omitempty"` + Text string `json:"text,omitempty" yaml:"text,omitempty"` + Comment string `json:"comment,omitempty" yaml:"comment,omitempty"` + Level checker.Level `json:"level" yaml:"level"` + Operation string `json:"operation,omitempty" yaml:"operation,omitempty"` + OperationId string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Path string `json:"path,omitempty" yaml:"path,omitempty"` + Source string `json:"source,omitempty" yaml:"source,omitempty"` + Section string `json:"section,omitempty" yaml:"section,omitempty"` + IsBreaking bool `json:"-" yaml:"-"` + Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"` + SourceLine int `json:"sourceLine,omitempty" yaml:"sourceLine,omitempty"` + SourceColumm int `json:"sourceColumn,omitempty" yaml:"sourceColumn,omitempty"` } type Changes []Change @@ -24,16 +26,18 @@ func NewChanges(originalChanges checker.Changes, l checker.Localizer) Changes { changes := make(Changes, len(originalChanges)) for i, change := range originalChanges { changes[i] = Change{ - Section: change.GetSection(), - Id: change.GetId(), - Text: change.GetUncolorizedText(l), - Comment: change.GetComment(l), - Level: change.GetLevel(), - Operation: change.GetOperation(), - OperationId: change.GetOperationId(), - Path: change.GetPath(), - Source: change.GetSource(), - Attributes: change.GetAttributes(), + Section: change.GetSection(), + Id: change.GetId(), + Text: change.GetUncolorizedText(l), + Comment: change.GetComment(l), + Level: change.GetLevel(), + Operation: change.GetOperation(), + OperationId: change.GetOperationId(), + Path: change.GetPath(), + Source: change.GetSource(), + Attributes: change.GetAttributes(), + SourceLine: change.GetSourceLine(), + SourceColumm: change.GetSourceColumn(), } } return changes diff --git a/formatters/format_githubactions.go b/formatters/format_githubactions.go index 34737ec9..ecfb5ec3 100644 --- a/formatters/format_githubactions.go +++ b/formatters/format_githubactions.go @@ -50,16 +50,16 @@ func (f GitHubActionsFormatter) RenderChangelog(changes checker.Changes, opts Re params = append(params, "file="+change.GetSourceFile()) } if change.GetSourceColumn() != 0 { - params = append(params, "col="+strconv.Itoa(change.GetSourceColumn()+1)) + params = append(params, "col="+strconv.Itoa(change.GetSourceColumn())) } if change.GetSourceColumnEnd() != 0 { - params = append(params, "endColumn="+strconv.Itoa(change.GetSourceColumnEnd()+1)) + params = append(params, "endColumn="+strconv.Itoa(change.GetSourceColumnEnd())) } if change.GetSourceLine() != 0 { - params = append(params, "line="+strconv.Itoa(change.GetSourceLine()+1)) + params = append(params, "line="+strconv.Itoa(change.GetSourceLine())) } if change.GetSourceLineEnd() != 0 { - params = append(params, "endLine="+strconv.Itoa(change.GetSourceLineEnd()+1)) + params = append(params, "endLine="+strconv.Itoa(change.GetSourceLineEnd())) } buf.WriteString(fmt.Sprintf("::%s %s::%s\n", githubActionsSeverity[change.GetLevel()], strings.Join(params, ","), getMessage(change, f.Localizer))) diff --git a/formatters/format_githubactions_test.go b/formatters/format_githubactions_test.go index ca931274..f9477c35 100644 --- a/formatters/format_githubactions_test.go +++ b/formatters/format_githubactions_test.go @@ -127,7 +127,7 @@ func TestGitHubActionsFormatter_RenderChangelog_FileLocation(t *testing.T) { // check output output, err := gitHubFormatter.RenderChangelog(testChanges, formatters.NewRenderOpts(), nil) assert.NoError(t, err) - expectedOutput := "::error title=change_id,file=openapi.json,col=6,endColumn=11,line=21,endLine=26::in API GET /api/test This is a breaking change.\n" + expectedOutput := "::error title=change_id,file=openapi.json,col=5,endColumn=10,line=20,endLine=25::in API GET /api/test This is a breaking change.\n" assert.Equal(t, expectedOutput, string(output)) } diff --git a/go.mod b/go.mod index 1b65afe6..5bc14c08 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.5 require ( cloud.google.com/go v0.115.0 github.com/TwiN/go-color v1.4.1 - github.com/getkin/kin-openapi v0.127.0 + github.com/getkin/kin-openapi v0.128.1-0.20241211220347-325cecc5e4e1 github.com/oasdiff/telemetry v0.1.2 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 @@ -23,6 +23,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 // indirect + github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -45,7 +47,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect diff --git a/go.sum b/go.sum index 3ea92b53..c7008498 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= -github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/getkin/kin-openapi v0.128.1-0.20241211220347-325cecc5e4e1 h1:jIUT6NT0F50dhHXlFWsGfdJI/5OnnzO6HZHSiSh5DfM= +github.com/getkin/kin-openapi v0.128.1-0.20241211220347-325cecc5e4e1/go.mod h1:gmWI+b/J45xqpyK5wJmRRZse5wefA5H0RDMK46kLUtI= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= @@ -29,8 +29,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -47,6 +45,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/oasdiff/telemetry v0.1.2 h1:hkmA5YTISVF2/zWa23y29WdPws1Q53pyLOFYMHXoZ1U= github.com/oasdiff/telemetry v0.1.2/go.mod h1:Y0DaW/CasxZ+vzR54btj430XsL6DGF+rMbJkRld6u3M= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 h1:nZspmSkneBbtxU9TopEAE0CY+SBJLxO8LPUlw2vG4pU= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80/go.mod h1:7tFDb+Y51LcDpn26GccuUgQXUk6t0CXZsivKjyimYX8= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 h1:t05Ww3DxZutOqbMN+7OIuqDwXbhl32HiZGpLy26BAPc= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= diff --git a/internal/diff.go b/internal/diff.go index 2474c16d..5ae54c2b 100644 --- a/internal/diff.go +++ b/internal/diff.go @@ -72,6 +72,7 @@ func calcDiff(flags *Flags) (*diffResult, *ReturnError) { loader := openapi3.NewLoader() loader.IsExternalRefsAllowed = true + loader.IncludeOrigin = true if flags.getComposed() { return composedDiff(loader, flags)