From a25eacd3d797e879e0da452f19e3572e9d56d71c Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Fri, 30 Aug 2024 16:04:26 +0300 Subject: [PATCH] add message generator --- checker/check_breaking_test.go | 2 +- checker/check_not_breaking_test.go | 32 ++--- ...eck_request_body_mediatype_updated_test.go | 6 +- ...equest_body_required_value_updated_test.go | 6 +- .../check_request_discriminator_updated.go | 37 +++-- ...heck_request_discriminator_updated_test.go | 122 ++++++++-------- checker/generator/checks.go | 125 +++++++++++++++++ checker/generator/checks_test.go | 12 ++ checker/generator/messages.yaml | 130 ++++++++++++++++++ checker/generator/value_set.go | 30 ++++ checker/localizations/localizations.go | 14 +- checker/localizations_src/en/messages.yaml | 12 +- go.mod | 1 + go.sum | 2 + scripts/get_tested_ids.sh | 6 +- 15 files changed, 429 insertions(+), 108 deletions(-) create mode 100644 checker/generator/checks.go create mode 100644 checker/generator/checks_test.go create mode 100644 checker/generator/messages.yaml create mode 100644 checker/generator/value_set.go diff --git a/checker/check_breaking_test.go b/checker/check_breaking_test.go index e7fe25cf..df70125e 100644 --- a/checker/check_breaking_test.go +++ b/checker/check_breaking_test.go @@ -304,7 +304,7 @@ func TestBreaking_OperationIdRemoved(t *testing.T) { require.Len(t, errs, 1) require.Equal(t, checker.APIOperationIdRemovedId, errs[0].GetId()) require.Equal(t, "api operation id 'GetSecurityScores' removed and replaced with 'newOperationId'", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) - verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APIOperationIdRemovedId) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APIOperationIdRemovedId, "api operation id 'GetSecurityScores' removed and replaced with 'newOperationId'") } // BC: removing/updating an enum in request body is breaking (optional): request-body-enum-value-removed diff --git a/checker/check_not_breaking_test.go b/checker/check_not_breaking_test.go index 69dcacbb..f3248938 100644 --- a/checker/check_not_breaking_test.go +++ b/checker/check_not_breaking_test.go @@ -11,7 +11,7 @@ import ( "github.com/tufin/oasdiff/diff" ) -func verifyNonBreakingChangeIsChangelogEntry(t *testing.T, d *diff.Diff, osm *diff.OperationsSourcesMap, changeId string) { +func verifyNonBreakingChangeIsChangelogEntry(t *testing.T, d *diff.Diff, osm *diff.OperationsSourcesMap, changeId string, text string) { t.Helper() // Check no breaking change is detected @@ -22,6 +22,7 @@ func verifyNonBreakingChangeIsChangelogEntry(t *testing.T, d *diff.Diff, osm *di require.Len(t, errs, 1) require.Equal(t, checker.INFO, errs[0].GetLevel()) require.Equal(t, changeId, errs[0].GetId()) + require.Equal(t, text, errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) } // BC: no change is not breaking @@ -50,7 +51,7 @@ func TestBreaking_AddingOptionalRequestBody(t *testing.T) { require.Empty(t, errs) } -// CL: changing an existing request body from required to optional +// CL: changing an existing request body from required to optional: request-body-became-optional func TestBreaking_RequestBodyRequiredDisabled(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) @@ -65,7 +66,7 @@ func TestBreaking_RequestBodyRequiredDisabled(t *testing.T) { d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.RequestBodyBecameOptionalId) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.RequestBodyBecameOptionalId, "request body became optional") } // BC: deleting a tag is not breaking @@ -129,7 +130,7 @@ func TestBreaking_NewOptionalHeaderParam(t *testing.T) { require.Empty(t, errs) } -// CL: changing an existing header param to optional +// CL: changing an existing header param to optional: request-parameter-became-optional func TestBreaking_HeaderParamRequiredDisabled(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) @@ -139,10 +140,7 @@ func TestBreaking_HeaderParamRequiredDisabled(t *testing.T) { d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - changes := checker.CheckBackwardCompatibilityUntilLevel(allChecksConfig(), d, osm, checker.INFO) - require.NotEmpty(t, changes) - require.Equal(t, checker.RequestParameterBecomeOptionalId, changes[0].GetId()) - require.Len(t, changes, 1) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.RequestParameterBecomeOptionalId, "'header' request parameter 'network-policies' became optional") } func deleteResponseHeader(response *openapi3.Response, name string) { @@ -195,7 +193,7 @@ func TestBreaking_ResponseAddMediaType(t *testing.T) { require.Empty(t, errs) } -// CL: deprecating an operation with sunset greater than min +// CL: deprecating an operation with sunset greater than min: endpoint-deprecated func TestBreaking_DeprecatedOperation(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) @@ -205,9 +203,7 @@ func TestBreaking_DeprecatedOperation(t *testing.T) { d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - errs := checker.CheckBackwardCompatibilityUntilLevel(allChecksConfig(), d, osm, checker.INFO) - require.Len(t, errs, 1) - require.Equal(t, errs[0].GetLevel(), checker.INFO) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.EndpointDeprecatedId, "endpoint deprecated") } // BC: deprecating a parameter is not breaking @@ -263,7 +259,7 @@ func TestBreaking_Servers(t *testing.T) { require.Empty(t, errs) } -// BC: adding a tag is not breaking +// BC: adding a tag is not breaking: api-tag-added func TestBreaking_TagAdded(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) @@ -271,10 +267,10 @@ func TestBreaking_TagAdded(t *testing.T) { s2.Spec.Paths.Value(securityScorePath).Get.Tags = append(s2.Spec.Paths.Value(securityScorePath).Get.Tags, "newTag") d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APITagAddedId) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APITagAddedId, "added api tag 'newTag'") } -// BC: adding an operation ID is not breaking +// BC: adding an operation ID is not breaking: api-operation-id-added func TestBreaking_OperationIdAdded(t *testing.T) { s1 := l(t, 1) s2 := l(t, 1) @@ -283,10 +279,10 @@ func TestBreaking_OperationIdAdded(t *testing.T) { d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APIOperationIdAddId) + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.APIOperationIdAddId, "api operation id 'GetSecurityScores' was added") } -// BC: adding a required property to response is not breaking +// BC: adding a required property to response is not breaking: response-required-property-added func TestBreaking_RequiredResponsePropertyAdded(t *testing.T) { s1, err := open("../data/checker/response_required_property_added_base.yaml") require.NoError(t, err) @@ -296,5 +292,5 @@ func TestBreaking_RequiredResponsePropertyAdded(t *testing.T) { d, osm, err := diff.GetWithOperationsSourcesMap(diff.NewConfig(), s1, s2) require.NoError(t, err) - verifyNonBreakingChangeIsChangelogEntry(t, d, osm, "response-required-property-added") + verifyNonBreakingChangeIsChangelogEntry(t, d, osm, checker.ResponseRequiredPropertyAddedId, "added required property 'data/new' to response status '200'") } diff --git a/checker/check_request_body_mediatype_updated_test.go b/checker/check_request_body_mediatype_updated_test.go index 61c337d9..2a29e4e0 100644 --- a/checker/check_request_body_mediatype_updated_test.go +++ b/checker/check_request_body_mediatype_updated_test.go @@ -9,7 +9,7 @@ import ( "github.com/tufin/oasdiff/load" ) -// CL: adding a new media type to request body +// CL: adding a new media type to request body: request-body-media-type-added func TestRequestBodyMediaTypeAdded(t *testing.T) { s1, err := open("../data/checker/request_body_media_type_updated_base.yaml") require.NoError(t, err) @@ -29,9 +29,10 @@ func TestRequestBodyMediaTypeAdded(t *testing.T) { Source: load.NewSource("../data/checker/request_body_media_type_updated_revision.yaml"), OperationId: "createOneGroup", }, errs[0]) + require.Equal(t, "added media type 'application/json' to the request body", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) } -// CL: removing media type from request body +// CL: removing media type from request body: request-body-media-type-removed func TestRequestBodyMediaTypeRemoved(t *testing.T) { s1, err := open("../data/checker/request_body_media_type_updated_revision.yaml") require.NoError(t, err) @@ -51,4 +52,5 @@ func TestRequestBodyMediaTypeRemoved(t *testing.T) { Source: load.NewSource("../data/checker/request_body_media_type_updated_base.yaml"), OperationId: "createOneGroup", }, errs[0]) + require.Equal(t, "removed media type 'application/json' from the request body", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) } diff --git a/checker/check_request_body_required_value_updated_test.go b/checker/check_request_body_required_value_updated_test.go index 67950e66..70fbbc3a 100644 --- a/checker/check_request_body_required_value_updated_test.go +++ b/checker/check_request_body_required_value_updated_test.go @@ -9,7 +9,7 @@ import ( "github.com/tufin/oasdiff/load" ) -// CL: changing request's body to required is breaking +// CL: changing request's body to required is breaking: request-body-became-required func TestRequestBodyBecameRequired(t *testing.T) { s1, err := open("../data/checker/request_body_became_required_base.yaml") require.NoError(t, err) @@ -30,9 +30,10 @@ func TestRequestBodyBecameRequired(t *testing.T) { Source: load.NewSource("../data/checker/request_body_became_required_base.yaml"), OperationId: "createOneGroup", }, errs[0]) + require.Equal(t, "request body became required", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) } -// CL: changing request's body to optional +// CL: changing request's body to optional: request-body-became-optional func TestRequestBodyBecameOptional(t *testing.T) { s1, err := open("../data/checker/request_body_became_optional_base.yaml") require.NoError(t, err) @@ -53,4 +54,5 @@ func TestRequestBodyBecameOptional(t *testing.T) { Source: load.NewSource("../data/checker/request_body_became_optional_base.yaml"), OperationId: "createOneGroup", }, errs[0]) + require.Equal(t, "request body became optional", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) } diff --git a/checker/check_request_discriminator_updated.go b/checker/check_request_discriminator_updated.go index bd011871..0a8f5b75 100644 --- a/checker/check_request_discriminator_updated.go +++ b/checker/check_request_discriminator_updated.go @@ -50,7 +50,7 @@ func RequestDiscriminatorUpdatedCheck(diffReport *diff.Diff, operationsSources * )) } - for _, mediaTypeDiff := range operationItem.RequestBodyDiff.ContentDiff.MediaTypeModified { + for mediaType, mediaTypeDiff := range operationItem.RequestBodyDiff.ContentDiff.MediaTypeModified { if mediaTypeDiff.SchemaDiff == nil { continue } @@ -58,6 +58,7 @@ func RequestDiscriminatorUpdatedCheck(diffReport *diff.Diff, operationsSources * processDiscriminatorDiffForRequest( mediaTypeDiff.SchemaDiff.DiscriminatorDiff, "", + mediaType, appendResultItem) CheckModifiedPropertiesDiff( @@ -66,6 +67,7 @@ func RequestDiscriminatorUpdatedCheck(diffReport *diff.Diff, operationsSources * processDiscriminatorDiffForRequest( propertyDiff.DiscriminatorDiff, propertyFullName(propertyPath, propertyName), + mediaType, appendResultItem) }) @@ -78,6 +80,7 @@ func RequestDiscriminatorUpdatedCheck(diffReport *diff.Diff, operationsSources * func processDiscriminatorDiffForRequest( discriminatorDiff *diff.DiscriminatorDiff, propertyName string, + mediaType string, appendResultItem func(messageId string, a ...any)) { if discriminatorDiff == nil { @@ -91,17 +94,17 @@ func processDiscriminatorDiffForRequest( if discriminatorDiff.Added { if propertyName == "" { - appendResultItem(messageIdPrefix + "-added") + appendResultItem(messageIdPrefix+"-added", mediaType) } else { - appendResultItem(messageIdPrefix+"-added", propertyName) + appendResultItem(messageIdPrefix+"-added", propertyName, mediaType) } return } if discriminatorDiff.Deleted { if propertyName == "" { - appendResultItem(messageIdPrefix + "-removed") + appendResultItem(messageIdPrefix+"-removed", mediaType) } else { - appendResultItem(messageIdPrefix+"-removed", propertyName) + appendResultItem(messageIdPrefix+"-removed", propertyName, mediaType) } return } @@ -110,12 +113,14 @@ func processDiscriminatorDiffForRequest( if propertyName == "" { appendResultItem(messageIdPrefix+"-property-name-changed", discriminatorDiff.PropertyNameDiff.From, - discriminatorDiff.PropertyNameDiff.To) + discriminatorDiff.PropertyNameDiff.To, + mediaType) } else { appendResultItem(messageIdPrefix+"-property-name-changed", propertyName, discriminatorDiff.PropertyNameDiff.From, - discriminatorDiff.PropertyNameDiff.To) + discriminatorDiff.PropertyNameDiff.To, + mediaType) } } @@ -123,22 +128,26 @@ func processDiscriminatorDiffForRequest( if len(discriminatorDiff.MappingDiff.Added) > 0 { if propertyName == "" { appendResultItem(messageIdPrefix+"-mapping-added", - discriminatorDiff.MappingDiff.Added) + discriminatorDiff.MappingDiff.Added, + mediaType) } else { appendResultItem(messageIdPrefix+"-mapping-added", discriminatorDiff.MappingDiff.Added, - propertyName) + propertyName, + mediaType) } } if len(discriminatorDiff.MappingDiff.Deleted) > 0 { if propertyName == "" { appendResultItem(messageIdPrefix+"-mapping-deleted", - discriminatorDiff.MappingDiff.Deleted) + discriminatorDiff.MappingDiff.Deleted, + mediaType) } else { appendResultItem(messageIdPrefix+"-mapping-deleted", discriminatorDiff.MappingDiff.Deleted, - propertyName) + propertyName, + mediaType) } } @@ -147,13 +156,15 @@ func processDiscriminatorDiffForRequest( appendResultItem(messageIdPrefix+"-mapping-changed", k, v.From, - v.To) + v.To, + mediaType) } else { appendResultItem(messageIdPrefix+"-mapping-changed", k, v.From, v.To, - propertyName) + propertyName, + mediaType) } } diff --git a/checker/check_request_discriminator_updated_test.go b/checker/check_request_discriminator_updated_test.go index c75ac798..dc7276a3 100644 --- a/checker/check_request_discriminator_updated_test.go +++ b/checker/check_request_discriminator_updated_test.go @@ -10,7 +10,7 @@ import ( "github.com/tufin/oasdiff/utils" ) -// CL: adding discriminator to the request body or request body property +// CL: adding discriminator to the request body or request body property: request-body-discriminator-added, request-property-discriminator-added func TestRequestDiscriminatorUpdatedCheckAdded(t *testing.T) { s1, err := open("../data/checker/request_property_discriminator_added_base.yaml") require.NoError(t, err) @@ -23,27 +23,30 @@ func TestRequestDiscriminatorUpdatedCheckAdded(t *testing.T) { require.Len(t, errs, 2) - require.ElementsMatch(t, []checker.ApiChange{ - { - Id: checker.RequestBodyDiscriminatorAddedId, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_revision.yaml"), - OperationId: "updatePets", - }, - { - Id: checker.RequestPropertyDiscriminatorAddedId, - Args: []any{"/oneOf[#/components/schemas/Dog]/breed"}, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_revision.yaml"), - OperationId: "updatePets", - }}, errs) + require.Equal(t, checker.ApiChange{ + Id: checker.RequestBodyDiscriminatorAddedId, + Args: []any{"application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_revision.yaml"), + OperationId: "updatePets", + }, errs[0]) + require.Equal(t, "added discriminator to media-type 'application/json' of request body", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) + + require.Equal(t, checker.ApiChange{ + Id: checker.RequestPropertyDiscriminatorAddedId, + Args: []any{"/oneOf[#/components/schemas/Dog]/breed", "application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_revision.yaml"), + OperationId: "updatePets", + }, errs[1]) + require.Equal(t, "added discriminator to property '/oneOf[#/components/schemas/Dog]/breed' of media-type 'application/json' of request body", errs[1].GetUncolorizedText(checker.NewDefaultLocalizer())) } -// CL: removing discriminator from the request body or request body property +// CL: removing discriminator from the request body or request body property: request-body-discriminator-removed, request-property-discriminator-removed func TestRequestDiscriminatorUpdatedCheckRemoved(t *testing.T) { s1, err := open("../data/checker/request_property_discriminator_added_revision.yaml") require.NoError(t, err) @@ -56,24 +59,27 @@ func TestRequestDiscriminatorUpdatedCheckRemoved(t *testing.T) { require.Len(t, errs, 2) - require.ElementsMatch(t, []checker.ApiChange{ - { - Id: checker.RequestBodyDiscriminatorRemovedId, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_base.yaml"), - OperationId: "updatePets", - }, - { - Id: checker.RequestPropertyDiscriminatorRemovedId, - Args: []any{"/oneOf[#/components/schemas/Dog]/breed"}, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_base.yaml"), - OperationId: "updatePets", - }}, errs) + require.Equal(t, checker.ApiChange{ + Id: checker.RequestBodyDiscriminatorRemovedId, + Args: []any{"application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_base.yaml"), + OperationId: "updatePets", + }, errs[0]) + require.Equal(t, "removed discriminator from media-type 'application/json' of request body", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) + + require.Equal(t, checker.ApiChange{ + Id: checker.RequestPropertyDiscriminatorRemovedId, + Args: []any{"/oneOf[#/components/schemas/Dog]/breed", "application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_base.yaml"), + OperationId: "updatePets", + }, errs[1]) + require.Equal(t, "removed discriminator from property '/oneOf[#/components/schemas/Dog]/breed' of media-type 'application/json' of request body", errs[1].GetUncolorizedText(checker.NewDefaultLocalizer())) } // CL: changing discriminator propertyName in the request body or request body property @@ -89,25 +95,27 @@ func TestRequestDiscriminatorUpdatedCheckPropertyNameChanging(t *testing.T) { require.Len(t, errs, 2) - require.ElementsMatch(t, []checker.ApiChange{ - { - Id: checker.RequestBodyDiscriminatorPropertyNameChangedId, - Args: []any{"petType", "petType2"}, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_property_name_changed.yaml"), - OperationId: "updatePets", - }, - { - Id: checker.RequestPropertyDiscriminatorPropertyNameChangedId, - Args: []any{"/oneOf[#/components/schemas/Dog]/breed", "name", "name2"}, - Level: checker.INFO, - Operation: "POST", - Path: "/pets", - Source: load.NewSource("../data/checker/request_property_discriminator_added_property_name_changed.yaml"), - OperationId: "updatePets", - }}, errs) + require.Equal(t, checker.ApiChange{ + Id: checker.RequestBodyDiscriminatorPropertyNameChangedId, + Args: []any{"petType", "petType2", "application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_property_name_changed.yaml"), + OperationId: "updatePets", + }, errs[0]) + require.Equal(t, "discriminator property name changed from 'petType' to 'petType2' for media-type 'application/json' of request body", errs[0].GetUncolorizedText(checker.NewDefaultLocalizer())) + + require.Equal(t, checker.ApiChange{ + Id: checker.RequestPropertyDiscriminatorPropertyNameChangedId, + Args: []any{"/oneOf[#/components/schemas/Dog]/breed", "name", "name2", "application/json"}, + Level: checker.INFO, + Operation: "POST", + Path: "/pets", + Source: load.NewSource("../data/checker/request_property_discriminator_added_property_name_changed.yaml"), + OperationId: "updatePets", + }, errs[1]) + require.Equal(t, "request discriminator property name changed from '/oneOf[#/components/schemas/Dog]/breed' to 'name' for 'name2' request property of media-type 'application/json'", errs[1].GetUncolorizedText(checker.NewDefaultLocalizer())) } // CL: changing discriminator mapping in the request body or request body property diff --git a/checker/generator/checks.go b/checker/generator/checks.go new file mode 100644 index 00000000..05ef3f24 --- /dev/null +++ b/checker/generator/checks.go @@ -0,0 +1,125 @@ +package generator + +import ( + "fmt" + "os" + "slices" + "strings" + + "github.com/iancoleman/strcase" +) + +func Generate() error { + out, err := os.Create("messages.yaml") + if err != nil { + return err + } + defer out.Close() + + // request + getSchemaValues([]string{"media-type", "request body"}, nil).generate(out) + getSchemaValues([]string{"property", "media-type", "request body"}, nil).generate(out) + getSchemaValues([]string{"request parameter"}, []bool{true}).generate(out) + + // response + getSchemaValues([]string{"media-type", "response"}, nil).generate(out) + getSchemaValues([]string{"property", "media-type", "response"}, nil).generate(out) + // getSchemaValues([]string{"request parameter"}, []bool{true}).generate(out) + + return nil +} + +func getSchemaValues(hierarchy []string, attributed []bool) valueSets { + return []valueSet{ + { + adjective: "value", + hierarchy: hierarchy, + attributed: attributed, + nouns: []string{"max", "maxLength", "min", "minLength", "minItems", "maxItems"}, + actions: []string{"set", "increase", "decrease"}, + }, + { + adjective: "", + hierarchy: hierarchy, + nouns: []string{"type/format"}, + actions: []string{"change", "generalize"}, + }, + { + adjective: "", + hierarchy: hierarchy, + nouns: []string{"anyOf", "oneOf", "allOf"}, + actions: []string{"add", "remove"}, + }, + } +} + +func generateId(hierarchy []string, noun, action string) string { + if before, _, found := strings.Cut(noun, "/"); found { + noun = before + } + return strcase.ToKebab(concat(hierarchy) + "-" + noun + "-" + conjugate(action)) +} + +func concat(list []string) string { + copy := slices.Clone(list) + slices.Reverse(copy) + return strings.Join(copy, "-") +} + +func generateMessage(hierarchy []string, atttibuted []bool, noun, adjective, action string) string { + return standardizeSpaces(fmt.Sprintf("%s %s of %s was %s", noun, adjective, getHierarchyMessage(hierarchy, atttibuted), getActionMessage(action))) +} + +func getHierarchyMessage(hierarchy []string, atttibuted []bool) string { + + copy := slices.Clone(hierarchy) + + if atttibuted != nil { + for i, s := range hierarchy { + if atttibuted[i] { + copy[i] = "%s " + s + } + } + } + + result := strings.Join(copy, " %s of ") + + if !isTopLevel(hierarchy[len(hierarchy)-1]) { + result += " %s" + } + + return result +} + +func isTopLevel(s string) bool { + return s == "request body" +} + +func standardizeSpaces(s string) string { + return strings.Join(strings.Fields(s), " ") +} + +func getActionMessage(action string) string { + if isUnary(action) { + return conjugate(action) + " to %s" + } + return conjugate(action) + " from %s to %s" +} + +func isUnary(action string) bool { + switch action { + case "set": + return true + } + return false +} + +func conjugate(verb string) string { + switch verb { + case "set": + return "set" + case "add": + return "added" + } + return verb + "d" +} diff --git a/checker/generator/checks_test.go b/checker/generator/checks_test.go new file mode 100644 index 00000000..877cb293 --- /dev/null +++ b/checker/generator/checks_test.go @@ -0,0 +1,12 @@ +package generator_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tufin/oasdiff/checker/generator" +) + +func TestGenerator(t *testing.T) { + require.NoError(t, generator.Generate()) +} diff --git a/checker/generator/messages.yaml b/checker/generator/messages.yaml new file mode 100644 index 00000000..81f46d93 --- /dev/null +++ b/checker/generator/messages.yaml @@ -0,0 +1,130 @@ +request-body-media-type-max-set: max value of media-type %s of request body was set to %s +request-body-media-type-max-increased: max value of media-type %s of request body was increased from %s to %s +request-body-media-type-max-decreased: max value of media-type %s of request body was decreased from %s to %s +request-body-media-type-max-length-set: maxLength value of media-type %s of request body was set to %s +request-body-media-type-max-length-increased: maxLength value of media-type %s of request body was increased from %s to %s +request-body-media-type-max-length-decreased: maxLength value of media-type %s of request body was decreased from %s to %s +request-body-media-type-min-set: min value of media-type %s of request body was set to %s +request-body-media-type-min-increased: min value of media-type %s of request body was increased from %s to %s +request-body-media-type-min-decreased: min value of media-type %s of request body was decreased from %s to %s +request-body-media-type-min-length-set: minLength value of media-type %s of request body was set to %s +request-body-media-type-min-length-increased: minLength value of media-type %s of request body was increased from %s to %s +request-body-media-type-min-length-decreased: minLength value of media-type %s of request body was decreased from %s to %s +request-body-media-type-min-items-set: minItems value of media-type %s of request body was set to %s +request-body-media-type-min-items-increased: minItems value of media-type %s of request body was increased from %s to %s +request-body-media-type-min-items-decreased: minItems value of media-type %s of request body was decreased from %s to %s +request-body-media-type-max-items-set: maxItems value of media-type %s of request body was set to %s +request-body-media-type-max-items-increased: maxItems value of media-type %s of request body was increased from %s to %s +request-body-media-type-max-items-decreased: maxItems value of media-type %s of request body was decreased from %s to %s +request-body-media-type-type-changed: type/format of media-type %s of request body was changed from %s to %s +request-body-media-type-type-generalized: type/format of media-type %s of request body was generalized from %s to %s +request-body-media-type-any-of-added: anyOf of media-type %s of request body was added from %s to %s +request-body-media-type-any-of-removed: anyOf of media-type %s of request body was removed from %s to %s +request-body-media-type-one-of-added: oneOf of media-type %s of request body was added from %s to %s +request-body-media-type-one-of-removed: oneOf of media-type %s of request body was removed from %s to %s +request-body-media-type-all-of-added: allOf of media-type %s of request body was added from %s to %s +request-body-media-type-all-of-removed: allOf of media-type %s of request body was removed from %s to %s +request-body-media-type-property-max-set: max value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-max-increased: max value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-max-decreased: max value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-max-length-set: maxLength value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-max-length-increased: maxLength value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-max-length-decreased: maxLength value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-min-set: min value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-min-increased: min value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-min-decreased: min value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-min-length-set: minLength value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-min-length-increased: minLength value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-min-length-decreased: minLength value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-min-items-set: minItems value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-min-items-increased: minItems value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-min-items-decreased: minItems value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-max-items-set: maxItems value of property %s of media-type %s of request body was set to %s +request-body-media-type-property-max-items-increased: maxItems value of property %s of media-type %s of request body was increased from %s to %s +request-body-media-type-property-max-items-decreased: maxItems value of property %s of media-type %s of request body was decreased from %s to %s +request-body-media-type-property-type-changed: type/format of property %s of media-type %s of request body was changed from %s to %s +request-body-media-type-property-type-generalized: type/format of property %s of media-type %s of request body was generalized from %s to %s +request-body-media-type-property-any-of-added: anyOf of property %s of media-type %s of request body was added from %s to %s +request-body-media-type-property-any-of-removed: anyOf of property %s of media-type %s of request body was removed from %s to %s +request-body-media-type-property-one-of-added: oneOf of property %s of media-type %s of request body was added from %s to %s +request-body-media-type-property-one-of-removed: oneOf of property %s of media-type %s of request body was removed from %s to %s +request-body-media-type-property-all-of-added: allOf of property %s of media-type %s of request body was added from %s to %s +request-body-media-type-property-all-of-removed: allOf of property %s of media-type %s of request body was removed from %s to %s +request-parameter-max-set: max value of %s request parameter %s was set to %s +request-parameter-max-increased: max value of %s request parameter %s was increased from %s to %s +request-parameter-max-decreased: max value of %s request parameter %s was decreased from %s to %s +request-parameter-max-length-set: maxLength value of %s request parameter %s was set to %s +request-parameter-max-length-increased: maxLength value of %s request parameter %s was increased from %s to %s +request-parameter-max-length-decreased: maxLength value of %s request parameter %s was decreased from %s to %s +request-parameter-min-set: min value of %s request parameter %s was set to %s +request-parameter-min-increased: min value of %s request parameter %s was increased from %s to %s +request-parameter-min-decreased: min value of %s request parameter %s was decreased from %s to %s +request-parameter-min-length-set: minLength value of %s request parameter %s was set to %s +request-parameter-min-length-increased: minLength value of %s request parameter %s was increased from %s to %s +request-parameter-min-length-decreased: minLength value of %s request parameter %s was decreased from %s to %s +request-parameter-min-items-set: minItems value of %s request parameter %s was set to %s +request-parameter-min-items-increased: minItems value of %s request parameter %s was increased from %s to %s +request-parameter-min-items-decreased: minItems value of %s request parameter %s was decreased from %s to %s +request-parameter-max-items-set: maxItems value of %s request parameter %s was set to %s +request-parameter-max-items-increased: maxItems value of %s request parameter %s was increased from %s to %s +request-parameter-max-items-decreased: maxItems value of %s request parameter %s was decreased from %s to %s +request-parameter-type-changed: type/format of request parameter %s was changed from %s to %s +request-parameter-type-generalized: type/format of request parameter %s was generalized from %s to %s +request-parameter-any-of-added: anyOf of request parameter %s was added from %s to %s +request-parameter-any-of-removed: anyOf of request parameter %s was removed from %s to %s +request-parameter-one-of-added: oneOf of request parameter %s was added from %s to %s +request-parameter-one-of-removed: oneOf of request parameter %s was removed from %s to %s +request-parameter-all-of-added: allOf of request parameter %s was added from %s to %s +request-parameter-all-of-removed: allOf of request parameter %s was removed from %s to %s +response-media-type-max-set: max value of media-type %s of response %s was set to %s +response-media-type-max-increased: max value of media-type %s of response %s was increased from %s to %s +response-media-type-max-decreased: max value of media-type %s of response %s was decreased from %s to %s +response-media-type-max-length-set: maxLength value of media-type %s of response %s was set to %s +response-media-type-max-length-increased: maxLength value of media-type %s of response %s was increased from %s to %s +response-media-type-max-length-decreased: maxLength value of media-type %s of response %s was decreased from %s to %s +response-media-type-min-set: min value of media-type %s of response %s was set to %s +response-media-type-min-increased: min value of media-type %s of response %s was increased from %s to %s +response-media-type-min-decreased: min value of media-type %s of response %s was decreased from %s to %s +response-media-type-min-length-set: minLength value of media-type %s of response %s was set to %s +response-media-type-min-length-increased: minLength value of media-type %s of response %s was increased from %s to %s +response-media-type-min-length-decreased: minLength value of media-type %s of response %s was decreased from %s to %s +response-media-type-min-items-set: minItems value of media-type %s of response %s was set to %s +response-media-type-min-items-increased: minItems value of media-type %s of response %s was increased from %s to %s +response-media-type-min-items-decreased: minItems value of media-type %s of response %s was decreased from %s to %s +response-media-type-max-items-set: maxItems value of media-type %s of response %s was set to %s +response-media-type-max-items-increased: maxItems value of media-type %s of response %s was increased from %s to %s +response-media-type-max-items-decreased: maxItems value of media-type %s of response %s was decreased from %s to %s +response-media-type-type-changed: type/format of media-type %s of response %s was changed from %s to %s +response-media-type-type-generalized: type/format of media-type %s of response %s was generalized from %s to %s +response-media-type-any-of-added: anyOf of media-type %s of response %s was added from %s to %s +response-media-type-any-of-removed: anyOf of media-type %s of response %s was removed from %s to %s +response-media-type-one-of-added: oneOf of media-type %s of response %s was added from %s to %s +response-media-type-one-of-removed: oneOf of media-type %s of response %s was removed from %s to %s +response-media-type-all-of-added: allOf of media-type %s of response %s was added from %s to %s +response-media-type-all-of-removed: allOf of media-type %s of response %s was removed from %s to %s +response-media-type-property-max-set: max value of property %s of media-type %s of response %s was set to %s +response-media-type-property-max-increased: max value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-max-decreased: max value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-max-length-set: maxLength value of property %s of media-type %s of response %s was set to %s +response-media-type-property-max-length-increased: maxLength value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-max-length-decreased: maxLength value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-min-set: min value of property %s of media-type %s of response %s was set to %s +response-media-type-property-min-increased: min value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-min-decreased: min value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-min-length-set: minLength value of property %s of media-type %s of response %s was set to %s +response-media-type-property-min-length-increased: minLength value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-min-length-decreased: minLength value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-min-items-set: minItems value of property %s of media-type %s of response %s was set to %s +response-media-type-property-min-items-increased: minItems value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-min-items-decreased: minItems value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-max-items-set: maxItems value of property %s of media-type %s of response %s was set to %s +response-media-type-property-max-items-increased: maxItems value of property %s of media-type %s of response %s was increased from %s to %s +response-media-type-property-max-items-decreased: maxItems value of property %s of media-type %s of response %s was decreased from %s to %s +response-media-type-property-type-changed: type/format of property %s of media-type %s of response %s was changed from %s to %s +response-media-type-property-type-generalized: type/format of property %s of media-type %s of response %s was generalized from %s to %s +response-media-type-property-any-of-added: anyOf of property %s of media-type %s of response %s was added from %s to %s +response-media-type-property-any-of-removed: anyOf of property %s of media-type %s of response %s was removed from %s to %s +response-media-type-property-one-of-added: oneOf of property %s of media-type %s of response %s was added from %s to %s +response-media-type-property-one-of-removed: oneOf of property %s of media-type %s of response %s was removed from %s to %s +response-media-type-property-all-of-added: allOf of property %s of media-type %s of response %s was added from %s to %s +response-media-type-property-all-of-removed: allOf of property %s of media-type %s of response %s was removed from %s to %s diff --git a/checker/generator/value_set.go b/checker/generator/value_set.go new file mode 100644 index 00000000..cc7fa8f0 --- /dev/null +++ b/checker/generator/value_set.go @@ -0,0 +1,30 @@ +package generator + +import ( + "fmt" + "io" +) + +type valueSets []valueSet + +func (vs valueSets) generate(out io.Writer) { + for _, v := range vs { + v.generate(out) + } +} + +type valueSet struct { + adjective string + hierarchy []string + attributed []bool + nouns []string + actions []string +} + +func (v valueSet) generate(out io.Writer) { + for _, noun := range v.nouns { + for _, action := range v.actions { + fmt.Fprintln(out, fmt.Sprintf("%s: %s", generateId(v.hierarchy, noun, action), generateMessage(v.hierarchy, v.attributed, noun, v.adjective, action))) + } + } +} diff --git a/checker/localizations/localizations.go b/checker/localizations/localizations.go index ea8bbd34..38cb602a 100644 --- a/checker/localizations/localizations.go +++ b/checker/localizations/localizations.go @@ -1,6 +1,6 @@ // Code generated by go-localize; DO NOT EDIT. // This file was generated by robots at -// 2024-08-29 17:33:13.914404 +0300 IDT m=+0.005655876 +// 2024-08-29 23:38:04.760758 +0300 IDT m=+0.007109585 package localizations @@ -133,7 +133,7 @@ var localizations = map[string]string{ "en.messages.request-body-default-value-changed-description": "request body default value modified", "en.messages.request-body-default-value-removed": "default value %s was removed from media-type %s of request body", "en.messages.request-body-default-value-removed-description": "request body default value unset", - "en.messages.request-body-discriminator-added": "added request discriminator", + "en.messages.request-body-discriminator-added": "added discriminator to media-type %s of request body", "en.messages.request-body-discriminator-added-description": "request body discriminator added", "en.messages.request-body-discriminator-mapping-added": "added %s mapping keys to request discriminator", "en.messages.request-body-discriminator-mapping-added-description": "request body discriminator mapping added", @@ -141,9 +141,9 @@ var localizations = map[string]string{ "en.messages.request-body-discriminator-mapping-changed-description": "request body discriminator mapping changed", "en.messages.request-body-discriminator-mapping-deleted": "removed %s mapping keys from request discriminator", "en.messages.request-body-discriminator-mapping-deleted-description": "request body discriminator mapping deleted", - "en.messages.request-body-discriminator-property-name-changed": "request discriminator property name changed from %s to %s", + "en.messages.request-body-discriminator-property-name-changed": "discriminator property name of media-type %s of request body changed from %s to %s", "en.messages.request-body-discriminator-property-name-changed-description": "request body discriminator property name changed", - "en.messages.request-body-discriminator-removed": "removed request discriminator", + "en.messages.request-body-discriminator-removed": "removed discriminator from media-type %s of request body", "en.messages.request-body-discriminator-removed-description": "request body discriminator deleted", "en.messages.request-body-enum-value-removed": "enum value %s removed from media-type %s of request body", "en.messages.request-body-enum-value-removed-description": "request body enum value deleted", @@ -303,7 +303,7 @@ var localizations = map[string]string{ "en.messages.request-property-default-value-changed-description": "request property default value changed", "en.messages.request-property-default-value-removed": "%s request property default value %s was removed", "en.messages.request-property-default-value-removed-description": "request property default value unset", - "en.messages.request-property-discriminator-added": "added discriminator to %s request property", + "en.messages.request-property-discriminator-added": "added discriminator to property %s of media-type %s of request body", "en.messages.request-property-discriminator-added-description": "request property discriminator added", "en.messages.request-property-discriminator-mapping-added": "added %s discriminator mapping keys to %s request property", "en.messages.request-property-discriminator-mapping-added-description": "request property discriminator mapping added", @@ -311,9 +311,9 @@ var localizations = map[string]string{ "en.messages.request-property-discriminator-mapping-changed-description": "request property discriminator mapping changed", "en.messages.request-property-discriminator-mapping-deleted": "removed %s discriminator mapping keys from %s request property", "en.messages.request-property-discriminator-mapping-deleted-description": "request property discriminator mapping deleted", - "en.messages.request-property-discriminator-property-name-changed": "request discriminator property name changed for %s request property from %s to %s", + "en.messages.request-property-discriminator-property-name-changed": "discriminator property name of request property %s of media-type %s changed from %s to %s", "en.messages.request-property-discriminator-property-name-changed-description": "request property discriminator property name changed", - "en.messages.request-property-discriminator-removed": "removed discriminator from %s request property", + "en.messages.request-property-discriminator-removed": "removed discriminator from property %s of media-type %s of request body", "en.messages.request-property-discriminator-removed-description": "request property discriminator removed", "en.messages.request-property-enum-value-added": "added %s enum value to request property %s of media type %s", "en.messages.request-property-enum-value-added-description": "request property enum value added", diff --git a/checker/localizations_src/en/messages.yaml b/checker/localizations_src/en/messages.yaml index 3d46146a..1699c807 100644 --- a/checker/localizations_src/en/messages.yaml +++ b/checker/localizations_src/en/messages.yaml @@ -246,12 +246,12 @@ response-body-discriminator-mapping-deleted: removed %s mapping keys from respon response-property-discriminator-mapping-deleted: removed %s discriminator mapping keys from %s response property for response status %s response-body-discriminator-mapping-changed: mapped value for key %s changed from %s to %s from the response discriminator for response status %s response-property-discriminator-mapping-changed: mapped value for discriminator key %s changed from %s to %s for %s response property for response status %s -request-body-discriminator-added: added request discriminator -request-property-discriminator-added: added discriminator to %s request property -request-body-discriminator-removed: removed request discriminator -request-property-discriminator-removed: removed discriminator from %s request property -request-body-discriminator-property-name-changed: request discriminator property name changed from %s to %s -request-property-discriminator-property-name-changed: request discriminator property name changed for %s request property from %s to %s +request-body-discriminator-added: added discriminator to media-type %s of request body +request-property-discriminator-added: added discriminator to property %s of media-type %s of request body +request-body-discriminator-removed: removed discriminator from media-type %s of request body +request-property-discriminator-removed: removed discriminator from property %s of media-type %s of request body +request-body-discriminator-property-name-changed: discriminator property name of media-type %s of request body changed from %s to %s +request-property-discriminator-property-name-changed: discriminator property name of request property %s of media-type %s changed from %s to %s request-body-discriminator-mapping-added: added %s mapping keys to request discriminator request-property-discriminator-mapping-added: added %s discriminator mapping keys to %s request property request-body-discriminator-mapping-deleted: removed %s mapping keys from request discriminator diff --git a/go.mod b/go.mod index 135aec61..42b052f1 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cloud.google.com/go v0.115.1 github.com/TwiN/go-color v1.4.1 github.com/getkin/kin-openapi v0.127.0 + github.com/iancoleman/strcase v0.3.0 github.com/oasdiff/telemetry v0.1.2 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 diff --git a/go.sum b/go.sum index 26b9c806..696bef36 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 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/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 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= diff --git a/scripts/get_tested_ids.sh b/scripts/get_tested_ids.sh index b23f6698..69c1ed3a 100755 --- a/scripts/get_tested_ids.sh +++ b/scripts/get_tested_ids.sh @@ -2,8 +2,10 @@ if [[ $1 == "lines" ]]; then grep -nRE --exclude-dir=localizations,localizations_src "// (CL|BC): .*: .*-.*" checker/* | cut -d":" -f1,2,5 -elif [[ $1 == "ids" ]]; then - grep -nRE --exclude-dir=localizations,localizations_src '// (CL|BC): .*: .*-.*' checker/* | cut -d: -f5 | tr , '\n' | awk '{$1=$1};1' | sort -u elif [[ $1 == "diff" ]]; then diff --side-by-side <(grep -nRE --exclude-dir=localizations,localizations_src '// (CL|BC): .*: .*-.*' checker/* | cut -d: -f5 | tr , '\n' | awk '{$1=$1};1' | sort -u) <(cat checker/localizations_src/en/messages.yaml | sed '/# warnings/q' | cut -d":" -f1 | sort -n) +elif [[ $1 == "all" ]]; then + cat checker/localizations_src/en/messages.yaml | sed '/# warnings/q' | cut -d":" -f1 | sort -n +else + grep -nRE --exclude-dir=localizations,localizations_src '// (CL|BC): .*: .*-.*' checker/* | cut -d: -f5 | tr , '\n' | awk '{$1=$1};1' | sort -u fi