async-matrix 2.1.0 → 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.
- checksums.yaml +4 -4
- data/Cargo.lock +8 -8
- data/README.md +33 -58
- data/ext/async_matrix_e2ee/Cargo.toml +7 -1
- data/lib/async/matrix/api/chain.rb +120 -94
- data/lib/async/matrix/api/concat.rb +28 -22
- data/lib/async/matrix/api/path_tree.rb +71 -44
- data/lib/async/matrix/api.rb +6 -1
- data/lib/async/matrix/auth_error.rb +20 -0
- data/lib/async/matrix/bad_json_error.rb +20 -0
- data/lib/async/matrix/client.rb +142 -110
- data/lib/async/matrix/config/vivify.rb +107 -0
- data/lib/async/matrix/config.rb +184 -0
- data/lib/async/matrix/content.rb +106 -0
- data/lib/async/matrix/double_puppet_client.rb +1 -1
- data/lib/async/matrix/error.rb +0 -27
- data/lib/async/matrix/{application_service/error_response.rb → error_response.rb} +10 -10
- data/lib/async/matrix/event.rb +197 -0
- data/lib/async/matrix/homeserver_error.rb +20 -0
- data/lib/async/matrix/invalid_endpoint_error.rb +20 -0
- data/lib/async/matrix/media_client.rb +46 -35
- data/lib/async/matrix/not_found_error.rb +20 -0
- data/lib/async/matrix/notifier.rb +7 -3
- data/lib/async/matrix/response_too_large_error.rb +20 -0
- data/lib/async/matrix/schema/registry.rb +367 -340
- data/lib/async/matrix/schema/validation_error.rb +221 -214
- data/lib/async/matrix/schema.rb +161 -161
- data/lib/async/matrix/version.rb +3 -3
- data/lib/async/matrix.rb +0 -7
- metadata +24 -132
- data/lib/async/discord/api/path_tree.rb +0 -127
- data/lib/async/discord/api.rb +0 -151
- data/lib/async/discord/client.rb +0 -283
- data/lib/async/discord/error.rb +0 -84
- data/lib/async/discord/gateway.rb +0 -359
- data/lib/async/discord.rb +0 -15
- data/lib/async/matrix/application_service/bot.rb +0 -232
- data/lib/async/matrix/application_service/config/schema/analytics.json +0 -21
- data/lib/async/matrix/application_service/config/schema/appservice.json +0 -82
- data/lib/async/matrix/application_service/config/schema/backfill.json +0 -91
- data/lib/async/matrix/application_service/config/schema/bridge.json +0 -209
- data/lib/async/matrix/application_service/config/schema/config.json +0 -61
- data/lib/async/matrix/application_service/config/schema/database.json +0 -38
- data/lib/async/matrix/application_service/config/schema/direct_media.json +0 -35
- data/lib/async/matrix/application_service/config/schema/double_puppet.json +0 -24
- data/lib/async/matrix/application_service/config/schema/encryption.json +0 -164
- data/lib/async/matrix/application_service/config/schema/homeserver.json +0 -58
- data/lib/async/matrix/application_service/config/schema/logging.json +0 -50
- data/lib/async/matrix/application_service/config/schema/management_room_texts.json +0 -25
- data/lib/async/matrix/application_service/config/schema/matrix.json +0 -45
- data/lib/async/matrix/application_service/config/schema/permissions.json +0 -54
- data/lib/async/matrix/application_service/config/schema/provisioning.json +0 -23
- data/lib/async/matrix/application_service/config/schema/public_media.json +0 -39
- data/lib/async/matrix/application_service/config/schema/relay.json +0 -43
- data/lib/async/matrix/application_service/config/vivify.rb +0 -109
- data/lib/async/matrix/application_service/config.rb +0 -225
- data/lib/async/matrix/application_service/dispatcher.rb +0 -185
- data/lib/async/matrix/application_service/event.rb +0 -285
- data/lib/async/matrix/application_service/server.rb +0 -430
- data/lib/async/matrix/application_service/transaction.rb +0 -66
- data/lib/async/matrix/application_service/transaction_handler.rb +0 -185
- data/lib/async/matrix/application_service/transaction_store.rb +0 -80
- data/lib/async/matrix/bridge/discord/db/connection.rb +0 -141
- data/lib/async/matrix/bridge/discord/db/file.rb +0 -118
- data/lib/async/matrix/bridge/discord/db/guild.rb +0 -120
- data/lib/async/matrix/bridge/discord/db/message.rb +0 -160
- data/lib/async/matrix/bridge/discord/db/migrations/001_create_users.rb +0 -14
- data/lib/async/matrix/bridge/discord/db/migrations/002_create_guilds.rb +0 -14
- data/lib/async/matrix/bridge/discord/db/migrations/003_create_portals.rb +0 -23
- data/lib/async/matrix/bridge/discord/db/migrations/004_create_puppets.rb +0 -19
- data/lib/async/matrix/bridge/discord/db/migrations/005_create_messages.rb +0 -20
- data/lib/async/matrix/bridge/discord/db/migrations/006_create_reactions.rb +0 -19
- data/lib/async/matrix/bridge/discord/db/migrations/007_create_files.rb +0 -18
- data/lib/async/matrix/bridge/discord/db/portal.rb +0 -150
- data/lib/async/matrix/bridge/discord/db/puppet.rb +0 -128
- data/lib/async/matrix/bridge/discord/db/reaction.rb +0 -165
- data/lib/async/matrix/bridge/discord/db/schema.rb +0 -18
- data/lib/async/matrix/bridge/discord/db/user.rb +0 -112
- data/lib/async/matrix/bridge/discord/db.rb +0 -138
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65f191895135eb9d4f0580991c9beca1a319ed524c12b29d4f52c2cbff4aebc9
|
|
4
|
+
data.tar.gz: d3331bde0b5cea1d2e69081ab72747e72903f4c6977c9fb331e990ff0e1c31f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: deffa5fa0c1bb2cbdf0ca642400bfa67c0b4bdc2839472a3923f3fc1aacccc060159b0b5d8646ffb8d544b577020a1035c07956fb884132ad2293ff78e27a3f1
|
|
7
|
+
data.tar.gz: 35de48c7bf9adb335031dbf8c9036947bea84f57cf8c0c3a4b22551b3be682c1df584eb9cc883c09c1ec109fd25772d96ad6d978c96385e7e87149301b032ff4
|
data/Cargo.lock
CHANGED
|
@@ -423,9 +423,9 @@ dependencies = [
|
|
|
423
423
|
|
|
424
424
|
[[package]]
|
|
425
425
|
name = "magnus"
|
|
426
|
-
version = "0.
|
|
426
|
+
version = "0.9.2"
|
|
427
427
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
428
|
-
checksum = "
|
|
428
|
+
checksum = "b9068710e00d21be762edca59b9c4c8946dbdb29820d4f76ad559afea07982c9"
|
|
429
429
|
dependencies = [
|
|
430
430
|
"magnus-macros",
|
|
431
431
|
"rb-sys",
|
|
@@ -435,9 +435,9 @@ dependencies = [
|
|
|
435
435
|
|
|
436
436
|
[[package]]
|
|
437
437
|
name = "magnus-macros"
|
|
438
|
-
version = "0.
|
|
438
|
+
version = "0.9.1"
|
|
439
439
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
440
|
-
checksum = "
|
|
440
|
+
checksum = "9dafde2eeb66f090e0f326112323eb8324b12cb32b5d1210e541d3df6b0ce77e"
|
|
441
441
|
dependencies = [
|
|
442
442
|
"proc-macro2",
|
|
443
443
|
"quote",
|
|
@@ -606,18 +606,18 @@ dependencies = [
|
|
|
606
606
|
|
|
607
607
|
[[package]]
|
|
608
608
|
name = "rb-sys"
|
|
609
|
-
version = "0.9.
|
|
609
|
+
version = "0.9.130"
|
|
610
610
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
611
|
-
checksum = "
|
|
611
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
612
612
|
dependencies = [
|
|
613
613
|
"rb-sys-build",
|
|
614
614
|
]
|
|
615
615
|
|
|
616
616
|
[[package]]
|
|
617
617
|
name = "rb-sys-build"
|
|
618
|
-
version = "0.9.
|
|
618
|
+
version = "0.9.130"
|
|
619
619
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
620
|
-
checksum = "
|
|
620
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
621
621
|
dependencies = [
|
|
622
622
|
"bindgen",
|
|
623
623
|
"lazy_static",
|
data/README.md
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
[](https://github.com/general-intelligence-systems/async-matrix/blob/main/LICENSE)
|
|
6
6
|
[](https://www.ruby-lang.org)
|
|
7
7
|
|
|
8
|
-
Async-native [Matrix](https://matrix.org)
|
|
8
|
+
Async-native [Matrix](https://matrix.org) protocol primitives for Ruby. Built on the [Socketry](https://github.com/socketry) ecosystem (`async`, `async-http`, Falcon). No threads, no callbacks -- just fibers.
|
|
9
|
+
|
|
10
|
+
This gem is the protocol layer: a Client-Server API client, schema-validated events, media, and end-to-end encryption. The *server* side of a bridge or bot -- receiving transactions from a homeserver and dispatching events to handlers -- is [async-matrix-bridge](https://github.com/general-intelligence-systems/async-matrix-bridge), which builds on this gem.
|
|
9
11
|
|
|
10
12
|
## Usage
|
|
11
13
|
|
|
@@ -20,60 +22,17 @@ gem "async-matrix"
|
|
|
20
22
|
## Quick Start
|
|
21
23
|
|
|
22
24
|
```ruby
|
|
23
|
-
# config.ru
|
|
24
25
|
require "async/matrix"
|
|
25
26
|
|
|
26
|
-
config = Async::Matrix::
|
|
27
|
+
config = Async::Matrix::Config.load("config/appservice.yml")
|
|
27
28
|
client = Async::Matrix::Client.new(config)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
on "m.room.message", msgtype: "m.text", not_from: :self do |event|
|
|
35
|
-
send_notice event.room_id, "Echo: #{event.content.body}"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
app = Async::Matrix::ApplicationService::Server.new(
|
|
40
|
-
hs_token: config.appservice.hs_token,
|
|
41
|
-
client: client
|
|
42
|
-
) do
|
|
43
|
-
dispatch bot
|
|
30
|
+
Async do
|
|
31
|
+
client.join_room("!room:example.org")
|
|
32
|
+
client.send_notice("!room:example.org", "Hello from a fiber")
|
|
44
33
|
end
|
|
45
|
-
|
|
46
|
-
run app
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
falcon serve --bind http://0.0.0.0:9292
|
|
51
34
|
```
|
|
52
35
|
|
|
53
|
-
A complete working example with Docker Compose and Synapse lives in [`examples/echo_bot/`](https://github.com/general-intelligence-systems/async-matrix/tree/main/examples/echo_bot).
|
|
54
|
-
|
|
55
|
-
## Handlers
|
|
56
|
-
|
|
57
|
-
Any object that responds to `#event_types` and `#call(event)` is a handler. Use this when you need more control than the Bot DSL provides.
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
class Echo
|
|
61
|
-
def initialize(client) = @client = client
|
|
62
|
-
|
|
63
|
-
def event_types = ["m.room.message"]
|
|
64
|
-
|
|
65
|
-
def call(event)
|
|
66
|
-
return unless event.content&.msgtype == "m.text"
|
|
67
|
-
return unless event.sender != @client.config.bot_mxid
|
|
68
|
-
@client.send_notice(event.room_id, "Echo: #{event.content.body}")
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
app.dispatch(Echo.new(client))
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Dispatch is fault-tolerant -- one handler raising won't take down the rest.
|
|
76
|
-
|
|
77
36
|
## Client
|
|
78
37
|
|
|
79
38
|
```ruby
|
|
@@ -93,40 +52,56 @@ client.api.createRoom.post(name: "Pub")
|
|
|
93
52
|
client.api.rooms("!room:ex.com").messages.get(dir: "b", limit: 10)
|
|
94
53
|
```
|
|
95
54
|
|
|
96
|
-
|
|
55
|
+
Binary routes (upload, download, thumbnail) are detected and dispatched to a dedicated `MediaClient`, so raw bytes never pass through JSON encoding.
|
|
56
|
+
|
|
57
|
+
All methods are fiber-safe with automatic connection pooling. The client retries 502/503/504 with exponential backoff and full jitter, honours `Retry-After` on 429, and caps response bodies while streaming them.
|
|
58
|
+
|
|
59
|
+
## Events
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
event = Async::Matrix::Schema.parse(raw_hash)
|
|
63
|
+
|
|
64
|
+
event.type # => "m.room.message"
|
|
65
|
+
event.sender # => "@alice:example.org"
|
|
66
|
+
event.content.body # => "hello"
|
|
67
|
+
event.valid? # => true
|
|
68
|
+
event.valid! # => true, or raises Schema::ValidationError
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Events validate against the official Matrix event JSON schemas, bundled into the gem.
|
|
97
72
|
|
|
98
73
|
## Configuration
|
|
99
74
|
|
|
100
|
-
|
|
75
|
+
`Async::Matrix::Config` loads a YAML file (or a plain hash) and exposes it through dot notation. `Client` reads `homeserver.address` and `appservice.as_token`; it ignores the rest.
|
|
101
76
|
|
|
102
77
|
```yaml
|
|
78
|
+
# config/appservice.yml
|
|
103
79
|
homeserver:
|
|
104
80
|
address: "http://synapse:8008"
|
|
105
81
|
domain: "localhost"
|
|
106
82
|
|
|
107
83
|
appservice:
|
|
108
84
|
as_token: "your-appservice-token"
|
|
109
|
-
hs_token: "your-homeserver-token"
|
|
110
85
|
bot:
|
|
111
86
|
username: "bot"
|
|
112
87
|
```
|
|
113
88
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
APPSERVICE_CONFIG=/etc/bot/appservice.yml falcon serve
|
|
89
|
+
```ruby
|
|
90
|
+
config = Async::Matrix::Config.load("config/appservice.yml")
|
|
91
|
+
config.homeserver.address # => "http://synapse:8008"
|
|
92
|
+
config.bot_mxid # => "@bot:localhost"
|
|
120
93
|
```
|
|
121
94
|
|
|
95
|
+
It validates nothing by default. A subclass adds validation by overriding `.validate!` -- which is how async-matrix-bridge layers a mautrix bridgev2 JSON Schema suite on top without reimplementing the loading.
|
|
96
|
+
|
|
122
97
|
## Built With
|
|
123
98
|
|
|
124
99
|
- [async](https://github.com/socketry/async) -- fiber-based concurrency framework
|
|
125
100
|
- [async-http](https://github.com/socketry/async-http) -- HTTP client/server with connection pooling
|
|
126
|
-
- [falcon](https://github.com/socketry/falcon) -- async Rack-compatible web server
|
|
127
101
|
- [json_schemer](https://github.com/davishmcclurg/json_schemer) -- JSON Schema validation
|
|
128
102
|
- [scampi](https://github.com/general-intelligence-systems/scampi) -- inline co-located test framework
|
|
129
103
|
- [string_builder](https://github.com/general-intelligence-systems/string_builder) -- method-chain string builder
|
|
104
|
+
- [vodozemac](https://github.com/matrix-org/vodozemac) -- Olm/Megolm, via a native Rust binding
|
|
130
105
|
|
|
131
106
|
## License
|
|
132
107
|
|
|
@@ -11,7 +11,13 @@ crate-type = ["cdylib"]
|
|
|
11
11
|
[dependencies]
|
|
12
12
|
# magnus 0.8 is required for Ruby 4.0 C-API compatibility (0.7 fails to compile
|
|
13
13
|
# against Ruby 4.0: rb_fiber_raise pointer mutability + RTypedData.typed_flag).
|
|
14
|
-
|
|
14
|
+
#
|
|
15
|
+
# 0.9 is required to cross-compile for arm-linux-musl. On 32-bit musl, bindgen
|
|
16
|
+
# renders timespec's padding around tv_nsec as bitfield members, so magnus 0.8's
|
|
17
|
+
# `From<Timespec> for timespec` struct literal fails with E0063 (missing fields
|
|
18
|
+
# `_bitfield_1`, `_bitfield_align_1`). 0.9 builds the struct with mem::zeroed
|
|
19
|
+
# and assigns the two fields instead.
|
|
20
|
+
magnus = "0.9"
|
|
15
21
|
|
|
16
22
|
# vodozemac 0.10.0 (2026-04-13), default features (libolm-compat).
|
|
17
23
|
#
|
|
@@ -108,122 +108,148 @@ module Async
|
|
|
108
108
|
|
|
109
109
|
private
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
def execute(method, body: nil, query: nil, content_type: nil, max_retries: nil)
|
|
112
|
+
segments = _rewrite_version(_build_full_segments)
|
|
113
|
+
result = @path_tree.match(segments, method)
|
|
114
|
+
|
|
115
|
+
if !result[:valid]
|
|
116
|
+
path = "/" + segments.join("/")
|
|
117
|
+
available = result[:methods]
|
|
118
|
+
if available.empty?
|
|
119
|
+
::Kernel.raise ::Async::Matrix::InvalidEndpointError.new(
|
|
120
|
+
"INVALID_ENDPOINT",
|
|
121
|
+
"No Matrix API endpoint matches: #{method} #{path}",
|
|
122
|
+
)
|
|
123
|
+
else
|
|
124
|
+
::Kernel.raise ::Async::Matrix::InvalidEndpointError.new(
|
|
125
|
+
"METHOD_NOT_ALLOWED",
|
|
126
|
+
"#{method} not allowed for #{path}. Valid methods: #{available.map(&:upcase).join(", ")}",
|
|
127
|
+
)
|
|
128
|
+
end
|
|
128
129
|
end
|
|
129
|
-
end
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
path = "/" + segments.map { |s| _encode(s) }.join("/")
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
if query && !query.empty?
|
|
134
|
+
qs = query.map { |k, v| "#{_encode(k.to_s)}=#{_encode(v.to_s)}" }.join("&")
|
|
135
|
+
path = "#{path}?#{qs}"
|
|
136
|
+
end
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
if _binary_route?(segments)
|
|
139
|
+
case method
|
|
140
|
+
when "GET"
|
|
141
|
+
@client.media_client.download(path)
|
|
142
|
+
when "POST"
|
|
143
|
+
@client.media_client.upload(
|
|
144
|
+
"POST",
|
|
145
|
+
path,
|
|
146
|
+
body,
|
|
147
|
+
content_type || "application/octet-stream",
|
|
148
|
+
)
|
|
149
|
+
when "PUT"
|
|
150
|
+
@client.media_client.upload(
|
|
151
|
+
"PUT",
|
|
152
|
+
path,
|
|
153
|
+
body,
|
|
154
|
+
content_type || "application/octet-stream",
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
if max_retries
|
|
159
|
+
retry_opts = {max_retries: max_retries}
|
|
160
|
+
else
|
|
161
|
+
retry_opts = {}
|
|
146
162
|
end
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
case method
|
|
164
|
+
when "GET"
|
|
165
|
+
@client.get(path, **retry_opts)
|
|
166
|
+
when "POST"
|
|
167
|
+
@client.post(path, body || {}, **retry_opts)
|
|
168
|
+
when "PUT"
|
|
169
|
+
@client.put(path, body || {}, **retry_opts)
|
|
170
|
+
when "DELETE"
|
|
171
|
+
@client.request(
|
|
172
|
+
"DELETE",
|
|
173
|
+
path,
|
|
174
|
+
nil,
|
|
175
|
+
**retry_opts,
|
|
176
|
+
)
|
|
177
|
+
when "PATCH"
|
|
178
|
+
@client.request(
|
|
179
|
+
"PATCH",
|
|
180
|
+
path,
|
|
181
|
+
body || {},
|
|
182
|
+
**retry_opts,
|
|
183
|
+
)
|
|
184
|
+
end
|
|
160
185
|
end
|
|
161
186
|
end
|
|
162
|
-
end
|
|
163
187
|
|
|
164
188
|
# Rewrites version segments for endpoints that only exist at
|
|
165
189
|
# a different version than the gateway prefix provides.
|
|
166
190
|
# _matrix/media/v3/create → _matrix/media/v1/create
|
|
167
191
|
# _matrix/client/v3/media/... → _matrix/client/v1/media/...
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
return segments
|
|
192
|
+
def _rewrite_version(segments)
|
|
193
|
+
# POST /_matrix/media/v1/create (only exists at v1)
|
|
194
|
+
media_create = segments.length == 4 &&
|
|
195
|
+
segments[0] == "_matrix" && segments[1] == "media" &&
|
|
196
|
+
segments[2] == "v3" && segments[3] == "create"
|
|
197
|
+
|
|
198
|
+
# Authenticated media endpoints live at /_matrix/client/v1/media/...
|
|
199
|
+
client_media = segments.length >= 5 &&
|
|
200
|
+
segments[0] == "_matrix" && segments[1] == "client" &&
|
|
201
|
+
segments[2] == "v3" && segments[3] == "media"
|
|
202
|
+
|
|
203
|
+
if media_create || client_media
|
|
204
|
+
segments.dup.tap { |rewritten| rewritten[2] = "v1" }
|
|
205
|
+
else
|
|
206
|
+
segments
|
|
207
|
+
end
|
|
185
208
|
end
|
|
186
209
|
|
|
187
|
-
segments
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
def _binary_route?(segments)
|
|
211
|
+
BINARY_ROUTES.any? do |pattern|
|
|
212
|
+
parts = pattern.split("/")
|
|
213
|
+
unless parts.length == segments.length + 1
|
|
214
|
+
next false
|
|
215
|
+
end # leading slash adds empty element
|
|
216
|
+
parts.shift # remove empty string from leading /
|
|
217
|
+
parts.length == segments.length &&
|
|
218
|
+
parts.zip(segments).all? { |pat, seg| pat == "*" || pat == seg }
|
|
219
|
+
end
|
|
197
220
|
end
|
|
198
|
-
end
|
|
199
221
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
def _extract_query_params(kwargs)
|
|
223
|
+
query = {}
|
|
224
|
+
kwargs.each_key do |k|
|
|
225
|
+
key_s = k.to_s
|
|
226
|
+
if key_s.start_with?("?")
|
|
227
|
+
query[key_s[1..]] = kwargs.delete(k)
|
|
228
|
+
end
|
|
206
229
|
end
|
|
230
|
+
query.empty? ? nil : query
|
|
207
231
|
end
|
|
208
|
-
query.empty? ? nil : query
|
|
209
|
-
end
|
|
210
232
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
def _build_full_segments
|
|
234
|
+
chain_segments = []
|
|
235
|
+
@buffer.each do |name, args|
|
|
236
|
+
chain_segments << name
|
|
237
|
+
if args.nil? || args.empty?
|
|
238
|
+
next
|
|
239
|
+
end
|
|
240
|
+
args.each do |arg|
|
|
241
|
+
if arg.is_a?(::Hash)
|
|
242
|
+
next
|
|
243
|
+
end
|
|
244
|
+
chain_segments << arg.to_s
|
|
245
|
+
end
|
|
219
246
|
end
|
|
247
|
+
@prefix + chain_segments
|
|
220
248
|
end
|
|
221
|
-
@prefix + chain_segments
|
|
222
|
-
end
|
|
223
249
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
250
|
+
def _encode(value)
|
|
251
|
+
::ERB::Util.url_encode(value.to_s)
|
|
252
|
+
end
|
|
227
253
|
end
|
|
228
254
|
end
|
|
229
255
|
end
|
|
@@ -24,36 +24,42 @@ module Async
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def initialize(buffer)
|
|
27
|
-
|
|
27
|
+
if buffer.respond_to?(:to_a)
|
|
28
|
+
@buffer = buffer.to_a
|
|
29
|
+
else
|
|
30
|
+
@buffer = buffer
|
|
31
|
+
end
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def segments
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
[].tap do |result|
|
|
36
|
+
@buffer.each do |entry|
|
|
37
|
+
case entry
|
|
38
|
+
when Symbol
|
|
39
|
+
# :slash, :dash -- ignore in URL context
|
|
40
|
+
next
|
|
41
|
+
when Array
|
|
42
|
+
name, args = entry
|
|
43
|
+
unless name.is_a?(String)
|
|
44
|
+
next
|
|
45
|
+
end
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if args.nil? || args.empty?
|
|
48
|
+
# Bare method -> path segment
|
|
49
|
+
result << name
|
|
50
|
+
else
|
|
51
|
+
# Method with args -> method name as segment, then each arg as segment
|
|
52
|
+
result << name
|
|
53
|
+
args.each do |arg|
|
|
54
|
+
if arg.is_a?(Hash)
|
|
55
|
+
next
|
|
56
|
+
end # kwargs are not path segments
|
|
57
|
+
result << arg.to_s
|
|
58
|
+
end
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
|
-
|
|
56
|
-
result
|
|
57
63
|
end
|
|
58
64
|
end
|
|
59
65
|
end
|