LAZR's field marshallers
************************

LAZR defines an interface for converting between the values that
come in on an HTTP request, and the object values appropriate for schema
fields. This is similar to Zope's widget interface, but much smaller.

To test the various marshallers we create a dummy request and
application root.

    >>> from lazr.restful.testing.webservice import WebServiceTestPublication
    >>> from lazr.restful.simple import Request
    >>> from lazr.restful.example.base.root import (
    ...     CookbookServiceRootResource)
    >>> request = Request("", {'HTTP_HOST': 'cookbooks.dev'})
    >>> request.annotations[request.VERSION_ANNOTATION] = '1.0'
    >>> application = CookbookServiceRootResource()
    >>> request.setPublication(WebServiceTestPublication(application))
    >>> request.processInputs()

IFieldMarshaller and SimpleFieldMarshaller
==========================================

There is a SimpleFieldMarshaller class that provides a good base to
implement that interface.

    >>> from zope.interface.verify import verifyObject
    >>> from lazr.restful.interfaces import IFieldMarshaller
    >>> from lazr.restful.marshallers import SimpleFieldMarshaller
    >>> from zope.schema import Text

    >>> field = Text(__name__='field_name')
    >>> marshaller = SimpleFieldMarshaller(field, request)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

representation_name
===================

The representation_name attribute is used to retrieve the name under
which the field should be stored in the JSON representation. In the
simple case, it's the same name as the field.

    >>> marshaller.representation_name
    'field_name'

marshall_from_json_data()
=========================

The marshall_from_json_data() method is used during PUT and PATCH
requests to transform the value provided in the JSON representation to a
value in the underlying schema field. In SimpleFieldMarshaller
implementation, the value is returned unchanged.

    >>> marshaller.marshall_from_json_data("foo")
    'foo'
    >>> marshaller.marshall_from_json_data(4)
    4
    >>> marshaller.marshall_from_json_data(u"unicode\u2122")
    u'unicode\u2122'
    >>> marshaller.marshall_from_json_data("")
    ''
    >>> print marshaller.marshall_from_json_data(None)
    None

marshall_from_request()
=======================

The marshall_from_request() method is used during operation invocation
to transform a value submitted via the query string or form-encoded POST
data into a value the will be accepted by the underlying schema field.

SimpleFieldMarshaller tries first to parse the value as a JSON-encoded
string, the resulting value is passed on to marshall_from_json_data().

    >>> print marshaller.marshall_from_request("null")
    None
    >>> marshaller.marshall_from_request("true")
    True
    >>> marshaller.marshall_from_request("false")
    False
    >>> marshaller.marshall_from_request('["True", "False"]')
    [u'True', u'False']
    >>> marshaller.marshall_from_request("1")
    1
    >>> marshaller.marshall_from_request("-10.5")
    -10.5
    >>> marshaller.marshall_from_request('"a string"')
    u'a string'
    >>> marshaller.marshall_from_request('"false"')
    u'false'
    >>> marshaller.marshall_from_request('"null"')
    u'null'

Invalid JSON-encoded strings are interpreted as string literals and
passed on directly to marshall_from_json_data(). That's for the
convenience of web clients, they don't need to encode string values in
quotes, or can pass lists using multiple key-value pairs.

    >>> marshaller.marshall_from_request(u"a string")
    u'a string'
    >>> marshaller.marshall_from_request('False')
    'False'
    >>> marshaller.marshall_from_request("")
    ''
    >>> marshaller.marshall_from_request(['value1', 'value2'])
    ['value1', 'value2']

unmarshall() and variants
=========================

The unmarshall() method is used to convert the field's value to a value
that can be serialized to JSON as part of an entry representation.  The
first parameter is the entry that the value is part of. That is used by
fields that transform the value into a URL, see the CollectionField
marshaller for an example. The second one is the value to convert.  In
the SimpleFieldMarshaller implementation, the value is returned
unchanged.

    >>> print marshaller.unmarshall(None, 'foo')
    foo
    >>> print marshaller.unmarshall(None, None)
    None

