Skip to content

Commit

Permalink
support a standard api for parsing media types
Browse files Browse the repository at this point in the history
  • Loading branch information
mmerickel committed Sep 13, 2018
1 parent 18a9b52 commit d150f9f
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
51 changes: 47 additions & 4 deletions src/webob/acceptparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,18 +407,61 @@ def generator(value):
)
return generator(value=value)

def _parse_and_normalize_offers(self, offers):
@classmethod
def parse_offer(cls, offer):
"""
Parse an offer into its component parts.
:param offer: A media type or range in the format
``type/subtype[;params]``.
:return: A tuple of ``(*type*, *subtype*, *params*)``.
| *params* is a list containing ``(*parameter name*, *value*)``
values.
:raises ValueError: If the offer does not match the required format.
"""
match = cls.media_type_compiled_re.match(offer)
if not match:
raise ValueError('Invalid value for an Accept offer.')

groups = match.groups()
offer_type, offer_subtype = groups[0].split('/')
offer_params = cls._parse_media_type_params(
media_type_params_segment=groups[1],
)
# offer_type, offer_subtype, offer_params, invalid, example
# == * == * true Y/N
# N N N N a/b
# N N Y N a/b;x=y
# N Y N N a/*
# N Y Y Y a/*;x=y
# Y N N Y */b
# Y N Y Y */b;x=y
# Y Y N N */*
# Y Y Y Y */*;x=y
# simplifies to (A and not B or B and C)
if (
(offer_type == '*' and offer_subtype != '*')
or (offer_subtype == '*' and offer_params)
):
raise ValueError('Invalid value for an Accept offer.')
return (offer_type, offer_subtype, offer_params)

@classmethod
def _parse_and_normalize_offers(cls, offers):
"""
Throw out any offers that do not match the media type ABNF.
Throw out any offers that do not match the media range ABNF.
:return: A list of offers split into the format ``[offer_index,
offer_type_subtype, offer_media_type_params]``.
"""
lowercased_offers_parsed = []
for index, offer in enumerate(offers):
match = self.media_type_compiled_re.match(offer.lower())
# we're willing to try to match any offer that matches the
match = cls.media_type_compiled_re.match(offer.lower())
# we're willing to try to match any range that matches the
# media type grammar can parse, but we'll throw out anything
# that doesn't fit the correct syntax - this is not saying that
# the media type is actually a real media type, just that it looks
Expand Down
27 changes: 27 additions & 0 deletions tests/test_acceptparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,33 @@ def test_parse__valid_header(self, value, expected_list):
list_of_returned = list(returned)
assert list_of_returned == expected_list

@pytest.mark.parametrize('offer, expected_return', [
['text/html', ('text', 'html', [])],
['text/html;charset=utf8', ('text', 'html', [('charset', 'utf8')])],
['text/html;charset=utf8;x-version=1', ('text', 'html', [
('charset', 'utf8'),
('x-version', '1'),
])],
['text/*', ('text', '*', [])],
['*/*', ('*', '*', [])],
])
def test_parse_offer__valid(self, offer, expected_return):
assert Accept.parse_offer(offer) == expected_return

@pytest.mark.parametrize('offer', [
'',
'foo',
'foo/bar/baz',
'*/plain',
'*/plain;charset=utf8',
'*/plain;charset=utf8;x-version=1',
'*/*;charset=utf8',
'text/*;charset=utf8',
])
def test_parse_offer__invalid(self, offer):
with pytest.raises(ValueError):
Accept.parse_offer(offer)


class TestAcceptValidHeader(object):
def test_parse__inherited(self):
Expand Down

0 comments on commit d150f9f

Please sign in to comment.