Skip to content

Commit

Permalink
Crossvalidate url and version, with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ml-evs authored and CasperWA committed Feb 6, 2020
1 parent 01974aa commit 6a60379
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 4 deletions.
25 changes: 22 additions & 3 deletions optimade/models/baseinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re

from typing import Dict, List, Optional
from pydantic import BaseModel, AnyUrl, Field, validator
from pydantic import BaseModel, AnyHttpUrl, Field, validator, root_validator

from .jsonapi import Resource

Expand All @@ -13,10 +13,11 @@
class AvailableApiVersion(BaseModel):
"""A JSON object containing information about an available API version"""

url: AnyUrl = Field(
url: AnyHttpUrl = Field(
...,
description="a string specifying a versioned base URL that MUST adhere to the rules in section Base URL",
)

version: str = Field(
...,
description="a string containing the full version number of the API served at that versioned base URL. "
Expand All @@ -31,12 +32,30 @@ def url_must_be_versioned_base_url(cls, v):
return v

@validator("version")
def version_must_not_prefix_v(cls, v):
def version_must_be_valid(cls, v):
"""The version number string MUST NOT be prefixed by, e.g., 'v'"""
if not re.match(r"[0-9]+\.[0-9]+(\.[0-9]+)?", v):
raise ValueError(f"version MUST NOT be prefixed by, e.g., 'v'. It is: {v}")
try:
_ = tuple(int(val) for val in v.split("."))
except Exception:
raise ValueError(f"failed to parse version {v} sections as integers.")

return v

@root_validator(pre=False, skip_on_failure=True)
def crosscheck_url_and_version(cls, values):
""" Check that URL version and API version are compatible. """
url_version = values["url"].split("/")[-2 if values["url"].endswith('/') else -1].replace('v', '')
url_version = tuple(int(val) for val in url_version.split('.'))
api_version = tuple(int(val) for val in values["version"].split("."))
if any(a != b for a, b in zip(url_version, api_version)):
raise ValueError(
"API version {api_version} is not compatible with url version {url_version}."
)

return values


class BaseInfoAttributes(BaseModel):
"""Attributes for Base URL Info endpoint"""
Expand Down
41 changes: 40 additions & 1 deletion tests/models/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

from pydantic import ValidationError, BaseModel, ConfigError
from optimade.models.utils import conlist
from optimade.models import StructureResource, EntryRelationships, ReferenceResource
from optimade.models import (
StructureResource,
EntryRelationships,
ReferenceResource,
AvailableApiVersion,
)
from optimade.server.mappers import StructureMapper, ReferenceMapper


Expand Down Expand Up @@ -130,6 +135,40 @@ def test_bad_references(self):
ReferenceResource(**ReferenceMapper.map_back(ref))


def test_available_api_versions():
bad_urls = [
"asfdsafhttps://example.com/optimade/v0.0",
"https://example.com/optimade",
"https://example.com/optimade/v0",
"https://example.com/optimade/v0999",
]
good_urls = [
{"url": "https://example.com/optimade/v0", "version": "0.1.9"},
{"url": "https://example.com/optimade/v1.0.2", "version": "1.0.2"},
{"url": "http://example.com/optimade/v2.3", "version": "2.3.1"},
]

bad_combos = [
{"url": "https://example.com/optimade/v0", "version": "1.0.0"},
{"url": "https://example.com/optimade/v1.0.2", "version": "1.0.3"},
{"url": "http://example.com/optimade/v2.3", "version": "2.0.1"},
]

for url in bad_urls:
with pytest.raises(ValueError, message=f"Url {url} should have failed"):
AvailableApiVersion(url=url, version="1.0")

for data in bad_combos:
with pytest.raises(
ValueError,
message=f"{data['url']} should have failed with version {data['version']}",
):
AvailableApiVersion(**data)

for data in good_urls:
AvailableApiVersion(**data)


def test_constrained_list():
class ConListModel(BaseModel):
v: conlist(len_eq=3)
Expand Down

0 comments on commit 6a60379

Please sign in to comment.