summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2017-10-22 15:56:51 +0200
committerGitHub <noreply@github.com>2017-10-22 15:56:51 +0200
commit9653bbb8b2eb9f1e6a6920d8239bd55b72f9550e (patch)
treede4527642fea0d264f367a577b0a526efe1d4416
parent262793a79cad797601fa855eec1e1020e1a534cb (diff)
downloadpyasn1-git-9653bbb8b2eb9f1e6a6920d8239bd55b72f9550e.tar.gz
docs on constraints added (#97)
-rw-r--r--docs/source/pyasn1/contents.rst23
-rw-r--r--docs/source/pyasn1/type/char/contents.rst4
-rw-r--r--docs/source/pyasn1/type/constraint/constraintsexclusion.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/constraintsintersection.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/constraintsunion.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/containedsubtype.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/contents.rst67
-rw-r--r--docs/source/pyasn1/type/constraint/permittedalphabet.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/singlevalue.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/valuerange.rst10
-rw-r--r--docs/source/pyasn1/type/constraint/valuesize.rst10
-rw-r--r--pyasn1/type/constraint.py335
12 files changed, 482 insertions, 27 deletions
diff --git a/docs/source/pyasn1/contents.rst b/docs/source/pyasn1/contents.rst
index 0795830..1dc9e58 100644
--- a/docs/source/pyasn1/contents.rst
+++ b/docs/source/pyasn1/contents.rst
@@ -6,12 +6,13 @@ ASN.1 types
The ASN.1 data description
`language <https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-X.208-198811-W!!PDF-E&type=items>`_
-defines a handful of built-in data types. ASN.1 types exhibit different semantics
-(e.g. number vs string) and can be distinguished from each other by
+defines a handful of built-in data types. ASN.1 types exhibit different
+semantics (e.g. number vs string) and can be distinguished from each other by
:ref:`tags <type.tag>`.
Subtypes can be created on top of base ASN.1 types by adding/overriding the
-tags and/or imposing additional *constraints* on accepted values.
+:ref:`tags <type.tag>` and/or imposing additional
+:ref:`constraints <type.constraint>` on accepted values.
ASN.1 types in pyasn1 are Python objects. One or more ASN.1 types
comprise a *schema* describing data structures of unbounded complexity.
@@ -95,7 +96,8 @@ New ASN.1 types can be created on top of existing ASN.1 types with
the *subtype()* method. Desired properties of the new type get
merged with the corresponding properties of the old type. Main use-case
for *.subtype()* is to assemble new ASN.1 types by :ref:`tagging <type.tag>`
-or applying additional constraints to accepted type's values.
+or applying additional :ref:`constraints <type.constraint>` to accepted
+type's values.
.. code-block:: python
@@ -116,7 +118,20 @@ or applying additional constraints to accepted type's values.
/pyasn1/type/univ/contents
/pyasn1/type/char/contents
/pyasn1/type/useful/contents
+
+ASN.1 type harness
+++++++++++++++++++
+
+The identification and behaviour of ASN.1 types is determined by
+:ref:`tags <type.tag>` and :ref:`constraints <type.constraint>`.
+The inner structure of *constructed* ASN.1 types is defined by
+its :ref:`fields <type.namedtype>` specification.
+
+.. toctree::
+ :maxdepth: 2
+
/pyasn1/type/tag/contents
+ /pyasn1/type/constraint/contents
/pyasn1/type/namedtype/contents
/pyasn1/type/opentype/contents
/pyasn1/type/namedval/contents
diff --git a/docs/source/pyasn1/type/char/contents.rst b/docs/source/pyasn1/type/char/contents.rst
index 87e3f22..e73c959 100644
--- a/docs/source/pyasn1/type/char/contents.rst
+++ b/docs/source/pyasn1/type/char/contents.rst
@@ -9,8 +9,8 @@ of text types. Most of these types come from the past trying to capture
the fragments of long-forgotten technologies.
These *character* types are all scalars. They are similar to
-:ref:`univ.OctetString` except that they all operate on text,
-not bytes.
+:ref:`OctetString <univ.OctetString>` except that they all operate on
+text, not bytes.
.. toctree::
:maxdepth: 2
diff --git a/docs/source/pyasn1/type/constraint/constraintsexclusion.rst b/docs/source/pyasn1/type/constraint/constraintsexclusion.rst
new file mode 100644
index 0000000..ad5cf8a
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/constraintsexclusion.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ConstraintsExclusion:
+
+.. |Constraint| replace:: ConstraintsExclusion
+
+Constraints exclusion
+---------------------
+
+.. autoclass:: pyasn1.type.constraint.ConstraintsExclusion(constraint)
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/constraintsintersection.rst b/docs/source/pyasn1/type/constraint/constraintsintersection.rst
new file mode 100644
index 0000000..15465b8
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/constraintsintersection.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ConstraintsIntersection:
+
+.. |Constraint| replace:: ConstraintsIntersection
+
+Constraints intersection
+------------------------
+
+.. autoclass:: pyasn1.type.constraint.ConstraintsIntersection(*constraints)
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/constraintsunion.rst b/docs/source/pyasn1/type/constraint/constraintsunion.rst
new file mode 100644
index 0000000..d42f3d9
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/constraintsunion.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ConstraintsUnion:
+
+.. |Constraint| replace:: ConstraintsUnion
+
+Constraints union
+-----------------
+
+.. autoclass:: pyasn1.type.constraint.ConstraintsUnion(*constraints)
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/containedsubtype.rst b/docs/source/pyasn1/type/constraint/containedsubtype.rst
new file mode 100644
index 0000000..39fc3a1
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/containedsubtype.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ContainedSubtypeConstraint:
+
+.. |Constraint| replace:: ContainedSubtypeConstraint
+
+Contained subtype constraint
+----------------------------
+
+.. autoclass:: pyasn1.type.constraint.ContainedSubtypeConstraint
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/contents.rst b/docs/source/pyasn1/type/constraint/contents.rst
new file mode 100644
index 0000000..8e4db7c
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/contents.rst
@@ -0,0 +1,67 @@
+
+.. _type.constraint:
+
+Constraints
+-----------
+
+ASN.1 standard has a built-in way of limiting the set of values
+a type can possibly have. Imposing value constraints on an ASN.1
+type, together with :ref:`tagging <type.tag>`, is a way of creating
+a more specialized subtype of an ASN.1 type.
+
+The pyasn1 implementation represents all flavors of constraints,
+as well as their combinations, as immutable Python objects. Ultimately,
+they get attached to ASN.1 type object at a *.subtypeSpec* attribute.
+
+.. code-block:: python
+
+ class Age(Integer):
+ """
+ ASN.1 specification:
+
+ Age ::= INTEGER (0..120)
+ """
+ subtypeSpec = ValueRangeConstraint(0, 120)
+
+
+.. toctree::
+ :maxdepth: 2
+
+ /pyasn1/type/constraint/singlevalue
+ /pyasn1/type/constraint/containedsubtype
+ /pyasn1/type/constraint/valuerange
+ /pyasn1/type/constraint/valuesize
+ /pyasn1/type/constraint/permittedalphabet
+
+
+Logic operations on constraints
++++++++++++++++++++++++++++++++
+
+Sometimes multiple constraints are applied on an ASN.1 type. To capture
+this situation, individual constraint objects can be glued together
+by the logic operator objects.
+
+The logic operators are Python objects that exhibit similar behaviour
+as the constraint objects do with the only difference that logic operators
+are instantiated on the constraint and logic operator objects, not on the
+bare values.
+
+.. code-block:: python
+
+ class PhoneNumber(NumericString):
+ """
+ ASN.1 specification:
+
+ PhoneNumber ::=
+ NumericString (FROM ("0".."9")) (SIZE (10))
+ """
+ subtypeSpec = ConstraintsIntersection(
+ ValueRangeConstraint('0', '9'), ValueSizeConstraint(10)
+ )
+
+.. toctree::
+ :maxdepth: 2
+
+ /pyasn1/type/constraint/constraintsintersection
+ /pyasn1/type/constraint/constraintsunion
+ /pyasn1/type/constraint/constraintsexclusion
diff --git a/docs/source/pyasn1/type/constraint/permittedalphabet.rst b/docs/source/pyasn1/type/constraint/permittedalphabet.rst
new file mode 100644
index 0000000..86d08c4
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/permittedalphabet.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.PermittedAlphabetConstraint:
+
+.. |Constraint| replace:: PermittedAlphabetConstraint
+
+Permitted alphabet constraint
+-----------------------------
+
+.. autoclass:: pyasn1.type.constraint.PermittedAlphabetConstraint(*alphabet)
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/singlevalue.rst b/docs/source/pyasn1/type/constraint/singlevalue.rst
new file mode 100644
index 0000000..26f2424
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/singlevalue.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.SingleValueConstraint:
+
+.. |Constraint| replace:: SingleValueConstraint
+
+Single value constraint
+-----------------------
+
+.. autoclass:: pyasn1.type.constraint.SingleValueConstraint
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/valuerange.rst b/docs/source/pyasn1/type/constraint/valuerange.rst
new file mode 100644
index 0000000..a3a91ac
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/valuerange.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ValueRangeConstraint:
+
+.. |Constraint| replace:: ValueRangeConstraint
+
+Value range constraint
+----------------------
+
+.. autoclass:: pyasn1.type.constraint.ValueRangeConstraint(start, end)
+ :members:
diff --git a/docs/source/pyasn1/type/constraint/valuesize.rst b/docs/source/pyasn1/type/constraint/valuesize.rst
new file mode 100644
index 0000000..62444df
--- /dev/null
+++ b/docs/source/pyasn1/type/constraint/valuesize.rst
@@ -0,0 +1,10 @@
+
+.. _constrain.ValueSizeConstraint:
+
+.. |Constraint| replace:: ValueSizeConstraint
+
+Value size constraint
+----------------------
+
+.. autoclass:: pyasn1.type.constraint.ValueSizeConstraint(minimum, maximum)
+ :members:
diff --git a/pyasn1/type/constraint.py b/pyasn1/type/constraint.py
index 35bb0e2..c4f4238 100644
--- a/pyasn1/type/constraint.py
+++ b/pyasn1/type/constraint.py
@@ -15,12 +15,6 @@ __all__ = ['SingleValueConstraint', 'ContainedSubtypeConstraint', 'ValueRangeCon
class AbstractConstraint(object):
- """Abstract base-class for constraint objects
-
- Constraints should be stored in a simple sequence in the
- namespace of their client Asn1Item sub-classes in cases
- when ASN.1 constraint is define.
- """
def __init__(self, *values):
self._valueMap = set()
@@ -96,9 +90,39 @@ class AbstractConstraint(object):
otherConstraint == self or
otherConstraint in self._valueMap)
+
class SingleValueConstraint(AbstractConstraint):
- """Value must be part of defined values constraint"""
+ """Create a SingleValueConstraint object.
+
+ The SingleValueConstraint satisfies any value that
+ is present in the set of permitted values.
+
+ The SingleValueConstraint object can be applied to
+ any ASN.1 type.
+
+ Parameters
+ ----------
+ \*values: :class:`int`
+ Full set of values permitted by this constraint object.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class DivisorOfSix(Integer):
+ '''
+ ASN.1 specification:
+
+ Divisor-Of-6 ::= INTEGER (1 | 2 | 3 | 6)
+ '''
+ subtypeSpec = SingleValueConstraint(1, 2, 3, 6)
+
+ # this will succeed
+ divisor_of_six = DivisorOfSix(1)
+ # this will raise ValueConstraintError
+ divisor_of_six = DivisorOfSix(7)
+ """
def _setValues(self, values):
self._values = values
self._set = set(values)
@@ -109,16 +133,85 @@ class SingleValueConstraint(AbstractConstraint):
class ContainedSubtypeConstraint(AbstractConstraint):
- """Value must satisfy all of defined set of constraints"""
+ """Create a ContainedSubtypeConstraint object.
+
+ The ContainedSubtypeConstraint satisfies any value that
+ is present in the set of permitted values and also
+ satisfies included constraints.
+
+ The ContainedSubtypeConstraint object can be applied to
+ any ASN.1 type.
+
+ Parameters
+ ----------
+ \*values:
+ Full set of values and constraint objects permitted
+ by this constraint object.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class DivisorOfEighteen(Integer):
+ '''
+ ASN.1 specification:
+
+ Divisors-of-18 ::= INTEGER (INCLUDES Divisors-of-6 | 9 | 18)
+ '''
+ subtypeSpec = ContainedSubtypeConstraint(
+ SingleValueConstraint(1, 2, 3, 6), 9, 18
+ )
+
+ # this will succeed
+ divisor_of_eighteen = DivisorOfEighteen(9)
+ # this will raise ValueConstraintError
+ divisor_of_eighteen = DivisorOfEighteen(10)
+ """
def _testValue(self, value, idx):
- for c in self._values:
- c(value, idx)
+ for constraint in self._values:
+ if isinstance(constraint, AbstractConstraint):
+ constraint(value, idx)
+ elif value not in self._set:
+ raise error.ValueConstraintError(value)
class ValueRangeConstraint(AbstractConstraint):
- """Value must be within start and stop values (inclusive)"""
+ """Create a ValueRangeConstraint object.
+
+ The ValueRangeConstraint satisfies any value that
+ falls in the range of permitted values.
+
+ The ValueRangeConstraint object can only be applied
+ to :class:`~pyasn1.type.univ.Integer` and
+ :class:`~pyasn1.type.univ.Real` types.
+
+ Parameters
+ ----------
+ start: :class:`int`
+ Minimum permitted value in the range (inclusive)
+
+ end: :class:`int`
+ Maximum permitted value in the range (inclusive)
+
+ Example
+ -------
+ .. code-block:: python
+
+ class TeenAgeYears(Integer):
+ '''
+ ASN.1 specification:
+ TeenAgeYears ::= INTEGER (13 .. 19)
+ '''
+ subtypeSpec = ValueRangeConstraint(13, 19)
+
+ # this will succeed
+ teen_year = TeenAgeYears(18)
+
+ # this will raise ValueConstraintError
+ teen_year = TeenAgeYears(20)
+ """
def _testValue(self, value, idx):
if value < self.start or value > self.stop:
raise error.ValueConstraintError(value)
@@ -140,8 +233,59 @@ class ValueRangeConstraint(AbstractConstraint):
class ValueSizeConstraint(ValueRangeConstraint):
- """len(value) must be within start and stop values (inclusive)"""
-
+ """Create a ValueSizeConstraint object.
+
+ The ValueSizeConstraint satisfies any value for
+ as long as its size falls within the range of
+ permitted sizes.
+
+ The ValueSizeConstraint object can be applied
+ to :class:`~pyasn1.type.univ.BitString`,
+ :class:`~pyasn1.type.univ.OctetString` (including
+ all :ref:`character ASN.1 types <type.char>`),
+ :class:`~pyasn1.type.univ.SequenceOf`
+ and :class:`~pyasn1.type.univ.SetOf` types.
+
+ Parameters
+ ----------
+ minimum: :class:`int`
+ Minimum permitted size of the value (inclusive)
+
+ maximum: :class:`int`
+ Maximum permitted size of the value (inclusive)
+
+ Example
+ -------
+ .. code-block:: python
+
+ class BaseballTeamRoster(SetOf):
+ '''
+ ASN.1 specification:
+
+ BaseballTeamRoster ::= SET SIZE (1..25) OF PlayerNames
+ '''
+ componentType = PlayerNames()
+ subtypeSpec = ValueSizeConstraint(1, 25)
+
+ # this will succeed
+ team = BaseballTeamRoster()
+ team.extend(['Jan', 'Matej'])
+ encode(team)
+
+ # this will raise ValueConstraintError
+ team = BaseballTeamRoster()
+ team.extend(['Jan'] * 26)
+ encode(team)
+
+ Note
+ ----
+ Whenever ValueSizeConstraint is applied to mutable types
+ (e.g. :class:`~pyasn1.type.univ.SequenceOf`,
+ :class:`~pyasn1.type.univ.SetOf`), constraint
+ validation only happens at the serialisation phase rather
+ than schema instantiation phase (as it is with immutable
+ types).
+ """
def _testValue(self, value, idx):
valueSize = len(value)
if valueSize < self.start or valueSize > self.stop:
@@ -149,6 +293,40 @@ class ValueSizeConstraint(ValueRangeConstraint):
class PermittedAlphabetConstraint(SingleValueConstraint):
+ """Create a PermittedAlphabetConstraint object.
+
+ The PermittedAlphabetConstraint satisfies any character
+ string for as long as all its characters are present in
+ the set of permitted characters.
+
+ The PermittedAlphabetConstraint object can only be applied
+ to the :ref:`character ASN.1 types <type.char>` such as
+ :class:`~pyasn1.type.char.IA5String`.
+
+ Parameters
+ ----------
+ \*alphabet: :class:`str`
+ Full set of characters permitted by this constraint object.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class BooleanValue(IA5String):
+ '''
+ ASN.1 specification:
+
+ BooleanValue ::= IA5String (FROM ('T' | 'F'))
+ '''
+ subtypeSpec = PermittedAlphabetConstraint('T', 'F')
+
+ # this will succeed
+ truth = BooleanValue('T')
+ truth = BooleanValue('TF')
+
+ # this will raise ValueConstraintError
+ garbage = BooleanValue('TAF')
+ """
def _setValues(self, values):
self._values = values
self._set = set(values)
@@ -160,7 +338,7 @@ class PermittedAlphabetConstraint(SingleValueConstraint):
# This is a bit kludgy, meaning two op modes within a single constraint
class InnerTypeConstraint(AbstractConstraint):
- """Value must satisfy type and presense constraints"""
+ """Value must satisfy the type and presence constraints"""
def _testValue(self, value, idx):
if self.__singleTypeConstraint:
@@ -184,11 +362,50 @@ class InnerTypeConstraint(AbstractConstraint):
AbstractConstraint._setValues(self, values)
-# Boolean ops on constraints
+# Logic operations on constraints
class ConstraintsExclusion(AbstractConstraint):
- """Value must not fit the single constraint"""
+ """Create a ConstraintsExclusion logic operator object.
+
+ The ConstraintsExclusion logic operator succeeds when the
+ value does *not* satisfy the operand constraint.
+
+ The ConstraintsExclusion object can be applied to
+ any constraint and logic operator object.
+
+ Parameters
+ ----------
+ constraint:
+ Constraint or logic operator object.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class Lipogramme(IA5STRING):
+ '''
+ ASN.1 specification:
+ Lipogramme ::=
+ IA5String (FROM (ALL EXCEPT ("e"|"E")))
+ '''
+ subtypeSpec = ConstraintsExclusion(
+ PermittedAlphabetConstraint('e', 'E')
+ )
+
+ # this will succeed
+ lipogramme = Lipogramme('A work of fiction?')
+
+ # this will raise ValueConstraintError
+ lipogramme = Lipogramme('Eel')
+
+ Warning
+ -------
+ The above example involving PermittedAlphabetConstraint might
+ not work due to the way how PermittedAlphabetConstraint works.
+ The other constraints might work with ConstraintsExclusion
+ though.
+ """
def _testValue(self, value, idx):
try:
self._values[0](value, idx)
@@ -200,11 +417,11 @@ class ConstraintsExclusion(AbstractConstraint):
def _setValues(self, values):
if len(values) != 1:
raise error.PyAsn1Error('Single constraint expected')
+
AbstractConstraint._setValues(self, values)
class AbstractConstraintSet(AbstractConstraint):
- """Value must not satisfy the single constraint"""
def __getitem__(self, idx):
return self._values[idx]
@@ -232,16 +449,88 @@ class AbstractConstraintSet(AbstractConstraint):
class ConstraintsIntersection(AbstractConstraintSet):
- """Value must satisfy all constraints"""
+ """Create a ConstraintsIntersection logic operator object.
+
+ The ConstraintsIntersection logic operator only succeeds
+ if *all* its operands succeed.
+
+ The ConstraintsIntersection object can be applied to
+ any constraint and logic operator objects.
+
+ The ConstraintsIntersection object duck-types the immutable
+ container object like Python :py:class:`tuple`.
+ Parameters
+ ----------
+ \*constraints:
+ Constraint or logic operator objects.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class CapitalAndSmall(IA5String):
+ '''
+ ASN.1 specification:
+
+ CapitalAndSmall ::=
+ IA5String (FROM ("A".."Z"|"a".."z"))
+ '''
+ subtypeSpec = ConstraintsIntersection(
+ PermittedAlphabetConstraint('A', 'Z'),
+ PermittedAlphabetConstraint('a', 'z')
+ )
+
+ # this will succeed
+ capital_and_small = CapitalAndSmall('Hello')
+
+ # this will raise ValueConstraintError
+ capital_and_small = CapitalAndSmall('hello')
+ """
def _testValue(self, value, idx):
for constraint in self._values:
constraint(value, idx)
class ConstraintsUnion(AbstractConstraintSet):
- """Value must satisfy at least one constraint"""
+ """Create a ConstraintsUnion logic operator object.
+
+ The ConstraintsUnion logic operator only succeeds if
+ *at least a single* operand succeeds.
+
+ The ConstraintsUnion object can be applied to
+ any constraint and logic operator objects.
+ The ConstraintsUnion object duck-types the immutable
+ container object like Python :py:class:`tuple`.
+
+ Parameters
+ ----------
+ \*constraints:
+ Constraint or logic operator objects.
+
+ Example
+ -------
+ .. code-block:: python
+
+ class CapitalOrSmall(IA5String):
+ '''
+ ASN.1 specification:
+
+ CapitalOrSmall ::=
+ IA5String (FROM ("A".."Z") | FROM ("a".."z"))
+ '''
+ subtypeSpec = ConstraintsIntersection(
+ PermittedAlphabetConstraint('A', 'Z'),
+ PermittedAlphabetConstraint('a', 'z')
+ )
+
+ # this will succeed
+ capital_or_small = CapitalAndSmall('Hello')
+
+ # this will raise ValueConstraintError
+ capital_or_small = CapitalOrSmall('hello!')
+ """
def _testValue(self, value, idx):
for constraint in self._values:
try:
@@ -250,9 +539,13 @@ class ConstraintsUnion(AbstractConstraintSet):
pass
else:
return
+
raise error.ValueConstraintError(
- 'all of %s failed for \"%s\"' % (self._values, value)
+ 'all of %s failed for "%s"' % (self._values, value)
)
-# XXX
+# TODO:
+# refactor InnerTypeConstraint
# add tests for type check
+# implement other constraint types
+# make constraint validation easy to skip