Decorator to parse and validate API body.

    :keyword schema: callable that accepts raw data and returns the coerced (or
        unchanged) data if it is valid. It should raise an error if the data is
        not valid.
    :keyword types: supported content types (default is ['application/json'])
    :keyword required: if true and no body specified will raise an error.
    :keyword default: default value to return if no body supplied

    Note: only json types are supported.

def body(schema=None, types=None, required=False, default=None):
    """Decorator to parse and validate API body.

    :keyword schema: callable that accepts raw data and returns the coerced (or
        unchanged) data if it is valid. It should raise an error if the data is
        not valid.
    :keyword types: supported content types (default is ['application/json'])
    :keyword required: if true and no body specified will raise an error.
    :keyword default: default value to return if no body supplied

    Note: only json types are supported.
    """
    if not types:
        types = ['application/json']
    if not all('json' in t for t in types):
        raise NotImplementedError("Only 'json' body supported.")

    def wrap(fxn):
        """Return a decorated callable."""
        def wrapped(*args, **kwargs):
            """Callable to called when the decorated function is called."""
            try:
                data = bottle.request.json
            except (ValueError, UnicodeDecodeError) as exc:
                bottle.abort(400, str(exc))
            if required and not data:
                bottle.abort(400, "Call body cannot be empty")
            if data is None:
                data = default
            if schema:
                try:
                    data = schema(data)
                except Exception as exc:
                    bottle.abort(400, str(exc))
            return fxn(data, *args, **kwargs)
        return functools.wraps(fxn)(wrapped)
    return wrap