apia 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/field_spec_parse_error'
4
+
5
+ module Apia
6
+ class FieldSpec
7
+
8
+ attr_reader :paths
9
+ attr_reader :excludes
10
+ attr_reader :parsed_string
11
+
12
+ def initialize(paths, excludes: [], parsed_string: nil)
13
+ @paths = paths
14
+ @excludes = excludes
15
+ @parsed_string = parsed_string
16
+ end
17
+
18
+ def include_field?(field_path)
19
+ if field_path.is_a?(String)
20
+ path = field_path.split('.')
21
+ else
22
+ path = field_path.map { |r| r.name.to_s }
23
+ end
24
+
25
+ # If the field path matches exactly any item in the list of paths
26
+ # allowed, then allow this path.
27
+ return true if @paths.include?(path.join('.'))
28
+
29
+ # If the field is purposely excluded, we'll check that and ensure that it
30
+ # isn't included.
31
+ return false if @excludes.include?(path.join('.'))
32
+
33
+ # If there's a wildcard at the root we can allow it at this point
34
+ # return true if @paths.include?('*')
35
+
36
+ # Check to see whether we're allowing a wildcard to be permitted at any
37
+ # point in the chain
38
+ path.size.times do |i|
39
+ parts = path[0, path.size - i - 1]
40
+
41
+ next unless @paths.include?((parts + ['*']).join('.'))
42
+
43
+ next_parts = path[0, path.size - i]
44
+ unless @paths.include?(next_parts.join('.'))
45
+ return true
46
+ end
47
+ end
48
+
49
+ false
50
+ end
51
+
52
+ class Parser
53
+
54
+ def initialize(string)
55
+ @string = string
56
+ @paths = Set.new
57
+ @excludes = Set.new
58
+ @type = :normal
59
+ @last_word = ''
60
+ @sections = []
61
+ end
62
+
63
+ def parse
64
+ @string.each_char do |character|
65
+ case character
66
+ when ','
67
+ next if @last_word.empty?
68
+
69
+ add_last_word
70
+
71
+ when '['
72
+ if @last_word.empty?
73
+ raise FieldSpecParseError, '[ requires a word before it'
74
+ end
75
+
76
+ @sections << @last_word
77
+ @paths << @sections.join('.')
78
+ @last_word = ''
79
+
80
+ when ']'
81
+ if @sections.last.nil?
82
+ raise FieldSpecParseError, 'unopened bracket closure'
83
+ end
84
+
85
+ add_last_word unless @last_word.empty?
86
+
87
+ @sections.pop
88
+
89
+ when /\s+/
90
+ # Ignore whitespace
91
+
92
+ when '-'
93
+ if @last_word.empty?
94
+ @type = :exclude
95
+ else
96
+ add_last_word
97
+ end
98
+
99
+ when 'a'..'z', '0'..'9', '_', '*'
100
+ @last_word += character
101
+
102
+ else
103
+ raise FieldSpecParseError, "invalid character #{character}"
104
+ end
105
+ end
106
+
107
+ unless @sections.empty?
108
+ raise FieldSpecParseError, 'unbalanced brackets'
109
+ end
110
+
111
+ add_last_word
112
+
113
+ FieldSpec.new(@paths, excludes: @excludes, parsed_string: @string)
114
+ end
115
+
116
+ private
117
+
118
+ def add_last_word
119
+ return if @last_word.empty?
120
+
121
+ case @type
122
+ when :exclude
123
+ destination = @excludes
124
+ else
125
+ destination = @paths
126
+ end
127
+
128
+ if @sections.empty?
129
+ destination << @last_word
130
+ else
131
+ destination << "#{@sections.join('.')}.#{@last_word}"
132
+ end
133
+
134
+ @last_word = ''
135
+ @type = :normal
136
+ end
137
+
138
+ end
139
+
140
+ class << self
141
+
142
+ # data_center # => Return all default attributes for data center
143
+ # data_center[name] # => Only return name for data center
144
+ # data_center[+country[id,name]] # => Add the country to the default parameters with it only containing id and name
145
+ # data_center[-country] # => Remove country from the default parameters (assuming it is part of them)
146
+ # data_center[name,+country] # => Pointless but should return name plus the default country params (same as name,country)
147
+ def parse(string)
148
+ parser = Parser.new(string)
149
+ parser.parse
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/scalars'
4
+
5
+ module Apia
6
+ module Helpers
7
+
8
+ class << self
9
+
10
+ # Convert a ruby class name into an ID for use by objects
11
+ #
12
+ # @param name [String]
13
+ # @return [String]
14
+ def class_name_to_id(name)
15
+ name.to_s.gsub('::', '/')
16
+ end
17
+
18
+ # Convert a string into CamelCase
19
+ #
20
+ # @param string [String, nil]
21
+ # @return [String, nil]
22
+ def camelize(string)
23
+ return nil if string.nil?
24
+
25
+ string = string.to_s.sub(/^[a-z\d]*/) { |match| match.capitalize }
26
+ string.gsub(/(?:_)([a-z\d]*)/) do
27
+ Regexp.last_match(1).capitalize.to_s
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ class HookSet
5
+
6
+ def initialize
7
+ @hooks = []
8
+ end
9
+
10
+ def add(block_by_var = nil, &block)
11
+ @hooks << block_by_var if block_by_var.is_a?(Proc)
12
+ @hooks << block if block_given?
13
+ end
14
+
15
+ def call(*args)
16
+ @hooks.map do |hook|
17
+ hook.call(*args)
18
+ end
19
+ end
20
+
21
+ def include?(proc)
22
+ @hooks.include?(proc)
23
+ end
24
+
25
+ def size
26
+ @hooks.size
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/argument_set'
4
+ require 'apia/definitions/lookup_argument_set'
5
+ require 'apia/lookup_environment'
6
+
7
+ module Apia
8
+ class LookupArgumentSet < ArgumentSet
9
+
10
+ class << self
11
+
12
+ # Return the definition for this argument set
13
+ #
14
+ # @return [Apia::Definitions::ArgumentSet]
15
+ def definition
16
+ @definition ||= Definitions::LookupArgumentSet.new(Helpers.class_name_to_id(name))
17
+ end
18
+
19
+ # Finds all objects referenced by this argument set and add them
20
+ # to the provided set.
21
+ #
22
+ # @param set [Apia::ObjectSet]
23
+ # @return [void]
24
+ def collate_objects(set)
25
+ super
26
+
27
+ definition.potential_errors.each do |error|
28
+ set.add_object(error)
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ def resolve(*args)
35
+ return if self.class.definition.resolver.nil?
36
+
37
+ environment.call(@request, *args, &self.class.definition.resolver)
38
+ end
39
+
40
+ def environment
41
+ @environment ||= LookupEnvironment.new(self)
42
+ end
43
+
44
+ def validate(argument, index: nil)
45
+ if @source.empty?
46
+ raise InvalidArgumentError.new(argument, issue: :missing_lookup_value, index: index, path: @path)
47
+ end
48
+
49
+ if @source.values.compact.size > 1
50
+ raise InvalidArgumentError.new(argument, issue: :ambiguous_lookup_values, index: index, path: @path)
51
+ end
52
+
53
+ true
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/environment_error_handling'
4
+
5
+ module Apia
6
+ class LookupEnvironment
7
+
8
+ include EnvironmentErrorHandling
9
+
10
+ def initialize(set)
11
+ @set = set
12
+ end
13
+
14
+ def call(request, *args, &block)
15
+ return unless block_given?
16
+
17
+ instance_exec(@set, request, *args, &block)
18
+ end
19
+
20
+ private
21
+
22
+ def potential_error_sources
23
+ [@set.class]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/errors/manifest_error'
4
+
5
+ module Apia
6
+ class ManifestErrors
7
+
8
+ attr_reader :errors
9
+
10
+ def initialize
11
+ @errors = {}
12
+ end
13
+
14
+ def add(object, code, message)
15
+ @errors[object] ||= Errors.new
16
+ @errors[object].add(code: code, message: message)
17
+ end
18
+
19
+ def for(object)
20
+ @errors[object] || Errors.new
21
+ end
22
+
23
+ def empty?
24
+ @errors.empty?
25
+ end
26
+
27
+ def raise_if_needed
28
+ return if empty?
29
+
30
+ raise ManifestError, self
31
+ end
32
+
33
+ class Errors
34
+
35
+ def initialize
36
+ @errors = []
37
+ end
38
+
39
+ def add(code:, message:)
40
+ @errors << { code: code, message: message }
41
+ end
42
+
43
+ def include?(code)
44
+ @errors.any? { |e| e[:code] == code }
45
+ end
46
+
47
+ def empty?
48
+ @errors.empty?
49
+ end
50
+
51
+ def each(&block)
52
+ @errors.each(&block)
53
+ end
54
+
55
+ def map(&block)
56
+ @errors.map(&block)
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/request'
4
+
5
+ module Apia
6
+ class MockRequest < Request
7
+
8
+ def json_body
9
+ @json_body ||= {}
10
+ end
11
+
12
+ def ip
13
+ @ip ||= '127.0.0.1'
14
+ end
15
+ attr_writer :ip
16
+
17
+ end
18
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/helpers'
4
+ require 'apia/definitions/object'
5
+ require 'apia/defineable'
6
+
7
+ module Apia
8
+ class Object
9
+
10
+ extend Defineable
11
+
12
+ class << self
13
+
14
+ # Return the definition for this type
15
+ #
16
+ # @return [Apia::Definitions::Object]
17
+ def definition
18
+ @definition ||= Definitions::Object.new(Helpers.class_name_to_id(name))
19
+ end
20
+
21
+ # Collate all objects that this type references and add them to the
22
+ # given object set
23
+ #
24
+ # @param set [Apia::ObjectSet]
25
+ # @return [void]
26
+ def collate_objects(set)
27
+ definition.fields.each_value do |field|
28
+ set.add_object(field.type.klass) if field.type.usable_for_field?
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ # Initialize an instance of this type with the value provided
35
+ #
36
+ # @param value [Object, Hash]
37
+ # @return [Apia::Object]
38
+ def initialize(value)
39
+ @value = value
40
+ end
41
+
42
+ # Return the raw value object for this type
43
+ #
44
+ # @return [Object, Hash]
45
+ attr_reader :value
46
+
47
+ # Generate a hash based on the fields defined in this type
48
+ #
49
+ # @param request [Apia::Request] the associated request
50
+ # @return [Hash]
51
+ def hash(request: nil, path: [])
52
+ self.class.definition.fields.generate_hash(@value, request: request, path: path)
53
+ end
54
+
55
+ # Should this type be included in any output?
56
+ #
57
+ # @param request [Apia::Request]
58
+ # @return [Boolean]
59
+ def include?(request)
60
+ return true if self.class.definition.conditions.empty?
61
+
62
+ self.class.definition.conditions.all? do |cond|
63
+ cond.call(@value, request) == true
64
+ end
65
+ end
66
+
67
+ end
68
+ end