doze 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. data/README +6 -0
  2. data/lib/doze/application.rb +92 -0
  3. data/lib/doze/collection/object.rb +14 -0
  4. data/lib/doze/entity.rb +62 -0
  5. data/lib/doze/error.rb +75 -0
  6. data/lib/doze/media_type.rb +135 -0
  7. data/lib/doze/negotiator.rb +107 -0
  8. data/lib/doze/request.rb +119 -0
  9. data/lib/doze/resource/error.rb +21 -0
  10. data/lib/doze/resource/proxy.rb +81 -0
  11. data/lib/doze/resource.rb +193 -0
  12. data/lib/doze/responder/error.rb +34 -0
  13. data/lib/doze/responder/main.rb +41 -0
  14. data/lib/doze/responder/resource.rb +262 -0
  15. data/lib/doze/responder.rb +58 -0
  16. data/lib/doze/response.rb +78 -0
  17. data/lib/doze/router/anchored_route_set.rb +68 -0
  18. data/lib/doze/router/route.rb +88 -0
  19. data/lib/doze/router/route_set.rb +34 -0
  20. data/lib/doze/router.rb +100 -0
  21. data/lib/doze/serialization/entity.rb +34 -0
  22. data/lib/doze/serialization/form_data_helpers.rb +40 -0
  23. data/lib/doze/serialization/html.rb +116 -0
  24. data/lib/doze/serialization/json.rb +29 -0
  25. data/lib/doze/serialization/multipart_form_data.rb +162 -0
  26. data/lib/doze/serialization/resource.rb +30 -0
  27. data/lib/doze/serialization/resource_proxy.rb +14 -0
  28. data/lib/doze/serialization/www_form_encoded.rb +42 -0
  29. data/lib/doze/serialization/yaml.rb +25 -0
  30. data/lib/doze/uri_template.rb +220 -0
  31. data/lib/doze/utils.rb +53 -0
  32. data/lib/doze/version.rb +3 -0
  33. data/lib/doze.rb +5 -0
  34. data/test/functional/auth_test.rb +69 -0
  35. data/test/functional/base.rb +159 -0
  36. data/test/functional/cache_header_test.rb +76 -0
  37. data/test/functional/direct_response_test.rb +16 -0
  38. data/test/functional/error_handling_test.rb +131 -0
  39. data/test/functional/get_and_conneg_test.rb +182 -0
  40. data/test/functional/media_type_extensions_test.rb +102 -0
  41. data/test/functional/media_type_test.rb +40 -0
  42. data/test/functional/method_support_test.rb +49 -0
  43. data/test/functional/non_get_method_test.rb +173 -0
  44. data/test/functional/precondition_test.rb +84 -0
  45. data/test/functional/raw_path_info_test.rb +69 -0
  46. data/test/functional/resource_representation_test.rb +14 -0
  47. data/test/functional/router_test.rb +196 -0
  48. data/test/functional/serialization_test.rb +142 -0
  49. data/test/functional/uri_template_test.rb +51 -0
  50. metadata +221 -0
