apia 3.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 (120) hide show
  1. checksums.yaml +7 -0
  2. data/VERSION +1 -0
  3. data/lib/apia.rb +21 -0
  4. data/lib/apia/api.rb +100 -0
  5. data/lib/apia/argument_set.rb +221 -0
  6. data/lib/apia/authenticator.rb +57 -0
  7. data/lib/apia/callable_with_environment.rb +43 -0
  8. data/lib/apia/controller.rb +32 -0
  9. data/lib/apia/defineable.rb +60 -0
  10. data/lib/apia/definition.rb +27 -0
  11. data/lib/apia/definitions/api.rb +51 -0
  12. data/lib/apia/definitions/argument.rb +77 -0
  13. data/lib/apia/definitions/argument_set.rb +33 -0
  14. data/lib/apia/definitions/authenticator.rb +46 -0
  15. data/lib/apia/definitions/controller.rb +41 -0
  16. data/lib/apia/definitions/endpoint.rb +74 -0
  17. data/lib/apia/definitions/enum.rb +31 -0
  18. data/lib/apia/definitions/error.rb +59 -0
  19. data/lib/apia/definitions/field.rb +117 -0
  20. data/lib/apia/definitions/lookup_argument_set.rb +27 -0
  21. data/lib/apia/definitions/object.rb +29 -0
  22. data/lib/apia/definitions/polymorph.rb +29 -0
  23. data/lib/apia/definitions/polymorph_option.rb +53 -0
  24. data/lib/apia/definitions/scalar.rb +23 -0
  25. data/lib/apia/definitions/type.rb +109 -0
  26. data/lib/apia/dsl.rb +23 -0
  27. data/lib/apia/dsls/api.rb +37 -0
  28. data/lib/apia/dsls/argument.rb +27 -0
  29. data/lib/apia/dsls/argument_set.rb +35 -0
  30. data/lib/apia/dsls/authenticator.rb +38 -0
  31. data/lib/apia/dsls/concerns/has_fields.rb +38 -0
  32. data/lib/apia/dsls/controller.rb +34 -0
  33. data/lib/apia/dsls/endpoint.rb +79 -0
  34. data/lib/apia/dsls/enum.rb +19 -0
  35. data/lib/apia/dsls/error.rb +26 -0
  36. data/lib/apia/dsls/field.rb +27 -0
  37. data/lib/apia/dsls/lookup_argument_set.rb +24 -0
  38. data/lib/apia/dsls/object.rb +19 -0
  39. data/lib/apia/dsls/polymorph.rb +19 -0
  40. data/lib/apia/dsls/route_group.rb +43 -0
  41. data/lib/apia/dsls/route_set.rb +40 -0
  42. data/lib/apia/dsls/scalar.rb +23 -0
  43. data/lib/apia/dsls/scope_descriptions.rb +17 -0
  44. data/lib/apia/endpoint.rb +110 -0
  45. data/lib/apia/enum.rb +43 -0
  46. data/lib/apia/environment_error_handling.rb +74 -0
  47. data/lib/apia/error.rb +61 -0
  48. data/lib/apia/error_set.rb +15 -0
  49. data/lib/apia/errors/error_exception_error.rb +32 -0
  50. data/lib/apia/errors/field_spec_parse_error.rb +23 -0
  51. data/lib/apia/errors/invalid_argument_error.rb +68 -0
  52. data/lib/apia/errors/invalid_enum_option_error.rb +21 -0
  53. data/lib/apia/errors/invalid_helper_error.rb +6 -0
  54. data/lib/apia/errors/invalid_json_error.rb +23 -0
  55. data/lib/apia/errors/invalid_polymorph_value_error.rb +21 -0
  56. data/lib/apia/errors/invalid_scalar_value_error.rb +21 -0
  57. data/lib/apia/errors/manifest_error.rb +43 -0
  58. data/lib/apia/errors/missing_argument_error.rb +40 -0
  59. data/lib/apia/errors/null_field_value_error.rb +37 -0
  60. data/lib/apia/errors/parse_error.rb +10 -0
  61. data/lib/apia/errors/runtime_error.rb +30 -0
  62. data/lib/apia/errors/scope_not_granted_error.rb +15 -0
  63. data/lib/apia/errors/standard_error.rb +6 -0
  64. data/lib/apia/field_set.rb +76 -0
  65. data/lib/apia/field_spec.rb +155 -0
  66. data/lib/apia/helpers.rb +34 -0
  67. data/lib/apia/hook_set.rb +30 -0
  68. data/lib/apia/lookup_argument_set.rb +57 -0
  69. data/lib/apia/lookup_environment.rb +27 -0
  70. data/lib/apia/manifest_errors.rb +62 -0
  71. data/lib/apia/mock_request.rb +18 -0
  72. data/lib/apia/object.rb +68 -0
  73. data/lib/apia/object_set.rb +21 -0
  74. data/lib/apia/pagination_object.rb +34 -0
  75. data/lib/apia/polymorph.rb +50 -0
  76. data/lib/apia/rack.rb +184 -0
  77. data/lib/apia/rack_error.rb +17 -0
  78. data/lib/apia/request.rb +67 -0
  79. data/lib/apia/request_environment.rb +84 -0
  80. data/lib/apia/request_headers.rb +42 -0
  81. data/lib/apia/response.rb +64 -0
  82. data/lib/apia/route.rb +61 -0
  83. data/lib/apia/route_group.rb +20 -0
  84. data/lib/apia/route_set.rb +89 -0
  85. data/lib/apia/scalar.rb +52 -0
  86. data/lib/apia/scalars.rb +25 -0
  87. data/lib/apia/scalars/base64.rb +31 -0
  88. data/lib/apia/scalars/boolean.rb +37 -0
  89. data/lib/apia/scalars/date.rb +45 -0
  90. data/lib/apia/scalars/decimal.rb +36 -0
  91. data/lib/apia/scalars/integer.rb +34 -0
  92. data/lib/apia/scalars/string.rb +24 -0
  93. data/lib/apia/scalars/unix_time.rb +40 -0
  94. data/lib/apia/schema/api_controller_schema_type.rb +17 -0
  95. data/lib/apia/schema/api_schema_type.rb +43 -0
  96. data/lib/apia/schema/argument_schema_type.rb +28 -0
  97. data/lib/apia/schema/argument_set_schema_type.rb +21 -0
  98. data/lib/apia/schema/authenticator_schema_type.rb +22 -0
  99. data/lib/apia/schema/controller.rb +39 -0
  100. data/lib/apia/schema/controller_endpoint_schema_type.rb +17 -0
  101. data/lib/apia/schema/controller_schema_type.rb +32 -0
  102. data/lib/apia/schema/endpoint_schema_type.rb +35 -0
  103. data/lib/apia/schema/enum_schema_type.rb +20 -0
  104. data/lib/apia/schema/enum_value_schema_type.rb +14 -0
  105. data/lib/apia/schema/error_schema_type.rb +23 -0
  106. data/lib/apia/schema/field_schema_type.rb +38 -0
  107. data/lib/apia/schema/field_spec_options_schema_type.rb +16 -0
  108. data/lib/apia/schema/lookup_argument_set_schema_type.rb +25 -0
  109. data/lib/apia/schema/object_schema_polymorph.rb +31 -0
  110. data/lib/apia/schema/object_schema_type.rb +21 -0
  111. data/lib/apia/schema/polymorph_option_schema_type.rb +16 -0
  112. data/lib/apia/schema/polymorph_schema_type.rb +20 -0
  113. data/lib/apia/schema/request_method_enum.rb +21 -0
  114. data/lib/apia/schema/route_group_schema_type.rb +19 -0
  115. data/lib/apia/schema/route_schema_type.rb +31 -0
  116. data/lib/apia/schema/route_set_schema_type.rb +20 -0
  117. data/lib/apia/schema/scalar_schema_type.rb +15 -0
  118. data/lib/apia/schema/scope_type.rb +14 -0
  119. data/lib/apia/version.rb +12 -0
  120. metadata +188 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 834322a8dd87426fd34415e096964a331ca3fefd5101e05e23a216da197e1820
