diff --git a/app/models/projects/identifier.rb b/app/models/projects/identifier.rb index 0649ce411cfd..b9835e3aff53 100644 --- a/app/models/projects/identifier.rb +++ b/app/models/projects/identifier.rb @@ -36,6 +36,10 @@ module Projects::Identifier # Classic identifier format: lowercase letters, digits, hyphens, underscores — but not all-numeric. CLASSIC_IDENTIFIER_FORMAT = /\A(?!\d+\z)[a-z0-9\-_]+\z/ + # Unanchored shape of a semantic project identifier ("PROJ", "MY_PROJECT_1"). + # Composed into `WorkPackage::SemanticIdentifier::SEMANTIC_ID_PATTERN`. + SEMANTIC_FORMAT = /[A-Z][A-Z0-9_]*/ + RESERVED_IDENTIFIERS = %w[new menu queries filters identifier_update_dialog identifier_suggestion].freeze included do diff --git a/app/models/work_package/semantic_identifier.rb b/app/models/work_package/semantic_identifier.rb index a1608d74860f..cfc0e528e73e 100644 --- a/app/models/work_package/semantic_identifier.rb +++ b/app/models/work_package/semantic_identifier.rb @@ -31,10 +31,15 @@ module WorkPackage::SemanticIdentifier extend ActiveSupport::Concern + # Semantic-identifier shape ("PROJ-42"). Use this when the numeric and + # semantic branches need different boundary rules; use `ID_ROUTE_CONSTRAINT` + # when both branches share one regex. + SEMANTIC_ID_PATTERN = /#{Projects::Identifier::SEMANTIC_FORMAT.source}-\d+/ + # Matches either a numeric ID ("12345") or a semantic identifier ("PROJ-42"). # Used in Rails route constraints so both forms are accepted in URLs. # The frontend equivalent lives in WP_ID_URL_PATTERN (work-package-id-pattern.ts). - ID_ROUTE_CONSTRAINT = /\d+|[A-Z][A-Z0-9_]*-\d+/ + ID_ROUTE_CONSTRAINT = /\d+|#{SEMANTIC_ID_PATTERN.source}/ # Raised when a finder is invoked in a way that cannot resolve a semantic # identifier — e.g. find_by(id: "PROJ-42") which reduces to a raw SQL diff --git a/app/validators/projects/identifier_validator.rb b/app/validators/projects/identifier_validator.rb index c1703d68e442..4960a37b9b12 100644 --- a/app/validators/projects/identifier_validator.rb +++ b/app/validators/projects/identifier_validator.rb @@ -30,6 +30,9 @@ module Projects class IdentifierValidator < ActiveModel::EachValidator + # Two anchored patterns, one per rule, so the start-character and body + # checks produce distinct error messages. The full unanchored shape + # lives at `Projects::Identifier::SEMANTIC_FORMAT`. SEMANTIC_START_FORMAT = /\A[A-Z]/ SEMANTIC_BODY_FORMAT = /\A[A-Z0-9_]*\z/