haveapi 0.30.0 → 0.30.2
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +1 -0
- data/benchmarks/documentation.rb +277 -0
- data/benchmarks/endpoints.rb +730 -0
- data/benchmarks/performance_budgets.json +1354 -0
- data/benchmarks/performance_compare.rb +579 -0
- data/doc/client-examples.md +233 -24
- data/doc/performance.md +272 -0
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/documentation_clients.rb +158 -35
- data/lib/haveapi/documentation_context.rb +42 -12
- data/lib/haveapi/documentation_schema.rb +73 -0
- data/lib/haveapi/server.rb +5 -0
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/version_page.erb +2 -2
- data/spec/client_examples/context_performance_spec.rb +72 -0
- data/spec/client_examples/context_spec.rb +206 -0
- data/spec/client_examples/dynamic_js_spec.rb +1 -0
- data/spec/client_examples/profile_mount_spec.rb +215 -0
- data/spec/client_examples/profiles_spec.rb +202 -32
- data/spec/client_examples/structural_schema_spec.rb +48 -0
- data/spec/documentation/client_examples_spec.rb +10 -0
- data/spec/documentation/performance_spec.rb +79 -0
- data/spec/performance/comparator_spec.rb +342 -0
- data/spec/performance/endpoint_contract_spec.rb +226 -0
- data/spec/performance/endpoint_scaling_spec.rb +82 -0
- data/spec/performance/event_baseline_spec.rb +279 -0
- data/spec/performance/json_activation_spec.rb +151 -0
- data/spec/performance/release_lineage_spec.rb +431 -0
- data/spec/performance/runner_spec.rb +476 -0
- data/spec/performance/sqlite_spec.rb +194 -0
- data/spec/performance/support/live_worker.rb +44 -0
- data/spec/performance/support/samples.rb +72 -0
- data/test_support/documentation_performance_fixture.rb +106 -0
- data/test_support/performance_endpoint_fixture.rb +350 -0
- data/test_support/performance_sqlite_fixture.rb +384 -0
- metadata +26 -3
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
require 'haveapi'
|
|
2
|
+
require 'active_record'
|
|
3
|
+
require 'sqlite3'
|
|
4
|
+
require 'haveapi/model_adapters/active_record'
|
|
5
|
+
require 'rack/mock'
|
|
6
|
+
require 'base64'
|
|
7
|
+
require 'digest'
|
|
8
|
+
require 'json'
|
|
9
|
+
require_relative '../benchmarks/performance_compare'
|
|
10
|
+
|
|
11
|
+
module HaveAPI
|
|
12
|
+
module PerformanceFixtures
|
|
13
|
+
# Neutral Rack scenarios; database setup and validation stay outside timing.
|
|
14
|
+
module SQLite
|
|
15
|
+
User = Struct.new(:login)
|
|
16
|
+
|
|
17
|
+
class BasicProvider < HaveAPI::Authentication::Basic::Provider
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def find_user(_request, username, password)
|
|
21
|
+
User.new(username) if %w[admin member].include?(username) && password == 'fixture-only'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.cases(profile: 'quick')
|
|
26
|
+
case profile
|
|
27
|
+
when 'quick'
|
|
28
|
+
[{ id: 'sqlite.page', dimensions: { rows: 128, page_size: 32 } },
|
|
29
|
+
{ id: 'sqlite.includes', dimensions: { rows: 32, include_depth: 2 } }]
|
|
30
|
+
when 'weekly'
|
|
31
|
+
cases + [{ id: 'sqlite.page', dimensions: { rows: 10_000, page_size: 32 } }] +
|
|
32
|
+
[32, 128, 1024].product(%w[admin member]).map do |rows, user|
|
|
33
|
+
{ id: 'sqlite.includes', dimensions: { rows:, include_depth: 2, user: } }
|
|
34
|
+
end
|
|
35
|
+
else raise ArgumentError, 'Unknown SQLite performance profile'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.build(case_spec)
|
|
40
|
+
Scenario.new(case_spec)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.database_namespace
|
|
44
|
+
@database_sequence = (@database_sequence || 0) + 1
|
|
45
|
+
const_set("Database#{@database_sequence}", Module.new)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Scenario
|
|
49
|
+
attr_reader :case_spec
|
|
50
|
+
|
|
51
|
+
def initialize(case_spec)
|
|
52
|
+
@case_spec = case_spec.transform_keys(&:to_sym)
|
|
53
|
+
@dimensions = @case_spec.fetch(:dimensions).transform_keys(&:to_sym)
|
|
54
|
+
@rows = @dimensions.fetch(:rows)
|
|
55
|
+
@user = @dimensions.fetch(:user, 'admin')
|
|
56
|
+
@depth = @dimensions.fetch(:include_depth, 0)
|
|
57
|
+
validate_case!
|
|
58
|
+
@namespace = SQLite.database_namespace
|
|
59
|
+
build_database
|
|
60
|
+
build_api
|
|
61
|
+
@request = Rack::MockRequest.new(@server.app)
|
|
62
|
+
@path = request_path
|
|
63
|
+
@headers = { 'HTTP_HOST' => 'example.test', 'HTTP_ACCEPT' => 'application/json',
|
|
64
|
+
'HTTP_AUTHORIZATION' => "Basic #{Base64.strict_encode64("#{@user}:fixture-only")}" }
|
|
65
|
+
@subscription = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
|
|
66
|
+
record_query(args.last)
|
|
67
|
+
end
|
|
68
|
+
reset!(iterations: 1)
|
|
69
|
+
rescue StandardError
|
|
70
|
+
close
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def reset!(iterations:)
|
|
75
|
+
raise ArgumentError, 'Iterations must be positive' unless iterations.is_a?(Integer) && iterations > 0
|
|
76
|
+
raise 'SQLite scenario is closed' if @closed
|
|
77
|
+
|
|
78
|
+
@operations = Array.new(iterations) { { queries: 0, authorizations: 0, executions: 0 } }
|
|
79
|
+
@index = 0
|
|
80
|
+
@active_operation = nil
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def call
|
|
85
|
+
raise 'SQLite scenario is closed' if @closed
|
|
86
|
+
raise 'SQLite scenario called beyond reset iterations' if @index >= @operations.length
|
|
87
|
+
|
|
88
|
+
@active_operation = @operations.fetch(@index)
|
|
89
|
+
@index += 1
|
|
90
|
+
@base.uncached { @request.get(@path, @headers) }
|
|
91
|
+
ensure
|
|
92
|
+
@active_operation = nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def validate!(response, index:)
|
|
96
|
+
unless index.is_a?(Integer) && index >= 0 && index < @index
|
|
97
|
+
raise ArgumentError, 'Invalid or uncalled SQLite operation index'
|
|
98
|
+
end
|
|
99
|
+
unless response.status == 200 && response['content-type']&.start_with?('application/json')
|
|
100
|
+
raise 'Unexpected SQLite HTTP/envelope status'
|
|
101
|
+
end
|
|
102
|
+
raise 'Unexpected SQLite operation counters' unless @operations.fetch(index) == expected_counters
|
|
103
|
+
|
|
104
|
+
return yield(-> { validate_body!(response) }) if block_given?
|
|
105
|
+
|
|
106
|
+
validate_body!(response)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def validate_body!(response)
|
|
110
|
+
identity = HaveAPI::Performance::Identity.json(response)
|
|
111
|
+
body = identity.fetch(:canonical_body)
|
|
112
|
+
unless body['status'] == true && body['message'].nil? && body['errors'].nil? &&
|
|
113
|
+
body.keys.sort == %w[errors message response status]
|
|
114
|
+
raise 'Unexpected SQLite HTTP/envelope status'
|
|
115
|
+
end
|
|
116
|
+
raise 'Unexpected SQLite response rows or values' unless body['response'] == expected_response
|
|
117
|
+
|
|
118
|
+
identity
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def counters
|
|
122
|
+
raise 'SQLite operation records are incomplete' unless @index == @operations.length
|
|
123
|
+
|
|
124
|
+
unless @operations.all? { |operation| operation == expected_counters }
|
|
125
|
+
raise 'Unexpected SQLite operation counters'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
{ operations: @operations.map(&:dup) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def close
|
|
132
|
+
return if @closed
|
|
133
|
+
|
|
134
|
+
ActiveSupport::Notifications.unsubscribe(@subscription) if @subscription
|
|
135
|
+
@base.remove_connection if @base
|
|
136
|
+
if @namespace
|
|
137
|
+
name = @namespace.name.split('::').last
|
|
138
|
+
SQLite.send(:remove_const, name) if SQLite.const_defined?(name, false) && SQLite.const_get(name).equal?(@namespace)
|
|
139
|
+
end
|
|
140
|
+
@closed = true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def authorize_record
|
|
144
|
+
@active_operation[:authorizations] += 1 if @active_operation
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def execute_record
|
|
148
|
+
@active_operation[:executions] += 1 if @active_operation
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def validate_case!
|
|
154
|
+
unless %w[sqlite.page sqlite.includes].include?(@case_spec.fetch(:id)) &&
|
|
155
|
+
@rows.is_a?(Integer) && @rows > 0 && %w[admin member].include?(@user)
|
|
156
|
+
raise ArgumentError, 'Invalid SQLite case'
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
allowed = @case_spec[:id] == 'sqlite.page' ? %i[rows page_size user] : %i[rows include_depth user]
|
|
160
|
+
raise ArgumentError, 'Unknown SQLite dimensions' unless (@dimensions.keys - allowed).empty?
|
|
161
|
+
|
|
162
|
+
if @case_spec[:id] == 'sqlite.page'
|
|
163
|
+
unless @dimensions.fetch(:page_size) == 32 && @rows >= 32
|
|
164
|
+
raise ArgumentError, 'SQLite pagination requires a fixed page of 32'
|
|
165
|
+
end
|
|
166
|
+
elsif ![1, 2].include?(@depth)
|
|
167
|
+
raise ArgumentError, 'SQLite include depth must be 1 or 2'
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def build_database
|
|
172
|
+
@base = @namespace.const_set(:Base, Class.new(ActiveRecord::Base))
|
|
173
|
+
@base.abstract_class = true
|
|
174
|
+
@base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
|
175
|
+
connection = @base.connection
|
|
176
|
+
connection.create_table(:performance_accounts) do |table|
|
|
177
|
+
table.string :label
|
|
178
|
+
table.string :secret
|
|
179
|
+
end
|
|
180
|
+
connection.create_table(:performance_groups) do |table|
|
|
181
|
+
table.string :label
|
|
182
|
+
table.string :secret
|
|
183
|
+
table.integer :account_id
|
|
184
|
+
end
|
|
185
|
+
connection.create_table(:performance_items) do |table|
|
|
186
|
+
table.string :label
|
|
187
|
+
table.boolean :enabled
|
|
188
|
+
table.integer :count
|
|
189
|
+
table.string :note
|
|
190
|
+
table.integer :group_id
|
|
191
|
+
end
|
|
192
|
+
@account = model(:Account, 'performance_accounts')
|
|
193
|
+
@group = model(:Group, 'performance_groups')
|
|
194
|
+
@item = model(:Item, 'performance_items')
|
|
195
|
+
@group.belongs_to(:account, class_name: @account.name)
|
|
196
|
+
@item.belongs_to(:group, class_name: @group.name)
|
|
197
|
+
@account.insert_all!((1..@rows).map { |id| { id:, label: "account#{id}", secret: "ACCOUNT_SECRET_#{id}" } })
|
|
198
|
+
@group.insert_all!((1..@rows).map { |id| { id:, label: "group#{id}", secret: "GROUP_SECRET_#{id}", account_id: id } })
|
|
199
|
+
@item.insert_all!((1..@rows).map { |id| { id:, label: '', enabled: false, count: 0, note: nil, group_id: id } })
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def model(name, table)
|
|
203
|
+
klass = @namespace.const_set(name, Class.new(@base))
|
|
204
|
+
klass.table_name = table
|
|
205
|
+
klass.columns
|
|
206
|
+
klass
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def build_api
|
|
210
|
+
scenario = self
|
|
211
|
+
depth = @depth
|
|
212
|
+
resources = @namespace.const_set(:API, Module.new)
|
|
213
|
+
account_resource = resource(resources, :Account, @account)
|
|
214
|
+
account_resource.define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
215
|
+
authorize do |user, params|
|
|
216
|
+
scenario.authorize_record
|
|
217
|
+
deny if user.login == 'member' && (params['account_id'].to_i % 3) == 0
|
|
218
|
+
output blacklist: [:secret] if user.login == 'member'
|
|
219
|
+
allow
|
|
220
|
+
end
|
|
221
|
+
output(:object) do
|
|
222
|
+
integer :id
|
|
223
|
+
string :label
|
|
224
|
+
string :secret
|
|
225
|
+
end
|
|
226
|
+
define_method(:exec) do
|
|
227
|
+
scenario.execute_record
|
|
228
|
+
raise 'Associated Show must not execute'
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
group_resource = resource(resources, :Group, @group)
|
|
232
|
+
group_resource.define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
233
|
+
authorize do |user, params|
|
|
234
|
+
scenario.authorize_record
|
|
235
|
+
deny if user.login == 'member' && params['group_id'].to_i.even?
|
|
236
|
+
output blacklist: [:secret] if user.login == 'member'
|
|
237
|
+
allow
|
|
238
|
+
end
|
|
239
|
+
output(:object) do
|
|
240
|
+
integer :id
|
|
241
|
+
string :label
|
|
242
|
+
string :secret
|
|
243
|
+
# The direct lane's selected output is scalar; it does not rely on
|
|
244
|
+
# the adapter's known missing default preload for nested resources.
|
|
245
|
+
resource account_resource if depth == 2
|
|
246
|
+
end
|
|
247
|
+
define_method(:exec) do
|
|
248
|
+
scenario.execute_record
|
|
249
|
+
raise 'Associated Show must not execute'
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
item_resource = resource(resources, :Item, @item)
|
|
253
|
+
add_index(item_resource, scenario)
|
|
254
|
+
add_includes(item_resource, group_resource, scenario)
|
|
255
|
+
@server = HaveAPI::Server.new(resources)
|
|
256
|
+
@server.use_version(1)
|
|
257
|
+
@server.default_version = 1
|
|
258
|
+
@server.auth_chain << BasicProvider
|
|
259
|
+
@server.mount('/performance/api/')
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def resource(resources, name, model)
|
|
263
|
+
klass = resources.const_set(name, Class.new(HaveAPI::Resource))
|
|
264
|
+
klass.resource_name = name.to_s
|
|
265
|
+
klass.version(1)
|
|
266
|
+
klass.auth(true)
|
|
267
|
+
klass.model(model)
|
|
268
|
+
unless name == :Item
|
|
269
|
+
klass.define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
270
|
+
authorize { deny }
|
|
271
|
+
output(:object_list) do
|
|
272
|
+
integer :id
|
|
273
|
+
string :label
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
klass
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def add_index(resource, scenario)
|
|
281
|
+
resource.define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
282
|
+
authorize do
|
|
283
|
+
scenario.authorize_record
|
|
284
|
+
allow
|
|
285
|
+
end
|
|
286
|
+
output(:object_list) do
|
|
287
|
+
integer :id
|
|
288
|
+
string :label
|
|
289
|
+
bool :enabled
|
|
290
|
+
integer :count
|
|
291
|
+
string :note, nullable: true
|
|
292
|
+
end
|
|
293
|
+
define_method(:exec) do
|
|
294
|
+
scenario.execute_record
|
|
295
|
+
with_pagination(self.class.model.order(id: :asc)).to_a
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def add_includes(resource, group_resource, scenario)
|
|
301
|
+
resource.define_action(:Included, superclass: HaveAPI::Actions::Default::Index) do
|
|
302
|
+
route 'included'
|
|
303
|
+
authorize do
|
|
304
|
+
scenario.authorize_record
|
|
305
|
+
allow
|
|
306
|
+
end
|
|
307
|
+
output(:object_list) do
|
|
308
|
+
integer :id
|
|
309
|
+
string :label
|
|
310
|
+
resource group_resource
|
|
311
|
+
end
|
|
312
|
+
define_method(:exec) do
|
|
313
|
+
scenario.execute_record
|
|
314
|
+
with_includes(self.class.model.order(id: :asc)).to_a
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def request_path
|
|
320
|
+
query = if @case_spec[:id] == 'sqlite.page'
|
|
321
|
+
{ item: { from_id: 0, limit: 32 } }
|
|
322
|
+
else
|
|
323
|
+
{ _meta: { includes: @depth == 1 ? 'group' : 'group__account' } }
|
|
324
|
+
end
|
|
325
|
+
route = @case_spec[:id] == 'sqlite.page' ? 'items' : 'items/included'
|
|
326
|
+
"/performance/api/v1/#{route}?#{Rack::Utils.build_nested_query(query)}"
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def record_query(payload)
|
|
330
|
+
return unless @active_operation && payload[:connection]&.pool.equal?(@base.connection_pool)
|
|
331
|
+
return if %w[SCHEMA TRANSACTION].include?(payload[:name])
|
|
332
|
+
return unless payload[:sql].match?(/\A\s*(?:SELECT|INSERT|UPDATE|DELETE)\b/i)
|
|
333
|
+
raise 'SQLite request unexpectedly used query cache' if payload[:cached]
|
|
334
|
+
|
|
335
|
+
@active_operation[:queries] += 1
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def expected_counters
|
|
339
|
+
return { queries: 1, authorizations: 1, executions: 1 } if @case_spec[:id] == 'sqlite.page'
|
|
340
|
+
|
|
341
|
+
nested = if @depth == 2
|
|
342
|
+
@user == 'admin' ? @rows : (@rows + 1) / 2
|
|
343
|
+
else
|
|
344
|
+
0
|
|
345
|
+
end
|
|
346
|
+
{ queries: @depth + 1, authorizations: 1 + @rows + nested, executions: 1 }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def expected_response
|
|
350
|
+
length = @case_spec[:id] == 'sqlite.page' ? 32 : @rows
|
|
351
|
+
items = (1..length).map do |id|
|
|
352
|
+
value = if @case_spec[:id] == 'sqlite.page'
|
|
353
|
+
{ 'id' => id, 'label' => '', 'enabled' => false, 'count' => 0, 'note' => nil }
|
|
354
|
+
else
|
|
355
|
+
{ 'id' => id, 'label' => '', 'group' => expected_group(id) }
|
|
356
|
+
end
|
|
357
|
+
value['_meta'] = { 'path_params' => [id], 'resolved' => true }
|
|
358
|
+
value
|
|
359
|
+
end
|
|
360
|
+
{ 'items' => items, '_meta' => {} }
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def expected_group(id)
|
|
364
|
+
denied = { '_meta' => { 'resolved' => false, 'authorized' => false } }
|
|
365
|
+
return denied if @user == 'member' && id.even?
|
|
366
|
+
|
|
367
|
+
result = { 'id' => id, 'label' => "group#{id}", '_meta' => { 'path_params' => [id], 'resolved' => true } }
|
|
368
|
+
result['secret'] = "GROUP_SECRET_#{id}" if @user == 'admin'
|
|
369
|
+
return result if @depth == 1
|
|
370
|
+
|
|
371
|
+
result['account'] = if @user == 'member' && (id % 3) == 0
|
|
372
|
+
denied
|
|
373
|
+
else
|
|
374
|
+
account = { 'id' => id, 'label' => "account#{id}",
|
|
375
|
+
'_meta' => { 'path_params' => [id], 'resolved' => @depth == 2 } }
|
|
376
|
+
account['secret'] = "ACCOUNT_SECRET_#{id}" if @user == 'admin' && @depth == 2
|
|
377
|
+
account
|
|
378
|
+
end
|
|
379
|
+
result
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.30.
|
|
4
|
+
version: 0.30.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.30.
|
|
32
|
+
version: 0.30.2
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.30.
|
|
39
|
+
version: 0.30.2
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: i18n
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -210,11 +210,16 @@ files:
|
|
|
210
210
|
- LICENSE.txt
|
|
211
211
|
- README.md
|
|
212
212
|
- Rakefile
|
|
213
|
+
- benchmarks/documentation.rb
|
|
214
|
+
- benchmarks/endpoints.rb
|
|
215
|
+
- benchmarks/performance_budgets.json
|
|
216
|
+
- benchmarks/performance_compare.rb
|
|
213
217
|
- doc/client-examples.md
|
|
214
218
|
- doc/create-client.md
|
|
215
219
|
- doc/hooks.erb
|
|
216
220
|
- doc/index.md
|
|
217
221
|
- doc/json-schema.html
|
|
222
|
+
- doc/performance.md
|
|
218
223
|
- doc/protocol.md
|
|
219
224
|
- doc/protocol.png
|
|
220
225
|
- doc/typed-input-validation.md
|
|
@@ -258,6 +263,7 @@ files:
|
|
|
258
263
|
- lib/haveapi/context.rb
|
|
259
264
|
- lib/haveapi/documentation_clients.rb
|
|
260
265
|
- lib/haveapi/documentation_context.rb
|
|
266
|
+
- lib/haveapi/documentation_schema.rb
|
|
261
267
|
- lib/haveapi/example.rb
|
|
262
268
|
- lib/haveapi/example_list.rb
|
|
263
269
|
- lib/haveapi/exceptions.rb
|
|
@@ -344,6 +350,7 @@ files:
|
|
|
344
350
|
- spec/authorization_spec.rb
|
|
345
351
|
- spec/client_examples/assets/highlighter_test.cjs
|
|
346
352
|
- spec/client_examples/assets/tabs_test.cjs
|
|
353
|
+
- spec/client_examples/context_performance_spec.rb
|
|
347
354
|
- spec/client_examples/context_spec.rb
|
|
348
355
|
- spec/client_examples/dynamic_acceptance.md
|
|
349
356
|
- spec/client_examples/dynamic_acceptance.rb
|
|
@@ -354,8 +361,10 @@ files:
|
|
|
354
361
|
- spec/client_examples/dynamic_mounted_paths_spec.rb
|
|
355
362
|
- spec/client_examples/dynamic_python_spec.rb
|
|
356
363
|
- spec/client_examples/go_spec.rb
|
|
364
|
+
- spec/client_examples/profile_mount_spec.rb
|
|
357
365
|
- spec/client_examples/profiles_spec.rb
|
|
358
366
|
- spec/client_examples/rust_client_spec.rb
|
|
367
|
+
- spec/client_examples/structural_schema_spec.rb
|
|
359
368
|
- spec/client_examples/syntax_spec.rb
|
|
360
369
|
- spec/common_spec.rb
|
|
361
370
|
- spec/documentation/auth_filtering_spec.rb
|
|
@@ -363,6 +372,7 @@ files:
|
|
|
363
372
|
- spec/documentation/current_user_html_escaping_spec.rb
|
|
364
373
|
- spec/documentation/examples_spec.rb
|
|
365
374
|
- spec/documentation/host_html_escaping_spec.rb
|
|
375
|
+
- spec/documentation/performance_spec.rb
|
|
366
376
|
- spec/documentation_spec.rb
|
|
367
377
|
- spec/envelope_spec.rb
|
|
368
378
|
- spec/extensions/action_exceptions_spec.rb
|
|
@@ -373,6 +383,16 @@ files:
|
|
|
373
383
|
- spec/packaging_spec.rb
|
|
374
384
|
- spec/parameters/typed_spec.rb
|
|
375
385
|
- spec/params_spec.rb
|
|
386
|
+
- spec/performance/comparator_spec.rb
|
|
387
|
+
- spec/performance/endpoint_contract_spec.rb
|
|
388
|
+
- spec/performance/endpoint_scaling_spec.rb
|
|
389
|
+
- spec/performance/event_baseline_spec.rb
|
|
390
|
+
- spec/performance/json_activation_spec.rb
|
|
391
|
+
- spec/performance/release_lineage_spec.rb
|
|
392
|
+
- spec/performance/runner_spec.rb
|
|
393
|
+
- spec/performance/sqlite_spec.rb
|
|
394
|
+
- spec/performance/support/live_worker.rb
|
|
395
|
+
- spec/performance/support/samples.rb
|
|
376
396
|
- spec/resource_spec.rb
|
|
377
397
|
- spec/server/integration_spec.rb
|
|
378
398
|
- spec/spec_helper.rb
|
|
@@ -388,9 +408,12 @@ files:
|
|
|
388
408
|
- spec/validators/presence_spec.rb
|
|
389
409
|
- test_support/client_test_api.rb
|
|
390
410
|
- test_support/client_test_server.rb
|
|
411
|
+
- test_support/documentation_performance_fixture.rb
|
|
391
412
|
- test_support/dynamic_documentation_api.rb
|
|
392
413
|
- test_support/dynamic_protocol_spec.rb
|
|
393
414
|
- test_support/dynamic_python_login_spec.rb
|
|
415
|
+
- test_support/performance_endpoint_fixture.rb
|
|
416
|
+
- test_support/performance_sqlite_fixture.rb
|
|
394
417
|
licenses:
|
|
395
418
|
- MIT
|
|
396
419
|
metadata: {}
|