kirei 0.10.0 → 0.11.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.
- checksums.yaml +4 -4
- data/README.md +42 -5
- data/lib/kirei/app.rb +21 -6
- data/lib/kirei/config.rb +24 -1
- data/lib/kirei/model/class_methods.rb +11 -12
- data/lib/kirei/routing/base.rb +98 -12
- data/lib/kirei/routing/request_rejected.rb +25 -0
- data/lib/kirei/routing/router.rb +12 -2
- data/lib/kirei/version.rb +1 -1
- data/sorbet/rbi/shims/base_model.rbi +1 -1
- data/sorbet/rbi/shims/sequel_pg_json.rbi +10 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85f4c3fa6b6cc6dd3492be32c1fbc959b15b7dce43ab2de1435ce9129c757474
|
|
4
|
+
data.tar.gz: d80206078da55131d0c1c5c33212303f6eccc5492727093e172b4926673ff201
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f48edabdddd9a567ad85f26bbf6bedc723258148426f9125cb45c48799d14695c6ad360bff9c9fa9ce29ea6b1804dd991b09d32b212f076de1ac1ef71c4abd79
|
|
7
|
+
data.tar.gz: dcdb445a12d614ff37a5396856bcade43e133546163f5adbe3a30b114e1973977de613d2ea486bf8081cdf410776835413f9a6fe2b786deb53586e87e273d00a
|
data/README.md
CHANGED
|
@@ -55,7 +55,7 @@ Find a test app in the [spec/test_app](spec/test_app) directory. It is a fully f
|
|
|
55
55
|
|
|
56
56
|
#### Models
|
|
57
57
|
|
|
58
|
-
All models must inherit from `T::Struct` and include `Kirei::Model`. They must implement `id` which must hold the primary key of the table. The primary key must be named `id` and be
|
|
58
|
+
All models must inherit from `T::Struct` and include `Kirei::Model`. They must implement `id` which must hold the primary key of the table. The primary key must be named `id` and be a `String`. Kirei generates it on `create` with `generate_human_id` (for example `user_K7mQ2xRtB9vN`) unless the caller passes one; database sequences are not supported. Override `human_id_length` or `human_id_prefix` per model to change the format.
|
|
59
59
|
|
|
60
60
|
Kirei models are immutable by convention - all properties are defined using `const` and updating a record returns a new instance rather than mutating the original. This immutability, combined with strict typing, makes them naturally suitable for both traditional data-centric applications and domain-driven design approaches.
|
|
61
61
|
|
|
@@ -66,7 +66,7 @@ class User < T::Struct
|
|
|
66
66
|
extend T::Sig
|
|
67
67
|
include Kirei::Model
|
|
68
68
|
|
|
69
|
-
const :id,
|
|
69
|
+
const :id, String
|
|
70
70
|
const :name, String
|
|
71
71
|
end
|
|
72
72
|
|
|
@@ -114,11 +114,11 @@ Kirei provides support for Domain-Driven Design patterns through `Kirei::Domain:
|
|
|
114
114
|
class Flight < T::Struct
|
|
115
115
|
include Kirei::Domain::Entity
|
|
116
116
|
|
|
117
|
-
const :id,
|
|
117
|
+
const :id, String
|
|
118
118
|
|
|
119
119
|
const :flight_number, String
|
|
120
|
-
const :departure_airport_id,
|
|
121
|
-
const :arrival_airport_id,
|
|
120
|
+
const :departure_airport_id, String
|
|
121
|
+
const :arrival_airport_id, String
|
|
122
122
|
const :scheduled_departure_at, Time
|
|
123
123
|
const :status, String
|
|
124
124
|
|
|
@@ -354,6 +354,43 @@ Request timing and service execution timing are tracked automatically.
|
|
|
354
354
|
|
|
355
355
|
To build a custom backend (e.g. Prometheus, OpenTelemetry), subclass `Kirei::Metrics::Backend` and implement `increment`, `measure`, and `gauge`.
|
|
356
356
|
|
|
357
|
+
### Web servers and request bodies
|
|
358
|
+
|
|
359
|
+
Kirei runs on any Rack server. It routes by `PATH_INFO`, accepts any readable `rack.input`, and keeps the request env fiber-local, so both thread-based servers (Puma) and fiber-based servers (Falcon) work. With Falcon, also set `config.db_global_extensions = [:fiber_concurrency]` (see below).
|
|
360
|
+
|
|
361
|
+
JSON and form bodies are buffered in memory. Bound them per app:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
class MyApp < Kirei::App
|
|
365
|
+
config.max_request_body_bytes = 65_536 # nil (default) means unlimited
|
|
366
|
+
end
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
A larger body gets a `413` JSON:API error with code `payload_too_large`. Multipart uploads stream to tempfiles through Rack and are not limited by this setting. Malformed JSON returns `400` with code `malformed_json`, and a JSON body that is not an object returns `400` with code `invalid_json_body`.
|
|
370
|
+
|
|
371
|
+
### Database connection
|
|
372
|
+
|
|
373
|
+
`Kirei::App.raw_db_connection` opens one Sequel database per process. Configure it in your app:
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
class MyApp < Kirei::App
|
|
377
|
+
# Global extensions change Sequel itself and are loaded via `Sequel.extension`
|
|
378
|
+
# before the connection exists. Use `:fiber_concurrency` with Async/Falcon.
|
|
379
|
+
config.db_global_extensions = [:fiber_concurrency]
|
|
380
|
+
|
|
381
|
+
# Database extensions are loaded on the connection via `Sequel::Database#extension`.
|
|
382
|
+
config.db_extensions += [:pgvector]
|
|
383
|
+
|
|
384
|
+
# Pool bounds and per-connection session setup; `nil` keeps the Sequel default.
|
|
385
|
+
config.db_max_connections = Integer(ENV.fetch("DB_POOL_SIZE", "5"))
|
|
386
|
+
config.db_pool_timeout = Float(ENV.fetch("DB_POOL_TIMEOUT", "2"))
|
|
387
|
+
config.db_connect_timeout = 5
|
|
388
|
+
config.db_connect_sqls = ["SET statement_timeout = '10s'"]
|
|
389
|
+
end
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
A global extension passed to `db_extensions` is silently ignored by Sequel, so keep the two lists apart.
|
|
393
|
+
|
|
357
394
|
### Goes well with these gems
|
|
358
395
|
|
|
359
396
|
* [pagy](https://github.com/ddnexus/pagy) for pagination
|
data/lib/kirei/app.rb
CHANGED
|
@@ -73,19 +73,34 @@ module Kirei
|
|
|
73
73
|
@raw_db_connection = T.let(@raw_db_connection, T.nilable(Sequel::Database))
|
|
74
74
|
return @raw_db_connection unless @raw_db_connection.nil?
|
|
75
75
|
|
|
76
|
+
config.db_global_extensions.each { |ext| Sequel.extension(ext) }
|
|
77
|
+
|
|
76
78
|
# calling "Sequel.connect" creates a new connection
|
|
77
|
-
|
|
79
|
+
database = Sequel.connect(App.config.db_url || default_db_url, db_connect_options)
|
|
80
|
+
@raw_db_connection = apply_db_extensions(database)
|
|
81
|
+
end
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
sig { params(database: Sequel::Database).returns(Sequel::Database) }
|
|
84
|
+
private def apply_db_extensions(database)
|
|
85
|
+
config.db_extensions.each { |ext| database.extension(ext) }
|
|
82
86
|
|
|
83
87
|
if config.db_extensions.include?(:pg_json)
|
|
84
88
|
# https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
|
85
|
-
|
|
89
|
+
database.wrap_json_primitives = true
|
|
86
90
|
end
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
database
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
sig { returns(T::Hash[Symbol, T.any(Integer, Float, T::Array[String])]) }
|
|
96
|
+
private def db_connect_options
|
|
97
|
+
connect_sqls = config.db_connect_sqls
|
|
98
|
+
{
|
|
99
|
+
max_connections: config.db_max_connections,
|
|
100
|
+
pool_timeout: config.db_pool_timeout,
|
|
101
|
+
connect_timeout: config.db_connect_timeout,
|
|
102
|
+
connect_sqls: connect_sqls.empty? ? nil : connect_sqls,
|
|
103
|
+
}.compact
|
|
89
104
|
end
|
|
90
105
|
end
|
|
91
106
|
end
|
data/lib/kirei/config.rb
CHANGED
|
@@ -28,11 +28,29 @@ module Kirei
|
|
|
28
28
|
|
|
29
29
|
prop :app_name, String, default: "kirei"
|
|
30
30
|
|
|
31
|
+
# Database extensions are loaded on the connection via `Sequel::Database#extension`.
|
|
32
|
+
# They add behaviour to one database object, e.g. column type parsing.
|
|
33
|
+
#
|
|
31
34
|
# must use "pg_json" to parse jsonb columns to hashes
|
|
32
35
|
#
|
|
33
36
|
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb
|
|
34
|
-
prop :db_extensions, T::Array[Symbol], default: %i[pg_json pg_array]
|
|
37
|
+
prop :db_extensions, T::Array[Symbol], default: %i[pg_json pg_array]
|
|
38
|
+
# Global extensions are loaded via `Sequel.extension` before the connection is created.
|
|
39
|
+
# They change Sequel itself, not a database object, e.g. `:fiber_concurrency` switches the
|
|
40
|
+
# concurrency primitive from `Thread.current` to `Fiber.current` for Async/Falcon servers.
|
|
41
|
+
# Loading one on a database object is silently ignored.
|
|
42
|
+
#
|
|
43
|
+
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/doc/extensions.rdoc
|
|
44
|
+
prop :db_global_extensions, T::Array[Symbol], default: []
|
|
35
45
|
prop :db_url, T.nilable(String)
|
|
46
|
+
# Connection pool bounds passed to `Sequel.connect`. `nil` keeps the Sequel default.
|
|
47
|
+
#
|
|
48
|
+
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/doc/opening_databases.rdoc
|
|
49
|
+
prop :db_max_connections, T.nilable(Integer)
|
|
50
|
+
prop :db_pool_timeout, T.nilable(Float)
|
|
51
|
+
prop :db_connect_timeout, T.nilable(Integer)
|
|
52
|
+
# SQL statements run on every new connection, e.g. session timeouts.
|
|
53
|
+
prop :db_connect_sqls, T::Array[String], default: []
|
|
36
54
|
# Extra or unknown properties present in the Hash do not raise exceptions at runtime
|
|
37
55
|
# unless the optional strict argument to from_hash is passed
|
|
38
56
|
#
|
|
@@ -40,5 +58,10 @@ module Kirei
|
|
|
40
58
|
prop :db_strict_type_resolving, T.nilable(T::Boolean), default: nil
|
|
41
59
|
|
|
42
60
|
prop :allowed_origins, T::Array[String], default: []
|
|
61
|
+
|
|
62
|
+
# Upper bound for JSON and form request bodies, which Kirei buffers in memory.
|
|
63
|
+
# Requests above it get a 413 JSON:API error. `nil` means no limit.
|
|
64
|
+
# Multipart uploads stream to tempfiles via Rack and are not subject to it.
|
|
65
|
+
prop :max_request_body_bytes, T.nilable(Integer), default: nil
|
|
43
66
|
end
|
|
44
67
|
end
|
|
@@ -54,20 +54,19 @@ module Kirei
|
|
|
54
54
|
resolve(query.all)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
57
|
+
# Default values defined in the model are used if omitted in the hash.
|
|
58
|
+
# A missing `id` is filled with `generate_human_id`; primary keys are
|
|
59
|
+
# always application-generated strings, never database sequences.
|
|
59
60
|
sig do
|
|
60
61
|
override.params(
|
|
61
62
|
hash: T::Hash[Symbol, T.untyped],
|
|
62
63
|
).returns(T.attached_class)
|
|
63
64
|
end
|
|
64
|
-
def create(hash)
|
|
65
|
+
def create(hash) # rubocop:disable Metrics/AbcSize
|
|
66
|
+
attributes = hash.key?(:id) ? hash : hash.merge(id: generate_human_id)
|
|
65
67
|
# instantiate a new object to ensure we use default values defined in the model
|
|
66
|
-
|
|
67
|
-
hash[:id] = "kirei-fake-id" if without_id
|
|
68
|
-
new_record = from_hash(Helpers.deep_stringify_keys(hash))
|
|
68
|
+
new_record = from_hash(Helpers.deep_stringify_keys(attributes))
|
|
69
69
|
all_attributes = T.let(new_record.serialize, T::Hash[String, T.untyped])
|
|
70
|
-
all_attributes.delete("id") if without_id && all_attributes["id"] == "kirei-fake-id"
|
|
71
70
|
|
|
72
71
|
wrap_jsonb_non_primivitives!(all_attributes)
|
|
73
72
|
|
|
@@ -78,11 +77,10 @@ module Kirei
|
|
|
78
77
|
all_attributes["updated_at"] = Time.now.utc
|
|
79
78
|
end
|
|
80
79
|
|
|
81
|
-
|
|
80
|
+
query.insert(all_attributes)
|
|
82
81
|
|
|
83
|
-
T.must(find_by({ id:
|
|
82
|
+
T.must(find_by({ id: new_record.id }))
|
|
84
83
|
end
|
|
85
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
86
84
|
|
|
87
85
|
sig { override.params(attributes: T::Hash[T.any(Symbol, String), T.untyped]).void }
|
|
88
86
|
def wrap_jsonb_non_primivitives!(attributes) # rubocop:disable Metrics/AbcSize
|
|
@@ -180,9 +178,10 @@ module Kirei
|
|
|
180
178
|
resolve(query.limit(1), strict_loading).first
|
|
181
179
|
end
|
|
182
180
|
|
|
183
|
-
#
|
|
181
|
+
# 12 characters from a 55-character alphabet; collisions are negligible
|
|
182
|
+
# even for large tables. Override per model for shorter, hand-typed ids.
|
|
184
183
|
sig { override.returns(Integer) }
|
|
185
|
-
def human_id_length =
|
|
184
|
+
def human_id_length = 12
|
|
186
185
|
|
|
187
186
|
# defaults to "model_name" (table_name without the trailing "s")
|
|
188
187
|
sig { override.returns(String) }
|
data/lib/kirei/routing/base.rb
CHANGED
|
@@ -8,6 +8,9 @@ module Kirei
|
|
|
8
8
|
extend T::Sig
|
|
9
9
|
|
|
10
10
|
NOT_FOUND = T.let([404, {}, ["Not Found"]], RackResponseType) # rubocop:disable Style/MutableConstant
|
|
11
|
+
# Oj raises EncodingError for malformed input in the modes Kirei uses, and
|
|
12
|
+
# Oj::ParseError for other parse failures.
|
|
13
|
+
JSON_PARSE_ERRORS = T.let([Oj::ParseError, EncodingError].freeze, T::Array[T.class_of(StandardError)])
|
|
11
14
|
|
|
12
15
|
sig { params(params: T::Hash[String, T.untyped]).void }
|
|
13
16
|
def initialize(params: {})
|
|
@@ -28,7 +31,7 @@ module Kirei
|
|
|
28
31
|
status = 500 # we use it in the "ensure" block, so we need to define early (Sorbet doesn't like `status ||= 418`)
|
|
29
32
|
|
|
30
33
|
http_verb = Verb.deserialize(env.fetch("REQUEST_METHOD"))
|
|
31
|
-
req_path =
|
|
34
|
+
req_path = request_path(env)
|
|
32
35
|
#
|
|
33
36
|
# TODO: reject requests from unexpected hosts -> allow configuring allowed hosts in a `cors.rb` file
|
|
34
37
|
# ( offer a scaffold for this file )
|
|
@@ -52,17 +55,7 @@ module Kirei
|
|
|
52
55
|
[k, v]
|
|
53
56
|
end
|
|
54
57
|
when Verb::POST, Verb::PUT, Verb::PATCH
|
|
55
|
-
|
|
56
|
-
# built-in support for JSON & XML
|
|
57
|
-
body = env.fetch("rack.input")
|
|
58
|
-
if body.nil? || !body.respond_to?(:read) || (body.respond_to?(:empty?) && body.empty?)
|
|
59
|
-
{}
|
|
60
|
-
else
|
|
61
|
-
body = T.cast(body, T.any(IO, StringIO))
|
|
62
|
-
res = Oj.load(body.read, Kirei::OJ_OPTIONS)
|
|
63
|
-
body.rewind # TODO: maybe don't rewind if we don't need to?
|
|
64
|
-
T.cast(res, T::Hash[String, T.untyped])
|
|
65
|
-
end
|
|
58
|
+
parse_request_body(env)
|
|
66
59
|
when Verb::HEAD, Verb::DELETE, Verb::OPTIONS
|
|
67
60
|
{}
|
|
68
61
|
else
|
|
@@ -122,6 +115,13 @@ module Kirei
|
|
|
122
115
|
headers,
|
|
123
116
|
response_body,
|
|
124
117
|
]
|
|
118
|
+
rescue RequestRejected => e
|
|
119
|
+
status = e.status
|
|
120
|
+
error = Errors::JsonApiError.new(code: e.code, detail: e.message)
|
|
121
|
+
body = Oj.dump({ "errors" => [error.serialize] }, Kirei::OJ_OPTIONS)
|
|
122
|
+
response_body = [body]
|
|
123
|
+
|
|
124
|
+
[status, { "Content-Type" => "application/json; charset=utf-8" }, response_body]
|
|
125
125
|
rescue StandardError => e
|
|
126
126
|
status = 500
|
|
127
127
|
|
|
@@ -162,10 +162,21 @@ module Kirei
|
|
|
162
162
|
|
|
163
163
|
# reset global variables after the request has been served
|
|
164
164
|
# and after all "after" hooks have run to avoid leaking
|
|
165
|
+
router.current_env = nil
|
|
165
166
|
Thread.current[:enduser_id] = nil
|
|
166
167
|
Thread.current[:request_id] = nil
|
|
167
168
|
end
|
|
168
169
|
|
|
170
|
+
# `PATH_INFO` is the Rack-specified key. `REQUEST_PATH` is a Puma
|
|
171
|
+
# extension that Falcon does not set.
|
|
172
|
+
sig { params(env: RackEnvType).returns(String) }
|
|
173
|
+
private def request_path(env)
|
|
174
|
+
path = T.cast(env.fetch("PATH_INFO", nil), T.nilable(String))
|
|
175
|
+
return path unless path.nil? || path.empty?
|
|
176
|
+
|
|
177
|
+
T.cast(env.fetch("REQUEST_PATH"), String)
|
|
178
|
+
end
|
|
179
|
+
|
|
169
180
|
#
|
|
170
181
|
# * "status": defaults to 200
|
|
171
182
|
# * "headers": Kirei adds some default headers for security, but the user can override them
|
|
@@ -317,6 +328,81 @@ module Kirei
|
|
|
317
328
|
|
|
318
329
|
result
|
|
319
330
|
end
|
|
331
|
+
|
|
332
|
+
#
|
|
333
|
+
# Body parsing by Content-Type:
|
|
334
|
+
# - multipart/form-data: form fields plus uploaded files wrapped in Rack::Multipart::UploadedFile
|
|
335
|
+
# - application/json: JSON object
|
|
336
|
+
# - anything else: JSON first, URL-encoded form data as fallback
|
|
337
|
+
#
|
|
338
|
+
# `rack.input` is only required to respond to `read`; Puma passes an IO or
|
|
339
|
+
# StringIO, Falcon passes a Protocol::Rack::Input.
|
|
340
|
+
#
|
|
341
|
+
sig { params(env: RackEnvType).returns(T::Hash[String, T.untyped]) }
|
|
342
|
+
private def parse_request_body(env)
|
|
343
|
+
content_type = T.cast(env.fetch("CONTENT_TYPE", ""), String)
|
|
344
|
+
body = env["rack.input"]
|
|
345
|
+
return {} if body.nil? || !body.respond_to?(:read)
|
|
346
|
+
|
|
347
|
+
return wrap_uploaded_files(Rack::Request.new(env).params) if content_type.include?("multipart/form-data")
|
|
348
|
+
|
|
349
|
+
raw = read_bounded_body(body)
|
|
350
|
+
return {} if raw.empty?
|
|
351
|
+
|
|
352
|
+
return parse_json_object(raw) if content_type.include?("application/json")
|
|
353
|
+
|
|
354
|
+
begin
|
|
355
|
+
parsed = Oj.load(raw, Kirei::OJ_OPTIONS)
|
|
356
|
+
return parsed if parsed.is_a?(Hash)
|
|
357
|
+
rescue *JSON_PARSE_ERRORS
|
|
358
|
+
# fall through to form data
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
T.cast(Rack::Request.new(env).params, T::Hash[String, T.untyped])
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
sig { params(body: T.untyped).returns(String) }
|
|
365
|
+
private def read_bounded_body(body)
|
|
366
|
+
limit = App.config.max_request_body_bytes
|
|
367
|
+
raw = limit.nil? ? body.read : body.read(limit + 1)
|
|
368
|
+
body.rewind if body.respond_to?(:rewind)
|
|
369
|
+
raw = T.cast(raw || "", String)
|
|
370
|
+
if !limit.nil? && raw.bytesize > limit
|
|
371
|
+
raise RequestRejected.new(
|
|
372
|
+
status: 413,
|
|
373
|
+
code: "payload_too_large",
|
|
374
|
+
detail: "Request body exceeds #{limit} bytes",
|
|
375
|
+
)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
raw
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
sig { params(raw: String).returns(T::Hash[String, T.untyped]) }
|
|
382
|
+
private def parse_json_object(raw)
|
|
383
|
+
parsed = Oj.load(raw, Kirei::OJ_OPTIONS)
|
|
384
|
+
unless parsed.is_a?(Hash)
|
|
385
|
+
raise RequestRejected.new(status: 400, code: "invalid_json_body", detail: "JSON body must be an object")
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
parsed
|
|
389
|
+
rescue *JSON_PARSE_ERRORS => e
|
|
390
|
+
raise RequestRejected.new(status: 400, code: "malformed_json", detail: e.message)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Rack's multipart parser yields a Hash (symbol keys) per uploaded file; wrap it in a typed object.
|
|
394
|
+
sig { params(params: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
|
395
|
+
private def wrap_uploaded_files(params)
|
|
396
|
+
params.transform_values do |value|
|
|
397
|
+
next value unless value.is_a?(Hash) && value.key?(:tempfile)
|
|
398
|
+
|
|
399
|
+
Rack::Multipart::UploadedFile.new(
|
|
400
|
+
io: value.fetch(:tempfile),
|
|
401
|
+
content_type: value.fetch(:type, "application/octet-stream"),
|
|
402
|
+
filename: value.fetch(:filename),
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
320
406
|
end
|
|
321
407
|
end
|
|
322
408
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Kirei
|
|
5
|
+
module Routing
|
|
6
|
+
# Raised while reading a request body when the request itself is at fault.
|
|
7
|
+
# `Base#call` renders it as a JSON:API error with the given status.
|
|
8
|
+
class RequestRejected < StandardError
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { returns(Integer) }
|
|
12
|
+
attr_reader :status
|
|
13
|
+
|
|
14
|
+
sig { returns(String) }
|
|
15
|
+
attr_reader :code
|
|
16
|
+
|
|
17
|
+
sig { params(status: Integer, code: String, detail: String).void }
|
|
18
|
+
def initialize(status:, code:, detail:)
|
|
19
|
+
@status = status
|
|
20
|
+
@code = code
|
|
21
|
+
super(detail)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/kirei/routing/router.rb
CHANGED
|
@@ -31,8 +31,18 @@ module Kirei
|
|
|
31
31
|
T.nilable([Route, T::Hash[String, String]])
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
# The router is a process-wide singleton, but a request env must be
|
|
35
|
+
# scoped to the request. `Thread.current[]` is fiber-local, so this is
|
|
36
|
+
# safe for both thread-based (Puma) and fiber-based (Falcon) servers.
|
|
37
|
+
sig { returns(T.nilable(RackEnvType)) }
|
|
38
|
+
def current_env
|
|
39
|
+
Thread.current[:kirei_current_env]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
sig { params(env: T.nilable(RackEnvType)).returns(T.nilable(RackEnvType)) }
|
|
43
|
+
def current_env=(env)
|
|
44
|
+
Thread.current[:kirei_current_env] = env
|
|
45
|
+
end
|
|
36
46
|
|
|
37
47
|
sig { void }
|
|
38
48
|
def initialize
|
data/lib/kirei/version.rb
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
|
|
3
|
+
# The pg_json database extension adds this setter at runtime via
|
|
4
|
+
# `Sequel::Database#extension(:pg_json)`; Tapioca cannot see it.
|
|
5
|
+
#
|
|
6
|
+
# Source: https://github.com/jeremyevans/sequel/blob/5.75.0/lib/sequel/extensions/pg_json.rb#L8
|
|
7
|
+
class Sequel::Database
|
|
8
|
+
sig { params(value: T::Boolean).returns(T::Boolean) }
|
|
9
|
+
def wrap_json_primitives=(value); end
|
|
10
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kirei
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ludwig Reinmiedl
|
|
@@ -196,6 +196,7 @@ files:
|
|
|
196
196
|
- lib/kirei/routing/rack_env_type.rb
|
|
197
197
|
- lib/kirei/routing/rack_response_type.rb
|
|
198
198
|
- lib/kirei/routing/request.rb
|
|
199
|
+
- lib/kirei/routing/request_rejected.rb
|
|
199
200
|
- lib/kirei/routing/route.rb
|
|
200
201
|
- lib/kirei/routing/router.rb
|
|
201
202
|
- lib/kirei/routing/verb.rb
|
|
@@ -206,6 +207,7 @@ files:
|
|
|
206
207
|
- sorbet/rbi/shims/base_model.rbi
|
|
207
208
|
- sorbet/rbi/shims/domain.rbi
|
|
208
209
|
- sorbet/rbi/shims/ruby.rbi
|
|
210
|
+
- sorbet/rbi/shims/sequel_pg_json.rbi
|
|
209
211
|
- sorbet/rbi/shims/statsd.rbi
|
|
210
212
|
homepage: https://github.com/swiknaba/kirei
|
|
211
213
|
licenses:
|
|
@@ -227,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
227
229
|
- !ruby/object:Gem::Version
|
|
228
230
|
version: '0'
|
|
229
231
|
requirements: []
|
|
230
|
-
rubygems_version: 4.0.
|
|
232
|
+
rubygems_version: 4.0.20
|
|
231
233
|
specification_version: 4
|
|
232
234
|
summary: Kirei is a typed Ruby micro/REST-framework for building scalable and performant
|
|
233
235
|
microservices.
|