async-matrix 4.0.0 → 4.0.1
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/CHANGELOG.md +26 -1
- data/README.md +3 -2
- data/context/design.md +26 -0
- data/context/getting-started.md +122 -0
- data/context/index.yaml +25 -0
- data/context/overview.md +46 -0
- data/context/server-server.md +79 -0
- data/context/testing.md +49 -0
- data/lib/async/matrix/abstract/api/push_gateway/push_notifier.rb +10 -2
- data/lib/async/matrix/e2ee/backup.rb +1 -2
- data/lib/async/matrix/e2ee/cross_signing.rb +2 -2
- data/lib/async/matrix/e2ee/devices.rb +1 -2
- data/lib/async/matrix/e2ee/machine.rb +81 -20
- data/lib/async/matrix/e2ee/secret_storage.rb +8 -11
- data/lib/async/matrix/e2ee/secrets.rb +33 -15
- data/lib/async/matrix/e2ee/store.rb +1 -2
- data/lib/async/matrix/error.rb +20 -0
- data/lib/async/matrix/events/registry.rb +10 -7
- data/lib/async/matrix/representation.rb +96 -2
- data/lib/async/matrix/schema.rb +466 -0
- data/lib/async/matrix/version.rb +1 -1
- metadata +22 -2
- data/NOTICE +0 -56
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the Apache License, Version 2.0.
|
|
4
|
+
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
+
|
|
6
|
+
require "json_schemer"
|
|
7
|
+
require "yaml"
|
|
8
|
+
|
|
9
|
+
require_relative "error"
|
|
10
|
+
|
|
11
|
+
module Async
|
|
12
|
+
module Matrix
|
|
13
|
+
# Raised where a document does not answer what the specification asks of it.
|
|
14
|
+
class InvalidDocumentError < Error
|
|
15
|
+
# @parameter operation [String] the operation ID, e.g. `login`.
|
|
16
|
+
# @parameter version [String] the specification version the document was read against.
|
|
17
|
+
# @parameter errors [Array(String)] what was wrong with it.
|
|
18
|
+
def initialize(operation, version, errors)
|
|
19
|
+
super("#{operation} does not answer what #{version} asks of it: #{errors.join("; ")}!")
|
|
20
|
+
|
|
21
|
+
@operation = operation
|
|
22
|
+
@version = version
|
|
23
|
+
@errors = errors
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @attribute [String] the operation ID.
|
|
27
|
+
attr :operation
|
|
28
|
+
|
|
29
|
+
# @attribute [String] the specification version.
|
|
30
|
+
attr :version
|
|
31
|
+
|
|
32
|
+
# @attribute [Array(String)] what was wrong with the document.
|
|
33
|
+
attr :errors
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Raised where a path names more than one operation, so that nothing can say which was called.
|
|
37
|
+
class AmbiguousPathError < Error
|
|
38
|
+
# @parameter path [String] the path which was asked about.
|
|
39
|
+
# @parameter operations [Array(String)] the operation IDs which share it.
|
|
40
|
+
def initialize(path, operations)
|
|
41
|
+
super("#{path} is the path of #{operations.join(", ")}: name the one to check against!")
|
|
42
|
+
|
|
43
|
+
@path = path
|
|
44
|
+
@operations = operations
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @attribute [String] the path which was asked about.
|
|
48
|
+
attr :path
|
|
49
|
+
|
|
50
|
+
# @attribute [Array(String)] the operation IDs which share it.
|
|
51
|
+
attr :operations
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# What the specification asks of one concept's documents, at one version.
|
|
55
|
+
class Schema
|
|
56
|
+
# The schemas this gem ships are generated from the specification: each names its operations, and each operation the fields of its request and of every response it describes.
|
|
57
|
+
#
|
|
58
|
+
# This reads one and answers a schema a document can be checked against, so that `required` is enforced rather than merely recorded.
|
|
59
|
+
|
|
60
|
+
# Where the generated schemas are, which ship with the gem.
|
|
61
|
+
ROOT = File.expand_path("abstract", __dir__)
|
|
62
|
+
|
|
63
|
+
# The keys the generated schemas use to describe a field, as against the names of the fields themselves.
|
|
64
|
+
#
|
|
65
|
+
# A field named `pattern` or `format` is a field, not a keyword: a descriptor is what carries a `type`.
|
|
66
|
+
STRUCTURE = ["type", "required", "description", "fields", "enum", "keyed_by"].freeze
|
|
67
|
+
|
|
68
|
+
class << self
|
|
69
|
+
# The schema for one concept at one version, built once and cached.
|
|
70
|
+
#
|
|
71
|
+
# @parameter api [String] the API, e.g. `client_server`, or `events`.
|
|
72
|
+
# @parameter concept [String] the concept, e.g. `session`.
|
|
73
|
+
# @parameter version [String] the specification version, e.g. `v1.19`.
|
|
74
|
+
# @returns [Schema | nil] the schema, or nil where the version describes no such concept.
|
|
75
|
+
def for(api, concept, version)
|
|
76
|
+
@schemas ||= {}
|
|
77
|
+
|
|
78
|
+
if @schemas.key?([api, concept, version])
|
|
79
|
+
@schemas[[api, concept, version]]
|
|
80
|
+
else
|
|
81
|
+
@schemas[[api, concept, version]] = self.load(api, concept, version)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Every concept the given API describes at the given version.
|
|
86
|
+
#
|
|
87
|
+
# @parameter api [String] the API, e.g. `client_server`.
|
|
88
|
+
# @parameter version [String] the specification version.
|
|
89
|
+
# @returns [Array(String)] the concepts, named as a Ruby file would be.
|
|
90
|
+
def concepts(api, version)
|
|
91
|
+
Dir.glob(File.join(self.directory(api), "*-#{version}.yaml")).filter_map do |path|
|
|
92
|
+
unless path.end_with?(".deltas.yaml")
|
|
93
|
+
File.basename(path).delete_suffix("-#{version}.yaml").tr("-", "_")
|
|
94
|
+
end
|
|
95
|
+
end.sort
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The operation a path names, which is how a document says what to check itself against.
|
|
99
|
+
#
|
|
100
|
+
# The operation a path names, where only one method answers it.
|
|
101
|
+
#
|
|
102
|
+
# @parameter api [String] the API, e.g. `client_server`.
|
|
103
|
+
# @parameter version [String] the specification version.
|
|
104
|
+
# @parameter path [String] the path the request was made against.
|
|
105
|
+
# @parameter method [String | nil] the HTTP method, where the path names more than one operation.
|
|
106
|
+
# @returns [Array(Schema, String)] the schema and the operation ID, or nil where no operation has that path.
|
|
107
|
+
def operation_for(api, version, path, method: nil)
|
|
108
|
+
# A path is shared by every method which answers it, so the `GET` and the `PUT` of one path are two operations.
|
|
109
|
+
matched = self.index(api, version).select{|pattern, _| pattern.match?(path)}.values.flatten(1)
|
|
110
|
+
|
|
111
|
+
found = matched.select do |concept, operation|
|
|
112
|
+
method.nil? || self.for(api, concept, version).operations.dig(operation, "method") == method.to_s.upcase
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
case found.uniq
|
|
116
|
+
in [[String => concept, String => operation]]
|
|
117
|
+
[self.for(api, concept, version), operation]
|
|
118
|
+
in [[String, String], *] => several
|
|
119
|
+
raise AmbiguousPathError.new(path, several.map(&:last))
|
|
120
|
+
else
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Every operation of an API at a version, by the pattern its path makes.
|
|
126
|
+
#
|
|
127
|
+
# @parameter api [String] the API.
|
|
128
|
+
# @parameter version [String] the specification version.
|
|
129
|
+
# @returns [Hash] the concepts and operation IDs, by pattern.
|
|
130
|
+
def index(api, version)
|
|
131
|
+
@indexes ||= {}
|
|
132
|
+
@indexes[[api, version]] ||= self.concepts(api, version).each_with_object({}) do |concept, index|
|
|
133
|
+
self.for(api, concept, version).operations.each do |operation, described|
|
|
134
|
+
if described["path"]
|
|
135
|
+
# Every method of a path is its own operation, so one pattern may name several.
|
|
136
|
+
(index[self.pattern(described["path"])] ||= []) << [concept, operation]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# What a path template matches, which is one segment per parameter it names.
|
|
143
|
+
#
|
|
144
|
+
# A state key may be empty, so a segment is allowed to be: `/state/m.room.name/` names one.
|
|
145
|
+
#
|
|
146
|
+
# @parameter template [String] the path as the specification writes it, e.g. `/rooms/{roomId}/state`.
|
|
147
|
+
# @returns [Regexp] what that path matches.
|
|
148
|
+
def pattern(template)
|
|
149
|
+
literal = template.split(/(\{[^}]+\})/).map do |part|
|
|
150
|
+
if part.start_with?("{")
|
|
151
|
+
"[^/]*"
|
|
152
|
+
else
|
|
153
|
+
Regexp.escape(part)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
/\A#{literal.join}\z/
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Where one API's schemas are.
|
|
161
|
+
#
|
|
162
|
+
# @parameter api [String] the API, e.g. `client_server`, or `events`.
|
|
163
|
+
# @returns [String] the directory.
|
|
164
|
+
def directory(api)
|
|
165
|
+
case api.to_s
|
|
166
|
+
in "events"
|
|
167
|
+
File.join(ROOT, "events", "schemas")
|
|
168
|
+
in String => named
|
|
169
|
+
File.join(ROOT, "api", named, "schemas")
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
# @parameter api [String] the API.
|
|
176
|
+
# @parameter concept [String] the concept.
|
|
177
|
+
# @parameter version [String] the specification version.
|
|
178
|
+
# @returns [Schema | nil] the schema, or nil where there is no such file.
|
|
179
|
+
def load(api, concept, version)
|
|
180
|
+
# The schema keeps the specification's own name, which may be hyphenated where a Ruby file cannot be.
|
|
181
|
+
path = [concept.to_s, concept.to_s.tr("_", "-")].uniq.lazy.map do |name|
|
|
182
|
+
File.join(self.directory(api), "#{name}-#{version}.yaml")
|
|
183
|
+
end.find{|candidate| File.exist?(candidate)}
|
|
184
|
+
|
|
185
|
+
if path
|
|
186
|
+
self.new(YAML.safe_load_file(path), version: version)
|
|
187
|
+
else
|
|
188
|
+
nil
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @parameter document [Hash] the schema as it was generated.
|
|
194
|
+
# @parameter version [String] the specification version it describes.
|
|
195
|
+
def initialize(document, version:)
|
|
196
|
+
@document = document
|
|
197
|
+
@version = version
|
|
198
|
+
@validators = {}
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @attribute [String] the specification version this describes.
|
|
202
|
+
attr :version
|
|
203
|
+
|
|
204
|
+
# @returns [Hash] the operations this concept has, by ID.
|
|
205
|
+
def operations
|
|
206
|
+
@document["operations"] || {}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# @returns [Hash] the events this schema describes, by type, where it describes events.
|
|
210
|
+
def events
|
|
211
|
+
@document["events"] || {}
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Whether a document answers what an operation's response asks of it.
|
|
215
|
+
#
|
|
216
|
+
# @parameter operation [String] the operation ID, e.g. `login`.
|
|
217
|
+
# @parameter value [Hash] the document the server answered with.
|
|
218
|
+
# @parameter status [Integer | String] which response it is, which is the 200 unless said otherwise.
|
|
219
|
+
# @returns [Array(String)] what was wrong with it, which is empty where nothing was.
|
|
220
|
+
def errors_in_response(operation, value, status: 200)
|
|
221
|
+
self.errors_against(self.response(operation, status: status), value)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Whether a document answers what an operation's request asks of it.
|
|
225
|
+
#
|
|
226
|
+
# @parameter operation [String] the operation ID.
|
|
227
|
+
# @parameter value [Hash] the document to send.
|
|
228
|
+
# @returns [Array(String)] what was wrong with it, which is empty where nothing was.
|
|
229
|
+
def errors_in_request(operation, value)
|
|
230
|
+
self.errors_against(self.request(operation), value)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The same as {#errors_in_response}, raising rather than answering.
|
|
234
|
+
#
|
|
235
|
+
# @parameter operation [String] the operation ID.
|
|
236
|
+
# @parameter value [Hash] the document the server answered with.
|
|
237
|
+
# @parameter status [Integer | String] which response it is.
|
|
238
|
+
# @returns [Hash] the document, so that this can be used where it is read.
|
|
239
|
+
def validate_response(operation, value, status: 200)
|
|
240
|
+
errors = self.errors_in_response(operation, value, status: status)
|
|
241
|
+
|
|
242
|
+
unless errors.empty?
|
|
243
|
+
raise InvalidDocumentError.new(operation, @version, errors)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
value
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# The same as {#errors_in_request}, raising rather than answering.
|
|
250
|
+
#
|
|
251
|
+
# @parameter operation [String] the operation ID.
|
|
252
|
+
# @parameter value [Hash] the document to send.
|
|
253
|
+
# @returns [Hash] the document, so that this can be used where it is sent.
|
|
254
|
+
def validate_request(operation, value)
|
|
255
|
+
errors = self.errors_in_request(operation, value)
|
|
256
|
+
|
|
257
|
+
unless errors.empty?
|
|
258
|
+
raise InvalidDocumentError.new(operation, @version, errors)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
value
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# The fields an operation's response always carries, which is what `required` records.
|
|
265
|
+
#
|
|
266
|
+
# @parameter operation [String] the operation ID.
|
|
267
|
+
# @parameter status [Integer | String] which response it is.
|
|
268
|
+
# @returns [Array(Symbol)] the names, which is empty where the response asks for none.
|
|
269
|
+
def required_in_response(operation, status: 200)
|
|
270
|
+
self.required_of(self.fields_of_response(operation, status: status))
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# The fields an operation's request always carries.
|
|
274
|
+
#
|
|
275
|
+
# @parameter operation [String] the operation ID.
|
|
276
|
+
# @returns [Array(Symbol)] the names.
|
|
277
|
+
def required_in_request(operation)
|
|
278
|
+
self.required_of(self.fields_of_request(operation))
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# One response as a JSON Schema document.
|
|
282
|
+
#
|
|
283
|
+
# @parameter operation [String] the operation ID.
|
|
284
|
+
# @parameter status [Integer | String] which response it is.
|
|
285
|
+
# @returns [Hash | nil] the schema, or nil where the version describes no such response.
|
|
286
|
+
def response(operation, status: 200)
|
|
287
|
+
self.object_schema(self.fields_of_response(operation, status: status))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# One request as a JSON Schema document.
|
|
291
|
+
#
|
|
292
|
+
# @parameter operation [String] the operation ID.
|
|
293
|
+
# @returns [Hash | nil] the schema, or nil where the operation sends no body.
|
|
294
|
+
def request(operation)
|
|
295
|
+
self.object_schema(self.fields_of_request(operation))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def inspect
|
|
299
|
+
"\#<#{self.class} #{@version} #{self.operations.keys.size} operations>"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
private
|
|
303
|
+
|
|
304
|
+
# @parameter operation [String] the operation ID.
|
|
305
|
+
# @parameter status [Integer | String] which response it is.
|
|
306
|
+
# @returns [Hash | nil] the field map, or nil where there is no such response.
|
|
307
|
+
def fields_of_response(operation, status: 200)
|
|
308
|
+
(self.operations.dig(operation, "responses") || {})[status.to_s]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# @parameter operation [String] the operation ID.
|
|
312
|
+
# @returns [Hash | nil] the field map, or nil where the operation sends no body.
|
|
313
|
+
def fields_of_request(operation)
|
|
314
|
+
self.operations.dig(operation, "request")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @parameter fields [Hash | nil] the field map.
|
|
318
|
+
# @returns [Array(Symbol)] the names the map requires.
|
|
319
|
+
def required_of(fields)
|
|
320
|
+
case fields
|
|
321
|
+
in Hash => named
|
|
322
|
+
named.select{|_, field| self.descriptor?(field) && field["required"]}.keys.map(&:to_sym)
|
|
323
|
+
else
|
|
324
|
+
[]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Check a document against a schema, which is nothing to check where the version describes none.
|
|
329
|
+
#
|
|
330
|
+
# @parameter schema [Hash | nil] the JSON Schema document.
|
|
331
|
+
# @parameter value [Hash] the document to check.
|
|
332
|
+
# @returns [Array(String)] what was wrong with it.
|
|
333
|
+
def errors_against(schema, value)
|
|
334
|
+
case schema
|
|
335
|
+
in Hash => against
|
|
336
|
+
self.validator(against).validate(self.stringify(value)).map do |error|
|
|
337
|
+
self.describe(error)
|
|
338
|
+
end
|
|
339
|
+
else
|
|
340
|
+
[]
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# @parameter schema [Hash] the JSON Schema document.
|
|
345
|
+
# @returns [JSONSchemer::Schema] the validator, built once per schema.
|
|
346
|
+
def validator(schema)
|
|
347
|
+
@validators[schema] ||= JSONSchemer.schema(schema)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# What json_schemer reported, as a line a person can read.
|
|
351
|
+
#
|
|
352
|
+
# @parameter error [Hash] one of json_schemer's errors.
|
|
353
|
+
# @returns [String] the description.
|
|
354
|
+
def describe(error)
|
|
355
|
+
case error["data_pointer"]
|
|
356
|
+
in ""
|
|
357
|
+
where = "the document"
|
|
358
|
+
in String => pointer
|
|
359
|
+
where = pointer
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
case error["type"]
|
|
363
|
+
in "required"
|
|
364
|
+
"#{where} is missing #{(error.dig("details", "missing_keys") || []).join(", ")}"
|
|
365
|
+
in String => type
|
|
366
|
+
"#{where} is not #{type}"
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# A field map as a JSON Schema object, which is open rather than closed.
|
|
371
|
+
#
|
|
372
|
+
# @parameter fields [Hash | nil] the field map.
|
|
373
|
+
# @returns [Hash | nil] the schema, or nil where there is no map to build one from.
|
|
374
|
+
def object_schema(fields)
|
|
375
|
+
case fields
|
|
376
|
+
in Hash => named
|
|
377
|
+
# The specification lets a server send fields a version does not describe, and a client which refused them would break on the next version rather than on a fault.
|
|
378
|
+
properties = named.reject{|name, _| name == "(*)"}
|
|
379
|
+
wildcard = named["(*)"]
|
|
380
|
+
|
|
381
|
+
{"type" => "object"}.tap do |schema|
|
|
382
|
+
schema["properties"] = properties.to_h{|name, field| [name, self.field_schema(field)]}
|
|
383
|
+
|
|
384
|
+
required = self.required_of(named).map(&:to_s)
|
|
385
|
+
|
|
386
|
+
unless required.empty?
|
|
387
|
+
schema["required"] = required
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
if wildcard
|
|
391
|
+
schema["additionalProperties"] = self.field_schema(wildcard)
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
else
|
|
395
|
+
nil
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# One field as a JSON Schema document.
|
|
400
|
+
#
|
|
401
|
+
# @parameter field [Hash] the descriptor.
|
|
402
|
+
# @returns [Hash] the schema.
|
|
403
|
+
def field_schema(field)
|
|
404
|
+
case self.descriptor?(field) && field["type"]
|
|
405
|
+
in "object"
|
|
406
|
+
self.object_schema(field["fields"]) || {"type" => "object"}
|
|
407
|
+
in "array"
|
|
408
|
+
{"type" => "array"}.tap do |schema|
|
|
409
|
+
items = self.items_schema(field["fields"])
|
|
410
|
+
|
|
411
|
+
if items
|
|
412
|
+
schema["items"] = items
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
in String => type
|
|
416
|
+
{"type" => type}.tap do |schema|
|
|
417
|
+
if field["enum"]
|
|
418
|
+
schema["enum"] = field["enum"]
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
else
|
|
422
|
+
{}
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# What an array's members are, which the schemas write as a map in a list of one.
|
|
427
|
+
#
|
|
428
|
+
# @parameter fields [Array | Hash | nil] what the descriptor said its members are.
|
|
429
|
+
# @returns [Hash | nil] the schema for one member, or nil where the members are unconstrained.
|
|
430
|
+
def items_schema(fields)
|
|
431
|
+
case fields
|
|
432
|
+
in [Hash => first, *]
|
|
433
|
+
self.object_schema(first)
|
|
434
|
+
in Hash => named
|
|
435
|
+
self.object_schema(named)
|
|
436
|
+
else
|
|
437
|
+
nil
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Whether this is a field's descriptor rather than a field map, which is what carrying a type says.
|
|
442
|
+
#
|
|
443
|
+
# @parameter field [Object] what the schema held.
|
|
444
|
+
# @returns [Boolean] whether it describes one field.
|
|
445
|
+
def descriptor?(field)
|
|
446
|
+
field.is_a?(Hash) && field["type"].is_a?(String)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# A document as json_schemer reads it, which is names as strings.
|
|
450
|
+
#
|
|
451
|
+
# @parameter value [Object] the document.
|
|
452
|
+
# @returns [Object] the same, with every name a string.
|
|
453
|
+
def stringify(value)
|
|
454
|
+
# The library answers symbol keys, because a Matrix document is read by pattern matching and a pattern names a symbol.
|
|
455
|
+
case value
|
|
456
|
+
in Hash => hash
|
|
457
|
+
hash.to_h{|name, held| [name.to_s, self.stringify(held)]}
|
|
458
|
+
in Array => array
|
|
459
|
+
array.map{|held| self.stringify(held)}
|
|
460
|
+
else
|
|
461
|
+
value
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
data/lib/async/matrix/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: async-matrix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0.
|
|
4
|
+
version: 4.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Kidd
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0.2'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: json_schemer
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '2.5'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '2.5'
|
|
82
96
|
- !ruby/object:Gem::Dependency
|
|
83
97
|
name: lefthook
|
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -104,8 +118,13 @@ extra_rdoc_files: []
|
|
|
104
118
|
files:
|
|
105
119
|
- CHANGELOG.md
|
|
106
120
|
- LICENSE
|
|
107
|
-
- NOTICE
|
|
108
121
|
- README.md
|
|
122
|
+
- context/design.md
|
|
123
|
+
- context/getting-started.md
|
|
124
|
+
- context/index.yaml
|
|
125
|
+
- context/overview.md
|
|
126
|
+
- context/server-server.md
|
|
127
|
+
- context/testing.md
|
|
109
128
|
- lib/async/matrix.rb
|
|
110
129
|
- lib/async/matrix/abstract/api/application_service/client.rb
|
|
111
130
|
- lib/async/matrix/abstract/api/application_service/ping.rb
|
|
@@ -6372,6 +6391,7 @@ files:
|
|
|
6372
6391
|
- lib/async/matrix/redacted.rb
|
|
6373
6392
|
- lib/async/matrix/removals.rb
|
|
6374
6393
|
- lib/async/matrix/representation.rb
|
|
6394
|
+
- lib/async/matrix/schema.rb
|
|
6375
6395
|
- lib/async/matrix/server_key.rb
|
|
6376
6396
|
- lib/async/matrix/signatures.rb
|
|
6377
6397
|
- lib/async/matrix/signatures/keyring.rb
|
data/NOTICE
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
Async::Matrix
|
|
2
|
-
Copyright 2026 General Intelligence Systems
|
|
3
|
-
|
|
4
|
-
This product includes software developed at The Matrix.org Foundation C.I.C.
|
|
5
|
-
|
|
6
|
-
--------------------------------------------------------------------------------
|
|
7
|
-
vodozemac
|
|
8
|
-
--------------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
Encryption here is driven through the vodozemac gem, which is Ruby bindings for
|
|
11
|
-
vodozemac, the Rust implementation of the Olm and Megolm cryptographic ratchets.
|
|
12
|
-
The bindings were extracted from this project and are distributed separately.
|
|
13
|
-
|
|
14
|
-
https://github.com/matrix-org/vodozemac
|
|
15
|
-
Copyright 2021-2026 The Matrix.org Foundation C.I.C.
|
|
16
|
-
Licensed under the Apache License, Version 2.0
|
|
17
|
-
|
|
18
|
-
--------------------------------------------------------------------------------
|
|
19
|
-
matrix-rust-sdk
|
|
20
|
-
--------------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
Parts of the encryption layer here follow matrix-rust-sdk, which is the
|
|
23
|
-
reference client-side implementation of Matrix end-to-end encryption and the
|
|
24
|
-
primary consumer of vodozemac. Where logic is derived from it, the derivation is
|
|
25
|
-
noted at the point it appears.
|
|
26
|
-
|
|
27
|
-
https://github.com/matrix-org/matrix-rust-sdk
|
|
28
|
-
Copyright 2020-2026 The Matrix.org Foundation C.I.C.
|
|
29
|
-
Licensed under the Apache License, Version 2.0
|
|
30
|
-
|
|
31
|
-
matrix-rust-sdk carries no NOTICE file of its own, so there is nothing further
|
|
32
|
-
to propagate under section 4(d) of the License.
|
|
33
|
-
|
|
34
|
-
--------------------------------------------------------------------------------
|
|
35
|
-
The Matrix specification
|
|
36
|
-
--------------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
The specification vendored under lib/async/matrix/e2ee/schema, and the schemas
|
|
39
|
-
the classes in lib/async/matrix are generated from, are copied from the
|
|
40
|
-
specification itself.
|
|
41
|
-
|
|
42
|
-
https://github.com/matrix-org/matrix-spec
|
|
43
|
-
Copyright 2014-2026 The Matrix.org Foundation C.I.C. and the contributors to
|
|
44
|
-
the Matrix specification
|
|
45
|
-
Licensed under the Apache License, Version 2.0
|
|
46
|
-
|
|
47
|
-
--------------------------------------------------------------------------------
|
|
48
|
-
On Fractal
|
|
49
|
-
--------------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
Fractal is a GTK Matrix client which is useful to read for how a client drives
|
|
52
|
-
encryption, and it appears in no part of this product. It is licensed
|
|
53
|
-
GPL-3.0-or-later, which is incompatible with the Apache License this work is
|
|
54
|
-
distributed under, so no code or comment may be copied from it. Reading it to
|
|
55
|
-
learn which operations a flow needs is fine; carrying its expression across is
|
|
56
|
-
not.
|