diff --git a/checker/generator/doc.go b/checker/generator/doc.go new file mode 100644 index 00000000..8a508ac5 --- /dev/null +++ b/checker/generator/doc.go @@ -0,0 +1,11 @@ +/* +Package generator generates the breaking-changes and changelog messages for the checker package. +The output, messages.yaml can be used by the checker package instead of the hardcoded messages under localizations_src. +Advatages over manuallly writing the messages: +- The generated ids and messages are consistent according to the logic in the generator. +- The generator can be easily extended to support more messages. +Additional work needed before using the generator: +- Check that all messages are covered by the generator. +- Decide what to do with Russian messages. +*/ +package generator diff --git a/checker/generator/generator.go b/checker/generator/generator.go new file mode 100644 index 00000000..4f90f6f5 --- /dev/null +++ b/checker/generator/generator.go @@ -0,0 +1,140 @@ +package generator + +import ( + "slices" + "strings" + + "github.com/iancoleman/strcase" +) + +type MessageGenerator interface { + generate() []string +} + +type Getter func() (MessageGenerator, error) + +func Generate(getter Getter) ([]string, error) { + data, err := getter() + if err != nil { + return nil, err + } + + return data.generate(), nil +} + +func isEmpty(s string) bool { + return s == "" +} + +func filterStrings(list []string, f func(string) bool) []string { + var result []string + for _, s := range list { + if !f(s) { + result = append(result, s) + } + } + return result +} + +func generateId(hierarchy []string, object, action, adverb string) string { + if prefix, _, found := strings.Cut(object, "/"); found { + object = prefix + } + + return strcase.ToKebab(strings.Join(filterStrings([]string{concat(hierarchy), object, conjugate(action), adverb}, isEmpty), "-")) +} + +func concat(list []string) string { + if len(list) == 0 { + return "" + } + + copy := slices.Clone(list) + slices.Reverse(copy) + return strings.Join(copy, "-") +} + +func getHierarchyPostfix(action string, hierarchy []string) string { + if len(hierarchy) == 0 { + return "" + } + + return getPreposition(action) + " " + getHierarchyMessage(hierarchy) +} + +func getHierarchyMessage(hierarchy []string) string { + + copy := slices.Clone(hierarchy) + + for i, s := range hierarchy { + if isAtttibuted(s) { + copy[i] = "%s " + s + } + } + result := strings.Join(copy, " %s of ") + + if hierarchy != nil && !isTopLevel(hierarchy[len(hierarchy)-1]) { + result += " %s" + } + + return result +} + +func isTopLevel(s string) bool { + return s == "request body" +} + +func isAtttibuted(s string) bool { + return s == "request parameter" +} + +func standardizeSpaces(s string) string { + return strings.Join(strings.Fields(s), " ") +} + +func getActionMessage(action string) string { + switch getArity(action) { + case 0: + return "" + case 1: + return " to %s" + case 2: + return " from %s to %s" + default: + return "" + } +} + +func getArity(action string) int { + switch action { + case "add", "remove": + return 0 + case "set": + return 1 + } + return 2 +} + +func conjugate(verb string) string { + switch verb { + case "set": + return "set" + case "add": + return "added" + case "fail to parse": + return "failed to parse" + } + return verb + "d" +} + +func getPreposition(action string) string { + switch action { + case "add": + return "to" + } + return "from" +} + +func addAttribute(name, attributiveAdjective, predicativeAdjective string) string { + return strings.Join([]string{attributiveAdjective + " " + name + " " + predicativeAdjective}, " ") +} diff --git a/checker/generator/generator_test.go b/checker/generator/generator_test.go new file mode 100644 index 00000000..574eecc5 --- /dev/null +++ b/checker/generator/generator_test.go @@ -0,0 +1,45 @@ +package generator_test + +import ( + "os" + "slices" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tufin/oasdiff/checker/generator" +) + +func WriteToFile(t *testing.T, filename string, lines []string) { + t.Helper() + + file, err := os.Create(filename) + require.NoError(t, err) + defer file.Close() + for _, line := range lines { + _, err = file.WriteString(line + "\n") + require.NoError(t, err) + } +} + +func TestTreeGenerator(t *testing.T) { + result, err := generator.Generate(generator.GetTree("tree.yaml")) + require.NoError(t, err) + slices.Sort(result) + WriteToFile(t, "messages.yaml", result) + require.Len(t, result, 263) + badId, unique := isUninueIds(result) + require.True(t, unique, badId) +} + +func isUninueIds(messages []string) (string, bool) { + ids := make(map[string]struct{}) + for _, message := range messages { + id := strings.SplitAfter(message, ":")[0] + if _, ok := ids[id]; ok { + return id, false + } + ids[id] = struct{}{} + } + return "", true +} diff --git a/checker/generator/messages.yaml b/checker/generator/messages.yaml new file mode 100644 index 00000000..9b8b6cfc --- /dev/null +++ b/checker/generator/messages.yaml @@ -0,0 +1,263 @@ +api-path-removed-before-sunset: api path was removed before sunset +api-path-removed-without-deprecation: api path was removed without deprecation +endpoint-added: added endpoint +endpoint-deprecated: deprecated endpoint +endpoint-reactivated: reactivated endpoint +endpoint-removed-before-sunset: endpoint was removed before sunset +endpoint-removed-without-deprecation: endpoint was removed without deprecation +endpoint-removed: removed endpoint +endpoint-scheme-security-added: added endpoint scheme security %s +endpoint-scheme-security-removed: removed endpoint scheme security %s +endpoint-security-scheme-security-scope-added: added security scope %s to endpoint security scheme %s +endpoint-security-scheme-security-scope-removed: removed security scope %s from endpoint security scheme %s +non-success-response-status-added: added non-success response status %s +non-success-response-status-removed: removed non-success response status %s +operation-id-added: added operation id %s +operation-id-changed: operation id was changed from %s to %s +operation-id-removed: removed operation id %s +optional-request-body-added: added %s optional request body %s +optional-request-body-removed: removed %s optional request body %s +request-body-media-type-all-of-list-schema-added: added schema %s to allOf list %s of media type %s of request body +request-body-media-type-all-of-list-schema-removed: removed schema %s from allOf list %s of media type %s of request body +request-body-media-type-any-of-list-schema-added: added schema %s to anyOf list %s of media type %s of request body +request-body-media-type-any-of-list-schema-removed: removed schema %s from anyOf list %s of media type %s of request body +request-body-media-type-default-value-added: added default value to media type %s of request body +request-body-media-type-default-value-removed: removed default value from media type %s of request body +request-body-media-type-discriminator-added: added discriminator %s to media type %s of request body +request-body-media-type-discriminator-property-name-changed: discriminator property name of media type %s of request body was changed from %s to %s +request-body-media-type-discriminator-removed: removed discriminator %s from media type %s of request body +request-body-media-type-mapping-keys-added: added mapping keys %s to media type %s of request body +request-body-media-type-mapping-keys-removed: removed mapping keys %s from media type %s of request body +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-increased: max 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-max-items-increased: maxItems value of media type %s of request body was increased 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-length-decreased: maxLength value of media type %s of request body was decreased from %s 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-set: maxLength value of media type %s of request body was set to %s +request-body-media-type-max-set: max value of media type %s of request body was set 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-increased: min 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-min-items-increased: minItems value of media type %s of request body was increased 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-length-decreased: minLength value of media type %s of request body was decreased from %s 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-set: minLength value of media type %s of request body was set 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-one-of-list-schema-added: added schema %s to oneOf list %s of media type %s of request body +request-body-media-type-one-of-list-schema-removed: removed schema %s from oneOf list %s of media type %s of request body +request-body-media-type-optional-property-changed: optional property of media type %s of request body was changed from %s to %s +request-body-media-type-pattern-added: added pattern %s to media type %s of request body +request-body-media-type-pattern-changed: pattern of media type %s of request body was changed from %s to %s +request-body-media-type-pattern-generalized: pattern of media type %s of request body was generalized from %s to %s +request-body-media-type-pattern-removed: removed pattern %s from media type %s of request body +request-body-media-type-property-all-of-list-schema-added: added schema %s to allOf list %s of property %s of media type %s of request body +request-body-media-type-property-all-of-list-schema-removed: removed schema %s from allOf list %s of property %s of media type %s of request body +request-body-media-type-property-any-of-list-schema-added: added schema %s to anyOf list %s of property %s of media type %s of request body +request-body-media-type-property-any-of-list-schema-removed: removed schema %s from anyOf list %s of property %s of media type %s of request body +request-body-media-type-property-default-value-added: added default value to property %s of media type %s of request body +request-body-media-type-property-default-value-removed: removed default value from property %s of media type %s of request body +request-body-media-type-property-discriminator-added: added discriminator %s to property %s of media type %s of request body +request-body-media-type-property-discriminator-property-name-changed: discriminator property name of property %s of media type %s of request body was changed from %s to %s +request-body-media-type-property-discriminator-removed: removed discriminator %s from property %s of media type %s of request body +request-body-media-type-property-mapping-keys-added: added mapping keys %s to property %s of media type %s of request body +request-body-media-type-property-mapping-keys-removed: removed mapping keys %s from property %s of media type %s of request body +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-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-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-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-set: maxItems value of property %s of media type %s of request body was set 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-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-set: maxLength value of property %s of media type %s of request body was set 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-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-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-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-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-set: minItems value of property %s of media type %s of request body was set 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-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-set: minLength value of property %s of media type %s of request body was set 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-one-of-list-schema-added: added schema %s to oneOf list %s of property %s of media type %s of request body +request-body-media-type-property-one-of-list-schema-removed: removed schema %s from oneOf list %s of property %s of media type %s of request body +request-body-media-type-property-optional-property-changed: optional property of property %s of media type %s of request body was changed from %s to %s +request-body-media-type-property-pattern-added: added pattern %s to property %s of media type %s of request body +request-body-media-type-property-pattern-changed: pattern of property %s of media type %s of request body was changed from %s to %s +request-body-media-type-property-pattern-generalized: pattern of property %s of media type %s of request body was generalized from %s to %s +request-body-media-type-property-pattern-removed: removed pattern %s from property %s of media type %s of request body +request-body-media-type-property-required-property-changed: required property of property %s of media type %s of request body was changed 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-required-property-changed: required property of media type %s of request body was changed 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-parameter-added: added request parameter +request-parameter-all-of-list-schema-added: added schema %s to allOf list %s of %s request parameter %s +request-parameter-all-of-list-schema-removed: removed schema %s from allOf list %s of %s request parameter %s +request-parameter-any-of-list-schema-added: added schema %s to anyOf list %s of %s request parameter %s +request-parameter-any-of-list-schema-removed: removed schema %s from anyOf list %s of %s request parameter %s +request-parameter-default-value-added: added default value to %s request parameter %s +request-parameter-default-value-removed: removed default value from %s request parameter %s +request-parameter-discriminator-added: added discriminator %s to %s request parameter %s +request-parameter-discriminator-property-name-changed: discriminator property name of %s request parameter %s was changed from %s to %s +request-parameter-discriminator-removed: removed discriminator %s from %s request parameter %s +request-parameter-mapping-keys-added: added mapping keys %s to %s request parameter %s +request-parameter-mapping-keys-removed: removed mapping keys %s from %s request parameter %s +request-parameter-max-decreased: max value of %s request parameter %s was decreased from %s to %s +request-parameter-max-increased: max 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-max-items-increased: maxItems value of %s request parameter %s was increased from %s to %s +request-parameter-max-items-set: maxItems value of %s request parameter %s was set to %s +request-parameter-max-length-decreased: maxLength value of %s request parameter %s was decreased from %s to %s +request-parameter-max-length-increased: maxLength value of %s request parameter %s was increased from %s to %s +request-parameter-max-length-set: maxLength value of %s request parameter %s was set to %s +request-parameter-max-set: max value of %s request parameter %s was set to %s +request-parameter-min-decreased: min value of %s request parameter %s was decreased from %s to %s +request-parameter-min-increased: min 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-min-items-increased: minItems value of %s request parameter %s was increased from %s to %s +request-parameter-min-items-set: minItems value of %s request parameter %s was set to %s +request-parameter-min-length-decreased: minLength value of %s request parameter %s was decreased from %s to %s +request-parameter-min-length-increased: minLength value of %s request parameter %s was increased from %s to %s +request-parameter-min-length-set: minLength value of %s request parameter %s was set to %s +request-parameter-min-set: min value of %s request parameter %s was set to %s +request-parameter-one-of-list-schema-added: added schema %s to oneOf list %s of %s request parameter %s +request-parameter-one-of-list-schema-removed: removed schema %s from oneOf list %s of %s request parameter %s +request-parameter-optional-property-changed: optional property of %s request parameter %s was changed from %s to %s +request-parameter-pattern-added: added pattern %s to %s request parameter %s +request-parameter-pattern-changed: pattern of %s request parameter %s was changed from %s to %s +request-parameter-pattern-generalized: pattern of %s request parameter %s was generalized from %s to %s +request-parameter-pattern-removed: removed pattern %s from %s request parameter %s +request-parameter-property-all-of-list-schema-added: added schema %s to allOf list %s of property %s of %s request parameter %s +request-parameter-property-all-of-list-schema-removed: removed schema %s from allOf list %s of property %s of %s request parameter %s +request-parameter-property-any-of-list-schema-added: added schema %s to anyOf list %s of property %s of %s request parameter %s +request-parameter-property-any-of-list-schema-removed: removed schema %s from anyOf list %s of property %s of %s request parameter %s +request-parameter-property-default-value-added: added default value to property %s of %s request parameter %s +request-parameter-property-default-value-removed: removed default value from property %s of %s request parameter %s +request-parameter-property-discriminator-added: added discriminator %s to property %s of %s request parameter %s +request-parameter-property-discriminator-property-name-changed: discriminator property name of property %s of %s request parameter %s was changed from %s to %s +request-parameter-property-discriminator-removed: removed discriminator %s from property %s of %s request parameter %s +request-parameter-property-mapping-keys-added: added mapping keys %s to property %s of %s request parameter %s +request-parameter-property-mapping-keys-removed: removed mapping keys %s from property %s of %s request parameter %s +request-parameter-property-max-decreased: max value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-max-increased: max value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-max-items-decreased: maxItems value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-max-items-increased: maxItems value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-max-items-set: maxItems value of property %s of %s request parameter %s was set to %s +request-parameter-property-max-length-decreased: maxLength value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-max-length-increased: maxLength value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-max-length-set: maxLength value of property %s of %s request parameter %s was set to %s +request-parameter-property-max-set: max value of property %s of %s request parameter %s was set to %s +request-parameter-property-min-decreased: min value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-min-increased: min value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-min-items-decreased: minItems value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-min-items-increased: minItems value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-min-items-set: minItems value of property %s of %s request parameter %s was set to %s +request-parameter-property-min-length-decreased: minLength value of property %s of %s request parameter %s was decreased from %s to %s +request-parameter-property-min-length-increased: minLength value of property %s of %s request parameter %s was increased from %s to %s +request-parameter-property-min-length-set: minLength value of property %s of %s request parameter %s was set to %s +request-parameter-property-min-set: min value of property %s of %s request parameter %s was set to %s +request-parameter-property-one-of-list-schema-added: added schema %s to oneOf list %s of property %s of %s request parameter %s +request-parameter-property-one-of-list-schema-removed: removed schema %s from oneOf list %s of property %s of %s request parameter %s +request-parameter-property-optional-property-changed: optional property of property %s of %s request parameter %s was changed from %s to %s +request-parameter-property-pattern-added: added pattern %s to property %s of %s request parameter %s +request-parameter-property-pattern-changed: pattern of property %s of %s request parameter %s was changed from %s to %s +request-parameter-property-pattern-generalized: pattern of property %s of %s request parameter %s was generalized from %s to %s +request-parameter-property-pattern-removed: removed pattern %s from property %s of %s request parameter %s +request-parameter-property-required-property-changed: required property of property %s of %s request parameter %s was changed from %s to %s +request-parameter-property-type-changed: type/format of property %s of %s request parameter %s was changed from %s to %s +request-parameter-property-type-generalized: type/format of property %s of %s request parameter %s was generalized from %s to %s +request-parameter-removed: removed request parameter +request-parameter-required-property-changed: required property of %s request parameter %s was changed from %s to %s +request-parameter-type-changed: type/format of %s request parameter %s was changed from %s to %s +request-parameter-type-generalized: type/format of %s request parameter %s was generalized from %s to %s +required-request-body-added: added %s required request body %s +required-request-body-removed: removed %s required request body %s +response-media-type-all-of-list-schema-added: added schema %s to allOf list %s of media-type %s of response %s +response-media-type-all-of-list-schema-removed: removed schema %s from allOf list %s of media-type %s of response %s +response-media-type-any-of-list-schema-added: added schema %s to anyOf list %s of media-type %s of response %s +response-media-type-any-of-list-schema-removed: removed schema %s from anyOf list %s of media-type %s of response %s +response-media-type-default-value-added: added default value to media-type %s of response %s +response-media-type-default-value-removed: removed default value from media-type %s of response %s +response-media-type-discriminator-added: added discriminator %s to media-type %s of response %s +response-media-type-discriminator-property-name-changed: discriminator property name of media-type %s of response %s was changed from %s to %s +response-media-type-discriminator-removed: removed discriminator %s from media-type %s of response %s +response-media-type-mapping-keys-added: added mapping keys %s to media-type %s of response %s +response-media-type-mapping-keys-removed: removed mapping keys %s from media-type %s of response %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-increased: max 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-max-items-increased: maxItems value of media-type %s of response %s was increased 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-length-decreased: maxLength value of media-type %s of response %s was decreased from %s 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-set: maxLength value of media-type %s of response %s was set to %s +response-media-type-max-set: max value of media-type %s of response %s was set 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-increased: min 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-min-items-increased: minItems value of media-type %s of response %s was increased 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-length-decreased: minLength value of media-type %s of response %s was decreased from %s 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-set: minLength value of media-type %s of response %s was set to %s +response-media-type-min-set: min value of media-type %s of response %s was set to %s +response-media-type-one-of-list-schema-added: added schema %s to oneOf list %s of media-type %s of response %s +response-media-type-one-of-list-schema-removed: removed schema %s from oneOf list %s of media-type %s of response %s +response-media-type-optional-property-changed: optional property of media-type %s of response %s was changed from %s to %s +response-media-type-pattern-added: added pattern %s to media-type %s of response %s +response-media-type-pattern-changed: pattern of media-type %s of response %s was changed from %s to %s +response-media-type-pattern-generalized: pattern of media-type %s of response %s was generalized from %s to %s +response-media-type-pattern-removed: removed pattern %s from media-type %s of response %s +response-media-type-property-all-of-list-schema-added: added schema %s to allOf list %s of property %s of media-type %s of response %s +response-media-type-property-all-of-list-schema-removed: removed schema %s from allOf list %s of property %s of media-type %s of response %s +response-media-type-property-any-of-list-schema-added: added schema %s to anyOf list %s of property %s of media-type %s of response %s +response-media-type-property-any-of-list-schema-removed: removed schema %s from anyOf list %s of property %s of media-type %s of response %s +response-media-type-property-default-value-added: added default value to property %s of media-type %s of response %s +response-media-type-property-default-value-removed: removed default value from property %s of media-type %s of response %s +response-media-type-property-discriminator-added: added discriminator %s to property %s of media-type %s of response %s +response-media-type-property-discriminator-property-name-changed: discriminator property name of property %s of media-type %s of response %s was changed from %s to %s +response-media-type-property-discriminator-removed: removed discriminator %s from property %s of media-type %s of response %s +response-media-type-property-mapping-keys-added: added mapping keys %s to property %s of media-type %s of response %s +response-media-type-property-mapping-keys-removed: removed mapping keys %s from property %s of media-type %s of response %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-increased: max 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-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-set: maxItems value of property %s of media-type %s of response %s was set 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-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-set: maxLength value of property %s of media-type %s of response %s was set 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-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-increased: min 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-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-set: minItems value of property %s of media-type %s of response %s was set 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-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-set: minLength value of property %s of media-type %s of response %s was set 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-one-of-list-schema-added: added schema %s to oneOf list %s of property %s of media-type %s of response %s +response-media-type-property-one-of-list-schema-removed: removed schema %s from oneOf list %s of property %s of media-type %s of response %s +response-media-type-property-optional-property-changed: optional property of property %s of media-type %s of response %s was changed from %s to %s +response-media-type-property-pattern-added: added pattern %s to property %s of media-type %s of response %s +response-media-type-property-pattern-changed: pattern of property %s of media-type %s of response %s was changed from %s to %s +response-media-type-property-pattern-generalized: pattern of property %s of media-type %s of response %s was generalized from %s to %s +response-media-type-property-pattern-removed: removed pattern %s from property %s of media-type %s of response %s +response-media-type-property-required-property-changed: required property of property %s of media-type %s of response %s was changed 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-required-property-changed: required property of media-type %s of response %s was changed 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 +stability-decreased: stability was decreased from %s to %s +success-response-status-added: added success response status %s +success-response-status-removed: removed success response status %s +sunset-date-failed-to-parse: failed to parse sunset date +tag-added: added tag %s +tag-removed: removed tag %s diff --git a/checker/generator/tree.go b/checker/generator/tree.go new file mode 100644 index 00000000..61d8791c --- /dev/null +++ b/checker/generator/tree.go @@ -0,0 +1,151 @@ +package generator + +import ( + "fmt" + "os" + "strings" + + "gopkg.in/yaml.v3" +) + +type ChangeTree struct { + Changes ChangeMap `yaml:"changes"` + Components ChangeMap `yaml:"components"` +} + +type ChangeMap map[string]Changes + +type Changes struct { + Ref string `yaml:"$ref"` + ExcludeFromHierarchy bool `yaml:"excludeFromHierarchy"` + Actions Actions `yaml:"actions"` + NextLevel ChangeMap `yaml:"nextLevel"` +} + +type Actions map[string]Objects +type Objects []Object + +type Object struct { + Hierarchy []string `yaml:"hierarchy"` + Names []string `yaml:"names"` + Adverbs []string `yaml:"adverbs"` + StartWithName bool `yaml:"startWithName"` + PredicativeAdjective string `yaml:"predicativeAdjective"` + AttributiveAdjective string `yaml:"attributiveAdjective"` +} + +func GetTree(file string) func() (MessageGenerator, error) { + return func() (MessageGenerator, error) { + yamlFile, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("yamlFile.Get err #%v ", err) + } + + var changeMap ChangeTree + err = yaml.Unmarshal(yamlFile, &changeMap) + if err != nil { + return nil, fmt.Errorf("unmarshal: %v", err) + } + + return changeMap, nil + } +} + +func (changeTree ChangeTree) generate() []string { + resolveRefs(changeTree.Changes, changeTree.Components) + fillHierarchy(changeTree.Changes, nil) + return generateRecursive(changeTree.Changes) +} + +func (changeMap ChangeMap) copy() ChangeMap { + result := ChangeMap{} + for key, value := range changeMap { + result[key] = value.copy() + } + return result +} + +func (changes Changes) copy() Changes { + return Changes{ + Ref: changes.Ref, + ExcludeFromHierarchy: changes.ExcludeFromHierarchy, + Actions: changes.Actions.copy(), + NextLevel: changes.NextLevel.copy(), + } +} + +func (actions Actions) copy() Actions { + result := Actions{} + for key, value := range actions { + result[key] = value.copy() + } + return result +} + +func (objects Objects) copy() Objects { + result := make(Objects, 0, len(objects)) + return append(result, objects...) +} + +func resolveRefs(changes ChangeMap, components ChangeMap) { + for container, change := range changes { + if change.Ref != "" { + changes[container] = components[change.Ref].copy() + } + resolveRefs(changes[container].NextLevel, components) + } +} + +func generateRecursive(changes ChangeMap) []string { + result := []string{} + + for _, change := range changes { + for action, objects := range change.Actions { + for _, object := range objects { + result = append(result, getValueSet(object, action).generate()...) + } + } + result = append(result, generateRecursive(change.NextLevel)...) + } + + return result +} + +func fillHierarchy(changes ChangeMap, hierarchy []string) { + for container, change := range changes { + containerHierarchy := getContainerHierarchy(container, change, hierarchy) + for action, objects := range change.Actions { + for i := range objects { + changes[container].Actions[action][i].Hierarchy = containerHierarchy + } + } + fillHierarchy(change.NextLevel, containerHierarchy) + } +} + +func getContainerHierarchy(container string, change Changes, hierarchy []string) []string { + if change.ExcludeFromHierarchy { + return hierarchy + } + return append([]string{container}, hierarchy...) +} + +func getValueSet(object Object, action string) IValueSet { + valueSet := ValueSet{ + AttributiveAdjective: object.AttributiveAdjective, + PredicativeAdjective: object.PredicativeAdjective, + Hierarchy: object.Hierarchy, + Names: object.Names, + Actions: parseAction(action), + Adverbs: object.Adverbs, + } + + if object.StartWithName { + return ValueSetA(valueSet) + } + return ValueSetB(valueSet) +} + +func parseAction(action string) []string { + return strings.Split(action, "/") +} diff --git a/checker/generator/tree.yaml b/checker/generator/tree.yaml new file mode 100644 index 00000000..aaab87d9 --- /dev/null +++ b/checker/generator/tree.yaml @@ -0,0 +1,148 @@ +changes: + paths: + excludeFromHierarchy: true + actions: + remove: + - names: [api path] + adverbs: [without deprecation, before sunset] + startWithName: true + nextLevel: + operations: + excludeFromHierarchy: true + actions: + remove: + - names: [endpoint] + adverbs: [without deprecation, before sunset] + startWithName: true + - names: [endpoint] + add: + - names: [endpoint] + deprecate: + - names: [endpoint] + reactivate: + - names: [endpoint] + nextLevel: + endpoint: + excludeFromHierarchy: true + actions: + change: + - names: [operation id] + startWithName: true + add/remove: + - names: [operation id, tag] + predicativeAdjective: "%s" + - names: [required request body, optional request body] + predicativeAdjective: "%s" + attributiveAdjective: "%s" + - names: [request parameter] + - names: [endpoint scheme security] + predicativeAdjective: "%s" + decrease: + - names: [stability] + startWithName: true + fail to parse: + - names: [sunset date] + nextLevel: + endpoint security scheme: + actions: + add/remove: + - names: [security scope] + predicativeAdjective: "%s" + responses: + excludeFromHierarchy: true + actions: + add/remove: + - names: [success response status, non-success response status] + predicativeAdjective: "%s" + nextLevel: + response: + nextLevel: + media-type: + nextLevel: + schema: + $ref: 'schema' + request parameter: + nextLevel: + schema: + $ref: 'schema' + request body: + nextLevel: + media type: + nextLevel: + schema: + $ref: 'schema' +components: + schema: + excludeFromHierarchy: true + actions: + set/increase/decrease: + - names: [max, maxLength, min, minLength, minItems, maxItems] + predicativeAdjective: value + startWithName: true + change/generalize: + - names: [type/format, pattern] + startWithName: true + change: + - names: [discriminator property name, required property, optional property] + startWithName: true + add/remove: + - names: [pattern] + predicativeAdjective: "%s" + - names: [default value] + - names: [discriminator, mapping keys] + predicativeAdjective: "%s" + nextLevel: + anyOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" + oneOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" + allOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" + properties: + excludeFromHierarchy: true + nextLevel: + property: + $ref: 'schemaLeaf' + schemaLeaf: + actions: + set/increase/decrease: + - names: [max, maxLength, min, minLength, minItems, maxItems] + predicativeAdjective: value + startWithName: true + change/generalize: + - names: [type/format, pattern] + startWithName: true + change: + - names: [discriminator property name, required property, optional property] + startWithName: true + add/remove: + - names: [pattern] + predicativeAdjective: "%s" + - names: [default value] + - names: [discriminator, mapping keys] + predicativeAdjective: "%s" + nextLevel: + anyOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" + oneOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" + allOf list: + actions: + add/remove: + - names: [schema] + predicativeAdjective: "%s" diff --git a/checker/generator/value_set.go b/checker/generator/value_set.go new file mode 100644 index 00000000..8eac76eb --- /dev/null +++ b/checker/generator/value_set.go @@ -0,0 +1,76 @@ +package generator + +import ( + "fmt" + "strings" +) + +type ValueSets []IValueSet + +type IValueSet interface { + generate() []string +} + +type ValueSetList []ValueSet + +type ValueSet struct { + AttributiveAdjective string // attributive adjectives are added before the object + PredicativeAdjective string // predicative adjectives are added after the object + Hierarchy []string + Names []string + Actions []string + Adverbs []string +} + +// ValueSetA messages start with the object +// for example: "api was removed without deprecation" +type ValueSetA ValueSet + +func (v ValueSetA) generate() []string { + generateMessage := func(hierarchy []string, name, attributiveAdjective, predicativeAdjective, action, adverb string) string { + prefix := addAttribute(name, attributiveAdjective, predicativeAdjective) + if hierarchyMessage := getHierarchyMessage(hierarchy); hierarchyMessage != "" { + prefix += " of " + hierarchyMessage + } + + return standardizeSpaces(fmt.Sprintf("%s was %s %s %s", prefix, conjugate(action), getActionMessage(action), adverb)) + } + + result := []string{} + for _, name := range v.Names { + for _, action := range v.Actions { + for _, adverb := range oneAtLeast(v.Adverbs) { + id := generateId(v.Hierarchy, name, action, adverb) + message := generateMessage(v.Hierarchy, name, v.AttributiveAdjective, v.PredicativeAdjective, action, adverb) + result = append(result, fmt.Sprintf("%s: %s", id, message)) + } + } + } + return result +} + +// ValueSetB messages start with the action +// for example: "removed %s request parameter %s" +type ValueSetB ValueSet + +func (v ValueSetB) generate() []string { + generateMessage := func(hierarchy []string, name, attributiveAdjective, predicativeAdjective, action string) string { + return standardizeSpaces(strings.Join([]string{conjugate(action), addAttribute(name, attributiveAdjective, predicativeAdjective), getHierarchyPostfix(action, hierarchy)}, " ")) + } + + result := []string{} + for _, name := range v.Names { + for _, action := range v.Actions { + result = append(result, fmt.Sprintf("%s: %s", generateId(v.Hierarchy, name, action, ""), generateMessage(v.Hierarchy, name, v.AttributiveAdjective, v.PredicativeAdjective, action))) + } + } + + return result +} + +func oneAtLeast(list []string) []string { + if len(list) == 0 { + return []string{""} + } + return list +} diff --git a/go.mod b/go.mod index 135aec61..6047a603 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ 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/iancoleman/strcase v0.3.0 github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect 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=