Source code for craft_platforms.validators
# This file is part of craft-platforms.
#
# Copyright 2026 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""Validator functions.
These functions both work locally to validate fields and can be used as Pydantic
validators.
"""
import unicodedata
from typing import List
from craft_platforms._errors import InvalidPlatformNameError
from craft_platforms._platforms import RESERVED_PLATFORM_NAMES
_ALLOWED_UNICODE_CATEGORIES = (
# See: https://www.unicode.org/reports/tr44/tr44-34.html#General_Category_Values
"L", # All letter characters
"N", # All numbers
"So", # Other symbols
)
_ALLOWED_MIDDLE_CHARACTERS = (
"-",
"@",
".",
":",
) # Characters only allowed in the middle of a name.
_APPLICATION_RESERVED_CHARACTERS = ("/", "_") # Only allowed for use by an application.
[docs]
def validate_strict_platform_name(
name: str, *, allow_app_characters: bool = True
) -> str:
"""Validate a strictly-defined platform name.
:param name: the platform name to validate.
:param allow_app_characters: Whether to allow characters that are reserved for use
by applications. If False, an app-generated platform name can raise an error.
:returns: the platform name, if valid.
:raises: InvalidPlatformName if the platform name is invalid.
"""
if name in RESERVED_PLATFORM_NAMES:
raise InvalidPlatformNameError(
message=f"Platform name {name!r} is reserved.",
resolution=f"Rename platform {name!r} to follow the naming rules.",
doc_slug="platform-name-rules",
reportable=False,
)
invalid_characters: List[str] = []
if not allow_app_characters:
invalid_characters.extend(
character
for character in _APPLICATION_RESERVED_CHARACTERS
if character in name
)
invalid_characters.extend(
character
for character in _ALLOWED_MIDDLE_CHARACTERS
if name.startswith(character) or name.endswith(character)
)
for character in name:
category = unicodedata.category(character)
if character in (_ALLOWED_MIDDLE_CHARACTERS + _APPLICATION_RESERVED_CHARACTERS):
continue
for allowed_category in _ALLOWED_UNICODE_CATEGORIES:
if category.startswith(allowed_category):
break
else:
invalid_characters.append(character)
if invalid_characters:
raise InvalidPlatformNameError(
message=f"Invalid platform name: {name!r}",
details=f"Platform name contains invalid characters: {invalid_characters}",
resolution=f"Rename platform {name!r} to follow the naming rules.",
doc_slug="platform-name-rules",
reportable=False,
)
return name