easy-jsonapi 1.0.0

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