{"record":{"id":"1f1cfd7f308b2608","repo":"bytebase/bytebase","slug":"rule-s-requires-naming-payload","errorCode":null,"errorMessage":"rule %s requires naming payload","messagePattern":"rule (.+?) requires naming payload","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"backend/api/v1/review_config_service.go","lineNumber":351,"sourceCode":"\t\t\treturn errors.Errorf(\"invalid rule engine: ENGINE_UNSPECIFIED is not allowed for rule %q\", rule.Type)\n\t\t}\n\t\tif err := validateSQLReviewRule(rule); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// validateSQLReviewRule validates a single SQL review rule's payload.\nfunc validateSQLReviewRule(rule *v1pb.SQLReviewRule) error {\n\truleType := storepb.SQLReviewRule_Type(rule.Type)\n\n\tswitch ruleType {\n\t// Naming rules with regex validation\n\tcase storepb.SQLReviewRule_NAMING_TABLE, storepb.SQLReviewRule_NAMING_COLUMN, storepb.SQLReviewRule_NAMING_COLUMN_AUTO_INCREMENT:\n\t\tpayload := rule.GetNamingPayload()\n\t\tif payload == nil {\n\t\t\treturn errors.Errorf(\"rule %s requires naming payload\", ruleType)\n\t\t}\n\t\t// At least one of format or maxLength must be set\n\t\tif payload.Format == \"\" && payload.MaxLength <= 0 {\n\t\t\treturn errors.Errorf(\"naming rule must specify either format or max_length for rule %s\", ruleType)\n\t\t}\n\t\t// If format is set, validate it compiles\n\t\tif payload.Format != \"\" {\n\t\t\tif _, err := regexp.Compile(payload.Format); err != nil {\n\t\t\t\treturn errors.Wrapf(err, \"invalid naming rule format pattern %q for rule %s\", payload.Format, ruleType)\n\t\t\t}\n\t\t}\n\t\t// If maxLength is set, validate it's positive (maxLength == 0 means not set)\n\t\tif payload.MaxLength < 0 {\n\t\t\treturn errors.Errorf(\"naming rule max_length cannot be negative for rule %s, got %d\", ruleType, payload.MaxLength)\n\t\t}\n\n\t// Naming rules with template token validation\n\tcase storepb.SQLReviewRule_NAMING_INDEX_FK, storepb.SQLReviewRule_NAMING_INDEX_IDX, storepb.SQLReviewRule_NAMING_INDEX_UK, storepb.SQLReviewRule_NAMING_INDEX_PK, storepb.SQLReviewRule_TABLE_DROP_NAMING_CONVENTION:","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/api/v1/review_config_service.go#L333-L369","documentation":"Naming rules with regex validation (NAMING_TABLE, NAMING_COLUMN, NAMING_COLUMN_AUTO_INCREMENT) carry their configuration in a NamingRulePayload. validateSQLReviewRule rejects such a rule when GetNamingPayload() returns nil, meaning the payload was never attached to the rule.","triggerScenarios":"Creating or updating a review config with one of the three regex naming rules but no payload field set (oneof payload left empty in the request).","commonSituations":"API consumers that set rule type/level/engine but forget the payload oneof; UIs that create the rule shell before the payload form is filled; migrations copying rules between configs and dropping the payload.","solutions":["Attach a NamingRulePayload (with format and/or maxLength) to the rule's payload oneof","Omit the rule entirely until its payload is configured","Verify the JSON/proto payload field name matches the oneof case (namingPayload)"],"exampleFix":"// before\n{type: \"NAMING_TABLE\", level: \"ERROR\", engine: \"ENGINE_MYSQL\"}\n// after\n{type: \"NAMING_TABLE\", level: \"ERROR\", engine: \"ENGINE_MYSQL\", namingPayload: {format: \"^t_[a-z]+$\"}}","handlingStrategy":"validation","validationCode":"if isRegexNamingRule(rule.Type) && rule.GetNamingPayload() == nil {\n  return fmt.Errorf(\"rule %s needs a namingPayload before saving\", rule.Type)\n}","typeGuard":"func hasNamingPayload(r *v1pb.SQLReviewRule) bool { return r.GetNamingPayload() != nil }","tryCatchPattern":null,"preventionTips":["Map each rule type to its required payload kind and enforce it in the form layer","Never send a rule shell without its payload oneof filled","Add a unit test asserting every naming rule fixture carries a payload"],"tags":["validation","sql-review","payload","api"],"backgroundTag":"missing-required-config-field","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}