apia 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/VERSION +1 -0
- data/lib/apia.rb +21 -0
- data/lib/apia/api.rb +100 -0
- data/lib/apia/argument_set.rb +221 -0
- data/lib/apia/authenticator.rb +57 -0
- data/lib/apia/callable_with_environment.rb +43 -0
- data/lib/apia/controller.rb +32 -0
- data/lib/apia/defineable.rb +60 -0
- data/lib/apia/definition.rb +27 -0
- data/lib/apia/definitions/api.rb +51 -0
- data/lib/apia/definitions/argument.rb +77 -0
- data/lib/apia/definitions/argument_set.rb +33 -0
- data/lib/apia/definitions/authenticator.rb +46 -0
- data/lib/apia/definitions/controller.rb +41 -0
- data/lib/apia/definitions/endpoint.rb +74 -0
- data/lib/apia/definitions/enum.rb +31 -0
- data/lib/apia/definitions/error.rb +59 -0
- data/lib/apia/definitions/field.rb +117 -0
- data/lib/apia/definitions/lookup_argument_set.rb +27 -0
- data/lib/apia/definitions/object.rb +29 -0
- data/lib/apia/definitions/polymorph.rb +29 -0
- data/lib/apia/definitions/polymorph_option.rb +53 -0
- data/lib/apia/definitions/scalar.rb +23 -0
- data/lib/apia/definitions/type.rb +109 -0
- data/lib/apia/dsl.rb +23 -0
- data/lib/apia/dsls/api.rb +37 -0
- data/lib/apia/dsls/argument.rb +27 -0
- data/lib/apia/dsls/argument_set.rb +35 -0
- data/lib/apia/dsls/authenticator.rb +38 -0
- data/lib/apia/dsls/concerns/has_fields.rb +38 -0
- data/lib/apia/dsls/controller.rb +34 -0
- data/lib/apia/dsls/endpoint.rb +79 -0
- data/lib/apia/dsls/enum.rb +19 -0
- data/lib/apia/dsls/error.rb +26 -0
- data/lib/apia/dsls/field.rb +27 -0
- data/lib/apia/dsls/lookup_argument_set.rb +24 -0
- data/lib/apia/dsls/object.rb +19 -0
- data/lib/apia/dsls/polymorph.rb +19 -0
- data/lib/apia/dsls/route_group.rb +43 -0
- data/lib/apia/dsls/route_set.rb +40 -0
- data/lib/apia/dsls/scalar.rb +23 -0
- data/lib/apia/dsls/scope_descriptions.rb +17 -0
- data/lib/apia/endpoint.rb +110 -0
- data/lib/apia/enum.rb +43 -0
- data/lib/apia/environment_error_handling.rb +74 -0
- data/lib/apia/error.rb +61 -0
- data/lib/apia/error_set.rb +15 -0
- data/lib/apia/errors/error_exception_error.rb +32 -0
- data/lib/apia/errors/field_spec_parse_error.rb +23 -0
- data/lib/apia/errors/invalid_argument_error.rb +68 -0
- data/lib/apia/errors/invalid_enum_option_error.rb +21 -0
- data/lib/apia/errors/invalid_helper_error.rb +6 -0
- data/lib/apia/errors/invalid_json_error.rb +23 -0
- data/lib/apia/errors/invalid_polymorph_value_error.rb +21 -0
- data/lib/apia/errors/invalid_scalar_value_error.rb +21 -0
- data/lib/apia/errors/manifest_error.rb +43 -0
- data/lib/apia/errors/missing_argument_error.rb +40 -0
- data/lib/apia/errors/null_field_value_error.rb +37 -0
- data/lib/apia/errors/parse_error.rb +10 -0
- data/lib/apia/errors/runtime_error.rb +30 -0
- data/lib/apia/errors/scope_not_granted_error.rb +15 -0
- data/lib/apia/errors/standard_error.rb +6 -0
- data/lib/apia/field_set.rb +76 -0
- data/lib/apia/field_spec.rb +155 -0
- data/lib/apia/helpers.rb +34 -0
- data/lib/apia/hook_set.rb +30 -0
- data/lib/apia/lookup_argument_set.rb +57 -0
- data/lib/apia/lookup_environment.rb +27 -0
- data/lib/apia/manifest_errors.rb +62 -0
- data/lib/apia/mock_request.rb +18 -0
- data/lib/apia/object.rb +68 -0
- data/lib/apia/object_set.rb +21 -0
- data/lib/apia/pagination_object.rb +34 -0
- data/lib/apia/polymorph.rb +50 -0
- data/lib/apia/rack.rb +184 -0
- data/lib/apia/rack_error.rb +17 -0
- data/lib/apia/request.rb +67 -0
- data/lib/apia/request_environment.rb +84 -0
- data/lib/apia/request_headers.rb +42 -0
- data/lib/apia/response.rb +64 -0
- data/lib/apia/route.rb +61 -0
- data/lib/apia/route_group.rb +20 -0
- data/lib/apia/route_set.rb +89 -0
- data/lib/apia/scalar.rb +52 -0
- data/lib/apia/scalars.rb +25 -0
- data/lib/apia/scalars/base64.rb +31 -0
- data/lib/apia/scalars/boolean.rb +37 -0
- data/lib/apia/scalars/date.rb +45 -0
- data/lib/apia/scalars/decimal.rb +36 -0
- data/lib/apia/scalars/integer.rb +34 -0
- data/lib/apia/scalars/string.rb +24 -0
- data/lib/apia/scalars/unix_time.rb +40 -0
- data/lib/apia/schema/api_controller_schema_type.rb +17 -0
- data/lib/apia/schema/api_schema_type.rb +43 -0
- data/lib/apia/schema/argument_schema_type.rb +28 -0
- data/lib/apia/schema/argument_set_schema_type.rb +21 -0
- data/lib/apia/schema/authenticator_schema_type.rb +22 -0
- data/lib/apia/schema/controller.rb +39 -0
- data/lib/apia/schema/controller_endpoint_schema_type.rb +17 -0
- data/lib/apia/schema/controller_schema_type.rb +32 -0
- data/lib/apia/schema/endpoint_schema_type.rb +35 -0
- data/lib/apia/schema/enum_schema_type.rb +20 -0
- data/lib/apia/schema/enum_value_schema_type.rb +14 -0
- data/lib/apia/schema/error_schema_type.rb +23 -0
- data/lib/apia/schema/field_schema_type.rb +38 -0
- data/lib/apia/schema/field_spec_options_schema_type.rb +16 -0
- data/lib/apia/schema/lookup_argument_set_schema_type.rb +25 -0
- data/lib/apia/schema/object_schema_polymorph.rb +31 -0
- data/lib/apia/schema/object_schema_type.rb +21 -0
- data/lib/apia/schema/polymorph_option_schema_type.rb +16 -0
- data/lib/apia/schema/polymorph_schema_type.rb +20 -0
- data/lib/apia/schema/request_method_enum.rb +21 -0
- data/lib/apia/schema/route_group_schema_type.rb +19 -0
- data/lib/apia/schema/route_schema_type.rb +31 -0
- data/lib/apia/schema/route_set_schema_type.rb +20 -0
- data/lib/apia/schema/scalar_schema_type.rb +15 -0
- data/lib/apia/schema/scope_type.rb +14 -0
- data/lib/apia/version.rb +12 -0
- metadata +188 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Apia
|
6
|
+
class ObjectSet < ::Set
|
7
|
+
|
8
|
+
def add_object(object)
|
9
|
+
return self if include?(object)
|
10
|
+
|
11
|
+
self << object
|
12
|
+
if object.respond_to?(:collate_objects)
|
13
|
+
# Attempt to add any other objects if the object responds to
|
14
|
+
# collate_objects.
|
15
|
+
object.collate_objects(self)
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/object'
|
4
|
+
require 'apia/scalars/integer'
|
5
|
+
require 'apia/scalars/boolean'
|
6
|
+
|
7
|
+
module Apia
|
8
|
+
class PaginationObject < Apia::Object
|
9
|
+
|
10
|
+
name 'Pagination Details'
|
11
|
+
description 'Provides information about how data has been paginated'
|
12
|
+
|
13
|
+
field :current_page, type: Scalars::Integer do
|
14
|
+
description 'The current page'
|
15
|
+
end
|
16
|
+
|
17
|
+
field :total_pages, type: Scalars::Integer, null: true do
|
18
|
+
description 'The total number of pages'
|
19
|
+
end
|
20
|
+
|
21
|
+
field :total, type: Scalars::Integer, null: true do
|
22
|
+
description 'The total number of items across all pages'
|
23
|
+
end
|
24
|
+
|
25
|
+
field :per_page, type: Scalars::Integer do
|
26
|
+
description 'The number of items per page'
|
27
|
+
end
|
28
|
+
|
29
|
+
field :large_set, type: Scalars::Boolean do
|
30
|
+
description 'Is this a large set and therefore the total number of records cannot be returned?'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/helpers'
|
4
|
+
require 'apia/defineable'
|
5
|
+
require 'apia/definitions/polymorph'
|
6
|
+
require 'apia/errors/invalid_polymorph_value_error'
|
7
|
+
|
8
|
+
module Apia
|
9
|
+
class Polymorph
|
10
|
+
|
11
|
+
extend Defineable
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Return the definition for this polymorph
|
16
|
+
#
|
17
|
+
# @return [Apia::Definitions::Polymorph]
|
18
|
+
def definition
|
19
|
+
@definition ||= Definitions::Polymorph.new(Helpers.class_name_to_id(name))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Collate all objects that this polymorph references and add them to the
|
23
|
+
# given object set
|
24
|
+
#
|
25
|
+
# @param set [Apia::ObjectSet]
|
26
|
+
# @return [void]
|
27
|
+
def collate_objects(set)
|
28
|
+
definition.options.each_value do |opt|
|
29
|
+
set.add_object(opt.type.klass) if opt.type.usable_for_field?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the type which should be returned for the given value by running
|
34
|
+
# through each of the matchers to find the appropriate type.
|
35
|
+
def option_for_value(value)
|
36
|
+
option = definition.options.values.find do |opt|
|
37
|
+
opt.matches?(value)
|
38
|
+
end
|
39
|
+
|
40
|
+
if option.nil?
|
41
|
+
raise InvalidPolymorphValueError.new(self, value)
|
42
|
+
end
|
43
|
+
|
44
|
+
option
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/apia/rack.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'apia/rack_error'
|
5
|
+
require 'apia/request'
|
6
|
+
require 'apia/response'
|
7
|
+
|
8
|
+
module Apia
|
9
|
+
class Rack
|
10
|
+
|
11
|
+
def initialize(app, api, namespace, **options)
|
12
|
+
@app = app
|
13
|
+
@api = api
|
14
|
+
@namespace = '/' + namespace.sub(/\A\/+/, '').sub(/\/+\z/, '')
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
# Is this supposed to be running in development? This will validate the whole
|
19
|
+
# API on each request as well as being more verbose about internal server
|
20
|
+
# errors that are encountered.
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
def development?
|
24
|
+
env_is_dev = ENV['RACK_ENV'] == 'development'
|
25
|
+
return true if env_is_dev && @options[:development].nil?
|
26
|
+
|
27
|
+
@options[:development] == true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse a given full path and return nil if it doesn't match our
|
31
|
+
# namespace or return a hash with the controller and endpoint
|
32
|
+
# named as available.
|
33
|
+
#
|
34
|
+
# @param path [String] /core/v1/controller/endpoint
|
35
|
+
# @return [nil, Hash]
|
36
|
+
def find_route(method, path)
|
37
|
+
return if api.nil?
|
38
|
+
|
39
|
+
api.definition.route_set.find(method.to_s.downcase.to_sym, path).first
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the API object
|
43
|
+
#
|
44
|
+
# @return [Apia::API]
|
45
|
+
def api
|
46
|
+
return Object.const_get(@api) if @api.is_a?(String) && development?
|
47
|
+
return @cached_api ||= Object.const_get(@api) if @api.is_a?(String)
|
48
|
+
|
49
|
+
@api
|
50
|
+
end
|
51
|
+
|
52
|
+
# Actually make the request
|
53
|
+
#
|
54
|
+
# @param env [Hash]
|
55
|
+
# @return [Array] a rack triplet
|
56
|
+
def call(env)
|
57
|
+
if @options[:hosts]&.none? { |host| host == env['HTTP_HOST'] }
|
58
|
+
return @app.call(env)
|
59
|
+
end
|
60
|
+
|
61
|
+
unless env['PATH_INFO'] =~ /\A#{Regexp.escape(@namespace)}\/([a-z].*)\z/i
|
62
|
+
return @app.call(env)
|
63
|
+
end
|
64
|
+
|
65
|
+
api_path = Regexp.last_match(1)
|
66
|
+
|
67
|
+
triplet = handle_request(env, api_path)
|
68
|
+
add_cors_headers(env, triplet)
|
69
|
+
triplet
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def handle_request(env, api_path)
|
75
|
+
if env['REQUEST_METHOD'].upcase == 'OPTIONS'
|
76
|
+
return [204, {}, ['']]
|
77
|
+
end
|
78
|
+
|
79
|
+
validate_api if development?
|
80
|
+
|
81
|
+
route = find_route(env['REQUEST_METHOD'], api_path)
|
82
|
+
if route.nil?
|
83
|
+
raise RackError.new(404, 'route_not_found', "No route matches '#{api_path}' for #{env['REQUEST_METHOD']}")
|
84
|
+
end
|
85
|
+
|
86
|
+
request = Apia::Request.new(env)
|
87
|
+
request.api_path = api_path
|
88
|
+
request.namespace = @namespace
|
89
|
+
request.api = api
|
90
|
+
request.controller = route.controller
|
91
|
+
request.endpoint = route.endpoint
|
92
|
+
request.route = route
|
93
|
+
|
94
|
+
response = request.endpoint.execute(request)
|
95
|
+
response.rack_triplet
|
96
|
+
rescue ::StandardError => e
|
97
|
+
if e.is_a?(RackError) || e.is_a?(Apia::ManifestError)
|
98
|
+
return e.triplet
|
99
|
+
end
|
100
|
+
|
101
|
+
api.definition.exception_handlers.call(e, {
|
102
|
+
env: env,
|
103
|
+
api: api,
|
104
|
+
request: defined?(request) ? request : nil
|
105
|
+
})
|
106
|
+
|
107
|
+
if development?
|
108
|
+
return triplet_for_exception(e)
|
109
|
+
end
|
110
|
+
|
111
|
+
self.class.error_triplet('unhandled_exception', status: 500)
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_api
|
115
|
+
api.validate_all.raise_if_needed
|
116
|
+
end
|
117
|
+
|
118
|
+
def triplet_for_exception(exception)
|
119
|
+
self.class.error_triplet(
|
120
|
+
'unhandled_exception',
|
121
|
+
description: 'This is an exception that has occurred and not been handled.',
|
122
|
+
detail: {
|
123
|
+
class: exception.class.name,
|
124
|
+
message: exception.message,
|
125
|
+
backtrace: exception.backtrace
|
126
|
+
},
|
127
|
+
status: 500
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add cross origin headers to the response triplet
|
132
|
+
#
|
133
|
+
# @param env [Hash]
|
134
|
+
# @param triplet [Array]
|
135
|
+
# @return [void]
|
136
|
+
def add_cors_headers(env, triplet)
|
137
|
+
triplet[1]['Access-Control-Allow-Origin'] = '*'
|
138
|
+
triplet[1]['Access-Control-Allow-Methods'] = '*'
|
139
|
+
if env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
|
140
|
+
triplet[1]['Access-Control-Allow-Headers'] = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
|
141
|
+
end
|
142
|
+
|
143
|
+
true
|
144
|
+
end
|
145
|
+
|
146
|
+
class << self
|
147
|
+
|
148
|
+
# Return a JSON-ready triplet for the given body.
|
149
|
+
#
|
150
|
+
# @param body [Hash, Array]
|
151
|
+
# @param status [Integer]
|
152
|
+
# @param headers [Hash]
|
153
|
+
# @return [Array]
|
154
|
+
def json_triplet(body, status: 200, headers: {})
|
155
|
+
body_as_json = body.to_json
|
156
|
+
[
|
157
|
+
status,
|
158
|
+
headers.merge('content-type' => 'application/json', 'content-length' => body_as_json.bytesize.to_s),
|
159
|
+
[body_as_json]
|
160
|
+
]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Return a triplet for a given error using the standard error schema
|
164
|
+
#
|
165
|
+
# @param code [String]
|
166
|
+
# @param description [String]
|
167
|
+
# @param detail [Hash]
|
168
|
+
# @param status [Integer]
|
169
|
+
# @param headers [Hash]
|
170
|
+
# @return [Array]
|
171
|
+
def error_triplet(code, description: nil, detail: {}, status: 500, headers: {})
|
172
|
+
json_triplet({
|
173
|
+
error: {
|
174
|
+
code: code,
|
175
|
+
description: description,
|
176
|
+
detail: detail
|
177
|
+
}
|
178
|
+
}, status: status, headers: headers.merge('x-api-schema' => 'json-error'))
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apia
|
4
|
+
class RackError < StandardError
|
5
|
+
|
6
|
+
def initialize(http_status, code, message)
|
7
|
+
@http_status = http_status
|
8
|
+
@code = code
|
9
|
+
@message = message
|
10
|
+
end
|
11
|
+
|
12
|
+
def triplet
|
13
|
+
Rack.error_triplet(@code, description: @message, status: @http_status)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/apia/request.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/request'
|
4
|
+
require 'apia/request_headers'
|
5
|
+
require 'apia/errors/invalid_json_error'
|
6
|
+
|
7
|
+
module Apia
|
8
|
+
class Request < Rack::Request
|
9
|
+
|
10
|
+
attr_accessor :api
|
11
|
+
attr_accessor :controller
|
12
|
+
attr_accessor :endpoint
|
13
|
+
attr_accessor :identity
|
14
|
+
attr_writer :arguments
|
15
|
+
attr_accessor :authenticator
|
16
|
+
attr_accessor :namespace
|
17
|
+
attr_accessor :route
|
18
|
+
attr_accessor :api_path
|
19
|
+
|
20
|
+
def self.empty(options: {})
|
21
|
+
new(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def arguments
|
25
|
+
@arguments ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def headers
|
29
|
+
@headers ||= RequestHeaders.create_from_request(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def json_body
|
33
|
+
return @json_body if instance_variable_defined?('@json_body')
|
34
|
+
|
35
|
+
@json_body = get_json_body_from_body || get_json_body_from_params
|
36
|
+
end
|
37
|
+
|
38
|
+
def body?
|
39
|
+
has_header?('rack.input')
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def parse_json_from_string(body)
|
45
|
+
return {} if body.empty?
|
46
|
+
|
47
|
+
JSON.parse(body)
|
48
|
+
rescue JSON::ParserError => e
|
49
|
+
raise InvalidJSONError, e.message
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_json_body_from_body
|
53
|
+
return unless content_type =~ /\Aapplication\/json/
|
54
|
+
return unless body?
|
55
|
+
|
56
|
+
parse_json_from_string(body.read)
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_json_body_from_params
|
60
|
+
return unless body?
|
61
|
+
return unless params['_arguments'].is_a?(String)
|
62
|
+
|
63
|
+
parse_json_from_string(params['_arguments'])
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'apia/environment_error_handling'
|
4
|
+
require 'apia/errors/invalid_helper_error'
|
5
|
+
|
6
|
+
module Apia
|
7
|
+
class RequestEnvironment
|
8
|
+
|
9
|
+
attr_reader :request
|
10
|
+
attr_reader :response
|
11
|
+
|
12
|
+
include EnvironmentErrorHandling
|
13
|
+
|
14
|
+
def initialize(request, response)
|
15
|
+
@request = request
|
16
|
+
@response = response
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(*args, &block)
|
20
|
+
return unless block_given?
|
21
|
+
|
22
|
+
instance_exec(@request, @response, *args, &block)
|
23
|
+
rescue ::StandardError => e
|
24
|
+
raise_exception(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Call a helper
|
28
|
+
#
|
29
|
+
# @param name [Symbol]
|
30
|
+
# @return [Object, nil]
|
31
|
+
def helper(name, *args)
|
32
|
+
helper = @request.controller.definition.helpers[name.to_sym]
|
33
|
+
if helper.nil?
|
34
|
+
raise InvalidHelperError, "No helper found with name #{name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
instance_exec(*args, &helper)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set appropriate pagination for the given set based on the configuration
|
41
|
+
# specified for the endpoint
|
42
|
+
#
|
43
|
+
# @param set [#limit, #count, #page, #per, #to_a, #total_pages, #current_page, #without_count]
|
44
|
+
# @param large_set [Boolean] whether or not this is expected to be a large set
|
45
|
+
# @return [void]
|
46
|
+
def paginate(set, potentially_large_set: false)
|
47
|
+
paginated_field = @request.endpoint.definition.paginated_field
|
48
|
+
if paginated_field.nil?
|
49
|
+
raise Apia::RuntimeError, 'Could not paginate response because no pagination has been configured for the endpoint'
|
50
|
+
end
|
51
|
+
|
52
|
+
paginated = set.page(@request.arguments[:page] || 1)
|
53
|
+
paginated = paginated.per(@request.arguments[:per_page] || 30)
|
54
|
+
|
55
|
+
large_set = false
|
56
|
+
if potentially_large_set
|
57
|
+
total_count = set.limit(1001).count
|
58
|
+
if total_count > 1000
|
59
|
+
large_set = true
|
60
|
+
paginated = paginated.without_count
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@response.add_field paginated_field, paginated.to_a
|
65
|
+
|
66
|
+
pagination_info = {}
|
67
|
+
pagination_info[:current_page] = paginated.current_page
|
68
|
+
pagination_info[:per_page] = paginated.limit_value
|
69
|
+
pagination_info[:large_set] = large_set
|
70
|
+
unless large_set
|
71
|
+
pagination_info[:total] = paginated.total_count
|
72
|
+
pagination_info[:total_pages] = paginated.total_pages
|
73
|
+
end
|
74
|
+
@response.add_field :pagination, pagination_info
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def potential_error_sources
|
80
|
+
[@request.endpoint, @request.authenticator].compact
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|