Use a stricter semantic version check for some version fields #225

Merged
Dalai Felinto merged 4 commits from fix-semver-versions into main 2024-07-23 15:50:54 +02:00
2 changed files with 85 additions and 21 deletions

View File

@ -400,11 +400,8 @@ class ValidateManifestFields(TestCase):
data['blender_version_min'] = '2.9'
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
self.assertEqual(1, len(e.exception.messages))
self.assertIn('blender_version_min', e.exception.messages[0])
self.assertIn('4.2.0', e.exception.messages[0])
data['blender_version_min'] = '4.1.0'
with self.assertRaises(ValidationError) as e:
@ -419,12 +416,8 @@ class ValidateManifestFields(TestCase):
data['blender_version_min'] = '4.2.0-beta'
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
self.assertEqual(
e.exception.messages,
[
'Manifest value error: <code>blender_version_min</code> should be at least "4.2.0"',
],
)
self.assertIn('blender_version_min', e.exception.messages[0])
self.assertIn('should follow', e.exception.messages[0])
def test_blender_version_max(self):
data = {
@ -443,6 +436,52 @@ class ValidateManifestFields(TestCase):
self.assertEqual(1, len(e.exception.messages))
self.assertIn('blender_version_max', e.exception.messages[0])
def test_non_semver_versions(self):
data = {
**self.mandatory_fields,
**self.optional_fields,
}
for field, value in (
('blender_version_min', '4.2'),
('blender_version_min', '4.2.1+alpha'),
('blender_version_min', '5'),
('blender_version_max', '4.3'),
('blender_version_max', '4.3.0+alpha'),
('blender_version_max', '5'),
('schema_version', '1.0'),
('schema_version', '1'),
('schema_version', '1.0.0.0'),
):
value_original = data[field]
data[field] = value
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
self.assertEqual(1, len(e.exception.messages))
self.assertIn(field, e.exception.messages[0])
data[field] = value_original
def test_version(self):
data = {
**self.mandatory_fields,
**self.optional_fields,
}
data['version'] = '1.2.0'
ManifestValidator(data)
data['version'] = '1.2.0+alpha'
ManifestValidator(data)
for bad_version in ['1.2', '1.2.0.0', '1']:
data['version'] = bad_version
with self.assertRaises(ValidationError) as e:
ManifestValidator(data)
self.assertEqual(1, len(e.exception.messages))
self.assertIn('version', e.exception.messages[0])
def test_licenses(self):
data = {
**self.mandatory_fields,

View File

@ -1,5 +1,6 @@
from semantic_version import Version
import logging
import re
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator, validate_unicode_slug
@ -20,6 +21,10 @@ import files.models
logger = logging.getLogger(__name__)
# Define the regex pattern for major.minor.patch (do not include any additional label).
# https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
version_regex_pattern = re.compile(r'^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$')
@deconstructible
class FileMIMETypeValidator:
@ -536,7 +541,35 @@ class VersionValidator:
)
class SchemaVersionValidator(VersionValidator):
class SemVerNoLabelValidator(StringValidator):
"""These version are a sub-set of the semantic version
They only support major.minor.patch.
dfelinto marked this conversation as resolved
Review

this import should be at the top of the file

this import should be at the top of the file
"""
@staticmethod
def is_version_string(version):
return version_regex_pattern.match(version)
dfelinto marked this conversation as resolved
Review

missing $ anchor

I would also declare this as a package "constant" at the top of the file, so that it is compiled once, and not on every validation

missing `$` anchor I would also declare this as a package "constant" at the top of the file, so that it is compiled once, and not on every validation
@classmethod
def validate(cls, *, name: str, value: str, manifest: dict) -> str:
"""Return error message if cannot validate, otherwise returns nothing"""
dfelinto marked this conversation as resolved
Review

this is unusual, why not use the $ anchor in the regex and do a regular match?

this is unusual, why not use the `$` anchor in the regex and do a regular match?
err_message = mark_safe(
f'Manifest value error: <code>{name}</code> should follow a '
f'<a href="https://semver.org/" target="_blank">semantic version</a> '
f'with only major.minor.patch '
f'e.g., "{cls.example}"'
)
if super().validate(name=name, value=value, manifest=manifest):
return err_message
# Make sure we only have major.minor.patch.
if not cls.is_version_string(version=value):
return err_message
dfelinto marked this conversation as resolved
Review

this is not needed, we need to fully rely on the check below

this is not needed, we need to fully rely on the check below
class SchemaVersionValidator(SemVerNoLabelValidator):
example = '1.0.0'
@classmethod
@ -558,7 +591,7 @@ class SchemaVersionValidator(VersionValidator):
)
class VersionMinValidator(VersionValidator):
class VersionMinValidator(SemVerNoLabelValidator):
example = '4.2.0'
@classmethod
@ -574,7 +607,7 @@ class VersionMinValidator(VersionValidator):
)
class VersionMaxValidator(VersionValidator):
class VersionMaxValidator(SemVerNoLabelValidator):
example = '4.3.0'
@classmethod
@ -591,15 +624,7 @@ class VersionMaxValidator(VersionValidator):
# assuming that VersionMinValidator has caught this and reported properly
return
try:
max = Version(value)
except (ValueError, TypeError):
return mark_safe(
f'Manifest value error: <code>{name}</code> should follow a '
f'<a href="https://semver.org/" target="_blank">semantic version</a>. '
f'e.g., "{cls.example}"'
)
max = Version(value)
if max <= min:
return mark_safe(
'Manifest value error: <code>blender_version_max</code> should be greater than '