Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow numbers in component names #4945

Merged
merged 3 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 33 additions & 30 deletions snapcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,26 +238,44 @@ def _validate_version_name(version: str, model_name: str) -> None:
)


def _validate_component_name(name: str) -> None:
"""Validate a component name."""
if not re.fullmatch(r"[a-z-]*[a-z][a-z-]*", name):
raise ValueError(
"Component names can only use ASCII lowercase letters and hyphens"
)
def _validate_name(*, name: str, field_name: str) -> str:
"""Validate a name.

if name.startswith("snap-"):
:param name: The name to validate.
:param field_name: The name of the field being validated.

:returns: The validated name.
"""
if not re.match(r"^[a-z0-9-]*[a-z][a-z0-9-]*$", name):
raise ValueError(
"Component names cannot start with the reserved namespace 'snap-'"
f"{field_name} names can only use lowercase alphanumeric "
"and hyphens and must have at least one letter"
)

if name.startswith("-"):
raise ValueError("Component names cannot start with a hyphen")
raise ValueError(f"{field_name} names cannot start with a hyphen")

if name.endswith("-"):
raise ValueError("Component names cannot end with a hyphen")
raise ValueError(f"{field_name} names cannot end with a hyphen")

if "--" in name:
raise ValueError("Component names cannot have two hyphens in a row")
raise ValueError(f"{field_name} names cannot have two hyphens in a row")

return name


def _validate_component(name: str) -> str:
"""Validate a component name.

:param name: The component name to validate.

:returns: The validated component name.
"""
if name.startswith("snap-"):
raise ValueError(
"component names cannot start with the reserved prefix 'snap-'"
)
return _validate_name(name=name, field_name="component")


def _get_partitions_from_components(
Expand Down Expand Up @@ -731,23 +749,8 @@ def _validate_mandatory_base(cls, values):

@pydantic.validator("name")
@classmethod
def _validate_name(cls, name):
if not re.match(r"^[a-z0-9-]*[a-z][a-z0-9-]*$", name):
raise ValueError(
"Snap names can only use ASCII lowercase letters, numbers, and hyphens, "
"and must have at least one letter"
)

if name.startswith("-"):
raise ValueError("Snap names cannot start with a hyphen")

if name.endswith("-"):
raise ValueError("Snap names cannot end with a hyphen")

if "--" in name:
raise ValueError("Snap names cannot have two hyphens in a row")

return name
def _validate_snap_name(cls, name):
return _validate_name(name=name, field_name="snap")

@pydantic.validator("version")
@classmethod
Expand All @@ -764,7 +767,7 @@ def _validate_version(cls, version, values):
def _validate_components(cls, components):
"""Validate component names."""
for component_name in components.keys():
_validate_component_name(component_name)
_validate_component(name=component_name)

return components

Expand Down Expand Up @@ -1077,7 +1080,7 @@ class ComponentProject(models.CraftBaseModel, extra=pydantic.Extra.ignore):
def _validate_components(cls, components):
"""Validate component names."""
for component_name in components.keys():
_validate_component_name(component_name)
_validate_component(name=component_name)

return components

Expand Down
51 changes: 27 additions & 24 deletions tests/unit/models/test_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,13 @@ def test_project_name_valid(self, name, project_yaml_data):
@pytest.mark.parametrize(
"name,error",
[
("name_with_underscores", "Snap names can only use"),
("name-with-UPPERCASE", "Snap names can only use"),
("name with spaces", "Snap names can only use"),
("-name-starts-with-hyphen", "Snap names cannot start with a hyphen"),
("name-ends-with-hyphen-", "Snap names cannot end with a hyphen"),
("name-has--two-hyphens", "Snap names cannot have two hyphens in a row"),
("123456", "Snap names can only use"),
("name_with_underscores", "snap names can only use"),
("name-with-UPPERCASE", "snap names can only use"),
("name with spaces", "snap names can only use"),
("-name-starts-with-hyphen", "snap names cannot start with a hyphen"),
("name-ends-with-hyphen-", "snap names cannot end with a hyphen"),
("name-has--two-hyphens", "snap names cannot have two hyphens in a row"),
("123456", "snap names can only use"),
(
"a2345678901234567890123456789012345678901",
"ensure this value has at most 40 characters",
Expand Down Expand Up @@ -2490,7 +2490,15 @@ def test_component_type_invalid(
project.unmarshal(project_yaml_data(components=component))

@pytest.mark.parametrize(
"name", ["name", "name-with-dashes", "x" * 40, "foo-snap-bar"]
"name",
[
"name",
"name-with-dashes",
"name-with-numbers-0123",
"0123-name-with-numbers",
"x" * 40,
"foo-snap-bar",
],
)
def test_component_name_valid(
self, project, name, project_yaml_data, stub_component_data
Expand All @@ -2505,26 +2513,21 @@ def test_component_name_valid(
@pytest.mark.parametrize(
"name,error",
[
(
"snap-",
"Component names cannot start with the reserved namespace 'snap-'",
),
(
pytest.param(
mr-cal marked this conversation as resolved.
Show resolved Hide resolved
"snap-foo",
"Component names cannot start with the reserved namespace 'snap-'",
"component names cannot start with the reserved prefix 'snap-'",
id="reserved prefix",
),
("123456", "Component names can only use"),
("name-ends-with-digits-0123", "Component names can only use"),
("456-name-starts-with-digits", "Component names can only use"),
("name-789-contains-digits", "Component names can only use"),
("name_with_underscores", "Component names can only use"),
("name-with-UPPERCASE", "Component names can only use"),
("name with spaces", "Component names can only use"),
("-name-starts-with-hyphen", "Component names cannot start with a hyphen"),
("name-ends-with-hyphen-", "Component names cannot end with a hyphen"),
pytest.param("123456", "component names can only use", id="no letters"),
("name_with_underscores", "component names can only use"),
("name-with-UPPERCASE", "component names can only use"),
("name with spaces", "component names can only use"),
("name-with-$symbols", "component names can only use"),
("-name-starts-with-hyphen", "component names cannot start with a hyphen"),
("name-ends-with-hyphen-", "component names cannot end with a hyphen"),
(
"name-has--two-hyphens",
"Component names cannot have two hyphens in a row",
"component names cannot have two hyphens in a row",
),
("x" * 41, "ensure this value has at most 40 characters"),
],
Expand Down
Loading