Skip to content

Commit

Permalink
feat(service): update template schema and errors (#2845)
Browse files Browse the repository at this point in the history
fix #2729

Co-authored-by: Ralf Grubenmann <ralf.grubenmann@sdsc.ethz.ch>
  • Loading branch information
lorenzo-cavazzi and Panaetius committed Apr 19, 2022
1 parent c2e08c8 commit 905d1ae
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 53 deletions.
46 changes: 12 additions & 34 deletions renku/ui/service/serializers/templates.py
Expand Up @@ -19,7 +19,7 @@

from urllib.parse import urlparse

from marshmallow import Schema, ValidationError, fields, post_load, pre_load, validates
from marshmallow import Schema, ValidationError, fields, post_load, pre_load
from yagup import GitURL
from yagup.exceptions import InvalidURL

Expand Down Expand Up @@ -54,29 +54,23 @@ class ProjectTemplateRequest(ProjectCloneContext, ManifestTemplatesRequest):
"""Request schema for listing manifest templates."""

identifier = fields.String(required=True)
initial_branch = fields.String(missing=None)
parameters = fields.List(fields.Nested(TemplateParameterSchema), missing=[])

project_name = fields.String(required=True)
project_namespace = fields.String(required=True)
project_repository = fields.String(required=True)
project_slug = fields.String(required=True)
project_description = fields.String(missing=None)
project_keywords = fields.List(fields.String(), missing=None)
project_custom_metadata = fields.Dict(missing=None)

new_project_url = fields.String(required=True)
project_name_stripped = fields.String(required=True)

initial_branch = fields.String(missing=None)

@pre_load()
def create_new_project_url(self, data, **kwargs):
"""Set owner and name fields."""
@post_load()
def add_required_fields(self, data, **kwargs):
"""Add necessary fields."""
project_name_stripped = normalize_to_ascii(data["project_name"])
if len(project_name_stripped) == 0:
raise ValidationError("Project name contains only unsupported characters")
new_project_url = f"{data['project_repository']}/{data['project_namespace']}/{project_name_stripped}"
try:
project_name_stripped = normalize_to_ascii(data["project_name"])
if len(project_name_stripped) == 0:
raise ValidationError("Project name contains only unsupported characters")
new_project_url = f"{data['project_repository']}/{data['project_namespace']}/{project_name_stripped}"
_ = GitURL.parse(new_project_url)
except InvalidURL as e:
raise ValidationError("`git_url` contains unsupported characters") from e
Expand All @@ -86,25 +80,9 @@ def create_new_project_url(self, data, **kwargs):
data["project_name_stripped"] = project_name_stripped
data["project_slug"] = project_slug

return data

@validates("new_project_url")
def validate_new_project_url(self, value):
"""Validates git url."""
try:
GitURL.parse(value)
except InvalidURL as e:
raise ValidationError(str(e))

return value

@post_load()
def format_new_project_url(self, data, **kwargs):
"""Format URL with an access token."""
new_project_url = urlparse(data["new_project_url"])

url = "oauth2:{0}@{1}".format(data["token"], new_project_url.netloc)
data["new_project_url_with_auth"] = new_project_url._replace(netloc=url).geturl()
new_project_url_parsed = urlparse(new_project_url)
url = "oauth2:{0}@{1}".format(data["token"], new_project_url_parsed.netloc)
data["new_project_url_with_auth"] = new_project_url_parsed._replace(netloc=url).geturl()

return data

Expand Down
30 changes: 16 additions & 14 deletions renku/ui/service/views/error_handlers.py
Expand Up @@ -251,25 +251,27 @@ def handle_templates_create_errors(f):
def decorated_function(*args, **kwargs):
"""Represents decorated function."""

def match_schema(target, message):
if re.match(f".*{target}.*contains.*unsupported characters.*", message):
return True
return False
def get_schema_error_message(e):
if isinstance(getattr(e, "messages", None), dict) and e.messages.get("_schema"):
message = (
"; ".join(e.messages.get("_schema"))
if isinstance(e.messages.get("_schema"), list)
else str(e.messages.get("_schema"))
)
return message
return None

try:
return f(*args, **kwargs)
except ValidationError as e:
if getattr(e, "field_name", None) == "_schema":
error_message = str(e)
if match_schema("Project name", error_message):
raise UserProjectCreationError(e, "project name must contain at least a valid character")
elif match_schema("git_url", error_message):
raise ProgramProjectCreationError(e, "git_url is invalid")
raise
except KeyError as e:
# NOTE: it's hard to determine if the error is user generated here
error_message = str(e).strip("'").replace("_", " ")
raise UserProjectCreationError(e, f"provide a value for {error_message}")
error_message = get_schema_error_message(e)
if error_message:
raise UserProjectCreationError(e, error_message)
else:
raise ProgramProjectCreationError(e, str(e))
else:
raise ProgramProjectCreationError(e)

return decorated_function

Expand Down
10 changes: 5 additions & 5 deletions tests/service/views/test_templates_views.py
Expand Up @@ -185,7 +185,7 @@ def test_create_project_from_template_failures(svc_client_templates_creation):
assert 200 == response.status_code
assert {"error"} == set(response.json.keys())
assert UserProjectCreationError.code == response.json["error"]["code"]
assert "project name" in response.json["error"]["devMessage"]
assert "project name" in response.json["error"]["devMessage"].lower()

# NOTE: fail on wrong git url - unexpected when invoked from the UI
payload_wrong_repo = deepcopy(payload)
Expand All @@ -194,7 +194,7 @@ def test_create_project_from_template_failures(svc_client_templates_creation):
response = svc_client.post("/templates.create_project", data=json.dumps(payload_wrong_repo), headers=headers)
assert 200 == response.status_code
assert {"error"} == set(response.json.keys())
assert ProgramProjectCreationError.code == response.json["error"]["code"]
assert UserProjectCreationError.code == response.json["error"]["code"]
assert "git_url" in response.json["error"]["devMessage"]

# NOTE: missing fields -- unlikely to happen. If that is the case, we should determine if it's a user error or not
Expand All @@ -204,12 +204,12 @@ def test_create_project_from_template_failures(svc_client_templates_creation):
response = svc_client.post("/templates.create_project", data=json.dumps(payload_missing_field), headers=headers)
assert 200 == response.status_code
assert {"error"} == set(response.json.keys())
assert UserProjectCreationError.code == response.json["error"]["code"]
assert "provide a value for project repository" in response.json["error"]["devMessage"]
assert ProgramProjectCreationError.code == response.json["error"]["code"]
assert "missing data for required field" in response.json["error"]["devMessage"].lower()

# NOTE: wrong template identifier
payload_fake_id = deepcopy(payload)
fake_identifier = "__FAKE_IDDENTIFIER__"
fake_identifier = "__FAKE_IDENTIFIER__"
payload_fake_id["identifier"] = fake_identifier

response = svc_client.post("/templates.create_project", data=json.dumps(payload_fake_id), headers=headers)
Expand Down

0 comments on commit 905d1ae

Please sign in to comment.