doze 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
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