4
+ data.tar.gz: d422b240f990920a61e520e360d15414aef1b8c0f39021684fa32d31d0aaf1a9
5
+ SHA512:
6
+ metadata.gz: 6a42e4aaf195d8bd6dc7053a473bbc107ff4303d184bfae3c5bf1e7bb25ef618f57dbc86436c697b06786847f67077d9a06ae9451c6211fbf6072a5cfd6b755e
7
+ data.tar.gz: 73762ac070550ec052ffa79f2a5e8fdbb0317b8cdb62020b74d494706a1f2e63a65d52691fed82da4f2ba60ed2d66a2572686d3e0c9fef237ec2632a67bc339b
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 3.0.0
data/lib/apia.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/api'
4
+ require 'apia/argument_set'
5
+ require 'apia/authenticator'
6
+ require 'apia/controller'
7
+ require 'apia/endpoint'
8
+ require 'apia/enum'
9
+ require 'apia/error'
10
+ require 'apia/lookup_argument_set'
11
+ require 'apia/scalar'
12
+ require 'apia/object'
13
+ require 'apia/polymorph'
14
+
15
+ require 'apia/scalars/string'
16
+ require 'apia/scalars/integer'
17
+ require 'apia/scalars/boolean'
18
+ require 'apia/scalars/date'
19
+ require 'apia/scalars/unix_time'
20
+ require 'apia/scalars/decimal'
21
+ require 'apia/scalars/base64'
data/lib/apia/api.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/defineable'
4
+ require 'apia/definitions/api'
5
+ require 'apia/helpers'
6
+ require 'apia/manifest_errors'
7
+ require 'apia/object_set'
8
+ require 'apia/errors/standard_error'
9
+ require 'apia/mock_request'
10
+
11
+ module Apia
12
+ class API
13
+
14
+ extend Defineable
15
+
16
+ class << self
17
+
18
+ # Return the definition for this API
19
+ #
20
+ # @return [Apia::Definitions::API]
21
+ def definition
22
+ @definition ||= Definitions::API.new(Helpers.class_name_to_id(name))
23
+ end
24
+
25
+ # Return all objects which are referenced by the API. This list is used for the purposes
26
+ # of validating all objects and generating schemas.
27
+ #
28
+ # @param include_apia_controller [Boolean] whether the schema/internal API should be included
29
+ # @return [Apia::ObjectSet]
30
+ def objects
31
+ set = ObjectSet.new([self])
32
+ if definition.authenticator
33
+ set.add_object(definition.authenticator)
34
+ end
35
+
36
+ definition.route_set.controllers.each do |con|
37
+ set.add_object(con)
38
+ end
39
+
40
+ definition.route_set.endpoints.each do |endpoint|
41
+ set.add_object(endpoint)
42
+ end
43
+
44
+ set
45
+ end
46
+
47
+ # Validate all objects in the API and return details of any issues encountered
48
+ #
49
+ # @return [Apia::ManifestErrors]
50
+ def validate_all
51
+ errors = ManifestErrors.new
52
+ objects.each do |object|
53
+ next unless object.respond_to?(:definition)
54
+
55
+ object.definition.validate(errors)
56
+ end
57
+ errors
58
+ end
59
+
60
+ # Return the schema hash for this API
61
+ #
62
+ # @param host [String]
63
+ # @param namespace [String]
64
+ # @return [Hash]
65
+ def schema(host:, namespace:)
66
+ require 'apia/schema/controller'
67
+ Schema::Controller.definition.endpoints[:schema].definition.fields.generate_hash({
68
+ schema_version: 1,
69
+ host: host,
70
+ namespace: namespace,
71
+ api: definition.id,
72
+ objects: objects.map(&:definition).select(&:schema?)
73
+ })
74
+ end
75
+
76
+ # Execute a request for a given controller & endpoint
77
+ #
78
+ # @param controller [Apia::Controller]
79
+ # @param endpoint_name [Symbol]
80
+ # @return [Apia::Response]
81
+ def test_endpoint(endpoint, controller: nil)
82
+ if controller && endpoint.is_a?(Symbol) || endpoint.is_a?(String)
83
+ endpoint_name = endpoint
84
+ endpoint = controller.definition.endpoints[endpoint.to_sym]
85
+ if endpoint.nil?
86
+ raise Apia::StandardError, "Invalid endpoint name '#{endpoint_name}' for '#{controller.name}'"
87
+ end
88
+ end
89
+
90
+ endpoint.test do |r|
91
+ r.api = self
92
+ r.controller = controller
93
+ yield r if block_given?
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/defineable'
4
+ require 'apia/definitions/argument_set'
5
+ require 'apia/errors/invalid_argument_error'
6
+ require 'apia/errors/missing_argument_error'
7
+ require 'apia/helpers'
8
+
9
+ module Apia
10
+ class ArgumentSet
11
+
12
+ # This is a constant that represents a missing value where `nil` means
13
+ # the user actually wanted to send null/nil.
14
+ class MissingValue
15
+
16
+ def self.singleton
17
+ @singleton ||= new
18
+ end
19
+
20
+ end
21
+
22
+ extend Defineable
23
+
24
+ class << self
25
+
26
+ # Return the definition for this argument set
27
+ #
28
+ # @return [Apia::Definitions::ArgumentSet]
29
+ def definition
30
+ @definition ||= Definitions::ArgumentSet.new(Helpers.class_name_to_id(name))
31
+ end
32
+
33
+ # Finds all objects referenced by this argument set and add them
34
+ # to the provided set.
35
+ #
36
+ # @param set [Apia::ObjectSet]
37
+ # @return [void]
38
+ def collate_objects(set)
39
+ definition.arguments.each_value do |argument|
40
+ set.add_object(argument.type.klass) if argument.type.usable_for_argument?
41
+ end
42
+ end
43
+
44
+ # Create a new argument set from a request object
45
+ #
46
+ # @param request [Apia::Request]
47
+ # @return [Apia::ArgumentSet]
48
+ def create_from_request(request)
49
+ new(request.json_body || request.params || {}, request: request)
50
+ end
51
+
52
+ end
53
+
54
+ # Create a new argument set by providing a hash containing the raw
55
+ # arguments
56
+ #
57
+ # @param hash [Hash]
58
+ # @param path [Array]
59
+ # @return [Apia::ArgumentSet]
60
+ def initialize(hash, path: [], request: nil)
61
+ unless hash.is_a?(Hash)
62
+ raise Apia::RuntimeError, 'Hash was expected for argument'
63
+ end
64
+
65
+ @path = path
66
+ @request = request
67
+ @source = self.class.definition.arguments.each_with_object({}) do |(arg_key, argument), source|
68
+ given_value = lookup_value(hash, arg_key, argument, request)
69
+
70
+ if argument.required? && (given_value.nil? || given_value.is_a?(MissingValue))
71
+ raise MissingArgumentError.new(argument, path: @path + [argument])
72
+ end
73
+
74
+ # If the given value is missing, we'll just skip adding this to the hash
75
+ next if given_value.is_a?(MissingValue)
76
+
77
+ given_value = parse_value(argument, given_value)
78
+ validation_errors = argument.validate_value(given_value)
79
+ unless validation_errors.empty?
80
+ raise InvalidArgumentError.new(argument, issue: :validation_errors, errors: validation_errors, path: @path + [argument])
81
+ end
82
+
83
+ source[argument.name.to_sym] = given_value
84
+ end
85
+ end
86
+
87
+ # Return an item from the argument set
88
+ #
89
+ # @param value [String, Symbol]
90
+ # @return [Object, nil]
91
+ def [](value)
92
+ @source[value.to_sym]
93
+ end
94
+
95
+ # Return an item from this argument set
96
+ #
97
+ # @param values [Array<String, Symbol>]
98
+ # @return [Object, nil]
99
+ def dig(*values)
100
+ @source.dig(*values)
101
+ end
102
+
103
+ # Return the source object
104
+ #
105
+ # @return [Hash]
106
+ def to_hash
107
+ @source.transform_values do |value|
108
+ value.is_a?(ArgumentSet) ? value.to_hash : value
109
+ end
110
+ end
111
+
112
+ # Return whether an argument has been provided or not?
113
+ #
114
+ # @param name [Symbol]
115
+ # @return [Boolean]
116
+ def has?(key)
117
+ @source.key?(key.to_sym)
118
+ end
119
+
120
+ # Validate an argument set and return any errors as appropriate
121
+ #
122
+ # @param argument [Apia::Argument]
123
+ def validate(argument, index: nil)
124
+ end
125
+
126
+ private
127
+
128
+ def lookup_value(hash, key, argument, request)
129
+ if hash.key?(key.to_s)
130
+ hash[key.to_s]
131
+ elsif hash.key?(key.to_sym)
132
+ hash[key.to_sym]
133
+ else
134
+ route_value = value_from_route(argument, request)
135
+ return route_value unless route_value.is_a?(MissingValue)
136
+ return argument.default unless argument.default.nil?
137
+
138
+ MissingValue.singleton
139
+ end
140
+ end
141
+
142
+ def parse_value(argument, value, index: nil, in_array: false)
143
+ if value.nil?
144
+ nil
145
+
146
+ elsif argument.array? && value.is_a?(Array)
147
+ value.each_with_index.map do |v, i|
148
+ parse_value(argument, v, index: i, in_array: true)
149
+ end
150
+
151
+ elsif argument.array? && !in_array
152
+ raise InvalidArgumentError.new(argument, issue: :array_expected, index: index, path: @path + [argument])
153
+
154
+ elsif argument.type.scalar?
155
+ begin
156
+ type = argument.type.klass.parse(value)
157
+ rescue Apia::ParseError => e
158
+ # If we cannot parse the given input, this is cause for a parse error to be raised.
159
+ raise InvalidArgumentError.new(argument, issue: :parse_error, errors: [e.message], index: index, path: @path + [argument])
160
+ end
161
+
162
+ unless argument.type.klass.valid?(type)
163
+ # If value we have parsed is not actually valid, we 'll raise an argument error.
164
+ # In most cases, it is likely that an integer has been provided to string etc...
165
+ raise InvalidArgumentError.new(argument, issue: :invalid_scalar, index: index, path: @path + [argument])
166
+ end
167
+
168
+ type
169
+
170
+ elsif argument.type.argument_set?
171
+ unless value.is_a?(Hash)
172
+ raise InvalidArgumentError.new(argument, issue: :object_expected, index: index, path: @path + [argument])
173
+ end
174
+
175
+ value = argument.type.klass.new(value, path: @path + [argument], request: @request)
176
+ value.validate(argument, index: index)
177
+ value
178
+
179
+ elsif argument.type.enum?
180
+ unless argument.type.klass.definition.values[value]
181
+ raise InvalidArgumentError.new(argument, issue: :invalid_enum_value, index: index, path: @path + [argument])
182
+ end
183
+
184
+ value
185
+ end
186
+ end
187
+
188
+ def check_for_missing_required_arguments
189
+ self.class.definition.arguments.each_value do |arg|
190
+ next unless arg.required?
191
+ next if self[arg.name]
192
+ end
193
+ end
194
+
195
+ def value_from_route(argument, request)
196
+ return MissingValue.singleton if request.nil?
197
+ return MissingValue.singleton if request.route.nil?
198
+
199
+ route_args = request.route.extract_arguments(request.api_path)
200
+ unless route_args.key?(argument.name.to_s)
201
+ return MissingValue.singleton
202
+ end
203
+
204
+ value_for_arg = route_args[argument.name.to_s]
205
+ return nil if value_for_arg.nil?
206
+
207
+ if argument.type.argument_set?
208
+ # If the argument is an argument set, we'll just want to try and
209
+ # populate the first argument.
210
+ if first_arg = argument.type.klass.definition.arguments.keys.first
211
+ { first_arg.to_s => value_for_arg }
212
+ else
213
+ {}
214
+ end
215
+ else
216
+ value_for_arg
217
+ end
218
+ end
219
+
220
+ end
221
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/defineable'
4
+ require 'apia/definitions/authenticator'
5
+ require 'apia/helpers'
6
+ require 'apia/callable_with_environment'
7
+
8
+ module Apia
9
+ class Authenticator
10
+
11
+ extend Defineable
12
+ include CallableWithEnvironment
13
+
14
+ class << self
15
+
16
+ # Return the definition for this authenticator
17
+ #
18
+ # @return [Apia::Definitions::Authenticator]
19
+ def definition
20
+ @definition ||= Definitions::Authenticator.new(Helpers.class_name_to_id(name))
21
+ end
22
+
23
+ # Finds all objects referenced by this authenticator and add them
24
+ # to the provided set.
25
+ #
26
+ # @param set [Apia::ObjectSet]
27
+ # @return [void]
28
+ def collate_objects(set)
29
+ definition.potential_errors.each do |error|
30
+ set.add_object(error)
31
+ end
32
+ end
33
+
34
+ # Execute this authenticator within the given environment
35
+ #
36
+ # @param environment [Apia::RequestEnvironment]
37
+ # @return [void]
38
+ def execute(environment)
39
+ new(environment).call
40
+ end
41
+
42
+ # If any of the given scopes are valid
43
+ #
44
+ # @param environment [Apia::RequestEnvironment]
45
+ # @param scope [String]
46
+ # @return [Boolean]
47
+ def authorized_scope?(environment, scopes)
48
+ return true if definition.scope_validator.nil?
49
+ return true if scopes.empty?
50
+
51
+ scopes.any? { |s| environment.call(s, &definition.scope_validator) }
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ module CallableWithEnvironment
5
+
6
+ def initialize(environment, action_name: :action)
7
+ @environment = environment
8
+ @action_name = action_name
9
+ end
10
+
11
+ def call
12
+ action = self.class.definition.send(@action_name)
13
+ return if action.nil?
14
+
15
+ instance_exec(@environment.request, @environment.response, &action)
16
+ end
17
+
18
+ # rubocop:disable Lint/RescueException
19
+ def call_with_error_handling
20
+ call
21
+ rescue Exception => e
22
+ raise_exception(e)
23
+ end
24
+ # rubocop:enable Lint/RescueException
25
+
26
+ def respond_to_missing?(name, _include_private = false)
27
+ @environment.respond_to?(name) || super
28
+ end
29
+
30
+ def method_missing(name, *args, **kwargs, &block)
31
+ if @environment.respond_to?(name)
32
+ if kwargs.empty?
33
+ return @environment.send(name, *args, &block)
34
+ end
35
+
36
+ return @environment.send(name, *args, **kwargs, &block)
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ end
43
+ end