easy-jsonapi 1.0.0

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 (91) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/publish-gem.yml +60 -0
  3. data/.github/workflows/rake.yml +35 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +5 -0
  9. data/Gemfile.lock +106 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +209 -0
  12. data/Rakefile +20 -0
  13. data/UsingTheRequestObject.md +74 -0
  14. data/UsingUserConfigurations.md +95 -0
  15. data/bin/bundle +114 -0
  16. data/bin/console +15 -0
  17. data/bin/htmldiff +29 -0
  18. data/bin/kramdown +29 -0
  19. data/bin/ldiff +29 -0
  20. data/bin/license_finder +29 -0
  21. data/bin/license_finder_pip.py +29 -0
  22. data/bin/maruku +29 -0
  23. data/bin/marutex +29 -0
  24. data/bin/nokogiri +29 -0
  25. data/bin/racc +29 -0
  26. data/bin/rackup +29 -0
  27. data/bin/rake +29 -0
  28. data/bin/redcarpet +29 -0
  29. data/bin/reverse_markdown +29 -0
  30. data/bin/rspec +29 -0
  31. data/bin/rubocop +29 -0
  32. data/bin/ruby-parse +29 -0
  33. data/bin/ruby-rewrite +29 -0
  34. data/bin/setup +8 -0
  35. data/bin/solargraph +29 -0
  36. data/bin/thor +29 -0
  37. data/bin/tilt +29 -0
  38. data/bin/yard +29 -0
  39. data/bin/yardoc +29 -0
  40. data/bin/yri +29 -0
  41. data/easy-jsonapi.gemspec +39 -0
  42. data/lib/easy/jsonapi.rb +12 -0
  43. data/lib/easy/jsonapi/collection.rb +144 -0
  44. data/lib/easy/jsonapi/config_manager.rb +144 -0
  45. data/lib/easy/jsonapi/config_manager/config.rb +49 -0
  46. data/lib/easy/jsonapi/document.rb +71 -0
  47. data/lib/easy/jsonapi/document/error.rb +48 -0
  48. data/lib/easy/jsonapi/document/error/error_member.rb +15 -0
  49. data/lib/easy/jsonapi/document/jsonapi.rb +26 -0
  50. data/lib/easy/jsonapi/document/jsonapi/jsonapi_member.rb +15 -0
  51. data/lib/easy/jsonapi/document/links.rb +36 -0
  52. data/lib/easy/jsonapi/document/links/link.rb +15 -0
  53. data/lib/easy/jsonapi/document/meta.rb +26 -0
  54. data/lib/easy/jsonapi/document/meta/meta_member.rb +14 -0
  55. data/lib/easy/jsonapi/document/resource.rb +56 -0
  56. data/lib/easy/jsonapi/document/resource/attributes.rb +37 -0
  57. data/lib/easy/jsonapi/document/resource/attributes/attribute.rb +29 -0
  58. data/lib/easy/jsonapi/document/resource/relationships.rb +40 -0
  59. data/lib/easy/jsonapi/document/resource/relationships/relationship.rb +50 -0
  60. data/lib/easy/jsonapi/document/resource_id.rb +28 -0
  61. data/lib/easy/jsonapi/exceptions.rb +27 -0
  62. data/lib/easy/jsonapi/exceptions/document_exceptions.rb +619 -0
  63. data/lib/easy/jsonapi/exceptions/headers_exceptions.rb +156 -0
  64. data/lib/easy/jsonapi/exceptions/naming_exceptions.rb +36 -0
  65. data/lib/easy/jsonapi/exceptions/query_params_exceptions.rb +67 -0
  66. data/lib/easy/jsonapi/exceptions/user_defined_exceptions.rb +253 -0
  67. data/lib/easy/jsonapi/field.rb +43 -0
  68. data/lib/easy/jsonapi/header_collection.rb +38 -0
  69. data/lib/easy/jsonapi/header_collection/header.rb +11 -0
  70. data/lib/easy/jsonapi/item.rb +88 -0
  71. data/lib/easy/jsonapi/middleware.rb +158 -0
  72. data/lib/easy/jsonapi/name_value_pair.rb +72 -0
  73. data/lib/easy/jsonapi/name_value_pair_collection.rb +78 -0
  74. data/lib/easy/jsonapi/parser.rb +38 -0
  75. data/lib/easy/jsonapi/parser/document_parser.rb +196 -0
  76. data/lib/easy/jsonapi/parser/headers_parser.rb +33 -0
  77. data/lib/easy/jsonapi/parser/rack_req_params_parser.rb +117 -0
  78. data/lib/easy/jsonapi/request.rb +40 -0
  79. data/lib/easy/jsonapi/request/query_param_collection.rb +56 -0
  80. data/lib/easy/jsonapi/request/query_param_collection/fields_param.rb +32 -0
  81. data/lib/easy/jsonapi/request/query_param_collection/fields_param/fieldset.rb +34 -0
  82. data/lib/easy/jsonapi/request/query_param_collection/filter_param.rb +28 -0
  83. data/lib/easy/jsonapi/request/query_param_collection/filter_param/filter.rb +34 -0
  84. data/lib/easy/jsonapi/request/query_param_collection/include_param.rb +119 -0
  85. data/lib/easy/jsonapi/request/query_param_collection/page_param.rb +55 -0
  86. data/lib/easy/jsonapi/request/query_param_collection/query_param.rb +47 -0
  87. data/lib/easy/jsonapi/request/query_param_collection/sort_param.rb +25 -0
  88. data/lib/easy/jsonapi/response.rb +22 -0
  89. data/lib/easy/jsonapi/utility.rb +158 -0
  90. data/lib/easy/jsonapi/version.rb +8 -0
  91. metadata +248 -0