When a more detailed representation is needed, unmarshall_to_closeup()
can be called. By default, this returns the same data as unmarshall(),
but specific marshallers may send more detailed information.

    >>> marshaller.unmarshall_to_closeup(None, 'foo')
    'foo'


Marshallers for basic data types
================================

Bool
----

The marshaller for a Bool field checks that the JSON value is either
True or False. A ValueError is raised when its not the case.

    >>> from zope.configuration import xmlconfig
    >>> zcmlcontext = xmlconfig.string("""
    ... <configure xmlns="http://namespaces.zope.org/zope">
    ...   <include package="lazr.restful" file="ftesting.zcml" />
    ... </configure>
    ... """)

    >>> from zope.component import getMultiAdapter
    >>> from zope.schema import Bool
    >>> field = Bool()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data(True)
    True
    >>> marshaller.marshall_from_json_data(False)
    False
    >>> marshaller.marshall_from_json_data("true")
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected bool: 'true'
    >>> marshaller.marshall_from_json_data(1)
    Traceback (most recent call last):
      ...
    ValueError: got 'int', expected bool: 1

None is passed through though.

    >>> print marshaller.marshall_from_json_data(None)
    None

Booleans are encoded using the standard JSON representation of 'true' or
'false'.

    >>> marshaller.marshall_from_request(u"true")
    True
    >>> marshaller.marshall_from_request(u"false")
    False

    >>> marshaller.marshall_from_request('True')
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected bool: 'True'

Int
---

The marshaller for an Int field checks that the JSON value is an
integer. A ValueError is raised when its not the case.

    >>> from zope.schema import Int
    >>> field = Int()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data(-10)
    -10
    >>> marshaller.marshall_from_json_data("-10")
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected int: '-10'

None is passed through though.

    >>> print marshaller.marshall_from_json_data(None)
    None

Integers are encoded using strings when in a request.

    >>> marshaller.marshall_from_request("4")
    4
    >>> marshaller.marshall_from_request(u"-4")
    -4

It raises a ValueError if the value cannot be converted to an integer.

    >>> marshaller.marshall_from_request("foo")
    Traceback (most recent call last):
    ...
    ValueError: got 'str', expected int: 'foo'

    >>> marshaller.marshall_from_request("4.62")
    Traceback (most recent call last):
    ...
    ValueError:  got 'float', expected int: 4.62...

Note that python octal and hexadecimal syntax isn't supported.

(This would 13 in octal notation.)

    >>> marshaller.marshall_from_request(u"015")
    Traceback (most recent call last):
      ...
    ValueError: got 'unicode', expected int: u'015'

    >>> marshaller.marshall_from_request(u"0x04")
    Traceback (most recent call last):
      ...
    ValueError: got 'unicode', expected int: u'0x04'

Float
-----

The marshaller for a Float field checks that the JSON value is indeed a
float.  A ValueError is raised when it's not the case.

    >>> from zope.schema import Float
    >>> field = Float()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data(1.0)
    1.0
    >>> marshaller.marshall_from_json_data(-1.0)
    -1.0
    >>> marshaller.marshall_from_json_data("true")
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected float, int: 'true'

None is passed through though.

    >>> print marshaller.marshall_from_json_data(None)
    None

And integers are automatically converted to a float.

    >>> marshaller.marshall_from_json_data(1)
    1.0

Floats are encoded using the standard JSON representation.

    >>> marshaller.marshall_from_request(u"1.2")
    1.2
    >>> marshaller.marshall_from_request(u"-1.2")
    -1.2
    >>> marshaller.marshall_from_request(u"-1")
    -1.0

    >>> marshaller.marshall_from_request('True')
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected float, int: 'True'

Datetime
--------

