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,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