Skip to content

Commit

Permalink
Make record/union deserializers ignore unknown fields
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Feb 20, 2018
1 parent 00e32fc commit 0483657
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 10 deletions.
8 changes: 7 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ Version 0.3.1

To be released.

### Python target

- Fixed record/union deserializers to ignore unknown fields in data payload.
Deserializers had raised `KeyError` before. [#232]

[#232]: https://github.com/spoqa/nirum/issues/232


Version 0.3.0
-------------
Expand Down Expand Up @@ -172,7 +179,6 @@ Released on February 18, 2018.
[#203]: https://github.com/spoqa/nirum/pull/203
[#204]: https://github.com/spoqa/nirum/pull/204
[#216]: https://github.com/spoqa/nirum/issues/216
[#222]: https://github.com/spoqa/nirum/pull/222
[#218]: https://github.com/spoqa/nirum/issues/218
[#222]: https://github.com/spoqa/nirum/pull/222
[#223]: https://github.com/spoqa/nirum/pull/223
Expand Down
9 changes: 9 additions & 0 deletions docs/refactoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ returns, but in program codes we become able to deal with distance using `meter`
type rather than primitive `bigint` type.


Removing a field
----------------

Any fields in payload that are unlisted in an interface definition are ignored
by a deserializer. If you are going to remove an existing field you should
deploy the newer version to a payload consumer first, and then a payload
provider last.


Interchangeability of enum type and `text`
------------------------------------------

Expand Down
7 changes: 7 additions & 0 deletions docs/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ It's represented in JSON to:
"uri": null
}

When a payload is deserialized, undefined fields are just ignored.
It can be used to drop an existing field without breaking
backward compatibility.


Union type
----------
Expand Down Expand Up @@ -243,6 +247,9 @@ It's represented in JSON to:
"uri": null
}

In a similar way to a recrod type, undefined fields in a payload are ignored
by deserializer.


Option type
-----------
Expand Down
14 changes: 11 additions & 3 deletions src/Nirum/Targets/Python.hs
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ class $className(object):
)
args = dict()
behind_names = cls.__nirum_field_names__.behind_names
field_types = cls.__nirum_field_types__
field_types = cls.__nirum_field_types__()
if callable(field_types):
field_types = field_types()
# old compiler could generate non-callable dictionary
Expand All @@ -966,7 +966,11 @@ class $className(object):
else:
name = attribute_name
try:
args[name] = deserialize_meta(field_types[name], item)
field_type = field_types[name]
except KeyError:
continue
try:
args[name] = deserialize_meta(field_type, item)
except ValueError as e:
errors.add('%s: %s' % (attribute_name, str(e)))
if errors:
Expand Down Expand Up @@ -1107,7 +1111,11 @@ class $className({T.intercalate "," $ compileExtendClasses annotations}):
name = attribute_name
tag_types = dict(cls.__nirum_tag_types__())
try:
args[name] = deserialize_meta(tag_types[name], item)
field_type = tag_types[name]
except KeyError:
continue
try:
args[name] = deserialize_meta(field_type, item)
except ValueError as e:
errors.add('%s: %s' % (attribute_name, str(e)))
if errors:
Expand Down
28 changes: 22 additions & 6 deletions test/python/primitive_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,18 @@ def test_record():
point2_serialize = {'_type': 'point2', 'left': 3, 'top': 14}
assert point2.__nirum_serialize__() == point2_serialize
assert Point2.__nirum_deserialize__(point2_serialize) == point2
with raises(ValueError):
with raises(ValueError): # no "_type" -- FIXME: "_type" is unnecessary
Point2.__nirum_deserialize__({'left': 3, 'top': 14})
with raises(ValueError):
with raises(ValueError): # no "left" and "top" fields
Point2.__nirum_deserialize__({'_type': 'foo'})
p = Point2.__nirum_deserialize__({
# extra field does not matter; just ignored
'_type': 'point2',
'left': 3,
'top': 14,
'extra': 'it does not matter',
})
assert p == Point2(left=IntUnbox(3), top=IntUnbox(14))
with raises(TypeError):
Point2(left=IntUnbox(1), top='a')
with raises(TypeError):
Expand Down Expand Up @@ -266,16 +274,24 @@ def test_union():
})
assert isinstance(name, MixedName.EastAsianName)
with raises(ValueError) as e:
MixedName.__nirum_deserialize__({
MixedName.__nirum_deserialize__({ # invalid field types
'_type': 'mixed_name',
'_tag': 'east_asian_name',
'family_name': 404,
'given_name': 503,
'family_name': 404, # not a text
'given_name': 503, # not a text
})
assert str(e.value) == '''\
family_name: '404' is not a string.
given_name: '503' is not a string.\
'''
''' # message can contain multiple errors
n = MixedName.__nirum_deserialize__({ # invalid field types
'_type': 'mixed_name',
'_tag': 'east_asian_name',
'family_name': u'John',
'given_name': u'Doe',
'extra': u'it does not matter',
})
assert n == EastAsianName(family_name=u'John', given_name=u'Doe')


def test_union_with_special_case():
Expand Down

0 comments on commit 0483657

Please sign in to comment.