summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2019-08-19 21:09:46 +0200
committerIlya Etingof <etingof@gmail.com>2019-08-27 10:07:02 +0200
commit6cc4b86dbc92d4f76c45f86c9828308ce6e77ef5 (patch)
treeceb16ce32a943bc79dc43cada938d2666b0febf4
parent41ce2e5cfeef488f847c3f58ff3d9d0fceb9ded7 (diff)
downloadpyasn1-git-add-with-components-constraint.tar.gz
Add `SET ... WITH COMPONENTS ...` ASN.1 construct supportadd-with-components-constraint
Added `WithComponentsConstraint` along with related `ComponentPresentConstraint` and `ComponentAbsentConstraint` classes to be used with `Sequence`/`Set` types representing `SET ... WITH COMPONENTS ...` like ASN.1 constructs.
-rw-r--r--CHANGES.rst4
-rw-r--r--docs/source/pyasn1/type/base/constructedasn1type.rst2
-rw-r--r--docs/source/pyasn1/type/constraint/contents.rst1
-rw-r--r--docs/source/pyasn1/type/constraint/withcomponents.rst16
-rw-r--r--pyasn1/type/base.py18
-rw-r--r--pyasn1/type/constraint.py133
-rw-r--r--pyasn1/type/univ.py73
-rw-r--r--tests/type/test_constraint.py63
-rw-r--r--tests/type/test_univ.py102
9 files changed, 393 insertions, 19 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index f9c00bb..88f5c63 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,10 @@ Revision 0.4.7, released XX-09-2019
objects as the type in field definition. When a bare Python value is
assigned, then field type object is cloned and initialized with the
bare value (constraints verificaton would run at this moment).
+- Added `WithComponentsConstraint` along with related
+ `ComponentPresentConstraint` and `ComponentAbsentConstraint` classes
+ to be used with `Sequence`/`Set` types representing
+ `SET ... WITH COMPONENTS ...` like ASN.1 constructs.
Revision 0.4.6, released 31-07-2019
-----------------------------------
diff --git a/docs/source/pyasn1/type/base/constructedasn1type.rst b/docs/source/pyasn1/type/base/constructedasn1type.rst
index cf7f665..8709066 100644
--- a/docs/source/pyasn1/type/base/constructedasn1type.rst
+++ b/docs/source/pyasn1/type/base/constructedasn1type.rst
@@ -7,4 +7,4 @@
------------
.. autoclass:: pyasn1.type.base.ConstructedAsn1Type(tagSet=TagSet(), subtypeSpec=ConstraintsIntersection(), componentType=None)
- :members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec, isInconsistent
+ :members: isSameTypeWith, isSuperTypeOf, tagSet, effectiveTagSet, tagMap, subtypeSpec
diff --git a/docs/source/pyasn1/type/constraint/contents.rst b/docs/source/pyasn1/type/constraint/contents.rst
index 8e4db7c..a4e2424 100644
--- a/docs/source/pyasn1/type/constraint/contents.rst
+++ b/docs/source/pyasn1/type/constraint/contents.rst
@@ -32,6 +32,7 @@ they get attached to ASN.1 type object at a *.subtypeSpec* attribute.
/pyasn1/type/constraint/valuerange
/pyasn1/type/constraint/valuesize
/pyasn1/type/constraint/permittedalphabet
+ /pyasn1/type/constraint/withcomponents
Logic operations on constraints
diff --git a/docs/source/pyasn1/type/constraint/withcomponents.rst b/docs/source/pyasn1/type/constraint/withcomponents.rst
new file mode 100644
index 0000000..f1556b0
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/withcomponents.rst
@@ -0,0 +1,16 @@
+
+.. _constrain.WithComponentsConstraint:
+
+.. |Constraint| replace:: WithComponentsConstraint
+
+WITH COMPONENTS constraint
+--------------------------
+
+.. autoclass:: pyasn1.type.constraint.WithComponentsConstraint(*fields)
+ :members:
+
+.. autoclass:: pyasn1.type.constraint.ComponentPresentConstraint()
+ :members:
+
+.. autoclass:: pyasn1.type.constraint.ComponentAbsentConstraint()
+ :members:
diff --git a/pyasn1/type/base.py b/pyasn1/type/base.py
index 834b76e..994f1c9 100644
--- a/pyasn1/type/base.py
+++ b/pyasn1/type/base.py
@@ -677,24 +677,6 @@ class ConstructedAsn1Type(Asn1Type):
return clone
- @property
- def isInconsistent(self):
- """Run necessary checks to ensure object consistency.
-
- Default action is to verify |ASN.1| object against constraints imposed
- by `subtypeSpec`.
-
- Raises
- ------
- :py:class:`~pyasn1.error.PyAsn1tError` on any inconsistencies found
- """
- try:
- self.subtypeSpec(self)
-
- except error.PyAsn1Error:
- exc = sys.exc_info()[1]
- return exc
-
def getComponentByPosition(self, idx):
raise error.PyAsn1Error('Method not implemented')
diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py
index 75db38a..b8aa0af 100644
--- a/pyasn1/type/constraint.py
+++ b/pyasn1/type/constraint.py
@@ -342,6 +342,139 @@ class PermittedAlphabetConstraint(SingleValueConstraint):
raise error.ValueConstraintError(value)
+class ComponentPresentConstraint(AbstractConstraint):
+ """Create a ComponentPresentConstraint object.
+
+ The ComponentPresentConstraint is only satisfied when the value
+ is not `None`.
+
+ The ComponentPresentConstraint object is typically used with
+ `WithComponentsConstraint`.
+
+ Examples
+ --------
+ .. code-block:: python
+
+ present = ComponentPresentConstraint()
+
+ # this will succeed
+ present('whatever')
+
+ # this will raise ValueConstraintError
+ present(None)
+ """
+ def _setValues(self, values):
+ self._values = ('<must be present>',)
+
+ if values:
+ raise error.PyAsn1Error('No arguments expected')
+
+ def _testValue(self, value, idx):
+ if value is None:
+ raise error.ValueConstraintError(
+ 'Component is not present:')
+
+
+class ComponentAbsentConstraint(AbstractConstraint):
+ """Create a ComponentAbsentConstraint object.
+
+ The ComponentAbsentConstraint is only satisfied when the value
+ is `None`.
+
+ The ComponentAbsentConstraint object is typically used with
+ `WithComponentsConstraint`.
+
+ Examples
+ --------
+ .. code-block:: python
+
+ absent = ComponentAbsentConstraint()
+
+ # this will succeed
+ absent(None)
+
+ # this will raise ValueConstraintError
+ absent('whatever')
+ """
+ def _setValues(self, values):
+ self._values = ('<must be absent>',)
+
+ if values:
+ raise error.PyAsn1Error('No arguments expected')
+
+ def _testValue(self, value, idx):
+ if value is not None:
+ raise error.ValueConstraintError(
+ 'Component is not absent: %r' % value)
+
+
+class WithComponentsConstraint(AbstractConstraint):
+ """Create a WithComponentsConstraint object.
+
+ The WithComponentsConstraint satisfies any mapping object that has
+ constrained fields present or absent, what is indicated by
+ `ComponentPresentConstraint` and `ComponentAbsentConstraint`
+ objects respectively.
+
+ The WithComponentsConstraint object is typically applied
+ to :class:`~pyasn1.type.univ.Set` or
+ :class:`~pyasn1.type.univ.Sequence` types.
+
+ Parameters
+ ----------
+ *fields: :class:`tuple`
+ Zero or more tuples of (`field`, `constraint`) indicating constrained
+ fields.
+
+ Examples
+ --------
+
+ .. code-block:: python
+
+ class Item(Sequence): # Set is similar
+ '''
+ ASN.1 specification:
+
+ Item ::= SEQUENCE {
+ id INTEGER OPTIONAL,
+ name OCTET STRING OPTIONAL
+ } WITH COMPONENTS id PRESENT, name ABSENT | id ABSENT, name PRESENT
+ '''
+ componentType = NamedTypes(
+ OptionalNamedType('id', Integer()),
+ OptionalNamedType('name', OctetString())
+ )
+ withComponents = ConstraintsIntersection(
+ WithComponentsConstraint(
+ ('id', ComponentPresentConstraint()),
+ ('name', ComponentAbsentConstraint())
+ ),
+ WithComponentsConstraint(
+ ('id', ComponentAbsentConstraint()),
+ ('name', ComponentPresentConstraint())
+ )
+ )
+
+ item = Item()
+
+ # This will succeed
+ item['id'] = 1
+
+ # This will succeed
+ item['name'] = 'John'
+
+ # This will fail on encoding
+ descr['id'] = 1
+ descr['name'] = 'John'
+ """
+ def _testValue(self, value, idx):
+ for field, constraint in self._values:
+ constraint(value.get(field))
+
+ def _setValues(self, values):
+ AbstractConstraint._setValues(self, values)
+
+
# This is a bit kludgy, meaning two op modes within a single constraint
class InnerTypeConstraint(AbstractConstraint):
"""Value must satisfy the type and presence constraints"""
diff --git a/pyasn1/type/univ.py b/pyasn1/type/univ.py
index fbf8ed5..aa688b2 100644
--- a/pyasn1/type/univ.py
+++ b/pyasn1/type/univ.py
@@ -2042,6 +2042,41 @@ class SequenceOfAndSetOfBase(base.ConstructedAsn1Type):
return True
+ @property
+ def isInconsistent(self):
+ """Run necessary checks to ensure |ASN.1| object consistency.
+
+ Default action is to verify |ASN.1| object against constraints imposed
+ by `subtypeSpec`.
+
+ Raises
+ ------
+ :py:class:`~pyasn1.error.PyAsn1tError` on any inconsistencies found
+ """
+ if self.componentType is noValue or not self.subtypeSpec:
+ return False
+
+ if self._componentValues is noValue:
+ return True
+
+ mapping = {}
+
+ for idx, value in self._componentValues.items():
+ # Absent fields are not in the mapping
+ if value is noValue:
+ continue
+
+ mapping[idx] = value
+
+ try:
+ # Represent SequenceOf/SetOf as a bare dict to constraints chain
+ self.subtypeSpec(mapping)
+
+ except error.PyAsn1Error:
+ exc = sys.exc_info()[1]
+ return exc
+
+ return False
class SequenceOf(SequenceOfAndSetOfBase):
__doc__ = SequenceOfAndSetOfBase.__doc__
@@ -2637,6 +2672,44 @@ class SequenceAndSetBase(base.ConstructedAsn1Type):
return True
+ @property
+ def isInconsistent(self):
+ """Run necessary checks to ensure |ASN.1| object consistency.
+
+ Default action is to verify |ASN.1| object against constraints imposed
+ by `subtypeSpec`.
+
+ Raises
+ ------
+ :py:class:`~pyasn1.error.PyAsn1tError` on any inconsistencies found
+ """
+ if self.componentType is noValue or not self.subtypeSpec:
+ return False
+
+ if self._componentValues is noValue:
+ return True
+
+ mapping = {}
+
+ for idx, value in enumerate(self._componentValues):
+ # Absent fields are not in the mapping
+ if value is noValue:
+ continue
+
+ name = self.componentType.getNameByPosition(idx)
+
+ mapping[name] = value
+
+ try:
+ # Represent Sequence/Set as a bare dict to constraints chain
+ self.subtypeSpec(mapping)
+
+ except error.PyAsn1Error:
+ exc = sys.exc_info()[1]
+ return exc
+
+ return False
+
def prettyPrint(self, scope=0):
"""Return an object representation string.
diff --git a/tests/type/test_constraint.py b/tests/type/test_constraint.py
index b5276cd..0f49c78 100644
--- a/tests/type/test_constraint.py
+++ b/tests/type/test_constraint.py
@@ -128,6 +128,69 @@ class PermittedAlphabetConstraintTestCase(SingleValueConstraintTestCase):
assert 0, 'constraint check fails'
+class WithComponentsConstraintTestCase(BaseTestCase):
+
+ def testGoodVal(self):
+ c = constraint.WithComponentsConstraint(
+ ('A', constraint.ComponentPresentConstraint()),
+ ('B', constraint.ComponentAbsentConstraint()))
+
+ try:
+ c({'A': 1})
+
+ except error.ValueConstraintError:
+ assert 0, 'constraint check fails'
+
+ def testGoodValWithExtraFields(self):
+ c = constraint.WithComponentsConstraint(
+ ('A', constraint.ComponentPresentConstraint()),
+ ('B', constraint.ComponentAbsentConstraint())
+ )
+
+ try:
+ c({'A': 1, 'C': 2})
+
+ except error.ValueConstraintError:
+ assert 0, 'constraint check fails'
+
+ def testEmptyConstraint(self):
+ c = constraint.WithComponentsConstraint()
+
+ try:
+ c({'A': 1})
+
+ except error.ValueConstraintError:
+ assert 0, 'constraint check fails'
+
+ def testBadVal(self):
+ c = constraint.WithComponentsConstraint(
+ ('A', constraint.ComponentPresentConstraint())
+ )
+
+ try:
+ c({'B': 2})
+
+ except error.ValueConstraintError:
+ pass
+
+ else:
+ assert 0, 'constraint check fails'
+
+ def testBadValExtraFields(self):
+ c = constraint.WithComponentsConstraint(
+ ('A', constraint.ComponentPresentConstraint())
+ )
+
+ try:
+ c({'B': 2, 'C': 3})
+
+ except error.ValueConstraintError:
+ pass
+
+ else:
+ assert 0, 'constraint check fails'
+
+
class ConstraintsIntersectionTestCase(BaseTestCase):
def setUp(self):
BaseTestCase.setUp(self)
diff --git a/tests/type/test_univ.py b/tests/type/test_univ.py
index d9f921b..9762959 100644
--- a/tests/type/test_univ.py
+++ b/tests/type/test_univ.py
@@ -1249,6 +1249,37 @@ class SequenceOf(BaseTestCase):
assert not s.isValue
+ def testIsInconsistentSizeConstraint(self):
+
+ class SequenceOf(univ.SequenceOf):
+ componentType = univ.OctetString()
+ subtypeSpec = constraint.ValueSizeConstraint(0, 1)
+
+ s = SequenceOf()
+
+ assert s.isInconsistent
+
+ s[0] = 'test'
+
+ assert not s.isInconsistent
+
+ s[0] = 'test'
+ s[1] = 'test'
+
+ assert s.isInconsistent
+
+ s.clear()
+
+ assert not s.isInconsistent
+
+ s.reset()
+
+ assert s.isInconsistent
+
+ s[1] = 'test'
+
+ assert not s.isInconsistent
+
class SequenceOfPicklingTestCase(unittest.TestCase):
@@ -1585,6 +1616,77 @@ class Sequence(BaseTestCase):
assert not s.isValue
+ def testIsInconsistentWithComponentsConstraint(self):
+
+ class Sequence(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.OptionalNamedType('name', univ.OctetString()),
+ namedtype.DefaultedNamedType('age', univ.Integer(65))
+ )
+ subtypeSpec = constraint.WithComponentsConstraint(
+ ('name', constraint.ComponentPresentConstraint()),
+ ('age', constraint.ComponentAbsentConstraint())
+ )
+
+ s = Sequence()
+
+ assert s.isInconsistent
+
+ s[0] = 'test'
+
+ assert not s.isInconsistent
+
+ s[0] = 'test'
+ s[1] = 23
+
+ assert s.isInconsistent
+
+ s.clear()
+
+ assert s.isInconsistent
+
+ s.reset()
+
+ assert s.isInconsistent
+
+ s[1] = 23
+
+ assert s.isInconsistent
+
+ def testIsInconsistentSizeConstraint(self):
+
+ class Sequence(univ.Sequence):
+ componentType = namedtype.NamedTypes(
+ namedtype.OptionalNamedType('name', univ.OctetString()),
+ namedtype.DefaultedNamedType('age', univ.Integer(65))
+ )
+ subtypeSpec = constraint.ValueSizeConstraint(0, 1)
+
+ s = Sequence()
+
+ assert not s.isInconsistent
+
+ s[0] = 'test'
+
+ assert not s.isInconsistent
+
+ s[0] = 'test'
+ s[1] = 23
+
+ assert s.isInconsistent
+
+ s.clear()
+
+ assert not s.isInconsistent
+
+ s.reset()
+
+ assert s.isInconsistent
+
+ s[1] = 23
+
+ assert not s.isInconsistent
+
class SequenceWithoutSchema(BaseTestCase):