The marshaller for a Datetime field checks that the JSON value is indeed a
parsable datetime stamp.

    >>> from zope.schema import Datetime
    >>> field = Datetime()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data('2009-07-07T13:15:00+0000')
    datetime.datetime(2009, 7, 7, 13, 15, tzinfo=<UTC>)

    >>> marshaller.marshall_from_json_data('2009-07-07T13:30:00-0000')
    datetime.datetime(2009, 7, 7, 13, 30, tzinfo=<UTC>)

    >>> marshaller.marshall_from_json_data('2009-07-07T13:45:00Z')
    datetime.datetime(2009, 7, 7, 13, 45, tzinfo=<UTC>)

    >>> marshaller.marshall_from_json_data('2009-07-08T14:30:00')
    datetime.datetime(2009, 7, 8, 14, 30, tzinfo=<UTC>)

    >>> marshaller.marshall_from_json_data('2009-07-09')
    datetime.datetime(2009, 7, 9, 0, 0, tzinfo=<UTC>)

The time zone must be UTC. An error is raised if is it clearly not UTC.

    >>> marshaller.marshall_from_json_data('2009-07-25T13:15:00+0500')
    Traceback (most recent call last):
      ...
    ValueError: Time not in UTC.

    >>> marshaller.marshall_from_json_data('2009-07-25T13:30:00-0200')
    Traceback (most recent call last):
      ...
    ValueError: Time not in UTC.

A ValueError is raised when the value is not parsable.

    >>> marshaller.marshall_from_json_data("now")
    Traceback (most recent call last):
      ...
    ValueError: Value doesn't look like a date.

    >>> marshaller.marshall_from_json_data('20090708')
    Traceback (most recent call last):
      ...
    ValueError: Value doesn't look like a date.

    >>> marshaller.marshall_from_json_data(20090708)
    Traceback (most recent call last):
      ...
    ValueError: Value doesn't look like a date.

Date
----

The marshaller for a Date field checks that the JSON value is indeed a
parsable date.

    >>> from zope.schema import Date
    >>> field = Date()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data('2009-07-09')
    datetime.date(2009, 7, 9)

The marshaller extends the Datetime marshaller. It will parse a datetime
stamp and return a date.

    >>> marshaller.marshall_from_json_data('2009-07-07T13:15:00+0000')
    datetime.date(2009, 7, 7)

Text
----

The marshaller for IText field checks that the value is a unicode
string. A ValueError is raised when that's not the case.

    >>> from zope.schema import Text
    >>> field = Text()
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data(u"Test")
    u'Test'
    >>> marshaller.marshall_from_json_data(1.0)
    Traceback (most recent call last):
      ...
    ValueError: got 'float', expected unicode: 1.0
    >>> marshaller.marshall_from_json_data('Test')
    Traceback (most recent call last):
      ...
    ValueError: got 'str', expected unicode: 'Test'

None is passed through though.

    >>> print marshaller.marshall_from_json_data(None)
    None

When coming from the request, everything is interpreted as a unicode
string:

    >>> marshaller.marshall_from_request('a string')
    u'a string'
    >>> marshaller.marshall_from_request(['a', 'b'])
    u"['a', 'b']"
    >>> marshaller.marshall_from_request('true')
    u'True'
    >>> marshaller.marshall_from_request('')
    u''

Except that 'null' still returns None.

    >>> print marshaller.marshall_from_request('null')
    None

Bytes
-----

Since there is no way to represent a bytes string in JSON, all strings
are converted to a byte string using UTF-8 encoding. If the value isn't
a string, a ValueError is raised.

    >>> from zope.schema import Bytes
    >>> field = Bytes(__name__='data')
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data(u"Test")
    'Test'
    >>> marshaller.marshall_from_json_data(u'int\xe9ressant')
    'int\xc3\xa9ressant'
    >>> marshaller.marshall_from_json_data(1.0)
    Traceback (most recent call last):
      ...
    ValueError: got 'float', expected str: 1.0

Again, except for None which is passed through.

    >>> print marshaller.marshall_from_json_data(None)
    None

When coming over the request, the value is also converted into a UTF-8
encoded string.

    >>> marshaller.marshall_from_request(u"Test")
    'Test'
    >>> marshaller.marshall_from_request(u'int\xe9ressant')
    'int\xc3\xa9ressant'
    >>> marshaller.marshall_from_request('1.0')
    '1.0'

