jsonapi-renderer 0.1.1.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f02425bfc2d75e4ea21cd4ecb42b6378b4c6939
4
+ data.tar.gz: 14400fddf957704ea29b97b422a0f814bad5f3a2
5
+ SHA512:
6
+ metadata.gz: 57a9d3c9d252fa70c29ee3734b322c8254b527b13661c226184cd3d4304eaaf9ae12b88a9c105a21f826b61f07fc71f6cd02e31f19c3a634001a4f178e204a26
7
+ data.tar.gz: c620d458eb042cc288c5b461af20181acc0e75dd9d0c0509c9f3bc6b9cb7edb1711db86bdbc1aa5c5805f882c148a1c4d57e708bcccfcb6fa4962a2b628c0555
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # jsonapi-renderer
2
+ Ruby gem for rendering [JSON API](http://jsonapi.org) documents.
3
+
4
+ ## Installation
5
+ ```ruby
6
+ # In Gemfile
7
+ gem 'jsonapi-renderer'
8
+ ```
9
+ then
10
+ ```
11
+ $ bundle
12
+ ```
13
+ or manually via
14
+ ```
15
+ $ gem install jsonapi-renderer
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ First, require the gem:
21
+ ```ruby
22
+ require 'jsonapi/renderer'
23
+ ```
24
+
25
+ ### Rendering resources
26
+
27
+ A resource here is any class that implements the following interface:
28
+ ```ruby
29
+ class ResourceInterface
30
+ # Returns the type of the resource.
31
+ # @return [String]
32
+ def jsonapi_type; end
33
+
34
+ # Returns the id of the resource.
35
+ # @return [String]
36
+ def jsonapi_id; end
37
+
38
+ # Returns a hash containing, for each included relationship, the resource(s)
39
+ # to be included from that one.
40
+ # @param [Array<Symbol>] included_relationships The keys of the relationships
41
+ # to be included.
42
+ # @return [Hash{Symbol => #ResourceInterface, Array<#ResourceInterface>}]
43
+ def jsonapi_related(included_relationships); end
44
+
45
+ # Returns a JSON API-compliant representation of the resource as a hash.
46
+ # @param [Hash] options
47
+ # @option [Array<Symbol>, Nil] fields The requested fields, or nil.
48
+ # @option [Array<Symbol>, Nil] included The requested included
49
+ # relationships, or nil.
50
+ # @return [Hash]
51
+ def as_jsonapi(options = {}); end
52
+ ```
53
+
54
+ #### Rendering a single resource
55
+ ```ruby
56
+ JSONAPI.render(resource,
57
+ include: include_string,
58
+ fields: fields_hash,
59
+ meta: meta_hash,
60
+ links: links_hash)
61
+ ```
62
+
63
+ This returns a JSON API compliant hash representing the described document.
64
+
65
+ #### Rendering a collection of resources
66
+ ```ruby
67
+ JSONAPI.render(resources,
68
+ include: include_string,
69
+ fields: fields_hash,
70
+ meta: meta_hash,
71
+ links: links_hash)
72
+ ```
73
+
74
+ This returns a JSON API compliant hash representing the described document.
75
+
76
+ ### Rendering errors
77
+
78
+ ```ruby
79
+ JSONAPI.render_errors(errors,
80
+ meta: meta_hash,
81
+ links: links_hash)
82
+ ```
83
+
84
+ where `errors` is an array of objects implementing the `as_jsonapi` method, that
85
+ returns a JSON API-compliant representation of the error.
86
+
87
+ This returns a JSON API compliant hash representing the described document.
88
+
89
+ ## License
90
+
91
+ jsonapi-renderer is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,65 @@
1
+ module JSONAPI
2
+ class IncludeDirective
3
+ # Utilities to create an IncludeDirective hash from various types of
4
+ # inputs.
5
+ module Parser
6
+ module_function
7
+
8
+ # @api private
9
+ def parse_include_args(include_args)
10
+ case include_args
11
+ when Symbol
12
+ { include_args => {} }
13
+ when Hash
14
+ parse_hash(include_args)
15
+ when Array
16
+ parse_array(include_args)
17
+ when String
18
+ parse_string(include_args)
19
+ else
20
+ {}
21
+ end
22
+ end
23
+
24
+ # @api private
25
+ def parse_string(include_string)
26
+ include_string.split(',')
27
+ .each_with_object({}) do |path, hash|
28
+ deep_merge!(hash, parse_path_string(path))
29
+ end
30
+ end
31
+
32
+ # @api private
33
+ def parse_path_string(include_path)
34
+ include_path.split('.')
35
+ .reverse
36
+ .reduce({}) { |a, e| { e.to_sym => a } }
37
+ end
38
+
39
+ # @api private
40
+ def parse_hash(include_hash)
41
+ include_hash.each_with_object({}) do |(key, value), hash|
42
+ hash[key.to_sym] = parse_include_args(value)
43
+ end
44
+ end
45
+
46
+ # @api private
47
+ def parse_array(include_array)
48
+ include_array.each_with_object({}) do |x, hash|
49
+ deep_merge!(hash, parse_include_args(x))
50
+ end
51
+ end
52
+
53
+ # @api private
54
+ def deep_merge!(src, ext)
55
+ ext.each do |k, v|
56
+ if src[k].is_a?(Hash) && v.is_a?(Hash)
57
+ deep_merge!(src[k], v)
58
+ else
59
+ src[k] = v
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,72 @@
1
+ require 'jsonapi/include_directive/parser'
2
+
3
+ module JSONAPI
4
+ # Represent a recursive set of include directives
5
+ # (c.f. http://jsonapi.org/format/#fetching-includes)
6
+ #
7
+ # Addition to the spec: two wildcards, namely '*' and '**'.
8
+ # The former stands for any one level of relationship, and the latter stands
9
+ # for any number of levels of relationships.
10
+ # @example 'posts.*' # => Include related posts, and all the included posts'
11
+ # related resources.
12
+ # @example 'posts.**' # => Include related posts, and all the included
13
+ # posts' related resources, and their related resources, recursively.
14
+ class IncludeDirective
15
+ # @param include_args (see Parser.parse_include_args)
16
+ def initialize(include_args, options = {})
17
+ include_hash = Parser.parse_include_args(include_args)
18
+ @hash = include_hash.each_with_object({}) do |(key, value), hash|
19
+ hash[key] = self.class.new(value, options)
20
+ end
21
+ @options = options
22
+ end
23
+
24
+ # @param key [Symbol, String]
25
+ def key?(key)
26
+ @hash.key?(key.to_sym) ||
27
+ (@options[:allow_wildcard] && (@hash.key?(:*) || @hash.key?(:**)))
28
+ end
29
+
30
+ # @return [Array<Symbol>]
31
+ def keys
32
+ @hash.keys
33
+ end
34
+
35
+ # @param key [Symbol, String]
36
+ # @return [IncludeDirective, nil]
37
+ def [](key)
38
+ case
39
+ when @hash.key?(key.to_sym)
40
+ @hash[key.to_sym]
41
+ when @options[:allow_wildcard] && @hash.key?(:**)
42
+ self.class.new({ :** => {} }, @options)
43
+ when @options[:allow_wildcard] && @hash.key?(:*)
44
+ @hash[:*]
45
+ end
46
+ end
47
+
48
+ # @return [Hash{Symbol => Hash}]
49
+ def to_hash
50
+ @hash.each_with_object({}) do |(key, value), hash|
51
+ hash[key] = value.to_hash
52
+ end
53
+ end
54
+
55
+ # @return [String]
56
+ def to_string
57
+ string_array = @hash.map do |(key, value)|
58
+ string_value = value.to_string
59
+ if string_value == ''
60
+ key.to_s
61
+ else
62
+ string_value
63
+ .split(',')
64
+ .map { |x| key.to_s + '.' + x }
65
+ .join(',')
66
+ end
67
+ end
68
+
69
+ string_array.join(',')
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,118 @@
1
+ require 'jsonapi/include_directive'
2
+
3
+ module JSONAPI
4
+ class Renderer
5
+ def initialize(resources, options = {})
6
+ @resources = resources
7
+ @errors = options[:errors] || false
8
+ @meta = options[:meta] || nil
9
+ @links = options[:links] || {}
10
+ @fields = options[:fields] || {}
11
+ # NOTE(beauby): Room for some nifty defaults on those.
12
+ @jsonapi = options[:jsonapi_object] || nil
13
+ @include = JSONAPI::IncludeDirective.new(options[:include] || {})
14
+ end
15
+
16
+ def as_json
17
+ return @json unless @json.nil?
18
+
19
+ process_resources
20
+ @json = {}
21
+
22
+ if @errors
23
+ @json[:errors] = @resources.map(&:as_jsonapi)
24
+ else
25
+ @json[:data] = @resources.respond_to?(:each) ? @primary : @primary[0]
26
+ @json[:included] = @included if @included.any?
27
+ end
28
+ @json[:links] = @links if @links.any?
29
+ @json[:meta] = @meta unless @meta.nil?
30
+ @json[:jsonapi] = @jsonapi unless @jsonapi.nil?
31
+
32
+ @json
33
+ end
34
+
35
+ private
36
+
37
+ def process_resources
38
+ @primary = []
39
+ @included = []
40
+ @hashes = {}
41
+ @processed = Set.new # NOTE(beauby): Set of [type, id, prefix].
42
+ @queue = []
43
+
44
+ Array(@resources).each do |res|
45
+ process_resource(res, '', @include, true)
46
+ @processed.add([res.jsonapi_type, res.jsonapi_id, ''])
47
+ end
48
+ until @queue.empty?
49
+ res, prefix, include_dir = @queue.pop
50
+ process_resource(res, prefix, include_dir, false)
51
+ end
52
+ end
53
+
54
+ def merge_resources!(a, b)
55
+ b[:relationships].each do |name, rel|
56
+ a[:relationships][name][:data] ||= rel[:data] if rel.key?(:data)
57
+ (a[:relationships][name][:links] ||= {})
58
+ .merge!(rel[:links]) if rel.key?(:links)
59
+ end
60
+ end
61
+
62
+ def process_resource(res, prefix, include_dir, is_primary)
63
+ ri = [res.jsonapi_type, res.jsonapi_id]
64
+ hash = res.as_jsonapi(fields: @fields[res.jsonapi_type.to_sym],
65
+ include: include_dir.keys)
66
+ if @hashes.key?(ri)
67
+ merge_resources!(@hashes[ri], hash)
68
+ else
69
+ (is_primary ? @primary : @included) << (@hashes[ri] = hash)
70
+ end
71
+ process_relationships(res, prefix, include_dir)
72
+ end
73
+
74
+ def process_relationships(res, prefix, include_dir)
75
+ res.jsonapi_related(include_dir.keys).each do |key, data|
76
+ Array(data).each do |child_res|
77
+ child_prefix = "#{prefix}.#{key}"
78
+ next unless @processed.add?([child_res.jsonapi_type,
79
+ child_res.jsonapi_id,
80
+ child_prefix])
81
+ @queue << [child_res, child_prefix, include_dir[key]]
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ module_function
88
+
89
+ # Render a success JSON API document.
90
+ #
91
+ # @param [(#jsonapi_id, #jsonapi_type, #jsonapi_related, #as_jsonapi),
92
+ # Array<(#jsonapi_id, #jsonapi_type, #jsonapi_related, #as_jsonapi)>,
93
+ # nil] resources The primary resource(s) to be rendered.
94
+ # @param [Hash] options All optional.
95
+ # @option [String, Hash{Symbol => Hash}] include Relationships to be
96
+ # included.
97
+ # @option [Hash{Symbol, Array<Symbol>}] fields List of requested fields
98
+ # for some or all of the resource types.
99
+ # @option [Hash] meta Non-standard top-level meta information to be
100
+ # included.
101
+ # @option [Hash] links Top-level links to be included.
102
+ # @option [Hash] jsonapi_object JSON API object.
103
+ def render(resources, options = {})
104
+ Renderer.new(resources, options).as_json
105
+ end
106
+
107
+ # Render an error JSON API document.
108
+ #
109
+ # @param [Array<#jsonapi_id>] errors Errors to be rendered.
110
+ # @param [Hash] options All optional.
111
+ # @option [Hash] meta Non-standard top-level meta information to be
112
+ # included.
113
+ # @option [Hash] links Top-level links to be included.
114
+ # @option [Hash] jsonapi_object JSON API object.
115
+ def render_errors(errors)
116
+ Renderer.new(errors, errors: true).as_json
117
+ end
118
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonapi-renderer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1.beta1
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Hosseini
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Low-level renderer for JSONAPI documents.
14
+ email: lucas.hosseini@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - README.md
20
+ - lib/jsonapi/include_directive.rb
21
+ - lib/jsonapi/include_directive/parser.rb
22
+ - lib/jsonapi/renderer.rb
23
+ homepage: https://github.com/beauby/jsonapi
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.3.1
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.5.1
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Render JSONAPI documents.
47
+ test_files: []