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

Resolver: Handle malformed URIs and provide error messages #307

Merged
merged 3 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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: 39 additions & 24 deletions api/resolve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from urllib.parse import parse_qs, urlparse

from fastapi import APIRouter, Query
from nxtools import logging

from ayon_server.api.dependencies import ClientSiteID, CurrentUser
from ayon_server.exceptions import BadRequestException, ServiceUnavailableException
Expand All @@ -24,6 +23,7 @@


SDF_REGEX = re.compile(r":SDF_FORMAT_ARGS.*$")
NAME_VALIDATOR = re.compile(NAME_REGEX)


def sanitize_uri(uri: str) -> str:
Expand All @@ -37,8 +37,8 @@ def validate_name(name: str | None) -> None:
return
if name == "*":
return
name_validator = re.compile(NAME_REGEX)
assert name_validator.match(name), f"Invalid name: {name}"
if not NAME_VALIDATOR.match(name):
raise ValueError(f"Invalid name: {name}")


def parse_uri(uri: str) -> ParsedURIModel:
Expand All @@ -53,16 +53,18 @@ def parse_uri(uri: str) -> ParsedURIModel:
uri = sanitize_uri(uri)

parsed_uri = urlparse(uri)
assert parsed_uri.scheme in [
"ayon",
"ayon+entity",
], f"Invalid scheme: {parsed_uri.scheme}"
if parsed_uri.scheme not in ["ayon", "ayon+entity"]:
raise ValueError(f"Invalid scheme: {parsed_uri.scheme}")

project_name = parsed_uri.netloc
name_validator = re.compile(NAME_REGEX)
assert name_validator.match(project_name), f"Invalid project name: {project_name}"
if not NAME_VALIDATOR.match(project_name):
raise ValueError(f"Invalid project name: {project_name}")

path = parsed_uri.path.strip("/") or None
if path:
for element in path.split("/"):
if not NAME_VALIDATOR.match(element):
raise ValueError(f"Invalid path element: {element}")

qs: dict[str, Any] = parse_qs(parsed_uri.query)

Expand All @@ -89,11 +91,14 @@ def parse_uri(uri: str) -> ParsedURIModel:
# assert we don't have incompatible arguments

if task_name is not None or workfile_name is not None:
assert product_name is None, "Tasks cannot be queried with products"
assert version_name is None, "Tasks cannot be queried with versions"
assert (
representation_name is None
), "Tasks cannot be queried with representations"
if product_name is not None:
raise ValueError("Tasks and workfiles cannot be queried with products")
if version_name is not None:
raise ValueError("Tasks and workfiles cannot be queried with versions")
if representation_name is not None:
raise ValueError(
"Tasks and workfiles cannot be queried with representations"
)

return ParsedURIModel(
uri=uri,
Expand Down Expand Up @@ -140,6 +145,7 @@ def get_version_conditions(version_name: str | None) -> list[str]:
return []

original_version_name = version_name
version_name = version_name.strip().lower()
if version_name.startswith("v"):
version_name = version_name[1:]

Expand All @@ -159,8 +165,7 @@ def get_version_conditions(version_name: str | None) -> list[str]:
if version_name == "hero":
return ["v.version < 0"]

logging.debug(f"Invalid version name: {original_version_name}")
return ["FALSE"]
raise ValueError(f"Invalid version name: {original_version_name}")


def get_representation_conditions(representation_name: str | None) -> list[str]:
Expand Down Expand Up @@ -357,18 +362,28 @@ async def resolve_uris(
async with Postgres.acquire() as conn:
async with conn.transaction():
for uri in request.uris:
parsed_uri = parse_uri(uri)
try:
parsed_uri = parse_uri(uri)
except ValueError as e:
result.append(ResolvedURIModel(uri=uri, error=str(e)))
continue

if parsed_uri.project_name != current_project:
await conn.execute(
f"SET LOCAL search_path TO project_{parsed_uri.project_name}"
)
current_project = parsed_uri.project_name
entities = await resolve_entities(
conn,
parsed_uri,
roots.get(current_project, {}),
site_id,
path_only=path_only,
)

try:
entities = await resolve_entities(
conn,
parsed_uri,
roots.get(current_project, {}),
site_id,
path_only=path_only,
)
except ValueError as e:
result.append(ResolvedURIModel(uri=uri, error=str(e)))
BigRoy marked this conversation as resolved.
Show resolved Hide resolved
continue
result.append(ResolvedURIModel(uri=uri, entities=entities))
return result
9 changes: 7 additions & 2 deletions api/resolve/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ class ResolvedURIModel(OPModel):
title="Resolved URI",
example="ayon+entity://demo_Big_Feature/assets/environments/01_pfueghtiaoft?product=layoutMain&version=v004&representation=ma",
)
entities: list[ResolvedEntityModel] = Field(
...,
entities: list[ResolvedEntityModel] | None = Field(
None,
title="Resolved entities",
example=[
{
Expand All @@ -89,6 +89,11 @@ class ResolvedURIModel(OPModel):
}
],
)
error: str | None = Field(
None,
title="Error",
description="Error message if the URI could not be resolved",
)


class ParsedURIModel(OPModel):
Expand Down