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,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ class RequestHeaders
5
+
6
+ def initialize(headers)
7
+ @headers = headers
8
+ end
9
+
10
+ def fetch(key, default = nil)
11
+ @headers[self.class.make_key(key)] || default
12
+ end
13
+
14
+ def [](key)
15
+ fetch(key)
16
+ end
17
+
18
+ def []=(key, value)
19
+ @headers[self.class.make_key(key)] = value
20
+ end
21
+
22
+ class << self
23
+
24
+ def make_key(key)
25
+ key.gsub('-', '_').upcase
26
+ end
27
+
28
+ def create_from_request(request)
29
+ hash = request.each_header.each_with_object({}) do |(key, value), inner_hash|
30
+ next unless key =~ /\AHTTP_(\w+)\z/
31
+
32
+ name = Regexp.last_match[1]
33
+
34
+ inner_hash[name] = value
35
+ end
36
+ new(hash)
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'apia/rack'
5
+
6
+ module Apia
7
+ class Response
8
+
9
+ attr_accessor :status
10
+ attr_reader :fields
11
+ attr_reader :headers
12
+ attr_writer :body
13
+
14
+ def initialize(request, endpoint)
15
+ @request = request
16
+ @endpoint = endpoint
17
+
18
+ @status = @endpoint.definition.http_status_code
19
+ @fields = {}
20
+ @headers = {}
21
+ end
22
+
23
+ # Add a field value for this endpoint
24
+ #
25
+ # @param name [Symbol]
26
+ # @param value [Hash, Object, nil]
27
+ # @return [void]
28
+ def add_field(name, value)
29
+ @fields[name.to_sym] = value
30
+ end
31
+
32
+ # Add a header to the response
33
+ #
34
+ # @param name [String]
35
+ # @param value [String]
36
+ # @return [void]
37
+ def add_header(name, value)
38
+ @headers[name.to_s] = value&.to_s
39
+ end
40
+
41
+ # Return the full hash of data that should be returned for this
42
+ # request.
43
+ #
44
+ # @return [Hash]
45
+ def hash
46
+ @hash ||= @endpoint.definition.fields.generate_hash(@fields, request: @request)
47
+ end
48
+
49
+ # Return the body that should be returned for this response
50
+ #
51
+ # @return [Hash]
52
+ def body
53
+ @body || hash
54
+ end
55
+
56
+ # Return the rack triplet for this response
57
+ #
58
+ # @return [Array]
59
+ def rack_triplet
60
+ Rack.json_triplet(body, headers: @headers, status: @status)
61
+ end
62
+
63
+ end
64
+ end
data/lib/apia/route.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/route_set'
4
+
5
+ module Apia
6
+ class Route
7
+
8
+ REQUEST_METHODS = [:get, :post, :patch, :put, :delete].freeze
9
+
10
+ attr_reader :path
11
+ attr_reader :controller
12
+ attr_reader :request_method
13
+ attr_reader :group
14
+ attr_writer :endpoint
15
+
16
+ def initialize(path, **options)
17
+ @path = path
18
+
19
+ @group = options[:group]
20
+
21
+ @controller = options[:controller]
22
+ @endpoint = options[:endpoint]
23
+
24
+ @request_method = options[:request_method] || :get
25
+ end
26
+
27
+ # Return the endpoint object for this route
28
+ #
29
+ # @return [Apia::Endpoint]
30
+ def endpoint
31
+ if @endpoint.is_a?(Symbol) && controller
32
+ return controller.definition.endpoints[@endpoint]
33
+ end
34
+
35
+ @endpoint
36
+ end
37
+
38
+ # Return the parts for this route
39
+ #
40
+ # @return [Array<String>]
41
+ def path_parts
42
+ @path_parts ||= RouteSet.split_path(@path)
43
+ end
44
+
45
+ # Extract arguments from the given path and return a hash of the arguments
46
+ # based on their naming from the route
47
+ #
48
+ # @param given_path [String]
49
+ # @return [Hash]
50
+ def extract_arguments(given_path)
51
+ given_path_parts = RouteSet.split_path(given_path)
52
+ path_parts.each_with_index.each_with_object({}) do |(part, index), hash|
53
+ next unless part =~ /\A:(\w+)/
54
+
55
+ value = given_path_parts[index]
56
+ hash[Regexp.last_match[1]] = value
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ class RouteGroup
5
+
6
+ attr_reader :id
7
+ attr_reader :parent
8
+ attr_accessor :name
9
+ attr_accessor :description
10
+ attr_accessor :default_controller
11
+ attr_reader :groups
12
+
13
+ def initialize(id, parent)
14
+ @id = id
15
+ @parent = parent
16
+ @groups = []
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/dsls/route_set'
4
+
5
+ module Apia
6
+ class RouteSet
7
+
8
+ attr_reader :map
9
+ attr_reader :routes
10
+ attr_reader :controllers
11
+ attr_reader :endpoints
12
+ attr_reader :groups
13
+
14
+ def initialize
15
+ @map = {}
16
+ @routes = []
17
+ @controllers = []
18
+ @endpoints = []
19
+ @groups = []
20
+ end
21
+
22
+ def dsl
23
+ @dsl ||= DSLs::RouteSet.new(self)
24
+ end
25
+
26
+ # Add a new route to the set
27
+ #
28
+ # @param route [Moonstone::Route]
29
+ # @return [Moonstone::Route]
30
+ def add(route)
31
+ @routes << route
32
+ if route.controller && !@controllers.include?(route.controller)
33
+ @controllers << route.controller
34
+ end
35
+
36
+ if route.endpoint && !@controllers.include?(route.endpoint)
37
+ @endpoints << route.endpoint
38
+ end
39
+
40
+ parts = self.class.split_path(route.path).map { |p| p =~ /\A:/ ? '?' : p }
41
+ parts.size.times do |i|
42
+ if i.zero?
43
+ source = @map
44
+ else
45
+ source = @map.dig(*parts[0, i])
46
+ end
47
+ source[parts[i]] ||= { _routes: [] }
48
+ source[parts[i]][:_routes] << route if i == parts.size - 1
49
+ end
50
+ route
51
+ end
52
+
53
+ # Find routes that exactly match a given path
54
+ #
55
+ # @param request_method [Symbol]
56
+ # @param path [String]
57
+ # @return [Array<Moonstone::Route>]
58
+ def find(request_method, path)
59
+ parts = self.class.split_path(path)
60
+ last = @map
61
+ parts.size.times do |i|
62
+ last = last[parts[i]] || last['?']
63
+ return [] if last.nil?
64
+ end
65
+ last[:_routes].select { |r| r.request_method == request_method }
66
+ end
67
+
68
+ class << self
69
+
70
+ # Remove slashes from the start and end of a given string
71
+ #
72
+ # @param string [String]
73
+ # @return [String]
74
+ def strip_slashes(string)
75
+ string.sub(/\A\/+/, '').sub(/\/\z/, '')
76
+ end
77
+
78
+ # Split a URL part into its appropriate parts
79
+ #
80
+ # @param path [String]
81
+ # @return [Array<String>]
82
+ def split_path(path)
83
+ strip_slashes(path).split('/')
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/helpers'
4
+ require 'apia/definitions/scalar'
5
+ require 'apia/defineable'
6
+
7
+ module Apia
8
+ class Scalar
9
+
10
+ extend Defineable
11
+
12
+ # Return the definition for this type
13
+ #
14
+ # @return [Apia::Definitions::Object]
15
+ def self.definition
16
+ @definition ||= Definitions::Scalar.new(Helpers.class_name_to_id(name))
17
+ end
18
+
19
+ def self.cast(value = nil, &block)
20
+ if block_given? && value.nil?
21
+ return definition.dsl.cast(&block)
22
+ end
23
+
24
+ unless valid?(value)
25
+ # Before casting, we'll also validate...
26
+ raise InvalidScalarValueError.new(self, value)
27
+ end
28
+
29
+ value = definition.cast.call(value) if definition.cast
30
+
31
+ value
32
+ end
33
+
34
+ def self.valid?(value)
35
+ return true if definition.validator.nil?
36
+
37
+ definition.validator.call(value)
38
+ end
39
+
40
+ def self.parse(value = nil, &block)
41
+ if block_given? && value.nil?
42
+ return definition.dsl.parse(&block)
43
+ end
44
+
45
+ return value if definition.parse.nil?
46
+ return nil if value.nil?
47
+
48
+ definition.parse.call(value)
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apia
4
+ module Scalars
5
+
6
+ class << self
7
+
8
+ def fetch(item, default = nil)
9
+ all[item.to_sym] || default
10
+ end
11
+
12
+ def register(name, klass)
13
+ all[name.to_sym] = klass
14
+ end
15
+
16
+ private
17
+
18
+ def all
19
+ @all ||= {}
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/scalars'
4
+ require 'apia/scalar'
5
+ require 'base64'
6
+
7
+ module Apia
8
+ module Scalars
9
+ class Base64 < Apia::Scalar
10
+
11
+ Scalars.register :base64, self
12
+
13
+ name 'Base64-encoded string'
14
+
15
+ cast do |value|
16
+ ::Base64.encode64(value).sub(/\n\z/, '')
17
+ end
18
+
19
+ validator { true }
20
+
21
+ parse do |value|
22
+ unless value.is_a?(::String)
23
+ raise Apia::ParseError, 'Base64 value must be provided as a string'
24
+ end
25
+
26
+ ::Base64.decode64(value)
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'apia/scalars'
4
+ require 'apia/scalar'
5
+
6
+ module Apia
7
+ module Scalars
8
+ class Boolean < Apia::Scalar
9
+
10
+ Scalars.register :boolean, self
11
+
12
+ name 'Boolean'
13
+
14
+ TRUE_VALUES = [true, 'true', 'yes', 1, '1'].freeze
15
+ FALSE_VALUES = [false, 'false', 'no', 0, '0'].freeze
16
+
17
+ cast do |value|
18
+ value ? true : false
19
+ end
20
+
21
+ validator do |value|
22
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
23
+ end
24
+
25
+ parse do |value|
26
+ if TRUE_VALUES.include?(value)
27
+ true
28
+ elsif FALSE_VALUES.include?(value)
29
+ false
30
+ else
31
+ raise Apia::ParseError, 'Boolean must be provided as a boolean, as a string containing true or false or as 0 or 1 as an integer.'
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'apia/scalars'
5
+ require 'apia/scalar'
6
+ require 'apia/errors/parse_error'
7
+
8
+ module Apia
9
+ module Scalars
10
+ class Date < Apia::Scalar
11
+
12
+ Scalars.register :date, self
13
+
14
+ name 'Date'
15
+
16
+ cast do |value|
17
+ value.strftime('%Y-%m-%d')
18
+ end
19
+
20
+ validator do |value|
21
+ value.is_a?(::Date)
22
+ end
23
+
24
+ parse do |string|
25
+ next string if string.is_a?(::Date)
26
+
27
+ begin
28
+ string = string.to_s
29
+ unless string =~ /\A\d{4}-\d{2}-\d{2}\z/
30
+ raise Apia::ParseError, 'Date must be in the format of yyyy-mm-dd'
31
+ end
32
+
33
+ ::Date.parse(string)
34
+ rescue ::ArgumentError => e
35
+ if e.message =~ /invalid date/
36
+ raise Apia::ParseError, 'Invalid date was entered (make sure the day exists)'
37
+ end
38
+
39
+ raise
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end