fix(marker): reject reserved global-prefix names only on create

The global-prefix reservation belongs on the create path, where it keeps
user-chosen names disjoint from inherited global-{name} markers. It was
implemented as a field_validator on MarkerCreate / MarkerDefinitionCreate,
which also back the PUT update bodies — so updating an inherited marker
while echoing its name in the body tripped the validator with a generic
422 before the controller could return the actionable 409 ("inherited,
use the marker-definitions API").

Drop the schema validators and enforce the prefix in the two create routes
(ControllerError -> 409). PUT no longer validates the body name (ignored
anyway — the target is the {name} path param), so editing an inherited
marker now reaches the controller's inheritance guard and returns the
clear 409. The name-format regex stays on the schema (still 422).
This commit is contained in:
YueGuobin 2026-07-14 00:10:51 +08:00
parent cdc27c37a7
commit b21780605e
No known key found for this signature in database
3 changed files with 5 additions and 15 deletions

View File

@ -458,6 +458,8 @@ async def create_marker(
# uuid suffix avoids the collision that `marker-{link.id[:8]}` alone would
# cause on the second anonymous marker on the same link (start_marker
# rejects duplicate names).
if marker_data.name and marker_data.name.lower().startswith("global"):
raise ControllerError('Names starting with "global" are reserved for inherited markers')
name = marker_data.name or f"marker-{link.id[:8]}-{uuid4().hex[:4]}"
await link.start_marker(
name=name,

View File

@ -260,6 +260,8 @@ async def create_marker_definition(
Required privilege: Project.Modify
"""
if def_data.name and def_data.name.lower().startswith("global"):
raise ControllerError('Names starting with "global" are reserved for inherited markers')
name = def_data.name or f"def-{project.id[:8]}"
await project.create_marker_definition(
name=name,

View File

@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from pydantic import BaseModel, Field, field_validator
from pydantic import BaseModel, Field
from typing import List, Optional, Tuple
from enum import Enum
from uuid import UUID, uuid4
@ -176,13 +176,6 @@ class MarkerCreate(BaseModel):
description="Whether the marker is active. Defaults to true on creation.",
)
@field_validator("name")
@classmethod
def _check_name_not_reserved(cls, v):
if v is not None and v.lower().startswith("global"):
raise ValueError('Names starting with "global" are reserved')
return v
class MarkerDefinitionCreate(BaseModel):
"""
@ -215,11 +208,4 @@ class MarkerDefinitionCreate(BaseModel):
),
)
@field_validator("name")
@classmethod
def _check_name_not_reserved(cls, v):
if v is not None and v.lower().startswith("global"):
raise ValueError('Names starting with "global" are reserved')
return v