@@ -0,0 +1,78 @@
1
+ class Doze::Response
2
+ include Doze::Utils
3
+
4
+ def initialize(status=STATUS_OK, headers={}, body='')
5
+ @headers = Rack::Utils::HeaderHash.new(headers)
6
+ @status = status
7
+ @body = body
8
+ @head_only = false
9
+ end
10
+
11
+ attr_reader :headers
12
+ attr_accessor :body, :status, :head_only
13
+
14
+ def finish(head_only=@head_only)
15
+ @headers["Content-Length"] ||= content_length.to_s unless @status == STATUS_NO_CONTENT || @status == STATUS_NOT_MODIFIED
16
+ @headers['Date'] = Time.now.httpdate
17
+ [@status, @headers.to_hash, head_only ? [] : [@body]]
18
+ end
19
+
20
+ def content_length
21
+ @body.respond_to?(:bytesize) ? @body.bytesize : @body.size
22
+ end
23
+
24
+ def entity=(entity)
25
+ content_type = entity.media_type.output_name
26
+ content_type = "#{content_type}; charset=#{entity.encoding}" if entity.encoding
27
+ language = entity.language
28
+ etag = entity.etag
29
+
30
+ @headers['Content-Type'] = content_type
31
+ @headers['Content-Language'] = language if language
32
+ @headers['ETag'] = quote(etag) if etag
33
+ @headers.merge!(entity.extra_content_headers)
34
+
35
+ @body = entity.binary_data
36
+ end
37
+
38
+ def self.new_from_entity(entity, status=STATUS_OK)
39
+ result = new(status)
40
+ result.entity = entity
41
+ result
42
+ end
43
+
44
+ def self.new_empty(status=STATUS_NO_CONTENT, headers={})
45
+ new(status, headers)
46
+ end
47
+
48
+ def set_redirect(resource, request, status=STATUS_SEE_OTHER)
49
+ raise 'Resource specified as a representation must have a uri in order to redirect to it' unless resource.uri
50
+ @status = status
51
+ set_location(resource, request)
52
+ @body ||= ''
53
+ end
54
+
55
+ def set_location(resource, request)
56
+ base_uri = request.app.config[:base_uri] || request_base_uri(request)
57
+ @headers['Location'] = base_uri.merge(resource.uri).to_s
58
+ end
59
+
60
+ def self.new_redirect(resource, request, status=STATUS_SEE_OTHER)
61
+ result = new
62
+ result.set_redirect(resource, request, status)
63
+ result
64
+ end
65
+
66
+ def add_header_values(header, *values)
67
+ values.unshift(@headers[header])
68
+ @headers[header] = values.compact.join(', ')
69
+ end
70
+
71
+ def set_cookie(key, value)
72
+ Rack::Utils.set_cookie_header!(@headers, key, value)
73
+ end
74
+
75
+ def delete_cookie(key, value={})
76
+ Rack::Utils.delete_cookie_header!(@headers, key, value)
77
+ end
78
+ end
@@ -0,0 +1,68 @@
1
+ module Doze::Router::AnchoredRouteSet
2
+
3
+ # Key interface to implement here is #routes and #router_uri_prefix
4
+
5
+ def routes
6
+ raise NotImplementedException
7
+ end
8
+
9
+ def router_uri_prefix
10
+ raise NotImplementedException
11
+ end
12
+
13
+ def router_uri_prefix=
14
+ raise NotImplementedException
15
+ end
16
+
17
+ # Some utility functions based on this interface
18
+
19
+ def route_template(name)
20
+ route = routes[name] and route.template(router_uri_prefix)
21
+ end
22
+
23
+ def expand_route_template(name, vars)
24
+ route = routes[name] and route.expand(vars, router_uri_prefix)
25
+ end
26
+
27
+ def partially_expand_route_template(name, vars)
28
+ route = routes[name] and route.partially_expand(vars, router_uri_prefix)
29
+ end
30
+
31
+ def get_route(name, vars={}, session=nil)
32
+ route = routes[name] and route.call(self, vars, session)
33
+ end
34
+
35
+ def perform_routing_with_parent(parent_router, path, session, base_uri)
36
+ for route in routes
37
+ match, uri, trailing = route.match(path)
38
+ next unless match
39
+ base_uri_for_match = base_uri + uri
40
+ result = route.call(parent_router, match, session, base_uri_for_match) or next
41
+ return [result, base_uri_for_match, trailing]
42
+ end
43
+ nil
44
+ end
45
+
46
+ # What this does:
47
+ # - Informs this instance of the fixed uri at which it's known to be anchored
48
+ # - Uses this information to infer fixed uris for the target_route_set of any of
49
+ # our routes which are not parameterized? and which routes_uniquely_to_target?,
50
+ # and recursively tell these AnchoredRouteSets their fixed uris.
51
+ #
52
+ # This is called on the root resource as part of application initialization,
53
+ # to ensure that statically-known information about routing paths is propagated
54
+ # as far as possible through the resource model, so that resource instances can
55
+ # know their uris without necessarily having been a part of the routing chain for
56
+ # the current request.
57
+ def propagate_static_routes(uri)
58
+ self.router_uri_prefix = uri
59
+ routes.each do |route|
60
+ next if route.parameterized? or !route.routes_uniquely_to_target?
61
+ target = route.target_route_set
62
+ if target
63
+ route_uri = route.template(router_uri_prefix).to_s
64
+ target.propagate_static_routes(route_uri)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,88 @@
1
+ class Doze::Router::Route
2
+
3
+ # Route options:
4
+ # :name :: override default name, which is based on stripping non-alphanumerics from the route template
5
+ # :session_specific :: pass session as an extra last argument to the routing block
6
+ # :static :: route is not dependent on the parent router instance; parent router instance will not be
7
+ # passed as first argument to routing block
8
+ # :to (Router or Resource)
9
+ # :: Route directly to this instance. Only for use where it is reachable by the
10
+ # propagate_static_routes or where its uri is otherwise guaranteed to be the one routed to
11
+ # :to (Class) :: Pass routing arguments to the constructor of this class, instead of block.
12
+ # Assumes :static => true by default.
13
+ # :: Whenever :to => xyz routing is used, and the target is an AnchoredRouteSet (eg a Router instance or
14
+ # a class that includes Router) remembers this as the target_route_set of the route.
15
+ # :uniquely :: for use with :to, indicates that this is the only, unique route to the given target.
16
+ # where the target uri is statically inferable, allows propagate_static_routes to set the uri
17
+ # on the target_route_set.
18
+ # :uniquely_to :: short-cut for :to => foo, :uniquely => true
19
+ # &block :: block which is passed (router, matched_uri, matched_vars_from_template, session), except:
20
+ # * router is ommitted when :static
21
+ # * matched_vars_from_template is ommitted when the template has no vars (!parameterized?)
22
+ # * session is ommitted unless :session_specific set
23
+ # not required where :to is used.
24
+ def initialize(template, options={}, &block)
25
+ template = Doze::URITemplate.compile(template, options[:regexps] || {}) unless template.is_a?(Doze::URITemplate)
26
+ @template = template
27
+ @name = options[:name] || template.to_s.gsub(/[^a-z0-9]+/, '_').match(/^_?(.*?)_?$/)[1]
28
+ @session_specific = options[:session_specific]
29
+ @static = options[:static]
30
+
31
+ if options[:uniquely_to]
32
+ options[:uniquely] = true
33
+ options[:to] = options[:uniquely_to]
34
+ end
35
+
36
+ target = options[:to]
37
+ case target
38
+ when Doze::Router, Doze::Resource
39
+ @static = true
40
+ @target_instance = target
41
+ when Class
42
+ @static = true if @static.nil?
43
+ @block = block || target.method(:new)
44
+ else
45
+ @block = block or raise "You must specify :to or give a block"
46
+ end
47
+
48
+ if target.is_a?(Doze::Router::AnchoredRouteSet)
49
+ @target_route_set = target
50
+ @routes_uniquely_to_target = !options[:uniquely].nil?
51
+ end
52
+ end
53
+
54
+ attr_reader :name, :block, :session_specific, :static, :target_route_set
55
+
56
+ def routes_uniquely_to_target?; @routes_uniquely_to_target; end
57
+ def static?; @static; end
58
+
59
+ def template(prefix=nil)
60
+ prefix ? @template.with_prefix(prefix) : @template
61
+ end
62
+
63
+ def call(router, vars=nil, session=nil, base_uri=nil)
64
+ return @target_instance if @target_instance
65
+ base_uri ||= expand(vars, router.router_uri_prefix) if router
66
+ args = [base_uri]
67
+ args << vars if vars && !vars.empty?
68
+ args << session if @session_specific
69
+ args.unshift(router) unless @static
70
+ @block.call(*args)
71
+ end
72
+
73
+ def match(path)
74
+ @template.match_with_trailing(path)
75
+ end
76
+
77
+ def expand(vars, prefix=nil)
78
+ template(prefix).expand(vars)
79
+ end
80
+
81
+ def partially_expand(vars, prefix=nil)
82
+ template(prefix).partially_expand(vars)
83
+ end
84
+
85
+ def parameterized?
86
+ !@template.variables.empty?
87
+ end
88
+ end
@@ -0,0 +1,34 @@
1
+ class Doze::Router::RouteSet
2
+ def initialize(&block)
3
+ @routes = []
4
+ @routes_by_name = {}
5
+ instance_eval(&block) if block
6
+ end
7
+
8
+ def <<(route)
9
+ @routes << route unless @routes_by_name.has_key?(route.name)
10
+ @routes_by_name[route.name] = route
11
+ self
12
+ end
13
+
14
+ def [](name)
15
+ @routes_by_name[name]
16
+ end
17
+
18
+ def route(*p, &b)
19
+ route = Doze::Router::Route.new(*p, &b)
20
+ self << route
21
+ route
22
+ end
23
+
24
+ def each(&b)
25
+ @routes.each(&b)
26
+ end
27
+
28
+ def dup(&block)
29
+ route_set = self.class.new
30
+ @routes.each {|r| route_set << r}
31
+ route_set.instance_eval(&block) if block
32
+ route_set
33
+ end
34
+ end
@@ -0,0 +1,100 @@
1
+ # A Doze::Router is a Doze::AnchoredRouteSet which routes with itself as the parent Router.
2
+ # Including it also extends the class with Doze::AnchoredRouteSet, and the instances delegates its routes
3
+ # to the class.
4
+ module Doze::Router
5
+ require 'doze/router/anchored_route_set'
6
+
7
+ include Doze::Router::AnchoredRouteSet
8
+
9
+ def self.included(mod)
10
+ mod.extend(ClassMethods) if mod.is_a?(Class) # if you include this in another module, its self.included should call this one
11
+ end
12
+
13
+ # The main method of the Router interface.
14
+ #
15
+ # You can override this if you like, but a flexible default implementation is provided
16
+ # which can be configured with the 'route' class method.
17
+ #
18
+ # args:
19
+ # path - the path to match.
20
+ # may be a suffix of the actual request path, if this router has been
21
+ # delegated to by a higher-level router)
22
+ # method - symbol for the http method being routed for this path
23
+ # session - session from the request - see Application.config[:session_from_rack_env]
24
+ # base_uri - the base uri at which the routing is taking place
25
+ #
26
+ # should return either:
27
+ # nil if no route matched / subresource not found, or
28
+ #
29
+ # [route_to, base_uri_for_match, trailing] where
30
+ #
31
+ # route_to - Resource or Router to route the request to
32
+ # base_uri_for_match - base uri for the resource or router which we matched
33
+ # trailing - any trailing bits of path following from base_uri_for_match to be passed onto the next router
34
+ def perform_routing(path, session, base_uri)
35
+ perform_routing_with_parent(self, path, session, base_uri)
36
+ end
37
+
38
+ # The default Router implementation can run against any RouteSet returned here.
39
+ # By default routes returns a RouteSet defined at the class level using the class-method routing helpers,
40
+ # but you can override this if you want to use some instance-specific RouteSet
41
+ def routes
42
+ @routes || self.class.routes
43
+ end
44
+
45
+ # Add an instance-specific route. This dups the default route-set
46
+ def add_route(*p, &b)
47
+ @routes ||= routes.dup
48
+ @routes.route(*p, &b)
49
+ end
50
+
51
+ # If this particular router instance has a uri prefix associated with it.
52
+ # Will use a resource's URI method where available, for the common case where the router is also a resource
53
+ def router_uri_prefix
54
+ @router_uri_prefix ||= begin
55
+ u = respond_to?(:uri) ? uri : nil
56
+ u && u.chomp('/')
57
+ end
58
+ end
59
+
60
+ # Used primarily by propagate_static_routes. As a (desired) side-effect, will also set the uri using #uri=
61
+ # where this router is also a resource.
62
+ def router_uri_prefix=(uri_prefix)
63
+ if respond_to?(:uri=)
64
+ self.uri = (uri_prefix.empty? ? '/' : uri_prefix)
65
+ end
66
+ @router_uri_prefix = uri_prefix
67
+ end
68
+
69
+ # called upon by the framework
70
+ # return false to blanket deny authorization to all methods on all routed subresources
71
+ def authorize_routing(session)
72
+ true
73
+ end
74
+
75
+ module ClassMethods
76
+ include Doze::Router::AnchoredRouteSet
77
+
78
+ def router_uri_prefix
79
+ return @router_uri_prefix if defined?(@router_uri_prefix)
80
+ @router_uri_prefix = (superclass.respond_to?(:router_uri_prefix) ? superclass.router_uri_prefix : nil)
81
+ end
82
+
83
+ def router_uri_prefix=(uri)
84
+ @router_uri_prefix = uri
85
+ module_eval("def uri; self.class.router_uri_prefix; end", __FILE__, __LINE__)
86
+ module_eval("def router_uri_prefix; self.class.router_uri_prefix; end", __FILE__, __LINE__)
87
+ end
88
+
89
+ def routes
90
+ @routes ||= (superclass.respond_to?(:routes) ? superclass.routes.dup : Doze::Router::RouteSet.new)
91
+ end
92
+
93
+ def route(*p, &b)
94
+ routes.route(*p, &b)
95
+ end
96
+ end
97
+ end
98
+
99
+ require 'doze/router/route'
100
+ require 'doze/router/route_set'
@@ -0,0 +1,34 @@
1
+ require 'doze/entity'
2
+
3
+ module Doze::Serialization
4
+ class Entity < Doze::Entity
5
+ def initialize(media_type, options={}, &lazy_object_data)
6
+ super(media_type, options, &nil) # ruby quirk: blocks are implicitely passed to super
7
+ @object_data = options[:object_data]
8
+ @lazy_object_data = lazy_object_data || options[:lazy_object_data]
9
+ end
10
+
11
+ def object_data(try_deserialize=true)
12
+ @object_data ||= if @lazy_object_data
13
+ @lazy_object_data.call
14
+ elsif try_deserialize
15
+ data = binary_data(false)
16
+ data && deserialize(data)
17
+ end
18
+ end
19
+
20
+ def binary_data(try_serialize=true)
21
+ super() || if try_serialize
22
+ @binary_data = serialize(object_data(false))
23
+ end
24
+ end
25
+
26
+ def serialize(object_data)
27
+ raise 'serialize: not supported'
28
+ end
29
+
30
+ def deserialize(binary_data)
31
+ raise 'deserialize: not supported'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ module Doze::Serialization::FormDataHelpers
2
+
3
+ private
4
+
5
+ def escape(s)
6
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
7
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
8
+ }.tr(' ', '+')
9
+ end
10
+
11
+ def unescape(s)
12
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
13
+ [$1.delete('%')].pack('H*')
14
+ }
15
+ end
16
+
17
+ def normalize_params(parms, name, val=nil)
18
+ name =~ %r([\[\]]*([^\[\]]+)\]*)
19
+ key = $1 || ''
20
+ after = $' || ''
21
+
22
+ if after == ""
23
+ parms[key] = val
24
+ elsif after == "[]"
25
+ (parms[key] ||= []) << val
26
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
27
+ child_key = $1
28
+ parms[key] ||= []
29
+ if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
30
+ parms[key].last.update(child_key => val)
31
+ else
32
+ parms[key] << { child_key => val }
33
+ end
34
+ else
35
+ parms[key] ||= {}
36
+ parms[key] = normalize_params(parms[key], after, val)
37
+ end
38
+ parms
39
+ end
40
+ end
@@ -0,0 +1,116 @@
1
+ require 'json'
2
+ require 'doze/media_type'
3
+ require 'doze/serialization/entity'
4
+ require 'doze/utils'
5
+
6
+ module Doze::Serialization
7
+ class Entity::HTML < Entity
8
+ def serialize(data)
9
+ # TODO move to a template
10
+ html = <<END
11
+ <html>
12
+ <head>
13
+ <style>
14
+ body {
15
+ font-family: Arial;
16
+ }
17
+ body, td {
18
+ font-size: 13px;
19
+ }
20
+ body > table {
21
+ border: 1px solid black;
22
+ }
23
+ table {
24
+ border-color: black;
25
+ border-collapse: collapse;
26
+ }
27
+ td {
28
+ padding: 0;
29
+ vertical-align: top;
30
+ }
31
+ td > span, td > a, td > form {
32
+ display: block;
33
+ padding: 0.3em;
34
+ margin: 0;
35
+ }
36
+ td:first-child {
37
+ text-align: right;
38
+ font-weight: bold;
39
+ width: 1%; /* force as small as possible */
40
+ }
41
+ td > table {
42
+ width: 100%;
43
+ }
44
+ li > table {
45
+ width: 100%;
46
+ }
47
+ td > ol {
48
+ padding: 0.3em 0.3em 0.3em 2.3em;
49
+ }
50
+ td > ol > li > table {
51
+ }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ #{make_html(data)}
56
+ </body>
57
+ </html>
58
+ END
59
+ end
60
+
61
+ private
62
+
63
+ def make_html(data)
64
+ case data
65
+ when Hash
66
+ pairs = data.map {|k,v| "<tr><td>#{make_html(k)}</td><td>#{make_html(v)}</td></tr>"}
67
+ "<table rules='all' frame='void'>#{pairs.join("\n")}</table>"
68
+ when Array
69
+ i=-1; items = data.map {|v| "<tr><td><span>#{i+=1}</span></td><td>#{make_html(v)}</td></tr>"}
70
+ items.empty? ? '&nbsp;' : "<table rules='all' frame='void'>#{items.join("\n")}</table>"
71
+ when URI
72
+ "<a href='#{Rack::Utils.escape_html(data)}'>#{Rack::Utils.escape_html(data)}</a>"
73
+ when Doze::URITemplate
74
+ if data.variables.length > 0
75
+ # Clever HTML rendering of a URI template.
76
+ # Make a HTML form which uses some javascript onsubmit to navigate to an expanded version of the URI template,
77
+ # with blanks filled in via INPUTs.
78
+ inputs = data.parts.map do |part|
79
+ case part
80
+ when Doze::URITemplate::String
81
+ Rack::Utils.escape_html(part.string)
82
+ when Doze::URITemplate::QuadHexBytesVariable
83
+ "/<input name='#{Rack::Utils.escape_html(part.name)}'>"
84
+ when Doze::URITemplate::Variable
85
+ "<input name='#{Rack::Utils.escape_html(part.name)}'>"
86
+ end
87
+ end.join
88
+
89
+ i=-1; js = data.parts.map do |part|
90
+ case part
91
+ when Doze::URITemplate::String
92
+ part.string.to_json
93
+ when Doze::URITemplate::QuadHexBytesVariable
94
+ i += 1; "(v = parseInt(this.elements[#{i}].value, '10').toString(16), (new Array(9-v.length).join('0')+v).replace(/(..)/g, '/$1'))"
95
+ when Doze::URITemplate::Variable
96
+ i += 1; "this.elements[#{i}].value"
97
+ end
98
+ end
99
+
100
+ js = "window.location.href = #{js.join(" + ")}; return false"
101
+ "<form method='GET' onsubmit='#{Rack::Utils.escape_html(js)}'>#{inputs}<input type='submit'></form>"
102
+ else
103
+ "<a href='#{Rack::Utils.escape_html(data)}'>#{Rack::Utils.escape_html(data)}</a>"
104
+ end
105
+ else
106
+ string = data.to_s.strip
107
+ string.empty? ? '&nbsp;' : "<span>#{Rack::Utils.escape_html(string)}</span>"
108
+ end
109
+ end
110
+ end
111
+
112
+ # A browser-friendly media type for use with Doze::Serialization::Resource.
113
+ # We don't register it, since you might not want to use it for submitted html entities,
114
+ # but you can call register! on it yourself if you do
115
+ HTML = Doze::MediaType.new('text/html', :entity_class => Entity::HTML, :extension => 'html')
116
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+ require 'doze/media_type'
3
+ require 'doze/serialization/entity'
4
+ require 'doze/error'
5
+ require 'doze/utils'
6
+
7
+ module Doze::Serialization
8
+ class Entity::JSON < Entity
9
+ def serialize(ruby_data)
10
+ ruby_data.to_json
11
+ end
12
+
13
+ def deserialize(binary_data)
14
+ begin
15
+ case binary_data
16
+ when /^[\[\{]/
17
+ ::JSON.parse(binary_data)
18
+ else
19
+ # A pox on the arbitrary syntactic limitation that a top-level piece of JSON must be a hash or array
20
+ ::JSON.parse("[#{binary_data}]").first
21
+ end
22
+ rescue ::JSON::ParserError
23
+ raise Doze::ClientEntityError, "Could not parse JSON"
24
+ end
25
+ end
26
+ end
27
+
28
+ JSON = Doze::MediaType.register('application/json', :plus_suffix => 'json', :entity_class => Entity::JSON, :extension => 'json')
29
+ end