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
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ # This is the error exception that must be raised when you wish to raise an
7
+ # error. It should be initialized with the Apia::Error class that you wish
8
+ # to raise.
9
+ class ErrorExceptionError < Apia::RuntimeError
10
+
11
+ attr_reader :error_class
12
+ attr_reader :fields
13
+
14
+ def initialize(error_class, fields = {})
15
+ @error_class = error_class
16
+ @fields = fields
17
+ end
18
+
19
+ def http_status
20
+ @error_class.definition.http_status || 500
21
+ end
22
+
23
+ def hash
24
+ {
25
+ code: @error_class.definition.code,
26
+ description: @error_class.definition.description,
27
+ detail: @error_class.definition.fields.generate_hash(@fields)
28
+ }
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class FieldSpecParseError < Apia::RuntimeError
7
+
8
+ def http_status
9
+ 400
10
+ end
11
+
12
+ def hash
13
+ {
14
+ code: 'invalid_field_spec',
15
+ description: 'The field spec string was invalid',
16
+ detail: {
17
+ details: message
18
+ }
19
+ }
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ # Raised when an argument set cannot be created based on the source object that
7
+ # has been provided. For example, if a validation rule exists or a scalar cannot
8
+ # be parsed for the underlying object.
9
+ #
10
+ # This is not raised for MISSING argument errors.
11
+ class InvalidArgumentError < Apia::RuntimeError
12
+
13
+ ISSUE_DESCRIPTIONS = {
14
+ invalid_scalar: 'The value provided was not of an appropriate type for the scalar that was requested. For example, you may have passed a string where an integer was required etc...',
15
+ parse_error: 'The value provided could not be parsed into an appropriate value by the server. For example, if a date was expected and the value could not be interpretted as such.',
16
+ validation_error: 'A validation rule that has been specified for this argument was not satisfied. See the further details in the response and in the documentation.',
17
+ invalid_enum_value: 'The value provided was not one of the options suitable for the enum.',
18
+ missing_lookup_value: 'A value for a lookup argument set has not been provided but at least one value is required.',
19
+ ambiguous_lookup_values: 'More than one value has been provided for a lookup argument set. Only one option may be provided.'
20
+ }.freeze
21
+
22
+ attr_reader :argument
23
+ attr_reader :index
24
+ attr_reader :path
25
+ attr_reader :errors
26
+ attr_reader :issue
27
+
28
+ def initialize(argument, issue: nil, index: nil, path: [], errors: [])
29
+ @argument = argument
30
+ @index = index
31
+ @path = path
32
+ @issue = issue
33
+ @errors = errors
34
+ end
35
+
36
+ def to_s
37
+ @issue.to_s
38
+ end
39
+
40
+ def http_status
41
+ 400
42
+ end
43
+
44
+ def path_string
45
+ @path.map(&:name).join('.')
46
+ end
47
+
48
+ def hash
49
+ {
50
+ code: 'invalid_argument',
51
+ description: "The '#{path_string}' argument is invalid",
52
+ detail: {
53
+ path: @path.map(&:name),
54
+ index: @index,
55
+ issue: @issue&.to_s,
56
+ issue_description: ISSUE_DESCRIPTIONS[@issue.to_sym],
57
+ errors: @errors,
58
+ argument: {
59
+ id: argument.id,
60
+ name: argument.name,
61
+ description: argument.description
62
+ }
63
+ }
64
+ }
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class InvalidEnumOptionError < Apia::RuntimeError
7
+
8
+ attr_reader :enum
9
+ attr_reader :given_value
10
+
11
+ def initialize(enum, given_value)
12
+ @enum = enum
13
+ @given_value = given_value
14
+ end
15
+
16
+ def to_s
17
+ "Invalid option for `#{enum.class.definition.name || 'AnonymousEnum'}` (got: #{@given_value.inspect} (#{@given_value.class}))"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ class InvalidHelperError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class InvalidJSONError < Apia::RuntimeError
7
+
8
+ def http_status
9
+ 400
10
+ end
11
+
12
+ def hash
13
+ {
14
+ code: 'invalid_json_body',
15
+ description: 'The JSON body provided with this request is invalid',
16
+ detail: {
17
+ details: message
18
+ }
19
+ }
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class InvalidPolymorphValueError < Apia::RuntimeError
7
+
8
+ attr_reader :polymorph
9
+ attr_reader :given_value
10
+
11
+ def initialize(polymorph, given_value)
12
+ @polymorph = polymorph
13
+ @given_value = given_value
14
+ end
15
+
16
+ def to_s
17
+ "Invalid value for `#{polymorph.definition.id}` (got: #{@given_value.inspect})"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class InvalidScalarValueError < Apia::RuntimeError
7
+
8
+ attr_reader :scalar
9
+ attr_reader :given_value
10
+
11
+ def initialize(scalar, given_value)
12
+ @scalar = scalar
13
+ @given_value = given_value
14
+ end
15
+
16
+ def to_s
17
+ "Invalid value for `#{scalar.name}` (got: #{@given_value.inspect} (#{@given_value.class}))"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/rack'
4
+
5
+ module Apia
6
+ class ManifestError < StandardError
7
+
8
+ def initialize(errors)
9
+ @errors = errors
10
+ end
11
+
12
+ def to_s
13
+ "#{@errors.errors.size} object(s) have issues that need attention (#{errors})"
14
+ end
15
+
16
+ def errors
17
+ @errors.errors.each_with_object([]) do |(object, errors), array|
18
+ errors.each do |error|
19
+ array << "#{object.id}: #{error[:code]} (#{error[:message]})"
20
+ end
21
+ end.join(', ')
22
+ end
23
+
24
+ def detail
25
+ @errors.errors.map do |object, errors|
26
+ {
27
+ object: object.id,
28
+ errors: errors.map do |error|
29
+ {
30
+ code: error[:code],
31
+ description: error[:message]
32
+ }
33
+ end
34
+ }
35
+ end
36
+ end
37
+
38
+ def triplet
39
+ Rack.error_triplet('manifest_error', description: 'An issue exists with the API manifest that needs resolving by the developer.', detail: detail)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ # This is raised when an argument set cannot be created because an argument
7
+ # that was required is not present on the source object.
8
+ class MissingArgumentError < Apia::RuntimeError
9
+
10
+ attr_reader :argument
11
+
12
+ def initialize(argument, path: [])
13
+ @argument = argument
14
+ @path = path
15
+ end
16
+
17
+ def http_status
18
+ 400
19
+ end
20
+
21
+ def path_string
22
+ @path.map(&:name).join('.')
23
+ end
24
+
25
+ def hash
26
+ {
27
+ code: 'missing_required_argument',
28
+ description: "The '#{path_string}' argument is required but has not been provided",
29
+ detail: {
30
+ path: @path.map(&:name),
31
+ argument: {
32
+ name: argument.name,
33
+ description: argument.description
34
+ }
35
+ }
36
+ }
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ class NullFieldValueError < Apia::RuntimeError
7
+
8
+ attr_reader :field
9
+
10
+ def initialize(field, source)
11
+ @field = field
12
+ @source = source
13
+ end
14
+
15
+ def to_s
16
+ "Value for `#{field.name}` is null (but cannot be)"
17
+ end
18
+
19
+ def http_status
20
+ 500
21
+ end
22
+
23
+ def hash
24
+ {
25
+ code: 'null_value_for_non_null_field',
26
+ description: to_s,
27
+ detail: {
28
+ field: {
29
+ id: @field.id,
30
+ name: @field.name
31
+ }
32
+ }
33
+ }
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/runtime_error'
4
+
5
+ module Apia
6
+ # A parse error is raised when we are unable to parse input provided by an
7
+ # API consumer to turn it into an appropriate Scalar or Type.
8
+ class ParseError < Apia::RuntimeError
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ # Runtime errors occurr during API requests because they could not
5
+ # be detected before an action is processed.
6
+ class RuntimeError < StandardError
7
+
8
+ # Return the default HTTP status code that should be returned when this
9
+ # error is encoutered over HTTP
10
+ #
11
+ # @return [Integer]
12
+ def http_status
13
+ 400
14
+ end
15
+
16
+ # Return the hash that describes this error
17
+ #
18
+ # @return [Hash]
19
+ def hash
20
+ {
21
+ code: 'generic_runtime_error',
22
+ description: message,
23
+ detail: {
24
+ class: self.class.name
25
+ }
26
+ }
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/error'
4
+
5
+ module Apia
6
+ class ScopeNotGrantedError < Apia::Error
7
+
8
+ code :scope_not_granted
9
+ http_status 403
10
+ description 'The scope required for this endpoint has not been granted to the authenticating identity'
11
+
12
+ field :scopes, [:string]
13
+
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ class StandardError < ::StandardError
5
+ end
6
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/helpers'
4
+ require 'apia/scalar'
5
+ require 'apia/object'
6
+ require 'apia/enum'
7
+ require 'apia/field_spec'
8
+
9
+ module Apia
10
+ class FieldSet < Hash
11
+
12
+ # Add a new field to the fieldset
13
+ #
14
+ # @param field [Apia::Field]
15
+ # @return [Apia::Field]
16
+ def add(field)
17
+ self[field.name] = field
18
+ end
19
+
20
+ # Validate this field set and add errors as appropriate
21
+ #
22
+ # @param errors [Apia::ManifestErrors]
23
+ # @param object [Object]
24
+ # @return [void]
25
+ def validate(errors, object)
26
+ each_value do |field|
27
+ unless field.type.usable_for_field?
28
+ errors.add object, 'InvalidFieldType', "Type for field #{field.name} must be a scalar, enum or object"
29
+ end
30
+ end
31
+ end
32
+
33
+ # Generate a hash for the fields that are defined on this object.
34
+ # It should receive the source object as well as a request
35
+ #
36
+ # @param source [Object, Hash]
37
+ # @param request [Apia::Request]
38
+ # @param only [Array]
39
+ # @return [Hash]
40
+ def generate_hash(source, request: nil, path: [])
41
+ each_with_object({}) do |(_, field), hash|
42
+ next unless field.include?(source, request)
43
+
44
+ field_path = path + [field]
45
+ next if request&.endpoint && !request&.endpoint&.include_field?(field_path)
46
+
47
+ value = field.value(source, request: request, path: field_path)
48
+ next if value == :skip
49
+
50
+ hash[field.name.to_sym] = value
51
+ end
52
+ end
53
+
54
+ # Generate a default field spec for this field set based on the values
55
+ # provided for the include option.
56
+ #
57
+ # @return [FieldSpec]
58
+ def spec
59
+ @spec ||= begin
60
+ spec = each_with_object([]) do |(key, field), array|
61
+ next if field.include == false
62
+
63
+ if field.include.is_a?(::String)
64
+ array << "#{key}[#{field.include}]"
65
+ elsif field.type.object? || field.type.polymorph?
66
+ array << "#{key}[*]"
67
+ else
68
+ array << key
69
+ end
70
+ end.join(',')
71
+ FieldSpec.parse(spec)
72
+ end
73
+ end
74
+
75
+ end
76
+ end