But again, None is returned as is.

    >>> print marshaller.marshall_from_request('null')
    None

Since multipart/form-data can be used to upload data, file-like objects
are read.

    >>> from cStringIO import StringIO
    >>> marshaller.marshall_from_request(StringIO('A line of data'))
    'A line of data'

Bytes field used in an entry are stored in the librarian, so their
representation name states that it's a link.

    >>> marshaller.representation_name
    'data_link'

And the unmarshall() method returns a link that will serve the file.

    >>> from lazr.restful import EntryResource
    >>> from lazr.restful.example.base.interfaces import ICookbookSet
    >>> from zope.component import getUtility
    >>> entry_resource = EntryResource(
    ...     getUtility(ICookbookSet).get('Everyday Greens'), request)

(The value would be the BytesStorage instance used to store the
content, but it's not needed.)

    >>> marshaller.unmarshall(entry_resource, None)
    'http://.../cookbooks/Everyday%20Greens/data'

ASCIILine
---------

ASCIILine is a subclass of Bytes but is marshalled like text.

    >>> from zope.schema import ASCIILine
    >>> field = ASCIILine(__name__='field')
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

Unicode objects remain Unicode objects.

    >>> marshaller.marshall_from_json_data(u"Test")
    u'Test'

Note that the marshaller accepts character values where bit 7 is set.

    >>> marshaller.marshall_from_json_data(u'int\xe9ressant')
    u'int\xe9ressant'

Non-string values like floats are rejected.

    >>> marshaller.marshall_from_json_data(1.0)
    Traceback (most recent call last):
      ...
    ValueError: got 'float', expected unicode: 1.0

None is passed through.

    >>> print marshaller.marshall_from_json_data(None)
    None

When coming from the request, everything is interpreted as a unicode
string:

    >>> marshaller.marshall_from_request('a string')
    u'a string'
    >>> marshaller.marshall_from_request(['a', 'b'])
    u"['a', 'b']"
    >>> marshaller.marshall_from_request('true')
    u'True'
    >>> marshaller.marshall_from_request('')
    u''
    >>> marshaller.marshall_from_request(u'int\xe9ressant')
    u'int\xe9ressant'
    >>> marshaller.marshall_from_request('1.0')
    u'1.0'

But again, 'null' is returned as None.

    >>> print marshaller.marshall_from_request('null')
    None

Unlike a Bytes field, an ASCIILine field used in an entry is stored
as an ordinary attribute, hence its representation name is the attribute
name itself.

    >>> marshaller.representation_name
    'field'

Choice marshallers
==================

The marshaller for a Choice is chosen based on the Choice's
vocabulary.

    >>> from zope.schema import Choice

Choice for IVocabularyTokenized
-------------------------------

The default marshaller will use the vocabulary getTermByToken to
retrieve the value to use. It raises an error if the value isn't in the
vocabulary.

    >>> field = Choice(__name__='simple', values=[10, 'a value', True])
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True
    >>> marshaller.marshall_from_json_data(10)
    10
    >>> marshaller.marshall_from_json_data("a value")
    'a value'
    >>> marshaller.marshall_from_json_data(True)
    True
    >>> marshaller.marshall_from_request('true')
    True
    >>> marshaller.marshall_from_request('a value')
    'a value'
    >>> marshaller.marshall_from_request('10')
    10

    >>> marshaller.marshall_from_json_data('100')
    Traceback (most recent call last):
      ...
    ValueError: '100' isn't a valid token

None is always returned unchanged.

    >>> print marshaller.marshall_from_json_data(None)
    None

Since this marshaller's Choice fields deal with small, fixed
vocabularies, their unmarshall_to_closeup() implementations to
describe the vocabulary as a whole.

    >>> for token in marshaller.unmarshall_to_closeup(None, '10'):
    ...     print sorted(token.items())
    [('title', None), ('token', '10')]
    [('title', None), ('token', 'a value')]
    [('title', None), ('token', 'True')]

Unicode Exceptions Sidebar
--------------------------

Because tracebacks with high-bit characters in them end up being displayed
like "ValueError: <unprintable ValueError object>" we'll use a helper to
display them the way we want.

    >>> def show_ValueError(callable, *args):
    ...     try:
    ...         callable(*args)
    ...     except ValueError, e:
    ...         print 'ValueError:', e.message


Choice of EnumeratedTypes
-------------------------

The JSON representation of the enumerated value is its title.  A string
that corresponds to one of the values is marshalled to the appropriate
value. A string that doesn't correspond to any enumerated value results
in a helpful ValueError.

    >>> from lazr.restful.example.base.interfaces import Cuisine
    >>> field = Choice(vocabulary=Cuisine)
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

    >>> marshaller.marshall_from_json_data("Dessert")
    <Item Cuisine.DESSERT...>

    >>> show_ValueError(marshaller.marshall_from_json_data, "NoSuchCuisine")
    ValueError: Invalid value "NoSuchCuisine". Acceptable values are: ...

    >>> show_ValueError(marshaller.marshall_from_json_data, "dessert")
    ValueError: Invalid value "dessert". Acceptable values are: ...

None is returned unchanged:

    >>> print marshaller.marshall_from_json_data(None)
    None

This marshaller is for a Choice field describing a small, fixed
vocabularies. Because the vocabulary is small, its
unmarshall_to_closeup() implementation can describe the whole
vocabulary.

    >>> for cuisine in sorted(
    ...     marshaller.unmarshall_to_closeup(None, "Triaged")):
    ...     print sorted(cuisine.items())
    [('title', 'American'), ('token', 'AMERICAN')]
    ...
    [('title', 'Vegetarian'), ('token', 'VEGETARIAN')]


Objects
-------

An object is marshalled to its URL.

    >>> from lazr.restful.fields import Reference
    >>> from lazr.restful.example.base.interfaces import ICookbook
    >>> reference_field = Reference(schema=ICookbook)
    >>> reference_marshaller = getMultiAdapter(
    ...     (reference_field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, reference_marshaller)
    True

    >>> from lazr.restful.example.base.root import COOKBOOKS
    >>> cookbook = COOKBOOKS[0]
    >>> cookbook_url = reference_marshaller.unmarshall(None, cookbook)
    >>> print cookbook_url
    http://.../cookbooks/Mastering%20the%20Art%20of%20French%20Cooking

A URL is unmarshalled to the underlying object.

    >>> cookbook = reference_marshaller.marshall_from_json_data(cookbook_url)
    >>> cookbook.name
    u'Mastering the Art of French Cooking'

    >>> reference_marshaller.marshall_from_json_data("not a url")
    Traceback (most recent call last):
    ...
    ValueError: "not a url" is not a valid URI.

    >>> reference_marshaller.marshall_from_json_data(4)
    Traceback (most recent call last):
    ...
    ValueError: got 'int', expected string: 4

    >>> print reference_marshaller.marshall_from_json_data(None)
    None

Relative URLs
~~~~~~~~~~~~~

Relative URLs are interpreted as would be expected:

    >>> cookbook = reference_marshaller.marshall_from_json_data(
    ...     '/cookbooks/Everyday%20Greens')
    >>> print cookbook.name
    Everyday Greens

Collections
-----------

The most complicated kind of marshaller is one that manages a
collection of objects associated with some other object. The generic
collection marshaller will take care of marshalling to the proper
collection type, and of marshalling the individual items using the
marshaller for its value_type.

    >>> from zope.schema import List, Tuple, Set
    >>> list_of_strings_field = List(value_type=Text())
    >>> from lazr.restful.example.base.interfaces import Cuisine
    >>> tuple_of_ints_field = Tuple(value_type=Int())
    >>> list_of_choices_field = List(
    ...     value_type=Choice(vocabulary=Cuisine))
    >>> set_of_choices_field = Set(
    ...  value_type=Choice(vocabulary=Cuisine)).bind(None)

    >>> list_marshaller = getMultiAdapter(
    ...     (list_of_strings_field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, list_marshaller)
    True

    >>> tuple_marshaller = getMultiAdapter(
    ...     (tuple_of_ints_field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, tuple_marshaller)
    True

    >>> choice_list_marshaller = getMultiAdapter(
    ...     (list_of_choices_field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, choice_list_marshaller)
    True

    >>> set_marshaller = getMultiAdapter(
    ...     (set_of_choices_field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, set_marshaller)
    True

The only JSON representation for the collection itself is a list, since
that's the only sequence type available in JSON. Anything else will
raise a ValueError.

    >>> list_marshaller.marshall_from_json_data([u"Test"])
    [u'Test']

    >>> list_marshaller.marshall_from_json_data(u"Test")
    Traceback (most recent call last):
      ...
    ValueError: got 'unicode', expected list: u'Test'

None is passed through though.

    >>> print list_marshaller.marshall_from_json_data(None)
    None

ValueError is also raised if one of the value in the list doesn't
validate against the more specific marshaller.

    >>> list_marshaller.marshall_from_json_data([u'Text', 1, 2])
    Traceback (most recent call last):
      ...
    ValueError: got 'int', expected unicode: 1

    >>> show_ValueError(choice_list_marshaller.marshall_from_request,
    ...     [u'Vegetarian', u'NoSuchChoice'])
    ValueError: Invalid value "NoSuchChoice"...

The return type is correctly typed to the concrete collection.

    >>> tuple_marshaller.marshall_from_json_data([1, 2, 3])
    (1, 2, 3)
    >>> marshalled_set = set_marshaller.marshall_from_json_data(
    ...     ['Vegetarian', 'Dessert'])
    >>> marshalled_set
    set([<Item Cuisine.DESSERT, ...>, <Item Cuisine.VEGETARIAN, ...>])

    >>> result = choice_list_marshaller.marshall_from_request(
    ...     [u'Vegetarian', u'General'])
    >>> type(result)
    <type 'list'>
    >>> [item.title for item in result]
    ['Vegetarian', 'General']


When coming from the request, either a list or a JSON-encoded
representation is accepted. The normal request rules for the
underlying type are then followed.

    >>> list_marshaller.marshall_from_request([u'1', u'2'])
    [u'1', u'2']
    >>> list_marshaller.marshall_from_request('["1", "2"]')
    [u'1', u'2']

    >>> tuple_marshaller.marshall_from_request([u'1', u'2'])
    (1, 2)

Except that 'null' still returns None.

    >>> print list_marshaller.marshall_from_request('null')
    None

Also, as a convenience for web client, so that they don't have to JSON
encode single-element list, non-list value are promoted into a
single-element list.

    >>> tuple_marshaller.marshall_from_request('1')
    (1,)

    >>> list_marshaller.marshall_from_request('test')
    [u'test']

The unmarshall() method will return a list containing the unmarshalled
representation of each its members.

    >>> sorted(set_marshaller.unmarshall(None, marshalled_set))
    ['Dessert', 'Vegetarian']

CollectionField
---------------

Since CollectionField are really a list of references to other
objects, and they are exposed using a dedicated CollectionResource,
the marshaller for this kind of field is simpler.  Let's do an example
with a collection of IRecipe objects associated with some
ICookbook. (This might be the list of recipes in the cookbook, or
something like that.)

    >>> from lazr.restful.fields import CollectionField
    >>> from lazr.restful.example.base.interfaces import IRecipe
    >>> field = CollectionField(
    ...     __name__='recipes', value_type=Reference(schema=IRecipe))
    >>> marshaller = getMultiAdapter((field, request), IFieldMarshaller)
    >>> verifyObject(IFieldMarshaller, marshaller)
    True

Instead of serving the actual collection, collection marshallers serve
a URL to that collection.

    >>> marshaller.unmarshall(entry_resource, ["recipe 1", "recipe 2"])
    'http://.../cookbooks/Everyday%20Greens/recipes'

They also annotate the representation name of the field, so that
clients know this is a link to a collection-type resource.

    >>> marshaller.representation_name
    'recipes_collection_link'