data/bin/tilt ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tilt' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("tilt", "tilt")
data/bin/yard ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yard' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yard")
data/bin/yardoc ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yardoc' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yardoc")
data/bin/yri ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yri' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yri")
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'easy-jsonapi'
5
+ spec.version = '1.0.0'
6
+ spec.authors = ['Joshua DeMoss, Joe Viscomi']
7
+ spec.email = ['demoss.joshua@gmail.com']
8
+
9
+ spec.summary = 'Middleware, Parser, and Validator for JSONAPI requests and serialized resopnses'
10
+ spec.description = 'Middleware to screen non-JSONAPI-compliant requests, a parser to provide Object Oriented access to requests, and a validator for validating JSONAPI Serialized responses.'
11
+ spec.homepage = 'https://rubygems.org/gems/easy-jsonapi'
12
+ spec.required_ruby_version = '>= 2.7'
13
+
14
+ spec.metadata["source_code_uri"] = "https://github.com/Curatess/easy-jsonapi"
15
+ spec.metadata["changelog_uri"] = "https://github.com/Curatess/easy-jsonapi/CHANGELOG.mg"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ # Dev Dependencies
27
+ spec.add_development_dependency 'license_finder', '~> 6.10'
28
+ spec.add_development_dependency 'rack', '~> 2.2'
29
+ spec.add_development_dependency 'rake', '~> 13.0'
30
+ spec.add_development_dependency 'redcarpet', '~> 3.5'
31
+ spec.add_development_dependency 'rspec', '~> 3.9'
32
+ spec.add_development_dependency 'rubocop', '~> 1.11'
33
+ spec.add_development_dependency 'solargraph', '~> 0.39'
34
+
35
+ # Dependencies
36
+ spec.add_dependency 'oj', '~> 3.10'
37
+
38
+ spec.license = 'MIT'
39
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'easy/jsonapi/middleware'
4
+ require 'easy/jsonapi/parser'
5
+ require 'easy/jsonapi/response'
6
+
7
+ # This module is the top level namespace for the curatess jsonapi middleware gem
8
+ #
9
+ # @author Joshua DeMoss
10
+ # @see https://app.lucidchart.com/invitations/accept/e24c2cfe-78f1-4192-8e88-6dbc4454a5ea UML Class Diagram
11
+ module JSONAPI
12
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONAPI
4
+ # Models a collection of items
5
+ class Collection
6
+ include Enumerable
7
+
8
+ # Assume collection is empty not innitialized with an array of objects.
9
+ # @param arr_of_obj [Object] The objects to be stored
10
+ # for block { |item| item[:name] }
11
+ # @yield [item] Determines what should be used as keys when storing objects in collection's internal hash
12
+ def initialize(arr_of_obj = [], item_type: Object, &block)
13
+ @item_type = item_type
14
+ @collection = {}
15
+
16
+ return unless (arr_of_obj != []) && block_given?
17
+
18
+ arr_of_obj.each do |obj|
19
+ add(obj, &block)
20
+ end
21
+ end
22
+
23
+ # Collection.new([
24
+ # { key: 'include', value: 'authors,comments,likes' },
25
+ # { key: 'lebron', value: 'james' },
26
+ # { key: 'charles', value: 'barkley' },
27
+ # { key: 'michael', value: 'jordan,jackson' },
28
+ # { key: 'kobe', value: 'bryant' }
29
+ # ]
30
+
31
+ # Checks to see if the collection is empty
32
+ # @return [TrueClass | FalseClass]
33
+ def empty?
34
+ @collection == {}
35
+ end
36
+
37
+ # Does the collection's internal hash include this key?
38
+ # @param key [String | Symbol] The key to search for in the hash
39
+ def include?(key)
40
+ @collection.include?(key.to_sym)
41
+ end
42
+
43
+ # Add an item to the collection, giving a block to indicate how the
44
+ # collection should create a hash key for the item.
45
+ # @param item [Object]
46
+ def add(item, &block)
47
+ raise 'a block must be passed to #add indicating what should be used as a key' unless block_given?
48
+ raise "Cannot add an item that is not #{@item_type}" unless item.is_a? @item_type
49
+ insert(block.call(item), item)
50
+ end
51
+
52
+ # Adds an item to Collection's internal hash
53
+ def insert(key, item)
54
+ if include?(key)
55
+ raise 'The hash key given already has an Item associated with it. ' \
56
+ 'Remove existing item first.'
57
+ end
58
+ set(key, item)
59
+ end
60
+
61
+ # Overwrites the item associated w a given key, or adds an association if no item is already associated.
62
+ def set(key, item)
63
+ raise "Cannot add an item that is not #{@item_type}" unless item.is_a? @item_type
64
+ @collection[key.to_sym] = item
65
+ end
66
+
67
+ # Yield the block given on all the items in the collection
68
+ def each
69
+ return @collection.each { |_, item| yield(item) } if block_given?
70
+ to_enum(:each)
71
+ end
72
+
73
+ # Remove an item from the collection
74
+ # @param (see #include)
75
+ # @return [Item | nil] the deleted item object if it exists
76
+ def remove(key)
77
+ return nil if @collection[key.to_sym].nil?
78
+ @collection.delete(key.to_sym)
79
+ end
80
+
81
+ # @param (see #remove)
82
+ # @return [Item | nil] The appropriate Item object if it exists
83
+ def get(key)
84
+ @collection[key.to_sym]
85
+ end
86
+
87
+ # Alias to #get
88
+ # @param (see #get)
89
+ # @param (see #get)
90
+ def [](key)
91
+ get(key)
92
+ end
93
+
94
+ # Alias to #set
95
+ # @param (see #set)
96
+ # @param (see #set)
97
+ def []=(key, item)
98
+ set(key, item)
99
+ end
100
+
101
+ # Allows the developer to treat the Collection class as a hash, retrieving all keys mapped to Items.
102
+ # @return [Array<Symbol>] An array of all the item keys stored in the Collection object.
103
+ def keys
104
+ @collection.keys
105
+ end
106
+
107
+ # @return [Integer] The number of items in the collection
108
+ def size
109
+ @collection.size
110
+ end
111
+
112
+ # Used to print out the Collection object with better formatting
113
+ # return [String] The collection object contents represented as a formatted string
114
+ def to_s
115
+ to_return = '{ '
116
+ is_first = true
117
+ @collection.each do |k, item|
118
+ if is_first
119
+ to_return += "\"#{k}\": #{item}"
120
+ is_first = false
121
+ else
122
+ to_return += ", \"#{k}\": #{item}"
123
+ end
124
+ end
125
+ to_return += ' }'
126
+ end
127
+
128
+ private
129
+
130
+ # Gets the Collection object whose hash key matches the method_name called
131
+ # @param method_name [Symbol] The name of the method called
132
+ # @param args If any arguments were passed to the method called
133
+ # @param block If a block was passed to the method called
134
+ def method_missing(method_name, *args, &block)
135
+ super unless @collection.include?(method_name)
136
+ get(method_name)
137
+ end
138
+
139
+ # Whether or not method missing should be called.
140
+ def respond_to_missing?(method_name, *)
141
+ @collection.include?(method_name) || super
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'easy/jsonapi/config_manager/config'
4
+
5
+ module JSONAPI
6
+
7
+ # Manages user configuration options
8
+ class ConfigManager
9
+
10
+ include Enumerable
11
+
12
+ # Config Manager always has an internal global config
13
+ def initialize
14
+ @class_type = JSONAPI::ConfigManager::Config
15
+ @config_manager = { global: JSONAPI::ConfigManager::Config.new }
16
+ end
17
+
18
+ # Yield the block given on all the config in the config_manager
19
+ def each(&block)
20
+ return @config_manager.each(&block) if block_given?
21
+ to_enum(:each)
22
+ end
23
+
24
+ # Are any user configurations set?
25
+ def default?
26
+ (@config_manager.size == 1 && @config_manager[:global].default?) || all_configs_default?
27
+ end
28
+
29
+ # Does the config_manager's internal hash include this res_name?
30
+ # @param res_name [String | Symbol] The res_name to search for in the hash
31
+ def include?(res_name)
32
+ @config_manager.include?(res_name.to_sym)
33
+ end
34
+
35
+ # Add an config to the config_manager
36
+ # @param config [Object]
37
+ def add(res_name, config)
38
+ raise "Cannot add a config that is not #{@class_type}" unless config.is_a? @class_type
39
+ insert(res_name, config)
40
+ end
41
+
42
+ # Adds an config to config_manager's internal hash
43
+ def insert(res_name, config)
44
+ if include?(res_name.to_sym)
45
+ raise "The resource type: #{res_name}, already has an config associated with it. " \
46
+ 'Remove existing config first.'
47
+ end
48
+ set(res_name, config)
49
+ end
50
+
51
+ # Overwrites the config associated w a given res_name, or adds an association if no config is already associated.
52
+ def set(res_name, config)
53
+ raise "Cannot add a config that is not #{@class_type}" unless config.is_a? @class_type
54
+ @config_manager[res_name.to_sym] = config
55
+ end
56
+
57
+ # Alias to #set
58
+ # @param (see #set)
59
+ # @param (see #set)
60
+ def []=(res_name, config)
61
+ set(res_name, config)
62
+ end
63
+
64
+ # @param (see #remove)
65
+ # @return [JSONAPI::ConfigManager::Config | nil] The appropriate Item object if it exists
66
+ def get(res_name)
67
+ @config_manager[res_name.to_sym]
68
+ end
69
+
70
+ # Alias to #get
71
+ # @param (see #get)
72
+ # @param (see #get)
73
+ def [](res_name)
74
+ get(res_name)
75
+ end
76
+
77
+ # Remove an config from the config_manager
78
+ # @param (see #include)
79
+ # @return [JSONAPI::ConfigManager::Config | nil] the deleted config object if it exists
80
+ def remove(res_name)
81
+ if res_name.to_s == 'global'
82
+ raise "Cannot remove global config"
83
+ end
84
+ @config_manager.delete(res_name.to_sym)
85
+ end
86
+
87
+ # @return [Integer] The number of config in the config_manager
88
+ def size
89
+ configs.size
90
+ end
91
+
92
+ # @return [Array<Symbol>] The names of the resource types the configs belong to
93
+ def configs
94
+ c_arr = []
95
+ @config_manager.each_key do |res_type|
96
+ c = self[res_type]
97
+ unless c.default?
98
+ c_arr << res_type
99
+ end
100
+ end
101
+ c_arr
102
+ end
103
+
104
+ # Used to print out the config_manager object with better formatting
105
+ # return [String] The config_manager object contents represented as a formatted string
106
+ def to_s
107
+ to_return = '{ '
108
+ is_first = true
109
+ each do |k, config|
110
+ if is_first
111
+ to_return += "#{k}: #{config}"
112
+ is_first = false
113
+ else
114
+ to_return += ", #{k}: #{config}"
115
+ end
116
+ end
117
+ to_return += ' }'
118
+ end
119
+
120
+ private
121
+
122
+ # Gets the config_manager object whose hash res_name matches the method_name called
123
+ # @param method_name [Symbol] The name of the method called
124
+ # @param args If any arguments were passed to the method called
125
+ # @param block If a block was passed to the method called
126
+ def method_missing(method_name, *args, &block)
127
+ super unless @config_manager.include?(method_name)
128
+ get(method_name)
129
+ end
130
+
131
+ # Whether or not method missing should be called.
132
+ def respond_to_missing?(method_name, *)
133
+ @config_manager.include?(method_name) || super
134
+ end
135
+
136
+ # All of the included configs are set to default?
137
+ # @return [TrueClass | FalseClass]
138
+ def all_configs_default?
139
+ res = true
140
+ @config_manager.each_value { |c| res &= c.default? }
141
+ res
142
+ end
143
+ end
144
+ end