dave-shawley/ietfparse

View on GitHub
ietfparse/_helpers.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
from __future__ import annotations

from . import errors


class ParameterParser(object):
    """
    Utility class to parse Link headers.

    :param strict: controls whether parsing follows all of
        the rules laid out in :rfc:`5988`

    This class parses the parameters for a single :mailheader:`Link`
    value.  It is used from within the guts of
    :function:`ietfparse.headers.parse_link_header` and not readily
    suited for other uses.  If *strict mode* is enabled, then the
    following rules from the RFC are obeyed:

    - section 5.3: when multiple "rel" attributes are present, then
      the first one is chosen.  The remaining are omitted from the
      value set.
    - section 5.4: "there MUST NOT be more than one media parameter".
      If more than one is present, then ``MalformedLinkValue`` is
      raised.
    - section 5.4: when multiple "type" attributes are present, then
      the first one is chosen.  The remaining are omitted form the
      value set.
    - section 5.4: when multiple "title" attributes are present, then
      the first one is chosen.  The remaining are omitted form the
      value set.
    - section 5.4: if both "title" and "title*" are present, then
      "title*" is preferred.  The value of "title*" will be used
      in all cases.
    - section 5.4: "there MUST NOT be more than one type parameter".
      If more than one is present, then ``MalformedLinkValue`` is
      raised.

    """
    def __init__(self, strict: bool = True) -> None:
        self.strict = strict
        self._values: list[tuple[str, str]] = []
        self._rfc_values: dict[str, str | None] = {
            'rel': None,
            'media': None,
            'type': None,
            'title': None,
            'title*': None,
        }

    def add_value(self, name: str, value: str) -> None:
        """
        Add a new value to the list.

        :param str name: name of the value that is being parsed
        :param str value: value that is being parsed
        :raises ietfparse.errors.MalformedLinkValue:
            if *strict mode* is enabled and a validation error
            is detected

        This method implements most of the validation mentioned in
        sections 5.3 and 5.4 of :rfc:`5988`.  The ``_rfc_values``
        dictionary contains the appropriate values for the attributes
        that get special handling.  If *strict mode* is enabled, then
        only values that are acceptable will be added to ``_values``.

        """
        try:
            if self._rfc_values[name] is None:
                self._rfc_values[name] = value
            elif self.strict:
                if name in ('media', 'type'):
                    raise errors.MalformedLinkValue(
                        'More than one {} parameter present'.format(name))
                return
        except KeyError:
            pass

        if self.strict and name in ('title', 'title*'):
            return

        self._values.append((name, value))

    @property
    def values(self) -> list[tuple[str, str]]:
        """The name/value mapping that was parsed."""
        values = self._values[:]
        if self.strict:
            preferred_title = self._rfc_values['title*']
            fallback_title = self._rfc_values['title']
            if preferred_title is not None:
                values.append(('title*', preferred_title))
                if fallback_title is not None:
                    values.append(('title', preferred_title))
            elif fallback_title is not None:
                values.append(('title', fallback_title))
        return values