@laserdata/laser-sdk 0.4.1 → 0.5.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.
- package/README.md +42 -2
- package/dist/agent/reliable-consumer.d.ts.map +1 -1
- package/dist/agent/reliable-consumer.js +5 -1
- package/dist/agent/reliable-consumer.js.map +1 -1
- package/dist/bridges/a2a.d.ts +1 -1
- package/dist/bridges/a2a.js +1 -1
- package/dist/client/capabilities.d.ts +15 -2
- package/dist/client/capabilities.d.ts.map +1 -1
- package/dist/client/capabilities.js +22 -1
- package/dist/client/capabilities.js.map +1 -1
- package/dist/client/errors.d.ts +23 -1
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +34 -0
- package/dist/client/errors.js.map +1 -1
- package/dist/client/laser.d.ts +6 -0
- package/dist/client/laser.d.ts.map +1 -1
- package/dist/client/laser.js +25 -0
- package/dist/client/laser.js.map +1 -1
- package/dist/client/managed.d.ts +1 -0
- package/dist/client/managed.d.ts.map +1 -1
- package/dist/client/managed.js +15 -2
- package/dist/client/managed.js.map +1 -1
- package/dist/iggy/apache-iggy.d.ts +63 -2
- package/dist/iggy/apache-iggy.d.ts.map +1 -1
- package/dist/iggy/apache-iggy.js +343 -14
- package/dist/iggy/apache-iggy.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/managed/filters.d.ts +358 -0
- package/dist/managed/filters.d.ts.map +1 -0
- package/dist/managed/filters.js +1913 -0
- package/dist/managed/filters.js.map +1 -0
- package/dist/stream/consumer.d.ts +11 -1
- package/dist/stream/consumer.d.ts.map +1 -1
- package/dist/stream/consumer.js +82 -9
- package/dist/stream/consumer.js.map +1 -1
- package/dist/stream/topic.d.ts +2 -0
- package/dist/stream/topic.d.ts.map +1 -1
- package/dist/stream/topic.js +6 -0
- package/dist/stream/topic.js.map +1 -1
- package/dist/wire/authz.d.ts +1 -1
- package/dist/wire/authz.d.ts.map +1 -1
- package/dist/wire/authz.js +12 -3
- package/dist/wire/authz.js.map +1 -1
- package/dist/wire/codes.d.ts +17 -0
- package/dist/wire/codes.d.ts.map +1 -1
- package/dist/wire/codes.js +17 -0
- package/dist/wire/codes.js.map +1 -1
- package/dist/wire/commands.d.ts +12 -1
- package/dist/wire/commands.d.ts.map +1 -1
- package/dist/wire/commands.js +13 -1
- package/dist/wire/commands.js.map +1 -1
- package/dist/wire/control.d.ts +4 -0
- package/dist/wire/control.d.ts.map +1 -1
- package/dist/wire/control.js +5 -0
- package/dist/wire/control.js.map +1 -1
- package/dist/wire/filter-codecs.d.ts +16 -0
- package/dist/wire/filter-codecs.d.ts.map +1 -0
- package/dist/wire/filter-codecs.js +519 -0
- package/dist/wire/filter-codecs.js.map +1 -0
- package/dist/wire/filter-eval.d.ts +75 -0
- package/dist/wire/filter-eval.d.ts.map +1 -0
- package/dist/wire/filter-eval.js +899 -0
- package/dist/wire/filter-eval.js.map +1 -0
- package/dist/wire/filter.d.ts +772 -0
- package/dist/wire/filter.d.ts.map +1 -0
- package/dist/wire/filter.js +2869 -0
- package/dist/wire/filter.js.map +1 -0
- package/dist/wire/hello.d.ts +18 -0
- package/dist/wire/hello.d.ts.map +1 -1
- package/dist/wire/hello.js +29 -3
- package/dist/wire/hello.js.map +1 -1
- package/dist/wire/http.d.ts +22 -0
- package/dist/wire/http.d.ts.map +1 -1
- package/dist/wire/http.js +54 -47
- package/dist/wire/http.js.map +1 -1
- package/dist/wire/limits.d.ts +25 -0
- package/dist/wire/limits.d.ts.map +1 -1
- package/dist/wire/limits.js +25 -0
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/result.d.ts +4 -0
- package/dist/wire/result.d.ts.map +1 -1
- package/dist/wire/result.js +50 -0
- package/dist/wire/result.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,2869 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
2
|
+
import { Token, Type } from "cborg";
|
|
3
|
+
import { CodecError, InvalidError } from "../client/errors.js";
|
|
4
|
+
import { expectArray, expectBytes, expectMap, expectString, expectU32, expectU64, field, singleVariantTag } from "./cbor.js";
|
|
5
|
+
import { decodeGrant, encodeGrant } from "./authz.js";
|
|
6
|
+
import { FILTER_OP_VERSION } from "./codes.js";
|
|
7
|
+
import { MAX_FILTER_BYTES, MAX_FILTER_DEPTH, MAX_FILTER_DESCRIPTION_BYTES, MAX_FILTER_LIST_ITEMS, MAX_FILTER_NAME_BYTES, MAX_FILTER_NODES, MAX_FILTER_PATH_BYTES, MAX_FILTER_PATH_SEGMENTS, MAX_FILTER_PREVIEW_EXAMINED, MAX_FILTER_PREVIEW_RECORDS, MAX_FILTER_SAMPLE_BYTES, MAX_FILTER_SAMPLE_HEADERS, MAX_FILTER_SOURCE_NAME_BYTES, MAX_FILTER_STRING_BYTES, MAX_FILTER_CATALOG_PAGE, MAX_FILTERED_PAGE_BYTES, MAX_FILTERED_PAGE_RECORDS } from "./limits.js";
|
|
8
|
+
import { resultCodeFromWord, resultCodeWord } from "./result.js";
|
|
9
|
+
import { decodeTypedValue } from "./schema.js";
|
|
10
|
+
/** The catalog byte limit a command logged before the field existed replays with. */
|
|
11
|
+
const DEFAULT_MAX_CATALOG_BYTES = 64n * 1024n * 1024n;
|
|
12
|
+
export const FILTER_EVALUATOR_VERSION = 1;
|
|
13
|
+
export const MAX_REGEX_PREDICATES = 4;
|
|
14
|
+
const DIGEST_DOMAIN = new TextEncoder().encode("agdx.consumer-filter.v1\0");
|
|
15
|
+
const UTF8 = new TextEncoder();
|
|
16
|
+
const CMP_OPS = new Set([
|
|
17
|
+
"eq",
|
|
18
|
+
"ne",
|
|
19
|
+
"lt",
|
|
20
|
+
"lte",
|
|
21
|
+
"gt",
|
|
22
|
+
"gte",
|
|
23
|
+
"in",
|
|
24
|
+
"contains",
|
|
25
|
+
"prefix"
|
|
26
|
+
]);
|
|
27
|
+
const TIMESTAMP_FORMATS = new Set([
|
|
28
|
+
"rfc3339",
|
|
29
|
+
"epoch_seconds",
|
|
30
|
+
"epoch_millis",
|
|
31
|
+
"epoch_micros"
|
|
32
|
+
]);
|
|
33
|
+
const STOP_REASONS = new Set([
|
|
34
|
+
"filled",
|
|
35
|
+
"budget",
|
|
36
|
+
"end_of_visible",
|
|
37
|
+
"fault",
|
|
38
|
+
"oversized_record"
|
|
39
|
+
]);
|
|
40
|
+
const FAULT_REASONS = new Set([
|
|
41
|
+
"malformed",
|
|
42
|
+
"too_large",
|
|
43
|
+
"too_deep",
|
|
44
|
+
"missing_schema",
|
|
45
|
+
"schema_not_allowed",
|
|
46
|
+
"schema_mismatch",
|
|
47
|
+
"foreign_codec",
|
|
48
|
+
"type_mismatch"
|
|
49
|
+
]);
|
|
50
|
+
const VERDICTS = new Set(["selected", "rejected", "fault"]);
|
|
51
|
+
const TRUTHS = new Set(["match", "no_match", "unknown"]);
|
|
52
|
+
const FILTER_STATES = new Set([
|
|
53
|
+
"active",
|
|
54
|
+
"archived",
|
|
55
|
+
"dropped"
|
|
56
|
+
]);
|
|
57
|
+
const FILTER_ERROR_REASONS = new Set([
|
|
58
|
+
"invalid_request",
|
|
59
|
+
"unsupported",
|
|
60
|
+
"version_skew",
|
|
61
|
+
"not_found",
|
|
62
|
+
"conflict",
|
|
63
|
+
"source_changed",
|
|
64
|
+
"membership_stale",
|
|
65
|
+
"not_primary",
|
|
66
|
+
"catalog_unavailable",
|
|
67
|
+
"revision_disabled",
|
|
68
|
+
"too_large",
|
|
69
|
+
"unauthenticated",
|
|
70
|
+
"forbidden",
|
|
71
|
+
"unavailable",
|
|
72
|
+
"capacity_exhausted",
|
|
73
|
+
"backend",
|
|
74
|
+
"unknown"
|
|
75
|
+
]);
|
|
76
|
+
/**
|
|
77
|
+
* A validated location inside a decoded payload. Keys are separated by `.`,
|
|
78
|
+
* array elements are selected with `[n]`, and a literal `.`, `[`, `]`, or `\`
|
|
79
|
+
* inside a key is escaped with `\`. The text form is canonical.
|
|
80
|
+
*/
|
|
81
|
+
export class FieldPath {
|
|
82
|
+
segments;
|
|
83
|
+
constructor(segments) {
|
|
84
|
+
this.segments = segments;
|
|
85
|
+
}
|
|
86
|
+
static parse(text) {
|
|
87
|
+
if (text.length === 0)
|
|
88
|
+
throw new InvalidError("field path must not be empty");
|
|
89
|
+
const bytes = UTF8.encode(text).byteLength;
|
|
90
|
+
if (bytes > MAX_FILTER_PATH_BYTES) {
|
|
91
|
+
throw new InvalidError(`field path is ${String(bytes)}B, exceeds cap ${String(MAX_FILTER_PATH_BYTES)}B`);
|
|
92
|
+
}
|
|
93
|
+
const segments = new PathParser(text).segments();
|
|
94
|
+
if (segments.length > MAX_FILTER_PATH_SEGMENTS) {
|
|
95
|
+
throw new InvalidError(`field path \`${text}\` has ${String(segments.length)} segments, exceeds cap ${String(MAX_FILTER_PATH_SEGMENTS)}`);
|
|
96
|
+
}
|
|
97
|
+
return new FieldPath(segments);
|
|
98
|
+
}
|
|
99
|
+
toString() {
|
|
100
|
+
return this.segments
|
|
101
|
+
.map((segment, position) => {
|
|
102
|
+
if (segment.kind === "index")
|
|
103
|
+
return `[${String(segment.index)}]`;
|
|
104
|
+
const escaped = segment.key.replace(/[.[\]\\]/g, (character) => `\\${character}`);
|
|
105
|
+
return position > 0 ? `.${escaped}` : escaped;
|
|
106
|
+
})
|
|
107
|
+
.join("");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
class PathParser {
|
|
111
|
+
text;
|
|
112
|
+
position = 0;
|
|
113
|
+
constructor(text) {
|
|
114
|
+
this.text = text;
|
|
115
|
+
}
|
|
116
|
+
segments() {
|
|
117
|
+
const segments = [this.peek() === "[" ? this.index() : this.key()];
|
|
118
|
+
while (this.position < this.text.length) {
|
|
119
|
+
const character = this.peek();
|
|
120
|
+
if (character === ".") {
|
|
121
|
+
this.position += 1;
|
|
122
|
+
segments.push(this.key());
|
|
123
|
+
}
|
|
124
|
+
else if (character === "[") {
|
|
125
|
+
segments.push(this.index());
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
throw this.invalid("expected `.` or `[` after a segment");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return segments;
|
|
132
|
+
}
|
|
133
|
+
key() {
|
|
134
|
+
let key = "";
|
|
135
|
+
while (this.position < this.text.length) {
|
|
136
|
+
const character = this.peek();
|
|
137
|
+
if (character === "." || character === "[")
|
|
138
|
+
break;
|
|
139
|
+
if (character === "]")
|
|
140
|
+
throw this.invalid("unescaped `]` inside a key");
|
|
141
|
+
if (character === "\\") {
|
|
142
|
+
this.position += 1;
|
|
143
|
+
const escaped = this.peek();
|
|
144
|
+
if (escaped !== "." && escaped !== "[" && escaped !== "]" && escaped !== "\\") {
|
|
145
|
+
throw this.invalid("`\\` must escape `.`, `[`, `]`, or `\\`");
|
|
146
|
+
}
|
|
147
|
+
key += escaped;
|
|
148
|
+
this.position += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const codePoint = this.text.codePointAt(this.position) ?? 0;
|
|
152
|
+
const next = String.fromCodePoint(codePoint);
|
|
153
|
+
key += next;
|
|
154
|
+
this.position += next.length;
|
|
155
|
+
}
|
|
156
|
+
if (key.length === 0)
|
|
157
|
+
throw this.invalid("empty key");
|
|
158
|
+
return { kind: "key", key };
|
|
159
|
+
}
|
|
160
|
+
index() {
|
|
161
|
+
this.position += 1;
|
|
162
|
+
const start = this.position;
|
|
163
|
+
while (/[0-9]/.test(this.peek() ?? ""))
|
|
164
|
+
this.position += 1;
|
|
165
|
+
const digits = this.text.slice(start, this.position);
|
|
166
|
+
if (this.peek() !== "]")
|
|
167
|
+
throw this.invalid("an index must be digits closed by `]`");
|
|
168
|
+
this.position += 1;
|
|
169
|
+
if (digits.length === 0 || (digits.length > 1 && digits.startsWith("0"))) {
|
|
170
|
+
throw this.invalid("an index must be a canonical decimal number");
|
|
171
|
+
}
|
|
172
|
+
const index = Number(digits);
|
|
173
|
+
if (!Number.isSafeInteger(index) || index > 0xffff_ffff) {
|
|
174
|
+
throw this.invalid("an index must fit in 32 bits");
|
|
175
|
+
}
|
|
176
|
+
return { kind: "index", index };
|
|
177
|
+
}
|
|
178
|
+
peek() {
|
|
179
|
+
return this.text[this.position];
|
|
180
|
+
}
|
|
181
|
+
invalid(reason) {
|
|
182
|
+
const byte = UTF8.encode(this.text.slice(0, this.position)).byteLength;
|
|
183
|
+
return new InvalidError(`field path \`${this.text}\` is invalid at byte ${String(byte)}: ${reason}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Builders for the `FilterExpr` type. */
|
|
187
|
+
export const FilterExpr = {
|
|
188
|
+
all(children) {
|
|
189
|
+
return { kind: "all", children };
|
|
190
|
+
},
|
|
191
|
+
any(children) {
|
|
192
|
+
return { kind: "any", children };
|
|
193
|
+
},
|
|
194
|
+
negate(child) {
|
|
195
|
+
return { kind: "not", child };
|
|
196
|
+
},
|
|
197
|
+
pred(fieldName, op, value) {
|
|
198
|
+
return { kind: "pred", predicate: { field: fieldName, op, value: typedLiteral(value) } };
|
|
199
|
+
},
|
|
200
|
+
predAs(fieldName, op, value, coerce) {
|
|
201
|
+
return {
|
|
202
|
+
kind: "pred_as",
|
|
203
|
+
predicate: { pred: { field: fieldName, op, value: typedLiteral(value) }, coerce }
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
present(path) {
|
|
207
|
+
return { kind: "present", path: FieldPath.parse(path) };
|
|
208
|
+
},
|
|
209
|
+
absent(path) {
|
|
210
|
+
return { kind: "absent", path: FieldPath.parse(path) };
|
|
211
|
+
},
|
|
212
|
+
header(key, op, value) {
|
|
213
|
+
return { kind: "header", predicate: { key, op, value: typedLiteral(value) } };
|
|
214
|
+
},
|
|
215
|
+
text(fieldName, kind, pattern, caseInsensitive = false) {
|
|
216
|
+
return { kind: "text", predicate: textPredicate(fieldName, kind, pattern, caseInsensitive) };
|
|
217
|
+
},
|
|
218
|
+
headerText(key, kind, pattern, caseInsensitive = false) {
|
|
219
|
+
return { kind: "header_text", predicate: textPredicate(key, kind, pattern, caseInsensitive) };
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
function textPredicate(fieldName, kind, pattern, caseInsensitive) {
|
|
223
|
+
return caseInsensitive
|
|
224
|
+
? { field: fieldName, kind, pattern, caseInsensitive: true }
|
|
225
|
+
: { field: fieldName, kind, pattern };
|
|
226
|
+
}
|
|
227
|
+
/** Builders for the `ConsumerFilter` type. */
|
|
228
|
+
export const ConsumerFilter = {
|
|
229
|
+
/** Decode the payload as JSON, with the default `stop` fault policy. */
|
|
230
|
+
json(expr, faultPolicy = "stop") {
|
|
231
|
+
return newFilter(expr, "json", faultPolicy);
|
|
232
|
+
},
|
|
233
|
+
cbor(expr, faultPolicy = "stop") {
|
|
234
|
+
return newFilter(expr, "cbor", faultPolicy);
|
|
235
|
+
},
|
|
236
|
+
avro(expr, schemaRefs, faultPolicy = "stop") {
|
|
237
|
+
return {
|
|
238
|
+
...newFilter(expr, "avro", faultPolicy),
|
|
239
|
+
schemaRefs: [...new Set(schemaRefs)].sort((left, right) => left - right)
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
protobuf(expr, schemaRefs, faultPolicy = "stop") {
|
|
243
|
+
return {
|
|
244
|
+
...newFilter(expr, "protobuf", faultPolicy),
|
|
245
|
+
schemaRefs: [...new Set(schemaRefs)].sort((left, right) => left - right)
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
/** Never decode the payload. Only header predicates are allowed. */
|
|
249
|
+
headersOnly(expr, faultPolicy = "stop") {
|
|
250
|
+
return newFilter(expr, "headers_only", faultPolicy);
|
|
251
|
+
},
|
|
252
|
+
/** The same filter under another policy for records that do not decode. */
|
|
253
|
+
withFaultPolicy(filter, faultPolicy) {
|
|
254
|
+
return { ...filter, faultPolicy };
|
|
255
|
+
},
|
|
256
|
+
/** The same filter under another policy for records of another codec. */
|
|
257
|
+
withForeignPolicy(filter, foreignPolicy) {
|
|
258
|
+
return { ...filter, foreignPolicy };
|
|
259
|
+
},
|
|
260
|
+
/** The same filter under another policy for a field of an unexpected type. */
|
|
261
|
+
withMismatchPolicy(filter, mismatchPolicy) {
|
|
262
|
+
return { ...filter, mismatchPolicy };
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function newFilter(expr, codec, faultPolicy) {
|
|
266
|
+
return {
|
|
267
|
+
v: FILTER_OP_VERSION,
|
|
268
|
+
evaluatorVersion: FILTER_EVALUATOR_VERSION,
|
|
269
|
+
expr,
|
|
270
|
+
codec,
|
|
271
|
+
faultPolicy,
|
|
272
|
+
schemaRefs: []
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function typedLiteral(value) {
|
|
276
|
+
if (value === null)
|
|
277
|
+
return { kind: "null" };
|
|
278
|
+
if (typeof value === "string")
|
|
279
|
+
return { kind: "string", value };
|
|
280
|
+
if (typeof value === "boolean")
|
|
281
|
+
return { kind: "boolean", value };
|
|
282
|
+
if (typeof value === "bigint")
|
|
283
|
+
return { kind: "long", value };
|
|
284
|
+
if (typeof value === "number") {
|
|
285
|
+
return Number.isInteger(value)
|
|
286
|
+
? { kind: "long", value: BigInt(value) }
|
|
287
|
+
: { kind: "double", value };
|
|
288
|
+
}
|
|
289
|
+
if (Array.isArray(value)) {
|
|
290
|
+
return { kind: "list", value: value.map(typedLiteral) };
|
|
291
|
+
}
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
export function filterExprReadsPayload(expr) {
|
|
295
|
+
switch (expr.kind) {
|
|
296
|
+
case "all":
|
|
297
|
+
case "any":
|
|
298
|
+
return expr.children.some(filterExprReadsPayload);
|
|
299
|
+
case "not":
|
|
300
|
+
return filterExprReadsPayload(expr.child);
|
|
301
|
+
case "header":
|
|
302
|
+
case "header_text":
|
|
303
|
+
return false;
|
|
304
|
+
case "pred":
|
|
305
|
+
case "pred_as":
|
|
306
|
+
case "present":
|
|
307
|
+
case "absent":
|
|
308
|
+
case "text":
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
export function filterExprReadsHeaders(expr) {
|
|
313
|
+
switch (expr.kind) {
|
|
314
|
+
case "all":
|
|
315
|
+
case "any":
|
|
316
|
+
return expr.children.some(filterExprReadsHeaders);
|
|
317
|
+
case "not":
|
|
318
|
+
return filterExprReadsHeaders(expr.child);
|
|
319
|
+
case "header":
|
|
320
|
+
case "header_text":
|
|
321
|
+
return true;
|
|
322
|
+
case "pred":
|
|
323
|
+
case "pred_as":
|
|
324
|
+
case "present":
|
|
325
|
+
case "absent":
|
|
326
|
+
case "text":
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* SHA-256 over a domain tag and the canonical JSON encoding of the filter, byte
|
|
332
|
+
* for byte what every server and SDK computes.
|
|
333
|
+
*/
|
|
334
|
+
export function consumerFilterDigest(filter) {
|
|
335
|
+
const encoded = UTF8.encode(consumerFilterJson(filter));
|
|
336
|
+
const input = new Uint8Array(DIGEST_DOMAIN.byteLength + encoded.byteLength);
|
|
337
|
+
input.set(DIGEST_DOMAIN, 0);
|
|
338
|
+
input.set(encoded, DIGEST_DOMAIN.byteLength);
|
|
339
|
+
return sha256(input);
|
|
340
|
+
}
|
|
341
|
+
/** The canonical JSON text of a filter, the encoding its digest covers. */
|
|
342
|
+
export function consumerFilterJson(filter) {
|
|
343
|
+
return writeJson(encodeConsumerFilterTree(filter, "json"));
|
|
344
|
+
}
|
|
345
|
+
/** Parse a filter from its canonical JSON text. */
|
|
346
|
+
export function decodeConsumerFilterJson(text) {
|
|
347
|
+
return decodeConsumerFilter(parseCanonicalJson(text), "ConsumerFilter");
|
|
348
|
+
}
|
|
349
|
+
export function validateConsumerFilter(filter) {
|
|
350
|
+
if (filter.v !== FILTER_OP_VERSION) {
|
|
351
|
+
throw new InvalidError(`filter version ${String(filter.v)} is not supported, expected ${String(FILTER_OP_VERSION)}`);
|
|
352
|
+
}
|
|
353
|
+
if (filter.evaluatorVersion !== FILTER_EVALUATOR_VERSION) {
|
|
354
|
+
throw new InvalidError(`evaluator version ${String(filter.evaluatorVersion)} is not supported, expected ${String(FILTER_EVALUATOR_VERSION)}`);
|
|
355
|
+
}
|
|
356
|
+
const schemaCodec = filter.codec === "avro" || filter.codec === "protobuf";
|
|
357
|
+
if (schemaCodec === (filter.schemaRefs.length === 0)) {
|
|
358
|
+
throw new InvalidError("Avro and Protobuf require schema_refs, other codecs require no schema_refs");
|
|
359
|
+
}
|
|
360
|
+
filter.schemaRefs.forEach((id, index) => {
|
|
361
|
+
if (!Number.isInteger(id) ||
|
|
362
|
+
id < 0 ||
|
|
363
|
+
id > 0xffffffff ||
|
|
364
|
+
(index > 0 && id <= (filter.schemaRefs[index - 1] ?? -1))) {
|
|
365
|
+
throw new InvalidError("schema_refs must be sorted distinct uint32 values");
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
const encodedBytes = UTF8.encode(consumerFilterJson(filter)).byteLength;
|
|
369
|
+
if (encodedBytes > MAX_FILTER_BYTES) {
|
|
370
|
+
throw new InvalidError(`filter is ${String(encodedBytes)}B, exceeds cap ${String(MAX_FILTER_BYTES)}B`);
|
|
371
|
+
}
|
|
372
|
+
if (filter.codec === "unknown") {
|
|
373
|
+
throw new InvalidError("the filter codec is not supported by this build");
|
|
374
|
+
}
|
|
375
|
+
if (filter.codec === "headers_only" && filterExprReadsPayload(filter.expr)) {
|
|
376
|
+
throw new InvalidError("a headers_only filter cannot use payload predicates");
|
|
377
|
+
}
|
|
378
|
+
const nodes = { count: 0 };
|
|
379
|
+
validateNode(filter.expr, 1, nodes);
|
|
380
|
+
if (compiledTextPredicates(filter.expr) > MAX_REGEX_PREDICATES) {
|
|
381
|
+
throw new InvalidError(`a filter holds at most ${String(MAX_REGEX_PREDICATES)} compiled text predicates (glob or regex)`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function compiledTextPredicates(expr) {
|
|
385
|
+
switch (expr.kind) {
|
|
386
|
+
case "all":
|
|
387
|
+
case "any":
|
|
388
|
+
return expr.children.reduce((total, child) => total + compiledTextPredicates(child), 0);
|
|
389
|
+
case "not":
|
|
390
|
+
return compiledTextPredicates(expr.child);
|
|
391
|
+
case "text":
|
|
392
|
+
case "header_text":
|
|
393
|
+
return Number(expr.predicate.kind === "regex" || expr.predicate.kind === "glob");
|
|
394
|
+
case "header":
|
|
395
|
+
case "absent":
|
|
396
|
+
case "pred":
|
|
397
|
+
case "present":
|
|
398
|
+
case "pred_as":
|
|
399
|
+
return 0;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function validateNode(expr, depth, nodes) {
|
|
403
|
+
if (depth > MAX_FILTER_DEPTH) {
|
|
404
|
+
throw new InvalidError(`filter nesting exceeds depth ${String(MAX_FILTER_DEPTH)}`);
|
|
405
|
+
}
|
|
406
|
+
countNodes(nodes, 1);
|
|
407
|
+
switch (expr.kind) {
|
|
408
|
+
case "all":
|
|
409
|
+
case "any":
|
|
410
|
+
if (expr.children.length === 0) {
|
|
411
|
+
throw new InvalidError("`all` and `any` need at least one child");
|
|
412
|
+
}
|
|
413
|
+
for (const child of expr.children)
|
|
414
|
+
validateNode(child, depth + 1, nodes);
|
|
415
|
+
return;
|
|
416
|
+
case "not":
|
|
417
|
+
validateNode(expr.child, depth + 1, nodes);
|
|
418
|
+
return;
|
|
419
|
+
case "pred":
|
|
420
|
+
FieldPath.parse(expr.predicate.field);
|
|
421
|
+
validateLiteral(expr.predicate.op, expr.predicate.value, "payload", nodes);
|
|
422
|
+
return;
|
|
423
|
+
case "pred_as":
|
|
424
|
+
FieldPath.parse(expr.predicate.pred.field);
|
|
425
|
+
validateCoerced(expr.predicate, nodes);
|
|
426
|
+
return;
|
|
427
|
+
case "present":
|
|
428
|
+
case "absent":
|
|
429
|
+
return;
|
|
430
|
+
case "header":
|
|
431
|
+
validateHeaderKey(expr.predicate.key);
|
|
432
|
+
validateLiteral(expr.predicate.op, expr.predicate.value, "header", nodes);
|
|
433
|
+
return;
|
|
434
|
+
case "text":
|
|
435
|
+
FieldPath.parse(expr.predicate.field);
|
|
436
|
+
validateTextPredicate(expr.predicate);
|
|
437
|
+
return;
|
|
438
|
+
case "header_text":
|
|
439
|
+
validateHeaderKey(expr.predicate.field);
|
|
440
|
+
validateTextPredicate(expr.predicate);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function validateHeaderKey(key) {
|
|
444
|
+
const keyBytes = UTF8.encode(key).byteLength;
|
|
445
|
+
if (keyBytes === 0 || keyBytes > MAX_FILTER_PATH_BYTES) {
|
|
446
|
+
throw new InvalidError(`header key must be 1..=${String(MAX_FILTER_PATH_BYTES)} bytes`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// The server compiles regexes with the Rust engine and is the authority on
|
|
450
|
+
// their syntax. These checks refuse what no SDK accepts, so a filter fails here
|
|
451
|
+
// before it travels.
|
|
452
|
+
function validateTextPredicate(predicate) {
|
|
453
|
+
if (UTF8.encode(predicate.pattern).byteLength > MAX_FILTER_STRING_BYTES) {
|
|
454
|
+
throw new InvalidError(`a text pattern exceeds ${String(MAX_FILTER_STRING_BYTES)}B`);
|
|
455
|
+
}
|
|
456
|
+
if (predicate.kind === "glob" && globTokens(predicate.pattern) === undefined) {
|
|
457
|
+
throw new InvalidError("a glob pattern cannot end with an unescaped backslash");
|
|
458
|
+
}
|
|
459
|
+
if (predicate.kind === "regex") {
|
|
460
|
+
const construct = unportableRegexConstruct(predicate.pattern);
|
|
461
|
+
if (construct !== undefined) {
|
|
462
|
+
throw new InvalidError(`regex ${construct} is not supported, write the pattern without it`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
export function globTokens(pattern) {
|
|
467
|
+
const tokens = [];
|
|
468
|
+
const characters = Array.from(pattern);
|
|
469
|
+
for (let index = 0; index < characters.length; index += 1) {
|
|
470
|
+
const character = characters[index];
|
|
471
|
+
if (character === "*")
|
|
472
|
+
tokens.push({ kind: "run" });
|
|
473
|
+
else if (character === "?")
|
|
474
|
+
tokens.push({ kind: "one" });
|
|
475
|
+
else if (character === "\\") {
|
|
476
|
+
const escaped = characters[index + 1];
|
|
477
|
+
if (escaped === undefined)
|
|
478
|
+
return undefined;
|
|
479
|
+
tokens.push({ kind: "literal", value: escaped });
|
|
480
|
+
index += 1;
|
|
481
|
+
}
|
|
482
|
+
else
|
|
483
|
+
tokens.push({ kind: "literal", value: character ?? "" });
|
|
484
|
+
}
|
|
485
|
+
return tokens;
|
|
486
|
+
}
|
|
487
|
+
export function unportableRegexConstruct(pattern) {
|
|
488
|
+
const characters = Array.from(pattern);
|
|
489
|
+
let inClass = false;
|
|
490
|
+
for (let index = 0; index < characters.length; index += 1) {
|
|
491
|
+
const character = characters[index];
|
|
492
|
+
if (character === "\\") {
|
|
493
|
+
const escaped = characters[index + 1] ?? "";
|
|
494
|
+
if (escaped >= "1" && escaped <= "9")
|
|
495
|
+
return "backreferences";
|
|
496
|
+
if (escaped === "k")
|
|
497
|
+
return "named backreferences";
|
|
498
|
+
index += 1;
|
|
499
|
+
}
|
|
500
|
+
else if (character === "[")
|
|
501
|
+
inClass = true;
|
|
502
|
+
else if (character === "]")
|
|
503
|
+
inClass = false;
|
|
504
|
+
else if (character === "(" && !inClass && characters[index + 1] === "?") {
|
|
505
|
+
if (characters[index + 2] !== ":")
|
|
506
|
+
return "groups starting with (? other than (?:";
|
|
507
|
+
index += 1;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
function countNodes(nodes, added) {
|
|
513
|
+
nodes.count += added;
|
|
514
|
+
if (nodes.count > MAX_FILTER_NODES) {
|
|
515
|
+
throw new InvalidError(`filter has more than ${String(MAX_FILTER_NODES)} nodes`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function validateLiteral(op, value, site, nodes) {
|
|
519
|
+
if (value.kind === "null") {
|
|
520
|
+
if ((op === "eq" || op === "ne") && site === "payload")
|
|
521
|
+
return;
|
|
522
|
+
throw new InvalidError(site === "payload"
|
|
523
|
+
? "a null literal only supports eq and ne"
|
|
524
|
+
: "a header predicate cannot compare against null");
|
|
525
|
+
}
|
|
526
|
+
if (op === "in") {
|
|
527
|
+
if (value.kind !== "list")
|
|
528
|
+
throw new InvalidError("`in` expects a list literal");
|
|
529
|
+
validateListSize(value.value.length);
|
|
530
|
+
countNodes(nodes, value.value.length);
|
|
531
|
+
for (const item of value.value) {
|
|
532
|
+
if (item.kind === "null" || item.kind === "list") {
|
|
533
|
+
throw new InvalidError("`in` list items must be non-null scalars");
|
|
534
|
+
}
|
|
535
|
+
validateScalar(item);
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (value.kind === "list")
|
|
540
|
+
throw new InvalidError("a list literal only supports `in`");
|
|
541
|
+
if ((op === "lt" || op === "lte" || op === "gt" || op === "gte") && value.kind === "boolean") {
|
|
542
|
+
throw new InvalidError("ordered comparisons need a number or string literal");
|
|
543
|
+
}
|
|
544
|
+
if (op === "prefix" && value.kind !== "string") {
|
|
545
|
+
throw new InvalidError("`prefix` expects a string literal");
|
|
546
|
+
}
|
|
547
|
+
validateScalar(value);
|
|
548
|
+
}
|
|
549
|
+
function validateListSize(length) {
|
|
550
|
+
if (length === 0 || length > MAX_FILTER_LIST_ITEMS) {
|
|
551
|
+
throw new InvalidError(`an \`in\` list needs 1..=${String(MAX_FILTER_LIST_ITEMS)} items`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function validateScalar(value) {
|
|
555
|
+
switch (value.kind) {
|
|
556
|
+
case "boolean":
|
|
557
|
+
case "int":
|
|
558
|
+
case "long":
|
|
559
|
+
return;
|
|
560
|
+
case "double":
|
|
561
|
+
if (!Number.isFinite(value.value))
|
|
562
|
+
throw new InvalidError("a double literal must be finite");
|
|
563
|
+
return;
|
|
564
|
+
case "string":
|
|
565
|
+
if (UTF8.encode(value.value).byteLength > MAX_FILTER_STRING_BYTES) {
|
|
566
|
+
throw new InvalidError(`a string literal exceeds ${String(MAX_FILTER_STRING_BYTES)}B`);
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
case "null":
|
|
570
|
+
case "float":
|
|
571
|
+
case "list":
|
|
572
|
+
case "time_micros":
|
|
573
|
+
case "timestamp_micros":
|
|
574
|
+
case "timestamp_tz_micros":
|
|
575
|
+
case "date":
|
|
576
|
+
case "decimal":
|
|
577
|
+
case "uuid":
|
|
578
|
+
case "fixed":
|
|
579
|
+
case "binary":
|
|
580
|
+
case "struct":
|
|
581
|
+
case "map":
|
|
582
|
+
throw new InvalidError("supported literals are null, boolean, int, long, double, string, and list");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function validateCoerced(coerced, nodes) {
|
|
586
|
+
const op = coerced.pred.op;
|
|
587
|
+
if (op === "contains" || op === "prefix") {
|
|
588
|
+
throw new InvalidError("a coerced predicate supports eq, ne, lt, lte, gt, gte, and in");
|
|
589
|
+
}
|
|
590
|
+
let items;
|
|
591
|
+
if (coerced.pred.value.kind === "list" && op === "in") {
|
|
592
|
+
validateListSize(coerced.pred.value.value.length);
|
|
593
|
+
countNodes(nodes, coerced.pred.value.value.length);
|
|
594
|
+
items = coerced.pred.value.value;
|
|
595
|
+
}
|
|
596
|
+
else if (op === "in") {
|
|
597
|
+
throw new InvalidError("`in` expects a list literal");
|
|
598
|
+
}
|
|
599
|
+
else if (coerced.pred.value.kind === "list") {
|
|
600
|
+
throw new InvalidError("a list literal only supports `in`");
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
items = [coerced.pred.value];
|
|
604
|
+
}
|
|
605
|
+
for (const item of items) {
|
|
606
|
+
if (coercedLiteralParses(coerced.coerce, item))
|
|
607
|
+
continue;
|
|
608
|
+
throw new InvalidError(`literal ${typedValueLabel(item)} does not parse under its coercion`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
// Mirrors the evaluator's compile step, so validation and evaluation agree on
|
|
612
|
+
// which literals a coercion accepts.
|
|
613
|
+
function coercedLiteralParses(coerce, value) {
|
|
614
|
+
if (coerce.kind === "number") {
|
|
615
|
+
if (value.kind === "int" || value.kind === "long")
|
|
616
|
+
return true;
|
|
617
|
+
return value.kind === "string" && ExactDecimal.parse(value.value) !== undefined;
|
|
618
|
+
}
|
|
619
|
+
if (value.kind === "string") {
|
|
620
|
+
return timestampFromText(coerce.format, value.value) !== undefined;
|
|
621
|
+
}
|
|
622
|
+
if (value.kind === "int" || value.kind === "long") {
|
|
623
|
+
return timestampFromInteger(coerce.format, BigInt(value.value)) !== undefined;
|
|
624
|
+
}
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
function typedValueLabel(value) {
|
|
628
|
+
switch (value.kind) {
|
|
629
|
+
case "string":
|
|
630
|
+
return JSON.stringify(value.value);
|
|
631
|
+
case "null":
|
|
632
|
+
return "null";
|
|
633
|
+
case "boolean":
|
|
634
|
+
case "int":
|
|
635
|
+
case "long":
|
|
636
|
+
case "double":
|
|
637
|
+
case "float":
|
|
638
|
+
return String(value.value);
|
|
639
|
+
case "list":
|
|
640
|
+
case "time_micros":
|
|
641
|
+
case "timestamp_micros":
|
|
642
|
+
case "timestamp_tz_micros":
|
|
643
|
+
case "date":
|
|
644
|
+
case "decimal":
|
|
645
|
+
case "uuid":
|
|
646
|
+
case "fixed":
|
|
647
|
+
case "binary":
|
|
648
|
+
case "struct":
|
|
649
|
+
case "map":
|
|
650
|
+
return value.kind;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const MAX_DECIMAL_DIGITS = 512;
|
|
654
|
+
/**
|
|
655
|
+
* An exact decimal number, compared without floating-point rounding. Leading
|
|
656
|
+
* and trailing zeros are normalized away, so `1.50` equals `1.5` and `-0`
|
|
657
|
+
* equals `0`.
|
|
658
|
+
*/
|
|
659
|
+
export class ExactDecimal {
|
|
660
|
+
negative;
|
|
661
|
+
integer;
|
|
662
|
+
fraction;
|
|
663
|
+
constructor(negative, integer, fraction) {
|
|
664
|
+
this.negative = negative;
|
|
665
|
+
this.integer = integer;
|
|
666
|
+
this.fraction = fraction;
|
|
667
|
+
}
|
|
668
|
+
/** Parse `[+-]digits[.digits]`: no exponent, no whitespace, at most 512 digits. */
|
|
669
|
+
static parse(text) {
|
|
670
|
+
const match = /^([+-]?)(\d*)(?:\.(\d+))?$/.exec(text);
|
|
671
|
+
if (match === null)
|
|
672
|
+
return undefined;
|
|
673
|
+
const [, sign = "", integer = "", fraction = ""] = match;
|
|
674
|
+
if (integer.length === 0 && fraction.length === 0)
|
|
675
|
+
return undefined;
|
|
676
|
+
if (integer.length + fraction.length > MAX_DECIMAL_DIGITS)
|
|
677
|
+
return undefined;
|
|
678
|
+
return ExactDecimal.normalized(sign === "-", integer, fraction);
|
|
679
|
+
}
|
|
680
|
+
static fromInteger(value) {
|
|
681
|
+
const negative = value < 0n;
|
|
682
|
+
return ExactDecimal.normalized(negative, (negative ? -value : value).toString(), "");
|
|
683
|
+
}
|
|
684
|
+
/** The exact value of a finite double, `undefined` for infinities and NaN. */
|
|
685
|
+
static fromDouble(value) {
|
|
686
|
+
if (!Number.isFinite(value))
|
|
687
|
+
return undefined;
|
|
688
|
+
if (value === 0)
|
|
689
|
+
return ExactDecimal.normalized(false, "", "");
|
|
690
|
+
const [mantissa = "", exponentText = "0"] = Math.abs(value).toExponential().split("e");
|
|
691
|
+
const digits = mantissa.replace(".", "");
|
|
692
|
+
const point = Number(exponentText) + 1;
|
|
693
|
+
const [integer, fraction] = point <= 0
|
|
694
|
+
? ["", "0".repeat(-point) + digits]
|
|
695
|
+
: point >= digits.length
|
|
696
|
+
? [digits + "0".repeat(point - digits.length), ""]
|
|
697
|
+
: [digits.slice(0, point), digits.slice(point)];
|
|
698
|
+
return ExactDecimal.normalized(value < 0, integer, fraction);
|
|
699
|
+
}
|
|
700
|
+
static normalized(negative, integer, fraction) {
|
|
701
|
+
// Linear scans, not `/^0+/` and `/0+$/`: a suffix-anchored run on library
|
|
702
|
+
// input backtracks quadratically on long zero runs.
|
|
703
|
+
let start = 0;
|
|
704
|
+
while (start < integer.length && integer.charCodeAt(start) === 48)
|
|
705
|
+
start += 1;
|
|
706
|
+
const trimmedInteger = integer.slice(start);
|
|
707
|
+
let end = fraction.length;
|
|
708
|
+
while (end > 0 && fraction.charCodeAt(end - 1) === 48)
|
|
709
|
+
end -= 1;
|
|
710
|
+
const trimmedFraction = fraction.slice(0, end);
|
|
711
|
+
const zero = trimmedInteger.length === 0 && trimmedFraction.length === 0;
|
|
712
|
+
return new ExactDecimal(negative && !zero, trimmedInteger, trimmedFraction);
|
|
713
|
+
}
|
|
714
|
+
/** `-1`, `0`, or `1` as this value is below, equal to, or above `other`. */
|
|
715
|
+
compare(other) {
|
|
716
|
+
if (!this.negative && other.negative)
|
|
717
|
+
return 1;
|
|
718
|
+
if (this.negative && !other.negative)
|
|
719
|
+
return -1;
|
|
720
|
+
const magnitude = ExactDecimal.magnitude(this, other);
|
|
721
|
+
return this.negative ? -magnitude : magnitude;
|
|
722
|
+
}
|
|
723
|
+
static magnitude(left, right) {
|
|
724
|
+
if (left.integer.length !== right.integer.length) {
|
|
725
|
+
return left.integer.length < right.integer.length ? -1 : 1;
|
|
726
|
+
}
|
|
727
|
+
if (left.integer !== right.integer)
|
|
728
|
+
return left.integer < right.integer ? -1 : 1;
|
|
729
|
+
if (left.fraction !== right.fraction)
|
|
730
|
+
return left.fraction < right.fraction ? -1 : 1;
|
|
731
|
+
return 0;
|
|
732
|
+
}
|
|
733
|
+
toString() {
|
|
734
|
+
const integer = this.integer.length === 0 ? "0" : this.integer;
|
|
735
|
+
const fraction = this.fraction.length === 0 ? "" : `.${this.fraction}`;
|
|
736
|
+
return `${this.negative ? "-" : ""}${integer}${fraction}`;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const MICROS_PER_SECOND = 1000000n;
|
|
740
|
+
const MICROS_PER_MILLI = 1000n;
|
|
741
|
+
const SECONDS_PER_DAY = 86400n;
|
|
742
|
+
const I64_MIN = -(1n << 63n);
|
|
743
|
+
const I64_MAX = (1n << 63n) - 1n;
|
|
744
|
+
function inI64(value) {
|
|
745
|
+
return value >= I64_MIN && value <= I64_MAX ? value : undefined;
|
|
746
|
+
}
|
|
747
|
+
/** Microseconds since the epoch for a text value, `undefined` when it does not parse exactly. */
|
|
748
|
+
export function timestampFromText(format, text) {
|
|
749
|
+
if (format === "rfc3339")
|
|
750
|
+
return rfc3339Micros(text);
|
|
751
|
+
if (!/^[+-]?\d+$/.test(text))
|
|
752
|
+
return undefined;
|
|
753
|
+
return timestampFromInteger(format, BigInt(text));
|
|
754
|
+
}
|
|
755
|
+
/** Microseconds since the epoch for an integer value, `undefined` on overflow or for RFC 3339. */
|
|
756
|
+
export function timestampFromInteger(format, value) {
|
|
757
|
+
if (inI64(value) === undefined)
|
|
758
|
+
return undefined;
|
|
759
|
+
switch (format) {
|
|
760
|
+
case "rfc3339":
|
|
761
|
+
return undefined;
|
|
762
|
+
case "epoch_seconds":
|
|
763
|
+
return inI64(value * MICROS_PER_SECOND);
|
|
764
|
+
case "epoch_millis":
|
|
765
|
+
return inI64(value * MICROS_PER_MILLI);
|
|
766
|
+
case "epoch_micros":
|
|
767
|
+
return value;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function rfc3339Micros(text) {
|
|
771
|
+
if (text.length < 20 || /[^ -~]/.test(text))
|
|
772
|
+
return undefined;
|
|
773
|
+
const digitsAt = (start, length) => {
|
|
774
|
+
const slice = text.slice(start, start + length);
|
|
775
|
+
return slice.length === length && /^\d+$/.test(slice) ? Number(slice) : undefined;
|
|
776
|
+
};
|
|
777
|
+
const year = digitsAt(0, 4);
|
|
778
|
+
const month = digitsAt(5, 2);
|
|
779
|
+
const day = digitsAt(8, 2);
|
|
780
|
+
const hour = digitsAt(11, 2);
|
|
781
|
+
const minute = digitsAt(14, 2);
|
|
782
|
+
const second = digitsAt(17, 2);
|
|
783
|
+
if (year === undefined ||
|
|
784
|
+
month === undefined ||
|
|
785
|
+
day === undefined ||
|
|
786
|
+
hour === undefined ||
|
|
787
|
+
minute === undefined ||
|
|
788
|
+
second === undefined) {
|
|
789
|
+
return undefined;
|
|
790
|
+
}
|
|
791
|
+
if (text[4] !== "-" ||
|
|
792
|
+
text[7] !== "-" ||
|
|
793
|
+
(text[10] !== "T" && text[10] !== "t") ||
|
|
794
|
+
text[13] !== ":" ||
|
|
795
|
+
text[16] !== ":") {
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
if (month < 1 ||
|
|
799
|
+
month > 12 ||
|
|
800
|
+
day === 0 ||
|
|
801
|
+
day > daysInMonth(year, month) ||
|
|
802
|
+
hour > 23 ||
|
|
803
|
+
minute > 59 ||
|
|
804
|
+
second > 59) {
|
|
805
|
+
return undefined;
|
|
806
|
+
}
|
|
807
|
+
let position = 19;
|
|
808
|
+
let fractionMicros = 0n;
|
|
809
|
+
if (text[position] === ".") {
|
|
810
|
+
position += 1;
|
|
811
|
+
const start = position;
|
|
812
|
+
while (/\d/.test(text[position] ?? ""))
|
|
813
|
+
position += 1;
|
|
814
|
+
const fraction = text.slice(start, position);
|
|
815
|
+
if (fraction.length === 0 || fraction.length > 9)
|
|
816
|
+
return undefined;
|
|
817
|
+
const kept = fraction.slice(0, 6);
|
|
818
|
+
if (/[1-9]/.test(fraction.slice(6)))
|
|
819
|
+
return undefined;
|
|
820
|
+
fractionMicros = BigInt(kept) * 10n ** BigInt(6 - kept.length);
|
|
821
|
+
}
|
|
822
|
+
let offsetSeconds;
|
|
823
|
+
const marker = text[position];
|
|
824
|
+
if ((marker === "Z" || marker === "z") && position + 1 === text.length) {
|
|
825
|
+
offsetSeconds = 0;
|
|
826
|
+
}
|
|
827
|
+
else if ((marker === "+" || marker === "-") && position + 6 === text.length) {
|
|
828
|
+
const offsetHour = digitsAt(position + 1, 2);
|
|
829
|
+
const offsetMinute = digitsAt(position + 4, 2);
|
|
830
|
+
if (offsetHour === undefined ||
|
|
831
|
+
offsetMinute === undefined ||
|
|
832
|
+
text[position + 3] !== ":" ||
|
|
833
|
+
offsetHour > 23 ||
|
|
834
|
+
offsetMinute > 59) {
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
const magnitude = offsetHour * 3600 + offsetMinute * 60;
|
|
838
|
+
offsetSeconds = marker === "-" ? -magnitude : magnitude;
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
return undefined;
|
|
842
|
+
}
|
|
843
|
+
const days = daysFromCivil(BigInt(year), BigInt(month), BigInt(day));
|
|
844
|
+
const seconds = days * SECONDS_PER_DAY + BigInt(hour * 3600 + minute * 60 + second) - BigInt(offsetSeconds);
|
|
845
|
+
return inI64(seconds * MICROS_PER_SECOND + fractionMicros);
|
|
846
|
+
}
|
|
847
|
+
function isLeapYear(year) {
|
|
848
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
849
|
+
}
|
|
850
|
+
function daysInMonth(year, month) {
|
|
851
|
+
if ([1, 3, 5, 7, 8, 10, 12].includes(month))
|
|
852
|
+
return 31;
|
|
853
|
+
if ([4, 6, 9, 11].includes(month))
|
|
854
|
+
return 30;
|
|
855
|
+
return isLeapYear(year) ? 29 : 28;
|
|
856
|
+
}
|
|
857
|
+
// Days since 1970-01-01 for a proleptic Gregorian date (H. Hinnant's algorithm).
|
|
858
|
+
function daysFromCivil(yearInput, month, day) {
|
|
859
|
+
const year = month <= 2n ? yearInput - 1n : yearInput;
|
|
860
|
+
const era = (year >= 0n ? year : year - 399n) / 400n;
|
|
861
|
+
const yearOfEra = year - era * 400n;
|
|
862
|
+
const shiftedMonth = month > 2n ? month - 3n : month + 9n;
|
|
863
|
+
const dayOfYear = (153n * shiftedMonth + 2n) / 5n + day - 1n;
|
|
864
|
+
const dayOfEra = yearOfEra * 365n + yearOfEra / 4n - yearOfEra / 100n + dayOfYear;
|
|
865
|
+
return era * 146097n + dayOfEra - 719468n;
|
|
866
|
+
}
|
|
867
|
+
/** The start of the next request: continue after `page`, or repeat `original`. */
|
|
868
|
+
export function nextFilteredStart(page, original) {
|
|
869
|
+
if (page.nextScanOffset === undefined)
|
|
870
|
+
return original;
|
|
871
|
+
return {
|
|
872
|
+
kind: "continue",
|
|
873
|
+
continuation: {
|
|
874
|
+
nextScanOffset: page.nextScanOffset,
|
|
875
|
+
generation: page.generation,
|
|
876
|
+
digest: page.policy.digest,
|
|
877
|
+
readMode: page.readMode,
|
|
878
|
+
...(page.policy.groupId !== undefined ? { groupId: page.policy.groupId } : {})
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function validateVersion(v) {
|
|
883
|
+
if (v !== FILTER_OP_VERSION) {
|
|
884
|
+
throw new InvalidError(`filter request version ${String(v)} is not supported, expected ${String(FILTER_OP_VERSION)}`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function validateName(label, name) {
|
|
888
|
+
const bytes = UTF8.encode(name).byteLength;
|
|
889
|
+
if (bytes === 0 || bytes > MAX_FILTER_SOURCE_NAME_BYTES) {
|
|
890
|
+
throw new InvalidError(`${label} must be 1..=${String(MAX_FILTER_SOURCE_NAME_BYTES)} bytes`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function validateSource(source) {
|
|
894
|
+
validateName("stream", source.stream);
|
|
895
|
+
validateName("topic", source.topic);
|
|
896
|
+
}
|
|
897
|
+
function validateConsumer(consumer) {
|
|
898
|
+
if (consumer.kind === "group_id") {
|
|
899
|
+
if (consumer.id < 0n || consumer.id > 0xffffffffn)
|
|
900
|
+
throw new InvalidError("consumer group id exceeds the native offset-key range");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
validateName(consumer.kind === "group" ? "consumer group" : "consumer", consumer.name);
|
|
904
|
+
}
|
|
905
|
+
function validateFilterRef(filter) {
|
|
906
|
+
if (filter.kind === "inline")
|
|
907
|
+
validateConsumerFilter(filter.filter);
|
|
908
|
+
}
|
|
909
|
+
function validateDigest(digest) {
|
|
910
|
+
if (digest.byteLength !== 32)
|
|
911
|
+
throw new InvalidError("a digest must be 32 bytes");
|
|
912
|
+
}
|
|
913
|
+
export function validateFilteredPollRequest(request) {
|
|
914
|
+
validateVersion(request.v);
|
|
915
|
+
validateSource(request.source);
|
|
916
|
+
validateConsumer(request.consumer);
|
|
917
|
+
validateFilterRef(request.filter);
|
|
918
|
+
if (request.filter.kind === "bound" && request.consumer.kind === "consumer") {
|
|
919
|
+
throw new InvalidError("a `bound` filter needs a consumer group");
|
|
920
|
+
}
|
|
921
|
+
if (!Number.isInteger(request.count) ||
|
|
922
|
+
request.count < 1 ||
|
|
923
|
+
request.count > MAX_FILTERED_PAGE_RECORDS) {
|
|
924
|
+
throw new InvalidError(`count must be 1..=${String(MAX_FILTERED_PAGE_RECORDS)}`);
|
|
925
|
+
}
|
|
926
|
+
if (!Number.isInteger(request.maxReplyBytes) ||
|
|
927
|
+
request.maxReplyBytes < 1 ||
|
|
928
|
+
request.maxReplyBytes > MAX_FILTERED_PAGE_BYTES) {
|
|
929
|
+
throw new InvalidError(`max_reply_bytes must be 1..=${String(MAX_FILTERED_PAGE_BYTES)}`);
|
|
930
|
+
}
|
|
931
|
+
if (request.start.kind === "continue") {
|
|
932
|
+
validateDigest(request.start.continuation.digest);
|
|
933
|
+
if ((request.consumer.kind !== "consumer") !==
|
|
934
|
+
(request.start.continuation.groupId !== undefined))
|
|
935
|
+
throw new InvalidError("a group continuation must retain its group identity");
|
|
936
|
+
if (request.start.continuation.generation.partitionId !== request.partitionId) {
|
|
937
|
+
throw new InvalidError("a continuation belongs to another partition");
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
export function validateFilteredAck(ack) {
|
|
942
|
+
validateVersion(ack.v);
|
|
943
|
+
validateSource(ack.source);
|
|
944
|
+
validateConsumer(ack.consumer);
|
|
945
|
+
validateDigest(ack.digest);
|
|
946
|
+
if ((ack.consumer.kind !== "consumer") !== (ack.groupId !== undefined))
|
|
947
|
+
throw new InvalidError("a group acknowledgment must retain its group identity");
|
|
948
|
+
if (ack.generation.partitionId !== ack.partitionId) {
|
|
949
|
+
throw new InvalidError("an acknowledgment names another partition's generation");
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
export function validateFilterPreviewRequest(request) {
|
|
953
|
+
validateVersion(request.v);
|
|
954
|
+
validateSource(request.source);
|
|
955
|
+
validateFilterRef(request.filter);
|
|
956
|
+
if (request.filter.kind === "bound") {
|
|
957
|
+
throw new InvalidError("a preview executes an inline filter or a saved revision");
|
|
958
|
+
}
|
|
959
|
+
if (request.maxExamined === 0 || request.maxExamined > MAX_FILTER_PREVIEW_EXAMINED) {
|
|
960
|
+
throw new InvalidError(`max_examined must be 1..=${String(MAX_FILTER_PREVIEW_EXAMINED)}`);
|
|
961
|
+
}
|
|
962
|
+
if (request.maxRecords === 0 || request.maxRecords > MAX_FILTER_PREVIEW_RECORDS) {
|
|
963
|
+
throw new InvalidError(`max_records must be 1..=${String(MAX_FILTER_PREVIEW_RECORDS)}`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
export function validateFilterTestRequest(request) {
|
|
967
|
+
validateVersion(request.v);
|
|
968
|
+
validateFilterRef(request.filter);
|
|
969
|
+
if (request.filter.kind === "bound") {
|
|
970
|
+
throw new InvalidError("a sample test executes an inline filter or a saved revision");
|
|
971
|
+
}
|
|
972
|
+
if (request.payload.byteLength > MAX_FILTER_SAMPLE_BYTES) {
|
|
973
|
+
throw new InvalidError(`a sample payload exceeds ${String(MAX_FILTER_SAMPLE_BYTES)}B`);
|
|
974
|
+
}
|
|
975
|
+
if (request.headers.length > MAX_FILTER_SAMPLE_HEADERS) {
|
|
976
|
+
throw new InvalidError(`a sample carries more than ${String(MAX_FILTER_SAMPLE_HEADERS)} headers`);
|
|
977
|
+
}
|
|
978
|
+
for (const header of request.headers) {
|
|
979
|
+
validateName("sample header key", header.key);
|
|
980
|
+
const bytes = header.value.kind === "string"
|
|
981
|
+
? UTF8.encode(header.value.value).byteLength
|
|
982
|
+
: header.value.kind === "raw"
|
|
983
|
+
? header.value.value.byteLength
|
|
984
|
+
: 0;
|
|
985
|
+
if (bytes > MAX_FILTER_STRING_BYTES)
|
|
986
|
+
throw new InvalidError("a sample header value exceeds its byte limit");
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
export function validateFilterGroupRef(group) {
|
|
990
|
+
validateName("stream", group.stream);
|
|
991
|
+
validateName("topic", group.topic);
|
|
992
|
+
validateName("consumer group", group.group);
|
|
993
|
+
}
|
|
994
|
+
export function validateFilterMutation(mutation) {
|
|
995
|
+
switch (mutation.kind) {
|
|
996
|
+
case "register":
|
|
997
|
+
validateFilterName(mutation.name);
|
|
998
|
+
validateDescription(mutation.description);
|
|
999
|
+
validateConsumerFilter(mutation.filter);
|
|
1000
|
+
return;
|
|
1001
|
+
case "revise":
|
|
1002
|
+
validateConsumerFilter(mutation.filter);
|
|
1003
|
+
return;
|
|
1004
|
+
case "describe":
|
|
1005
|
+
validateDescription(mutation.description);
|
|
1006
|
+
return;
|
|
1007
|
+
case "set_revision_enabled":
|
|
1008
|
+
if (!Number.isSafeInteger(mutation.revision) ||
|
|
1009
|
+
mutation.revision <= 0 ||
|
|
1010
|
+
mutation.revision > 0xffff_ffff)
|
|
1011
|
+
throw new InvalidError("revision must be positive and fit u32");
|
|
1012
|
+
return;
|
|
1013
|
+
case "archive":
|
|
1014
|
+
case "drop":
|
|
1015
|
+
return;
|
|
1016
|
+
case "bind":
|
|
1017
|
+
validateFilterGroupRef(mutation.group);
|
|
1018
|
+
return;
|
|
1019
|
+
case "unbind":
|
|
1020
|
+
validateFilterGroupRef(mutation.group);
|
|
1021
|
+
validateDigest(mutation.expectedDigest);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
function validateFilterName(name) {
|
|
1025
|
+
const bytes = UTF8.encode(name).byteLength;
|
|
1026
|
+
if (bytes === 0 || bytes > MAX_FILTER_NAME_BYTES) {
|
|
1027
|
+
throw new InvalidError(`a filter name must be 1..=${String(MAX_FILTER_NAME_BYTES)} bytes`);
|
|
1028
|
+
}
|
|
1029
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) {
|
|
1030
|
+
throw new InvalidError("a filter name has a disallowed byte: allowed are ASCII letters, digits, '-', '_', '.'");
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function validateDescription(description) {
|
|
1034
|
+
if (UTF8.encode(description).byteLength > MAX_FILTER_DESCRIPTION_BYTES) {
|
|
1035
|
+
throw new InvalidError(`a filter description exceeds ${String(MAX_FILTER_DESCRIPTION_BYTES)}B`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
export function validateFilterMutationRequest(request) {
|
|
1039
|
+
validateVersion(request.v);
|
|
1040
|
+
if (request.operationId === 0n)
|
|
1041
|
+
throw new InvalidError("operation_id must not be zero");
|
|
1042
|
+
validateFilterMutation(request.mutation);
|
|
1043
|
+
}
|
|
1044
|
+
export function validateCatalogPage(page, pageSize) {
|
|
1045
|
+
if (!Number.isInteger(page) || page < 0 || page > 0xffff_ffff)
|
|
1046
|
+
throw new InvalidError("page must be a whole number that fits in 32 bits");
|
|
1047
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > MAX_FILTER_CATALOG_PAGE) {
|
|
1048
|
+
throw new InvalidError(`page_size must be 1..=${String(MAX_FILTER_CATALOG_PAGE)}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
class JsonDouble {
|
|
1052
|
+
value;
|
|
1053
|
+
constructor(value) {
|
|
1054
|
+
this.value = value;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function mapOf(entries) {
|
|
1058
|
+
return new Map(entries);
|
|
1059
|
+
}
|
|
1060
|
+
function setIf(map, key, value) {
|
|
1061
|
+
if (value !== undefined)
|
|
1062
|
+
map.set(key, value);
|
|
1063
|
+
}
|
|
1064
|
+
function encodeTypedLiteral(value, mode) {
|
|
1065
|
+
const map = new Map([["kind", value.kind]]);
|
|
1066
|
+
switch (value.kind) {
|
|
1067
|
+
case "null":
|
|
1068
|
+
return map;
|
|
1069
|
+
case "double":
|
|
1070
|
+
case "float":
|
|
1071
|
+
map.set("value", mode === "json" ? new JsonDouble(value.value) : new Token(Type.float, value.value));
|
|
1072
|
+
return map;
|
|
1073
|
+
case "list":
|
|
1074
|
+
map.set("value", value.value.map((item) => encodeTypedLiteral(item, mode)));
|
|
1075
|
+
return map;
|
|
1076
|
+
case "boolean":
|
|
1077
|
+
case "int":
|
|
1078
|
+
case "long":
|
|
1079
|
+
case "string":
|
|
1080
|
+
map.set("value", value.value);
|
|
1081
|
+
return map;
|
|
1082
|
+
case "time_micros":
|
|
1083
|
+
case "timestamp_micros":
|
|
1084
|
+
case "timestamp_tz_micros":
|
|
1085
|
+
case "date":
|
|
1086
|
+
case "decimal":
|
|
1087
|
+
case "uuid":
|
|
1088
|
+
case "fixed":
|
|
1089
|
+
case "binary":
|
|
1090
|
+
case "struct":
|
|
1091
|
+
case "map":
|
|
1092
|
+
throw new InvalidError("supported literals are null, boolean, int, long, double, string, and list");
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function encodePredicateTree(predicate, mode) {
|
|
1096
|
+
return mapOf([
|
|
1097
|
+
["field", predicate.field],
|
|
1098
|
+
["op", predicate.op],
|
|
1099
|
+
["value", encodeTypedLiteral(predicate.value, mode)]
|
|
1100
|
+
]);
|
|
1101
|
+
}
|
|
1102
|
+
function encodeCoerce(coerce) {
|
|
1103
|
+
return coerce.kind === "number"
|
|
1104
|
+
? mapOf([["kind", "number"]])
|
|
1105
|
+
: mapOf([
|
|
1106
|
+
["kind", "timestamp"],
|
|
1107
|
+
["format", coerce.format]
|
|
1108
|
+
]);
|
|
1109
|
+
}
|
|
1110
|
+
function encodeExprTree(expr, mode) {
|
|
1111
|
+
switch (expr.kind) {
|
|
1112
|
+
case "all":
|
|
1113
|
+
case "any":
|
|
1114
|
+
return mapOf([[expr.kind, expr.children.map((child) => encodeExprTree(child, mode))]]);
|
|
1115
|
+
case "not":
|
|
1116
|
+
return mapOf([["not", encodeExprTree(expr.child, mode)]]);
|
|
1117
|
+
case "pred":
|
|
1118
|
+
return mapOf([["pred", encodePredicateTree(expr.predicate, mode)]]);
|
|
1119
|
+
case "pred_as":
|
|
1120
|
+
return mapOf([
|
|
1121
|
+
[
|
|
1122
|
+
"pred_as",
|
|
1123
|
+
mapOf([
|
|
1124
|
+
["pred", encodePredicateTree(expr.predicate.pred, mode)],
|
|
1125
|
+
["coerce", encodeCoerce(expr.predicate.coerce)]
|
|
1126
|
+
])
|
|
1127
|
+
]
|
|
1128
|
+
]);
|
|
1129
|
+
case "present":
|
|
1130
|
+
case "absent":
|
|
1131
|
+
return mapOf([[expr.kind, expr.path.toString()]]);
|
|
1132
|
+
case "header":
|
|
1133
|
+
return mapOf([
|
|
1134
|
+
[
|
|
1135
|
+
"header",
|
|
1136
|
+
mapOf([
|
|
1137
|
+
["key", expr.predicate.key],
|
|
1138
|
+
["op", expr.predicate.op],
|
|
1139
|
+
["value", encodeTypedLiteral(expr.predicate.value, mode)]
|
|
1140
|
+
])
|
|
1141
|
+
]
|
|
1142
|
+
]);
|
|
1143
|
+
case "text":
|
|
1144
|
+
case "header_text": {
|
|
1145
|
+
const predicate = mapOf([
|
|
1146
|
+
["field", expr.predicate.field],
|
|
1147
|
+
["kind", expr.predicate.kind],
|
|
1148
|
+
["pattern", expr.predicate.pattern]
|
|
1149
|
+
]);
|
|
1150
|
+
if (expr.predicate.caseInsensitive === true)
|
|
1151
|
+
predicate.set("case_insensitive", true);
|
|
1152
|
+
return mapOf([[expr.kind, predicate]]);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// Field order and omitted defaults follow the Rust encoding, which the digest covers.
|
|
1157
|
+
function encodeConsumerFilterTree(filter, mode) {
|
|
1158
|
+
const map = mapOf([
|
|
1159
|
+
["v", filter.v],
|
|
1160
|
+
["evaluator_version", filter.evaluatorVersion],
|
|
1161
|
+
["expr", encodeExprTree(filter.expr, mode)],
|
|
1162
|
+
["codec", filter.codec],
|
|
1163
|
+
["fault_policy", filter.faultPolicy]
|
|
1164
|
+
]);
|
|
1165
|
+
if (filter.foreignPolicy === "pass")
|
|
1166
|
+
map.set("foreign_policy", "pass");
|
|
1167
|
+
if (filter.mismatchPolicy === "pass")
|
|
1168
|
+
map.set("mismatch_policy", "pass");
|
|
1169
|
+
if (filter.schemaRefs.length > 0)
|
|
1170
|
+
map.set("schema_refs", [...filter.schemaRefs]);
|
|
1171
|
+
return map;
|
|
1172
|
+
}
|
|
1173
|
+
export function encodeFilterExpr(expr) {
|
|
1174
|
+
return encodeExprTree(expr, "cbor");
|
|
1175
|
+
}
|
|
1176
|
+
export function encodeConsumerFilter(filter) {
|
|
1177
|
+
return encodeConsumerFilterTree(filter, "cbor");
|
|
1178
|
+
}
|
|
1179
|
+
export function encodeFilterSource(source) {
|
|
1180
|
+
return mapOf([
|
|
1181
|
+
["stream", source.stream],
|
|
1182
|
+
["topic", source.topic]
|
|
1183
|
+
]);
|
|
1184
|
+
}
|
|
1185
|
+
export function encodeFilterConsumer(consumer) {
|
|
1186
|
+
if (consumer.kind === "group_id")
|
|
1187
|
+
return mapOf([
|
|
1188
|
+
["kind", "group_id"],
|
|
1189
|
+
["id", consumer.id]
|
|
1190
|
+
]);
|
|
1191
|
+
return mapOf([
|
|
1192
|
+
["kind", consumer.kind],
|
|
1193
|
+
["name", consumer.name]
|
|
1194
|
+
]);
|
|
1195
|
+
}
|
|
1196
|
+
export function encodeFilterRef(filter) {
|
|
1197
|
+
switch (filter.kind) {
|
|
1198
|
+
case "inline":
|
|
1199
|
+
return mapOf([["inline", encodeConsumerFilter(filter.filter)]]);
|
|
1200
|
+
case "revision":
|
|
1201
|
+
return mapOf([
|
|
1202
|
+
[
|
|
1203
|
+
"revision",
|
|
1204
|
+
mapOf([
|
|
1205
|
+
["filter_id", filter.filterId],
|
|
1206
|
+
["revision", filter.revision]
|
|
1207
|
+
])
|
|
1208
|
+
]
|
|
1209
|
+
]);
|
|
1210
|
+
case "bound":
|
|
1211
|
+
return "bound";
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
export function encodeSourceGeneration(generation) {
|
|
1215
|
+
return mapOf([
|
|
1216
|
+
["stream_id", generation.streamId],
|
|
1217
|
+
["stream_created_at_micros", generation.streamCreatedAtMicros],
|
|
1218
|
+
["topic_id", generation.topicId],
|
|
1219
|
+
["topic_created_at_micros", generation.topicCreatedAtMicros],
|
|
1220
|
+
["partition_id", generation.partitionId],
|
|
1221
|
+
["partition_created_revision", generation.partitionCreatedRevision],
|
|
1222
|
+
["purge_generation", generation.purgeGeneration]
|
|
1223
|
+
]);
|
|
1224
|
+
}
|
|
1225
|
+
export function encodeFilteredStart(start) {
|
|
1226
|
+
switch (start.kind) {
|
|
1227
|
+
case "next":
|
|
1228
|
+
case "first":
|
|
1229
|
+
case "last":
|
|
1230
|
+
return start.kind;
|
|
1231
|
+
case "offset":
|
|
1232
|
+
return mapOf([["offset", start.offset]]);
|
|
1233
|
+
case "timestamp":
|
|
1234
|
+
return mapOf([["timestamp", start.micros]]);
|
|
1235
|
+
case "continue":
|
|
1236
|
+
return mapOf([
|
|
1237
|
+
[
|
|
1238
|
+
"continue",
|
|
1239
|
+
mapOf([
|
|
1240
|
+
...(start.continuation.groupId === undefined
|
|
1241
|
+
? []
|
|
1242
|
+
: [["group_id", start.continuation.groupId]]),
|
|
1243
|
+
["next_scan_offset", start.continuation.nextScanOffset],
|
|
1244
|
+
["generation", encodeSourceGeneration(start.continuation.generation)],
|
|
1245
|
+
["digest", start.continuation.digest],
|
|
1246
|
+
...(start.continuation.readMode === "primary"
|
|
1247
|
+
? []
|
|
1248
|
+
: [["read_mode", start.continuation.readMode]])
|
|
1249
|
+
])
|
|
1250
|
+
]
|
|
1251
|
+
]);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
export function encodeFilteredPollRequest(request) {
|
|
1255
|
+
return mapOf([
|
|
1256
|
+
["v", request.v],
|
|
1257
|
+
["source", encodeFilterSource(request.source)],
|
|
1258
|
+
["partition_id", request.partitionId],
|
|
1259
|
+
["consumer", encodeFilterConsumer(request.consumer)],
|
|
1260
|
+
["filter", encodeFilterRef(request.filter)],
|
|
1261
|
+
["start", encodeFilteredStart(request.start)],
|
|
1262
|
+
["count", request.count],
|
|
1263
|
+
["max_reply_bytes", request.maxReplyBytes],
|
|
1264
|
+
["read_mode", request.readMode]
|
|
1265
|
+
]);
|
|
1266
|
+
}
|
|
1267
|
+
export function encodeFilteredAck(ack) {
|
|
1268
|
+
return mapOf([
|
|
1269
|
+
...(ack.groupId === undefined ? [] : [["group_id", ack.groupId]]),
|
|
1270
|
+
["v", ack.v],
|
|
1271
|
+
["source", encodeFilterSource(ack.source)],
|
|
1272
|
+
["partition_id", ack.partitionId],
|
|
1273
|
+
["consumer", encodeFilterConsumer(ack.consumer)],
|
|
1274
|
+
["generation", encodeSourceGeneration(ack.generation)],
|
|
1275
|
+
["digest", ack.digest],
|
|
1276
|
+
["offset", ack.offset]
|
|
1277
|
+
]);
|
|
1278
|
+
}
|
|
1279
|
+
export function encodeFilterPreviewRequest(request) {
|
|
1280
|
+
return mapOf([
|
|
1281
|
+
["v", request.v],
|
|
1282
|
+
["source", encodeFilterSource(request.source)],
|
|
1283
|
+
["partition_id", request.partitionId],
|
|
1284
|
+
["filter", encodeFilterRef(request.filter)],
|
|
1285
|
+
["from_offset", request.fromOffset],
|
|
1286
|
+
["max_examined", request.maxExamined],
|
|
1287
|
+
["max_records", request.maxRecords],
|
|
1288
|
+
["explain", request.explain]
|
|
1289
|
+
]);
|
|
1290
|
+
}
|
|
1291
|
+
export function encodeHeaderScalar(value) {
|
|
1292
|
+
return mapOf([
|
|
1293
|
+
["kind", value.kind],
|
|
1294
|
+
["value", value.kind === "float" ? new Token(Type.float, value.value) : value.value]
|
|
1295
|
+
]);
|
|
1296
|
+
}
|
|
1297
|
+
export function encodeFilterTestRequest(request) {
|
|
1298
|
+
const map = mapOf([
|
|
1299
|
+
["v", request.v],
|
|
1300
|
+
["filter", encodeFilterRef(request.filter)],
|
|
1301
|
+
["payload", request.payload]
|
|
1302
|
+
]);
|
|
1303
|
+
if (request.headers.length > 0) {
|
|
1304
|
+
map.set("headers", request.headers.map((header) => mapOf([
|
|
1305
|
+
["key", header.key],
|
|
1306
|
+
["value", encodeHeaderScalar(header.value)]
|
|
1307
|
+
])));
|
|
1308
|
+
}
|
|
1309
|
+
return map;
|
|
1310
|
+
}
|
|
1311
|
+
function encodeAppliedPolicy(policy) {
|
|
1312
|
+
const map = mapOf([]);
|
|
1313
|
+
setIf(map, "group_id", policy.groupId);
|
|
1314
|
+
map.set("digest", policy.digest);
|
|
1315
|
+
setIf(map, "filter_id", policy.filterId);
|
|
1316
|
+
setIf(map, "revision", policy.revision);
|
|
1317
|
+
return map;
|
|
1318
|
+
}
|
|
1319
|
+
export function encodeFilteredPage(page) {
|
|
1320
|
+
const map = mapOf([
|
|
1321
|
+
["v", page.v],
|
|
1322
|
+
["partition_id", page.partitionId],
|
|
1323
|
+
["policy", encodeAppliedPolicy(page.policy)],
|
|
1324
|
+
["generation", encodeSourceGeneration(page.generation)],
|
|
1325
|
+
["read_mode", page.readMode]
|
|
1326
|
+
]);
|
|
1327
|
+
setIf(map, "next_scan_offset", page.nextScanOffset);
|
|
1328
|
+
setIf(map, "safe_ack_offset", page.safeAckOffset);
|
|
1329
|
+
map.set("frontier", page.frontier);
|
|
1330
|
+
map.set("examined", page.examined);
|
|
1331
|
+
map.set("matched", page.matched);
|
|
1332
|
+
map.set("stop", page.stop);
|
|
1333
|
+
if (page.fault !== undefined) {
|
|
1334
|
+
map.set("fault", mapOf([
|
|
1335
|
+
["offset", page.fault.offset],
|
|
1336
|
+
["reason", page.fault.reason]
|
|
1337
|
+
]));
|
|
1338
|
+
}
|
|
1339
|
+
if (page.unevaluated.length > 0)
|
|
1340
|
+
map.set("unevaluated", [...page.unevaluated]);
|
|
1341
|
+
if (page.evaluationLimits !== undefined)
|
|
1342
|
+
map.set("evaluation_limits", mapOf([
|
|
1343
|
+
["max_payload_bytes", page.evaluationLimits.maxPayloadBytes],
|
|
1344
|
+
["max_depth", page.evaluationLimits.maxDepth]
|
|
1345
|
+
]));
|
|
1346
|
+
map.set("records", page.records);
|
|
1347
|
+
return map;
|
|
1348
|
+
}
|
|
1349
|
+
function encodeAckReceipt(receipt) {
|
|
1350
|
+
return mapOf([
|
|
1351
|
+
["partition_id", receipt.partitionId],
|
|
1352
|
+
["offset", receipt.offset],
|
|
1353
|
+
["generation", encodeSourceGeneration(receipt.generation)]
|
|
1354
|
+
]);
|
|
1355
|
+
}
|
|
1356
|
+
export function encodeFilterError(error) {
|
|
1357
|
+
return mapOf([
|
|
1358
|
+
["code", resultCodeWord(error.code) ?? "backend"],
|
|
1359
|
+
["reason", error.reason],
|
|
1360
|
+
["message", error.message]
|
|
1361
|
+
]);
|
|
1362
|
+
}
|
|
1363
|
+
function encodeExplainNode(node) {
|
|
1364
|
+
const map = mapOf([["label", node.label]]);
|
|
1365
|
+
setIf(map, "truth", node.truth);
|
|
1366
|
+
if (node.children.length > 0)
|
|
1367
|
+
map.set("children", node.children.map(encodeExplainNode));
|
|
1368
|
+
return map;
|
|
1369
|
+
}
|
|
1370
|
+
function encodeExplanation(explanation) {
|
|
1371
|
+
const map = mapOf([["verdict", explanation.verdict]]);
|
|
1372
|
+
setIf(map, "fault", explanation.fault);
|
|
1373
|
+
map.set("root", encodeExplainNode(explanation.root));
|
|
1374
|
+
return map;
|
|
1375
|
+
}
|
|
1376
|
+
function encodeFilterOutcome(outcome) {
|
|
1377
|
+
switch (outcome.kind) {
|
|
1378
|
+
case "page":
|
|
1379
|
+
return mapOf([["page", encodeFilteredPage(outcome.page)]]);
|
|
1380
|
+
case "acknowledged":
|
|
1381
|
+
return mapOf([["acknowledged", encodeAckReceipt(outcome.receipt)]]);
|
|
1382
|
+
case "tested":
|
|
1383
|
+
return mapOf([
|
|
1384
|
+
[
|
|
1385
|
+
"tested",
|
|
1386
|
+
mapOf([
|
|
1387
|
+
["v", outcome.result.v],
|
|
1388
|
+
["policy", encodeAppliedPolicy(outcome.result.policy)],
|
|
1389
|
+
["explanation", encodeExplanation(outcome.result.explanation)]
|
|
1390
|
+
])
|
|
1391
|
+
]
|
|
1392
|
+
]);
|
|
1393
|
+
case "validated":
|
|
1394
|
+
return mapOf([
|
|
1395
|
+
[
|
|
1396
|
+
"validated",
|
|
1397
|
+
mapOf([
|
|
1398
|
+
["v", outcome.validation.v],
|
|
1399
|
+
["digest", outcome.validation.digest],
|
|
1400
|
+
["reads_payload", outcome.validation.readsPayload],
|
|
1401
|
+
["reads_headers", outcome.validation.readsHeaders]
|
|
1402
|
+
])
|
|
1403
|
+
]
|
|
1404
|
+
]);
|
|
1405
|
+
case "preview":
|
|
1406
|
+
return mapOf([["preview", encodeFilterPreview(outcome.preview)]]);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
function encodeFilterPreview(preview) {
|
|
1410
|
+
const map = mapOf([
|
|
1411
|
+
["v", preview.v],
|
|
1412
|
+
["partition_id", preview.partitionId],
|
|
1413
|
+
["policy", encodeAppliedPolicy(preview.policy)],
|
|
1414
|
+
["read_mode", preview.readMode],
|
|
1415
|
+
["examined", preview.examined],
|
|
1416
|
+
["matched", preview.matched],
|
|
1417
|
+
["faults", preview.faults],
|
|
1418
|
+
["stop", preview.stop]
|
|
1419
|
+
]);
|
|
1420
|
+
setIf(map, "next_offset", preview.nextOffset);
|
|
1421
|
+
map.set("frontier", preview.frontier);
|
|
1422
|
+
map.set("records", preview.records.map((record) => {
|
|
1423
|
+
const entry = mapOf([
|
|
1424
|
+
["offset", record.offset],
|
|
1425
|
+
["timestamp_micros", record.timestampMicros],
|
|
1426
|
+
["verdict", record.verdict]
|
|
1427
|
+
]);
|
|
1428
|
+
setIf(entry, "fault", record.fault);
|
|
1429
|
+
entry.set("payload_text", record.payloadText);
|
|
1430
|
+
entry.set("payload_truncated", record.payloadTruncated);
|
|
1431
|
+
if (record.explanation !== undefined) {
|
|
1432
|
+
entry.set("explanation", encodeExplanation(record.explanation));
|
|
1433
|
+
}
|
|
1434
|
+
return entry;
|
|
1435
|
+
}));
|
|
1436
|
+
return map;
|
|
1437
|
+
}
|
|
1438
|
+
export function encodeFilterReply(reply) {
|
|
1439
|
+
return reply.kind === "ok"
|
|
1440
|
+
? mapOf([["ok", encodeFilterOutcome(reply.outcome)]])
|
|
1441
|
+
: mapOf([["err", encodeFilterError(reply.error)]]);
|
|
1442
|
+
}
|
|
1443
|
+
export function encodeFilterGroupRef(group) {
|
|
1444
|
+
return mapOf([
|
|
1445
|
+
["stream", group.stream],
|
|
1446
|
+
["topic", group.topic],
|
|
1447
|
+
["group", group.group]
|
|
1448
|
+
]);
|
|
1449
|
+
}
|
|
1450
|
+
export function encodeFilterGroupIdentity(identity) {
|
|
1451
|
+
return mapOf([
|
|
1452
|
+
["stream_id", identity.streamId],
|
|
1453
|
+
["stream_created_at_micros", identity.streamCreatedAtMicros],
|
|
1454
|
+
["topic_id", identity.topicId],
|
|
1455
|
+
["topic_created_at_micros", identity.topicCreatedAtMicros],
|
|
1456
|
+
["group_id", identity.groupId]
|
|
1457
|
+
]);
|
|
1458
|
+
}
|
|
1459
|
+
export function encodeFilterMutation(mutation) {
|
|
1460
|
+
switch (mutation.kind) {
|
|
1461
|
+
case "register":
|
|
1462
|
+
return mapOf([
|
|
1463
|
+
[
|
|
1464
|
+
"register",
|
|
1465
|
+
mapOf([
|
|
1466
|
+
["name", mutation.name],
|
|
1467
|
+
["description", mutation.description],
|
|
1468
|
+
["filter", encodeConsumerFilter(mutation.filter)]
|
|
1469
|
+
])
|
|
1470
|
+
]
|
|
1471
|
+
]);
|
|
1472
|
+
case "revise":
|
|
1473
|
+
return mapOf([
|
|
1474
|
+
[
|
|
1475
|
+
"revise",
|
|
1476
|
+
mapOf([
|
|
1477
|
+
["filter_id", mutation.filterId],
|
|
1478
|
+
["expected_revision", mutation.expectedRevision],
|
|
1479
|
+
["filter", encodeConsumerFilter(mutation.filter)]
|
|
1480
|
+
])
|
|
1481
|
+
]
|
|
1482
|
+
]);
|
|
1483
|
+
case "describe":
|
|
1484
|
+
return mapOf([
|
|
1485
|
+
[
|
|
1486
|
+
"describe",
|
|
1487
|
+
mapOf([
|
|
1488
|
+
["filter_id", mutation.filterId],
|
|
1489
|
+
["description", mutation.description]
|
|
1490
|
+
])
|
|
1491
|
+
]
|
|
1492
|
+
]);
|
|
1493
|
+
case "set_revision_enabled":
|
|
1494
|
+
return mapOf([
|
|
1495
|
+
[
|
|
1496
|
+
mutation.kind,
|
|
1497
|
+
mapOf([
|
|
1498
|
+
["filter_id", mutation.filterId],
|
|
1499
|
+
["revision", mutation.revision],
|
|
1500
|
+
["enabled", mutation.enabled]
|
|
1501
|
+
])
|
|
1502
|
+
]
|
|
1503
|
+
]);
|
|
1504
|
+
case "archive":
|
|
1505
|
+
case "drop":
|
|
1506
|
+
return mapOf([[mutation.kind, mapOf([["filter_id", mutation.filterId]])]]);
|
|
1507
|
+
case "bind":
|
|
1508
|
+
return mapOf([
|
|
1509
|
+
[
|
|
1510
|
+
"bind",
|
|
1511
|
+
mapOf([
|
|
1512
|
+
["group", encodeFilterGroupRef(mutation.group)],
|
|
1513
|
+
["filter_id", mutation.filterId],
|
|
1514
|
+
["revision", mutation.revision]
|
|
1515
|
+
])
|
|
1516
|
+
]
|
|
1517
|
+
]);
|
|
1518
|
+
case "unbind":
|
|
1519
|
+
return mapOf([
|
|
1520
|
+
[
|
|
1521
|
+
"unbind",
|
|
1522
|
+
mapOf([
|
|
1523
|
+
["group", encodeFilterGroupRef(mutation.group)],
|
|
1524
|
+
["expected_digest", mutation.expectedDigest],
|
|
1525
|
+
...(mutation.expectedIdentity === undefined
|
|
1526
|
+
? []
|
|
1527
|
+
: [
|
|
1528
|
+
[
|
|
1529
|
+
"expected_identity",
|
|
1530
|
+
encodeFilterGroupIdentity(mutation.expectedIdentity)
|
|
1531
|
+
]
|
|
1532
|
+
])
|
|
1533
|
+
])
|
|
1534
|
+
]
|
|
1535
|
+
]);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
export function encodeFilterMutationRequest(request) {
|
|
1539
|
+
return mapOf([
|
|
1540
|
+
["v", request.v],
|
|
1541
|
+
["operation_id", request.operationId],
|
|
1542
|
+
["mutation", encodeFilterMutation(request.mutation)]
|
|
1543
|
+
]);
|
|
1544
|
+
}
|
|
1545
|
+
export function encodeFilterCatalogCommand(command) {
|
|
1546
|
+
const map = mapOf([
|
|
1547
|
+
["operation_id", command.operationId],
|
|
1548
|
+
["actor_user_id", command.actorUserId],
|
|
1549
|
+
["mutation", encodeFilterMutation(command.mutation)]
|
|
1550
|
+
]);
|
|
1551
|
+
if (command.identity !== undefined) {
|
|
1552
|
+
map.set("identity", encodeFilterGroupIdentity(command.identity));
|
|
1553
|
+
}
|
|
1554
|
+
setIf(map, "filter_id", command.filterId);
|
|
1555
|
+
if (command.grants !== undefined && command.grants.length > 0) {
|
|
1556
|
+
map.set("grants", command.grants.map((grant) => encodeGrant(grant)));
|
|
1557
|
+
}
|
|
1558
|
+
if (command.limits !== undefined) {
|
|
1559
|
+
map.set("limits", mapOf([
|
|
1560
|
+
["max_definitions", command.limits.maxDefinitions],
|
|
1561
|
+
["max_tombstones", command.limits.maxTombstones],
|
|
1562
|
+
["max_revisions", command.limits.maxRevisions],
|
|
1563
|
+
["max_group_identities", command.limits.maxGroupIdentities],
|
|
1564
|
+
["max_catalog_bytes", command.limits.maxCatalogBytes]
|
|
1565
|
+
]));
|
|
1566
|
+
}
|
|
1567
|
+
return map;
|
|
1568
|
+
}
|
|
1569
|
+
export function encodeGetFilter(request) {
|
|
1570
|
+
return mapOf([
|
|
1571
|
+
["v", request.v],
|
|
1572
|
+
["filter_id", request.filterId]
|
|
1573
|
+
]);
|
|
1574
|
+
}
|
|
1575
|
+
export function encodeListFilters(request) {
|
|
1576
|
+
const map = mapOf([["v", request.v]]);
|
|
1577
|
+
setIf(map, "name_contains", request.nameContains);
|
|
1578
|
+
setIf(map, "state", request.state);
|
|
1579
|
+
setIf(map, "before_id", request.beforeId);
|
|
1580
|
+
map.set("page", request.page);
|
|
1581
|
+
map.set("page_size", request.pageSize);
|
|
1582
|
+
return map;
|
|
1583
|
+
}
|
|
1584
|
+
export function encodeListFilterRevisions(request) {
|
|
1585
|
+
return mapOf([
|
|
1586
|
+
["v", request.v],
|
|
1587
|
+
["filter_id", request.filterId],
|
|
1588
|
+
["page", request.page],
|
|
1589
|
+
["page_size", request.pageSize]
|
|
1590
|
+
]);
|
|
1591
|
+
}
|
|
1592
|
+
export function encodeGetFilterBinding(request) {
|
|
1593
|
+
const map = mapOf([
|
|
1594
|
+
["v", request.v],
|
|
1595
|
+
["group", encodeFilterGroupRef(request.group)]
|
|
1596
|
+
]);
|
|
1597
|
+
if (request.identity !== undefined) {
|
|
1598
|
+
map.set("identity", encodeFilterGroupIdentity(request.identity));
|
|
1599
|
+
}
|
|
1600
|
+
return map;
|
|
1601
|
+
}
|
|
1602
|
+
export function encodeListFilterBindings(request) {
|
|
1603
|
+
const map = mapOf([["v", request.v]]);
|
|
1604
|
+
setIf(map, "filter_id", request.filterId);
|
|
1605
|
+
setIf(map, "stream", request.stream);
|
|
1606
|
+
setIf(map, "topic", request.topic);
|
|
1607
|
+
map.set("page", request.page);
|
|
1608
|
+
map.set("page_size", request.pageSize);
|
|
1609
|
+
return map;
|
|
1610
|
+
}
|
|
1611
|
+
export function encodeGetFilterOperation(request) {
|
|
1612
|
+
return mapOf([
|
|
1613
|
+
["v", request.v],
|
|
1614
|
+
["operation_id", request.operationId]
|
|
1615
|
+
]);
|
|
1616
|
+
}
|
|
1617
|
+
function encodeRevisionRef(revision) {
|
|
1618
|
+
return mapOf([
|
|
1619
|
+
["filter_id", revision.filterId],
|
|
1620
|
+
["revision", revision.revision],
|
|
1621
|
+
["digest", revision.digest]
|
|
1622
|
+
]);
|
|
1623
|
+
}
|
|
1624
|
+
function encodeFilterBinding(binding) {
|
|
1625
|
+
return mapOf([
|
|
1626
|
+
["group", encodeFilterGroupRef(binding.group)],
|
|
1627
|
+
["identity", encodeFilterGroupIdentity(binding.identity)],
|
|
1628
|
+
["filter_id", binding.filterId],
|
|
1629
|
+
["revision", binding.revision],
|
|
1630
|
+
["digest", binding.digest],
|
|
1631
|
+
["bound_at_micros", binding.boundAtMicros]
|
|
1632
|
+
]);
|
|
1633
|
+
}
|
|
1634
|
+
function encodeMutationResult(result) {
|
|
1635
|
+
switch (result.kind) {
|
|
1636
|
+
case "revision_state":
|
|
1637
|
+
return mapOf([
|
|
1638
|
+
[
|
|
1639
|
+
result.kind,
|
|
1640
|
+
mapOf([
|
|
1641
|
+
["filter_id", result.filterId],
|
|
1642
|
+
["revision", result.revision],
|
|
1643
|
+
["enabled", result.enabled]
|
|
1644
|
+
])
|
|
1645
|
+
]
|
|
1646
|
+
]);
|
|
1647
|
+
case "registered":
|
|
1648
|
+
case "revised":
|
|
1649
|
+
return mapOf([[result.kind, encodeRevisionRef(result.revision)]]);
|
|
1650
|
+
case "described":
|
|
1651
|
+
case "archived":
|
|
1652
|
+
case "dropped":
|
|
1653
|
+
return mapOf([[result.kind, mapOf([["filter_id", result.filterId]])]]);
|
|
1654
|
+
case "bound":
|
|
1655
|
+
case "unbound":
|
|
1656
|
+
return mapOf([[result.kind, encodeFilterBinding(result.binding)]]);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
function encodeMutationOutcome(outcome) {
|
|
1660
|
+
const status = outcome.status.kind === "pending"
|
|
1661
|
+
? "pending"
|
|
1662
|
+
: outcome.status.kind === "applied"
|
|
1663
|
+
? mapOf([["applied", encodeMutationResult(outcome.status.result)]])
|
|
1664
|
+
: mapOf([["rejected", encodeFilterError(outcome.status.error)]]);
|
|
1665
|
+
return mapOf([
|
|
1666
|
+
["v", outcome.v],
|
|
1667
|
+
["operation_id", outcome.operationId],
|
|
1668
|
+
["status", status]
|
|
1669
|
+
]);
|
|
1670
|
+
}
|
|
1671
|
+
function encodeRevisionInfo(info) {
|
|
1672
|
+
return mapOf([
|
|
1673
|
+
["enabled", info.enabled],
|
|
1674
|
+
["revision", info.revision],
|
|
1675
|
+
["digest", info.digest],
|
|
1676
|
+
["filter", encodeConsumerFilter(info.filter)],
|
|
1677
|
+
["created_at_micros", info.createdAtMicros]
|
|
1678
|
+
]);
|
|
1679
|
+
}
|
|
1680
|
+
function encodeSummary(summary) {
|
|
1681
|
+
return mapOf([
|
|
1682
|
+
["id", summary.id],
|
|
1683
|
+
["name", summary.name],
|
|
1684
|
+
["description", summary.description],
|
|
1685
|
+
["state", summary.state],
|
|
1686
|
+
["latest_revision", summary.latestRevision],
|
|
1687
|
+
["latest_digest", summary.latestDigest],
|
|
1688
|
+
["codec", summary.codec],
|
|
1689
|
+
["bindings", summary.bindings],
|
|
1690
|
+
["created_at_micros", summary.createdAtMicros],
|
|
1691
|
+
["updated_at_micros", summary.updatedAtMicros]
|
|
1692
|
+
]);
|
|
1693
|
+
}
|
|
1694
|
+
function encodeCatalogOutcome(outcome) {
|
|
1695
|
+
switch (outcome.kind) {
|
|
1696
|
+
case "filter":
|
|
1697
|
+
return mapOf([
|
|
1698
|
+
[
|
|
1699
|
+
"filter",
|
|
1700
|
+
mapOf([
|
|
1701
|
+
["summary", encodeSummary(outcome.detail.summary)],
|
|
1702
|
+
["latest", encodeRevisionInfo(outcome.detail.latest)],
|
|
1703
|
+
["bindings", outcome.detail.bindings.map(encodeFilterBinding)]
|
|
1704
|
+
])
|
|
1705
|
+
]
|
|
1706
|
+
]);
|
|
1707
|
+
case "filters":
|
|
1708
|
+
return mapOf([
|
|
1709
|
+
[
|
|
1710
|
+
"filters",
|
|
1711
|
+
mapOf([
|
|
1712
|
+
["items", outcome.page.items.map(encodeSummary)],
|
|
1713
|
+
["page", outcome.page.page],
|
|
1714
|
+
["page_size", outcome.page.pageSize],
|
|
1715
|
+
["total", outcome.page.total]
|
|
1716
|
+
])
|
|
1717
|
+
]
|
|
1718
|
+
]);
|
|
1719
|
+
case "revisions":
|
|
1720
|
+
return mapOf([
|
|
1721
|
+
[
|
|
1722
|
+
"revisions",
|
|
1723
|
+
mapOf([
|
|
1724
|
+
["filter_id", outcome.page.filterId],
|
|
1725
|
+
["items", outcome.page.items.map(encodeRevisionInfo)],
|
|
1726
|
+
["page", outcome.page.page],
|
|
1727
|
+
["page_size", outcome.page.pageSize],
|
|
1728
|
+
["total", outcome.page.total]
|
|
1729
|
+
])
|
|
1730
|
+
]
|
|
1731
|
+
]);
|
|
1732
|
+
case "binding":
|
|
1733
|
+
return mapOf([["binding", encodeFilterBinding(outcome.binding)]]);
|
|
1734
|
+
case "bindings":
|
|
1735
|
+
return mapOf([
|
|
1736
|
+
[
|
|
1737
|
+
"bindings",
|
|
1738
|
+
mapOf([
|
|
1739
|
+
["items", outcome.page.items.map(encodeFilterBinding)],
|
|
1740
|
+
["page", outcome.page.page],
|
|
1741
|
+
["page_size", outcome.page.pageSize],
|
|
1742
|
+
["total", outcome.page.total]
|
|
1743
|
+
])
|
|
1744
|
+
]
|
|
1745
|
+
]);
|
|
1746
|
+
case "mutation":
|
|
1747
|
+
return mapOf([["mutation", encodeMutationOutcome(outcome.outcome)]]);
|
|
1748
|
+
case "policy":
|
|
1749
|
+
return mapOf([
|
|
1750
|
+
[
|
|
1751
|
+
"policy",
|
|
1752
|
+
mapOf([
|
|
1753
|
+
["filter_id", outcome.policy.filterId],
|
|
1754
|
+
["revision", outcome.policy.revision],
|
|
1755
|
+
["digest", outcome.policy.digest],
|
|
1756
|
+
["state", outcome.policy.state],
|
|
1757
|
+
["filter", encodeConsumerFilter(outcome.policy.filter)]
|
|
1758
|
+
])
|
|
1759
|
+
]
|
|
1760
|
+
]);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
export function encodeFilterCatalogReply(reply) {
|
|
1764
|
+
return reply.kind === "ok"
|
|
1765
|
+
? mapOf([["ok", encodeCatalogOutcome(reply.outcome)]])
|
|
1766
|
+
: mapOf([["err", encodeFilterError(reply.error)]]);
|
|
1767
|
+
}
|
|
1768
|
+
// Decoding accepts the CBOR tree and the canonical JSON tree alike: both reach
|
|
1769
|
+
// here as maps, JSON integers as bigints and JSON bytes as number arrays.
|
|
1770
|
+
function oneOf(value, known, context) {
|
|
1771
|
+
const text = expectString(value, context);
|
|
1772
|
+
if (!known.has(text)) {
|
|
1773
|
+
throw new CodecError(`\`${text}\` is not recognized in ${context}`, context, "value");
|
|
1774
|
+
}
|
|
1775
|
+
return text;
|
|
1776
|
+
}
|
|
1777
|
+
function u32(value, context) {
|
|
1778
|
+
return typeof value === "bigint" ? expectU32(Number(value), context) : expectU32(value, context);
|
|
1779
|
+
}
|
|
1780
|
+
function requiredU32(map, key, context) {
|
|
1781
|
+
if (!map.has(key))
|
|
1782
|
+
throw new CodecError(`missing required field \`${key}\` in ${context}`, context, key);
|
|
1783
|
+
return u32(map.get(key), `${context}.${key}`);
|
|
1784
|
+
}
|
|
1785
|
+
function optionalU32(map, key, context) {
|
|
1786
|
+
return map.has(key) ? u32(map.get(key), `${context}.${key}`) : undefined;
|
|
1787
|
+
}
|
|
1788
|
+
function bytesOf(value, context) {
|
|
1789
|
+
if (Array.isArray(value)) {
|
|
1790
|
+
return Uint8Array.from(value, (byte) => {
|
|
1791
|
+
const number = u32(byte, context);
|
|
1792
|
+
if (number > 0xff)
|
|
1793
|
+
throw new CodecError(`byte out of range in ${context}`, context, "value");
|
|
1794
|
+
return number;
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
return expectBytes(value, context);
|
|
1798
|
+
}
|
|
1799
|
+
function requiredBytes(map, key, context) {
|
|
1800
|
+
if (!map.has(key))
|
|
1801
|
+
throw new CodecError(`missing required field \`${key}\` in ${context}`, context, key);
|
|
1802
|
+
return bytesOf(map.get(key), `${context}.${key}`);
|
|
1803
|
+
}
|
|
1804
|
+
function decodeTypedLiteral(value, context) {
|
|
1805
|
+
const map = expectMap(value, context);
|
|
1806
|
+
const kind = field.requiredString(map, "kind", context);
|
|
1807
|
+
if (kind === "double" || kind === "float") {
|
|
1808
|
+
const number = map.get("value");
|
|
1809
|
+
if (typeof number === "number")
|
|
1810
|
+
return { kind, value: number };
|
|
1811
|
+
if (typeof number === "bigint")
|
|
1812
|
+
return { kind, value: Number(number) };
|
|
1813
|
+
throw new CodecError(`field \`value\` in ${context} must be a number`, context, "value");
|
|
1814
|
+
}
|
|
1815
|
+
if (kind === "int") {
|
|
1816
|
+
const number = map.get("value");
|
|
1817
|
+
return { kind, value: typeof number === "bigint" ? Number(number) : number };
|
|
1818
|
+
}
|
|
1819
|
+
if (kind === "list") {
|
|
1820
|
+
return {
|
|
1821
|
+
kind,
|
|
1822
|
+
value: field.requiredArray(map, "value", context, (item, index) => decodeTypedLiteral(item, `${context}[${String(index)}]`))
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
return decodeTypedValue(map, context);
|
|
1826
|
+
}
|
|
1827
|
+
function decodePredicateTree(value, context) {
|
|
1828
|
+
const map = expectMap(value, context);
|
|
1829
|
+
return {
|
|
1830
|
+
field: field.requiredString(map, "field", context),
|
|
1831
|
+
op: oneOf(map.get("op"), CMP_OPS, `${context}.op`),
|
|
1832
|
+
value: decodeTypedLiteral(map.get("value"), `${context}.value`)
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function decodeCoerce(value, context) {
|
|
1836
|
+
const map = expectMap(value, context);
|
|
1837
|
+
const kind = field.requiredString(map, "kind", context);
|
|
1838
|
+
if (kind === "number")
|
|
1839
|
+
return { kind };
|
|
1840
|
+
if (kind === "timestamp") {
|
|
1841
|
+
return {
|
|
1842
|
+
kind,
|
|
1843
|
+
format: oneOf(map.get("format"), TIMESTAMP_FORMATS, `${context}.format`)
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
throw new CodecError(`\`${kind}\` is not a recognized coercion`, context, "kind");
|
|
1847
|
+
}
|
|
1848
|
+
function fieldPathOf(value, context) {
|
|
1849
|
+
try {
|
|
1850
|
+
return FieldPath.parse(expectString(value, context));
|
|
1851
|
+
}
|
|
1852
|
+
catch (cause) {
|
|
1853
|
+
throw new CodecError(`invalid field path in ${context}`, context, "path", { cause });
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
export function decodeFilterExpr(value, context) {
|
|
1857
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
1858
|
+
switch (tag) {
|
|
1859
|
+
case "all":
|
|
1860
|
+
case "any":
|
|
1861
|
+
return {
|
|
1862
|
+
kind: tag,
|
|
1863
|
+
children: expectArray(inner, context).map((child, index) => decodeFilterExpr(child, `${context}.${tag}[${String(index)}]`))
|
|
1864
|
+
};
|
|
1865
|
+
case "not":
|
|
1866
|
+
return { kind: "not", child: decodeFilterExpr(inner, `${context}.not`) };
|
|
1867
|
+
case "pred":
|
|
1868
|
+
return { kind: "pred", predicate: decodePredicateTree(inner, `${context}.pred`) };
|
|
1869
|
+
case "pred_as": {
|
|
1870
|
+
const map = expectMap(inner, `${context}.pred_as`);
|
|
1871
|
+
return {
|
|
1872
|
+
kind: "pred_as",
|
|
1873
|
+
predicate: {
|
|
1874
|
+
pred: decodePredicateTree(map.get("pred"), `${context}.pred_as.pred`),
|
|
1875
|
+
coerce: decodeCoerce(map.get("coerce"), `${context}.pred_as.coerce`)
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1879
|
+
case "present":
|
|
1880
|
+
case "absent":
|
|
1881
|
+
return { kind: tag, path: fieldPathOf(inner, `${context}.${tag}`) };
|
|
1882
|
+
case "header": {
|
|
1883
|
+
const map = expectMap(inner, `${context}.header`);
|
|
1884
|
+
return {
|
|
1885
|
+
kind: "header",
|
|
1886
|
+
predicate: {
|
|
1887
|
+
key: field.requiredString(map, "key", context),
|
|
1888
|
+
op: oneOf(map.get("op"), CMP_OPS, `${context}.header.op`),
|
|
1889
|
+
value: decodeTypedLiteral(map.get("value"), `${context}.header.value`)
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
case "text":
|
|
1894
|
+
case "header_text": {
|
|
1895
|
+
const map = expectMap(inner, `${context}.${tag}`);
|
|
1896
|
+
const caseInsensitive = map.get("case_insensitive");
|
|
1897
|
+
if (caseInsensitive !== undefined && typeof caseInsensitive !== "boolean") {
|
|
1898
|
+
throw new CodecError(`${context}.${tag}.case_insensitive must be a boolean`, context, "expr");
|
|
1899
|
+
}
|
|
1900
|
+
return {
|
|
1901
|
+
kind: tag,
|
|
1902
|
+
predicate: textPredicate(field.requiredString(map, "field", context), oneOf(map.get("kind"), TEXT_MATCHES, `${context}.${tag}.kind`), field.requiredString(map, "pattern", context), caseInsensitive === true)
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
default:
|
|
1906
|
+
throw new CodecError(`\`${tag}\` is not a recognized filter expression`, context, "expr");
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
// An operation id is a CBOR integer on the binary carriage and decimal text in
|
|
1910
|
+
// JSON, where a bare 128-bit number would not survive most parsers.
|
|
1911
|
+
function operationIdOf(map, context) {
|
|
1912
|
+
const value = map.get("operation_id");
|
|
1913
|
+
if (typeof value === "string" && /^[0-9]{1,39}$/.test(value)) {
|
|
1914
|
+
const parsed = BigInt(value);
|
|
1915
|
+
if (parsed < 1n << 128n)
|
|
1916
|
+
return parsed;
|
|
1917
|
+
}
|
|
1918
|
+
return field.requiredU128(map, "operation_id", context);
|
|
1919
|
+
}
|
|
1920
|
+
// A reason this build does not know decodes as `unknown`, so an older client
|
|
1921
|
+
// still reads the error and its code.
|
|
1922
|
+
function decodeFilterErrorReason(value, context) {
|
|
1923
|
+
const reason = expectString(value, context);
|
|
1924
|
+
return FILTER_ERROR_REASONS.has(reason)
|
|
1925
|
+
? reason
|
|
1926
|
+
: "unknown";
|
|
1927
|
+
}
|
|
1928
|
+
export function decodeFilterCodec(value, context) {
|
|
1929
|
+
const codec = expectString(value, context);
|
|
1930
|
+
return codec === "json" ||
|
|
1931
|
+
codec === "headers_only" ||
|
|
1932
|
+
codec === "cbor" ||
|
|
1933
|
+
codec === "avro" ||
|
|
1934
|
+
codec === "protobuf"
|
|
1935
|
+
? codec
|
|
1936
|
+
: "unknown";
|
|
1937
|
+
}
|
|
1938
|
+
const TEXT_MATCHES = new Set(["equals", "prefix", "suffix", "contains", "glob", "regex"]);
|
|
1939
|
+
// A default `reject` stays absent, so a decoded filter compares equal to one a builder made.
|
|
1940
|
+
function recordPolicyOf(map, key, name, context) {
|
|
1941
|
+
if (!map.has(key))
|
|
1942
|
+
return {};
|
|
1943
|
+
const policy = oneOf(map.get(key), new Set(["reject", "pass"]), `${context}.${key}`);
|
|
1944
|
+
return policy === "pass" ? { [name]: policy } : {};
|
|
1945
|
+
}
|
|
1946
|
+
export function decodeConsumerFilter(value, context) {
|
|
1947
|
+
const map = expectMap(value, context);
|
|
1948
|
+
return {
|
|
1949
|
+
v: requiredU32(map, "v", context),
|
|
1950
|
+
evaluatorVersion: requiredU32(map, "evaluator_version", context),
|
|
1951
|
+
expr: decodeFilterExpr(map.get("expr"), `${context}.expr`),
|
|
1952
|
+
codec: decodeFilterCodec(map.get("codec"), `${context}.codec`),
|
|
1953
|
+
faultPolicy: map.has("fault_policy")
|
|
1954
|
+
? oneOf(map.get("fault_policy"), new Set(["stop", "pass", "drop"]), `${context}.fault_policy`)
|
|
1955
|
+
: "stop",
|
|
1956
|
+
...recordPolicyOf(map, "foreign_policy", "foreignPolicy", context),
|
|
1957
|
+
...recordPolicyOf(map, "mismatch_policy", "mismatchPolicy", context),
|
|
1958
|
+
schemaRefs: field.optionalArray(map, "schema_refs", context, (item, index) => u32(item, `${context}.schema_refs[${String(index)}]`))
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
export function decodeFilterSource(value, context) {
|
|
1962
|
+
const map = expectMap(value, context);
|
|
1963
|
+
return {
|
|
1964
|
+
stream: field.requiredString(map, "stream", context),
|
|
1965
|
+
topic: field.requiredString(map, "topic", context)
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
export function decodeFilterConsumer(value, context) {
|
|
1969
|
+
const map = expectMap(value, context);
|
|
1970
|
+
const kind = field.requiredString(map, "kind", context);
|
|
1971
|
+
if (kind === "group_id")
|
|
1972
|
+
return { kind, id: field.requiredU64(map, "id", context) };
|
|
1973
|
+
if (kind !== "consumer" && kind !== "group") {
|
|
1974
|
+
throw new CodecError(`\`${kind}\` is not a recognized consumer kind`, context, "kind");
|
|
1975
|
+
}
|
|
1976
|
+
return { kind, name: field.requiredString(map, "name", context) };
|
|
1977
|
+
}
|
|
1978
|
+
export function decodeFilterRef(value, context) {
|
|
1979
|
+
if (value === "bound")
|
|
1980
|
+
return { kind: "bound" };
|
|
1981
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
1982
|
+
if (tag === "inline") {
|
|
1983
|
+
return { kind: "inline", filter: decodeConsumerFilter(inner, `${context}.inline`) };
|
|
1984
|
+
}
|
|
1985
|
+
if (tag === "revision") {
|
|
1986
|
+
const map = expectMap(inner, `${context}.revision`);
|
|
1987
|
+
return {
|
|
1988
|
+
kind: "revision",
|
|
1989
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
1990
|
+
revision: requiredU32(map, "revision", context)
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
throw new CodecError(`\`${tag}\` is not a recognized filter reference`, context, "filter");
|
|
1994
|
+
}
|
|
1995
|
+
export function decodeSourceGeneration(value, context) {
|
|
1996
|
+
const map = expectMap(value, context);
|
|
1997
|
+
return {
|
|
1998
|
+
streamId: requiredU32(map, "stream_id", context),
|
|
1999
|
+
streamCreatedAtMicros: field.requiredU64(map, "stream_created_at_micros", context),
|
|
2000
|
+
topicId: requiredU32(map, "topic_id", context),
|
|
2001
|
+
topicCreatedAtMicros: field.requiredU64(map, "topic_created_at_micros", context),
|
|
2002
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2003
|
+
partitionCreatedRevision: field.requiredU64(map, "partition_created_revision", context),
|
|
2004
|
+
purgeGeneration: field.requiredU64(map, "purge_generation", context)
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
export function decodeFilteredStart(value, context) {
|
|
2008
|
+
if (value === "next" || value === "first" || value === "last")
|
|
2009
|
+
return { kind: value };
|
|
2010
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2011
|
+
switch (tag) {
|
|
2012
|
+
case "offset":
|
|
2013
|
+
return {
|
|
2014
|
+
kind: "offset",
|
|
2015
|
+
offset: field.requiredU64(new Map([["offset", inner]]), "offset", context)
|
|
2016
|
+
};
|
|
2017
|
+
case "timestamp":
|
|
2018
|
+
return {
|
|
2019
|
+
kind: "timestamp",
|
|
2020
|
+
micros: field.requiredU64(new Map([["micros", inner]]), "micros", context)
|
|
2021
|
+
};
|
|
2022
|
+
case "continue": {
|
|
2023
|
+
const map = expectMap(inner, `${context}.continue`);
|
|
2024
|
+
const groupId = field.optionalU64(map, "group_id", context);
|
|
2025
|
+
return {
|
|
2026
|
+
kind: "continue",
|
|
2027
|
+
continuation: {
|
|
2028
|
+
...(groupId !== undefined ? { groupId } : {}),
|
|
2029
|
+
nextScanOffset: field.requiredU64(map, "next_scan_offset", context),
|
|
2030
|
+
generation: decodeSourceGeneration(map.get("generation"), `${context}.generation`),
|
|
2031
|
+
digest: requiredBytes(map, "digest", context),
|
|
2032
|
+
readMode: map.has("read_mode")
|
|
2033
|
+
? decodeReadMode(map.get("read_mode"), `${context}.read_mode`)
|
|
2034
|
+
: "primary"
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
default:
|
|
2039
|
+
throw new CodecError(`\`${tag}\` is not a recognized filtered start`, context, "start");
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
function decodeReadMode(value, context) {
|
|
2043
|
+
return oneOf(value, new Set(["primary", "local"]), context);
|
|
2044
|
+
}
|
|
2045
|
+
export function decodeFilteredPollRequest(value, context) {
|
|
2046
|
+
const map = expectMap(value, context);
|
|
2047
|
+
return {
|
|
2048
|
+
v: requiredU32(map, "v", context),
|
|
2049
|
+
source: decodeFilterSource(map.get("source"), `${context}.source`),
|
|
2050
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2051
|
+
consumer: decodeFilterConsumer(map.get("consumer"), `${context}.consumer`),
|
|
2052
|
+
filter: decodeFilterRef(map.get("filter"), `${context}.filter`),
|
|
2053
|
+
start: decodeFilteredStart(map.get("start"), `${context}.start`),
|
|
2054
|
+
count: requiredU32(map, "count", context),
|
|
2055
|
+
maxReplyBytes: requiredU32(map, "max_reply_bytes", context),
|
|
2056
|
+
readMode: map.has("read_mode")
|
|
2057
|
+
? decodeReadMode(map.get("read_mode"), `${context}.read_mode`)
|
|
2058
|
+
: "primary"
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
export function decodeFilteredAck(value, context) {
|
|
2062
|
+
const map = expectMap(value, context);
|
|
2063
|
+
const groupId = field.optionalU64(map, "group_id", context);
|
|
2064
|
+
return {
|
|
2065
|
+
...(groupId !== undefined ? { groupId } : {}),
|
|
2066
|
+
v: requiredU32(map, "v", context),
|
|
2067
|
+
source: decodeFilterSource(map.get("source"), `${context}.source`),
|
|
2068
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2069
|
+
consumer: decodeFilterConsumer(map.get("consumer"), `${context}.consumer`),
|
|
2070
|
+
generation: decodeSourceGeneration(map.get("generation"), `${context}.generation`),
|
|
2071
|
+
digest: requiredBytes(map, "digest", context),
|
|
2072
|
+
offset: field.requiredU64(map, "offset", context)
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
function decodeAppliedPolicy(value, context) {
|
|
2076
|
+
const map = expectMap(value, context);
|
|
2077
|
+
const filterId = optionalU32(map, "filter_id", context);
|
|
2078
|
+
const revision = optionalU32(map, "revision", context);
|
|
2079
|
+
const groupId = field.optionalU64(map, "group_id", context);
|
|
2080
|
+
return {
|
|
2081
|
+
...(groupId !== undefined ? { groupId } : {}),
|
|
2082
|
+
digest: requiredBytes(map, "digest", context),
|
|
2083
|
+
...(filterId !== undefined ? { filterId } : {}),
|
|
2084
|
+
...(revision !== undefined ? { revision } : {})
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function decodeFaultReason(value, context) {
|
|
2088
|
+
return oneOf(value, FAULT_REASONS, context);
|
|
2089
|
+
}
|
|
2090
|
+
export function decodeFilteredPage(value, context) {
|
|
2091
|
+
const map = expectMap(value, context);
|
|
2092
|
+
const nextScanOffset = field.optionalU64(map, "next_scan_offset", context);
|
|
2093
|
+
const safeAckOffset = field.optionalU64(map, "safe_ack_offset", context);
|
|
2094
|
+
const fault = field.optionalMap(map, "fault", context);
|
|
2095
|
+
const evaluationLimits = field.optionalMap(map, "evaluation_limits", context);
|
|
2096
|
+
return {
|
|
2097
|
+
v: requiredU32(map, "v", context),
|
|
2098
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2099
|
+
policy: decodeAppliedPolicy(map.get("policy"), `${context}.policy`),
|
|
2100
|
+
generation: decodeSourceGeneration(map.get("generation"), `${context}.generation`),
|
|
2101
|
+
readMode: decodeReadMode(map.get("read_mode"), `${context}.read_mode`),
|
|
2102
|
+
...(nextScanOffset !== undefined ? { nextScanOffset } : {}),
|
|
2103
|
+
...(safeAckOffset !== undefined ? { safeAckOffset } : {}),
|
|
2104
|
+
frontier: field.requiredU64(map, "frontier", context),
|
|
2105
|
+
examined: requiredU32(map, "examined", context),
|
|
2106
|
+
matched: requiredU32(map, "matched", context),
|
|
2107
|
+
stop: oneOf(map.get("stop"), STOP_REASONS, `${context}.stop`),
|
|
2108
|
+
...(fault !== undefined
|
|
2109
|
+
? {
|
|
2110
|
+
fault: {
|
|
2111
|
+
offset: field.requiredU64(fault, "offset", `${context}.fault`),
|
|
2112
|
+
reason: decodeFaultReason(fault.get("reason"), `${context}.fault.reason`)
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
: {}),
|
|
2116
|
+
...(evaluationLimits === undefined
|
|
2117
|
+
? {}
|
|
2118
|
+
: {
|
|
2119
|
+
evaluationLimits: {
|
|
2120
|
+
maxPayloadBytes: requiredU32(evaluationLimits, "max_payload_bytes", context),
|
|
2121
|
+
maxDepth: requiredU32(evaluationLimits, "max_depth", context)
|
|
2122
|
+
}
|
|
2123
|
+
}),
|
|
2124
|
+
unevaluated: field.optionalArray(map, "unevaluated", context, (item, index) => expectU64(item, `${context}.unevaluated[${String(index)}]`)),
|
|
2125
|
+
records: requiredBytes(map, "records", context)
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
function decodeAckReceipt(value, context) {
|
|
2129
|
+
const map = expectMap(value, context);
|
|
2130
|
+
return {
|
|
2131
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2132
|
+
offset: field.requiredU64(map, "offset", context),
|
|
2133
|
+
generation: decodeSourceGeneration(map.get("generation"), `${context}.generation`)
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
export function decodeFilterError(value, context) {
|
|
2137
|
+
const map = expectMap(value, context);
|
|
2138
|
+
return {
|
|
2139
|
+
code: resultCodeOf(field.requiredString(map, "code", context), context),
|
|
2140
|
+
reason: decodeFilterErrorReason(map.get("reason"), `${context}.reason`),
|
|
2141
|
+
message: field.requiredString(map, "message", context)
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
function resultCodeOf(word, context) {
|
|
2145
|
+
const code = resultCodeFromWord(word);
|
|
2146
|
+
if (code === undefined)
|
|
2147
|
+
throw new CodecError(`unknown result code \`${word}\``, context, "code");
|
|
2148
|
+
return code;
|
|
2149
|
+
}
|
|
2150
|
+
function decodeExplainNode(value, context) {
|
|
2151
|
+
const map = expectMap(value, context);
|
|
2152
|
+
const truth = map.has("truth")
|
|
2153
|
+
? oneOf(map.get("truth"), TRUTHS, `${context}.truth`)
|
|
2154
|
+
: undefined;
|
|
2155
|
+
return {
|
|
2156
|
+
label: field.requiredString(map, "label", context),
|
|
2157
|
+
...(truth !== undefined ? { truth } : {}),
|
|
2158
|
+
children: field.optionalArray(map, "children", context, (child, index) => decodeExplainNode(child, `${context}.children[${String(index)}]`))
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
function decodeExplanation(value, context) {
|
|
2162
|
+
const map = expectMap(value, context);
|
|
2163
|
+
const fault = map.has("fault")
|
|
2164
|
+
? decodeFaultReason(map.get("fault"), `${context}.fault`)
|
|
2165
|
+
: undefined;
|
|
2166
|
+
return {
|
|
2167
|
+
verdict: oneOf(map.get("verdict"), VERDICTS, `${context}.verdict`),
|
|
2168
|
+
...(fault !== undefined ? { fault } : {}),
|
|
2169
|
+
root: decodeExplainNode(map.get("root"), `${context}.root`)
|
|
2170
|
+
};
|
|
2171
|
+
}
|
|
2172
|
+
function decodeFilterPreview(value, context) {
|
|
2173
|
+
const map = expectMap(value, context);
|
|
2174
|
+
const nextOffset = field.optionalU64(map, "next_offset", context);
|
|
2175
|
+
return {
|
|
2176
|
+
v: requiredU32(map, "v", context),
|
|
2177
|
+
partitionId: requiredU32(map, "partition_id", context),
|
|
2178
|
+
policy: decodeAppliedPolicy(map.get("policy"), `${context}.policy`),
|
|
2179
|
+
readMode: decodeReadMode(map.get("read_mode"), `${context}.read_mode`),
|
|
2180
|
+
examined: requiredU32(map, "examined", context),
|
|
2181
|
+
matched: requiredU32(map, "matched", context),
|
|
2182
|
+
faults: requiredU32(map, "faults", context),
|
|
2183
|
+
stop: oneOf(map.get("stop"), STOP_REASONS, `${context}.stop`),
|
|
2184
|
+
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
2185
|
+
frontier: field.requiredU64(map, "frontier", context),
|
|
2186
|
+
records: field.requiredArray(map, "records", context, (item, index) => {
|
|
2187
|
+
const recordContext = `${context}.records[${String(index)}]`;
|
|
2188
|
+
const record = expectMap(item, recordContext);
|
|
2189
|
+
const fault = record.has("fault")
|
|
2190
|
+
? decodeFaultReason(record.get("fault"), `${recordContext}.fault`)
|
|
2191
|
+
: undefined;
|
|
2192
|
+
const explanation = record.has("explanation")
|
|
2193
|
+
? decodeExplanation(record.get("explanation"), `${recordContext}.explanation`)
|
|
2194
|
+
: undefined;
|
|
2195
|
+
return {
|
|
2196
|
+
offset: field.requiredU64(record, "offset", recordContext),
|
|
2197
|
+
timestampMicros: field.requiredU64(record, "timestamp_micros", recordContext),
|
|
2198
|
+
verdict: oneOf(record.get("verdict"), VERDICTS, `${recordContext}.verdict`),
|
|
2199
|
+
...(fault !== undefined ? { fault } : {}),
|
|
2200
|
+
payloadText: field.requiredString(record, "payload_text", recordContext),
|
|
2201
|
+
payloadTruncated: field.optionalBoolean(record, "payload_truncated", recordContext) ?? false,
|
|
2202
|
+
...(explanation !== undefined ? { explanation } : {})
|
|
2203
|
+
};
|
|
2204
|
+
})
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function decodeFilterOutcome(value, context) {
|
|
2208
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2209
|
+
switch (tag) {
|
|
2210
|
+
case "page":
|
|
2211
|
+
return { kind: "page", page: decodeFilteredPage(inner, `${context}.page`) };
|
|
2212
|
+
case "acknowledged":
|
|
2213
|
+
return { kind: "acknowledged", receipt: decodeAckReceipt(inner, `${context}.acknowledged`) };
|
|
2214
|
+
case "preview":
|
|
2215
|
+
return { kind: "preview", preview: decodeFilterPreview(inner, `${context}.preview`) };
|
|
2216
|
+
case "tested": {
|
|
2217
|
+
const map = expectMap(inner, `${context}.tested`);
|
|
2218
|
+
return {
|
|
2219
|
+
kind: "tested",
|
|
2220
|
+
result: {
|
|
2221
|
+
v: requiredU32(map, "v", context),
|
|
2222
|
+
policy: decodeAppliedPolicy(map.get("policy"), `${context}.tested.policy`),
|
|
2223
|
+
explanation: decodeExplanation(map.get("explanation"), `${context}.tested.explanation`)
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
case "validated": {
|
|
2228
|
+
const map = expectMap(inner, `${context}.validated`);
|
|
2229
|
+
return {
|
|
2230
|
+
kind: "validated",
|
|
2231
|
+
validation: {
|
|
2232
|
+
v: requiredU32(map, "v", context),
|
|
2233
|
+
digest: requiredBytes(map, "digest", context),
|
|
2234
|
+
readsPayload: field.requiredBoolean(map, "reads_payload", context),
|
|
2235
|
+
readsHeaders: field.requiredBoolean(map, "reads_headers", context)
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
default:
|
|
2240
|
+
throw new CodecError(`\`${tag}\` is not a recognized filter outcome`, context, "outcome");
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
export function decodeFilterReply(value, context) {
|
|
2244
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2245
|
+
if (tag === "ok")
|
|
2246
|
+
return { kind: "ok", outcome: decodeFilterOutcome(inner, `${context}.ok`) };
|
|
2247
|
+
if (tag === "err")
|
|
2248
|
+
return { kind: "err", error: decodeFilterError(inner, `${context}.err`) };
|
|
2249
|
+
throw new CodecError(`\`${tag}\` is not a recognized filter reply`, context, "reply");
|
|
2250
|
+
}
|
|
2251
|
+
export function decodeHeaderScalar(value, context) {
|
|
2252
|
+
const map = expectMap(value, context);
|
|
2253
|
+
const kind = field.requiredString(map, "kind", context);
|
|
2254
|
+
const inner = map.get("value");
|
|
2255
|
+
switch (kind) {
|
|
2256
|
+
case "bool":
|
|
2257
|
+
if (typeof inner === "boolean")
|
|
2258
|
+
return { kind, value: inner };
|
|
2259
|
+
break;
|
|
2260
|
+
case "int":
|
|
2261
|
+
case "uint":
|
|
2262
|
+
if (typeof inner === "bigint")
|
|
2263
|
+
return { kind, value: inner };
|
|
2264
|
+
if (typeof inner === "number" && Number.isSafeInteger(inner))
|
|
2265
|
+
return { kind, value: BigInt(inner) };
|
|
2266
|
+
break;
|
|
2267
|
+
case "float":
|
|
2268
|
+
if (typeof inner === "number")
|
|
2269
|
+
return { kind, value: inner };
|
|
2270
|
+
if (typeof inner === "bigint")
|
|
2271
|
+
return { kind, value: Number(inner) };
|
|
2272
|
+
break;
|
|
2273
|
+
case "string":
|
|
2274
|
+
if (typeof inner === "string")
|
|
2275
|
+
return { kind, value: inner };
|
|
2276
|
+
break;
|
|
2277
|
+
case "raw":
|
|
2278
|
+
return { kind, value: bytesOf(inner, `${context}.value`) };
|
|
2279
|
+
default:
|
|
2280
|
+
throw new CodecError(`\`${kind}\` is not a recognized header kind`, context, "kind");
|
|
2281
|
+
}
|
|
2282
|
+
throw new CodecError(`header value in ${context} does not match its kind`, context, "value");
|
|
2283
|
+
}
|
|
2284
|
+
export function decodeFilterGroupRef(value, context) {
|
|
2285
|
+
const map = expectMap(value, context);
|
|
2286
|
+
return {
|
|
2287
|
+
stream: field.requiredString(map, "stream", context),
|
|
2288
|
+
topic: field.requiredString(map, "topic", context),
|
|
2289
|
+
group: field.requiredString(map, "group", context)
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
export function decodeFilterGroupIdentity(value, context) {
|
|
2293
|
+
const map = expectMap(value, context);
|
|
2294
|
+
return {
|
|
2295
|
+
streamId: requiredU32(map, "stream_id", context),
|
|
2296
|
+
streamCreatedAtMicros: field.requiredU64(map, "stream_created_at_micros", context),
|
|
2297
|
+
topicId: requiredU32(map, "topic_id", context),
|
|
2298
|
+
topicCreatedAtMicros: field.requiredU64(map, "topic_created_at_micros", context),
|
|
2299
|
+
groupId: field.requiredU64(map, "group_id", context)
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
export function decodeFilterMutation(value, context) {
|
|
2303
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2304
|
+
const map = expectMap(inner, `${context}.${tag}`);
|
|
2305
|
+
switch (tag) {
|
|
2306
|
+
case "register":
|
|
2307
|
+
return {
|
|
2308
|
+
kind: "register",
|
|
2309
|
+
name: field.requiredString(map, "name", context),
|
|
2310
|
+
description: field.requiredString(map, "description", context),
|
|
2311
|
+
filter: decodeConsumerFilter(map.get("filter"), `${context}.register.filter`)
|
|
2312
|
+
};
|
|
2313
|
+
case "revise":
|
|
2314
|
+
return {
|
|
2315
|
+
kind: "revise",
|
|
2316
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2317
|
+
expectedRevision: requiredU32(map, "expected_revision", context),
|
|
2318
|
+
filter: decodeConsumerFilter(map.get("filter"), `${context}.revise.filter`)
|
|
2319
|
+
};
|
|
2320
|
+
case "describe":
|
|
2321
|
+
return {
|
|
2322
|
+
kind: "describe",
|
|
2323
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2324
|
+
description: field.requiredString(map, "description", context)
|
|
2325
|
+
};
|
|
2326
|
+
case "set_revision_enabled":
|
|
2327
|
+
return {
|
|
2328
|
+
kind: tag,
|
|
2329
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2330
|
+
revision: requiredU32(map, "revision", context),
|
|
2331
|
+
enabled: field.requiredBoolean(map, "enabled", context)
|
|
2332
|
+
};
|
|
2333
|
+
case "archive":
|
|
2334
|
+
case "drop":
|
|
2335
|
+
return { kind: tag, filterId: requiredU32(map, "filter_id", context) };
|
|
2336
|
+
case "bind":
|
|
2337
|
+
return {
|
|
2338
|
+
kind: "bind",
|
|
2339
|
+
group: decodeFilterGroupRef(map.get("group"), `${context}.bind.group`),
|
|
2340
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2341
|
+
revision: requiredU32(map, "revision", context)
|
|
2342
|
+
};
|
|
2343
|
+
case "unbind":
|
|
2344
|
+
return {
|
|
2345
|
+
kind: "unbind",
|
|
2346
|
+
group: decodeFilterGroupRef(map.get("group"), `${context}.unbind.group`),
|
|
2347
|
+
expectedDigest: requiredBytes(map, "expected_digest", context),
|
|
2348
|
+
...(map.has("expected_identity") && map.get("expected_identity") !== null
|
|
2349
|
+
? {
|
|
2350
|
+
expectedIdentity: decodeFilterGroupIdentity(map.get("expected_identity"), `${context}.expected_identity`)
|
|
2351
|
+
}
|
|
2352
|
+
: {})
|
|
2353
|
+
};
|
|
2354
|
+
default:
|
|
2355
|
+
throw new CodecError(`\`${tag}\` is not a recognized filter mutation`, context, "mutation");
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
export function decodeFilterMutationRequest(value, context) {
|
|
2359
|
+
const map = expectMap(value, context);
|
|
2360
|
+
return {
|
|
2361
|
+
v: requiredU32(map, "v", context),
|
|
2362
|
+
operationId: operationIdOf(map, context),
|
|
2363
|
+
mutation: decodeFilterMutation(map.get("mutation"), `${context}.mutation`)
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
export function decodeFilterCatalogCommand(value, context) {
|
|
2367
|
+
const map = expectMap(value, context);
|
|
2368
|
+
return {
|
|
2369
|
+
...(map.has("limits")
|
|
2370
|
+
? { limits: decodeFilterCatalogLimits(map.get("limits"), `${context}.limits`) }
|
|
2371
|
+
: {}),
|
|
2372
|
+
operationId: operationIdOf(map, context),
|
|
2373
|
+
actorUserId: requiredU32(map, "actor_user_id", context),
|
|
2374
|
+
mutation: decodeFilterMutation(map.get("mutation"), `${context}.mutation`),
|
|
2375
|
+
...(map.has("identity")
|
|
2376
|
+
? { identity: decodeFilterGroupIdentity(map.get("identity"), `${context}.identity`) }
|
|
2377
|
+
: {}),
|
|
2378
|
+
...(map.has("filter_id") ? { filterId: requiredU32(map, "filter_id", context) } : {}),
|
|
2379
|
+
...(map.has("grants")
|
|
2380
|
+
? {
|
|
2381
|
+
grants: expectArray(map.get("grants"), `${context}.grants`).map((grant, index) => decodeGrant(expectMap(grant, `${context}.grants[${String(index)}]`), context))
|
|
2382
|
+
}
|
|
2383
|
+
: {})
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
function decodeFilterCatalogLimits(value, context) {
|
|
2387
|
+
const map = expectMap(value, context);
|
|
2388
|
+
return {
|
|
2389
|
+
maxDefinitions: requiredU32(map, "max_definitions", context),
|
|
2390
|
+
maxTombstones: requiredU32(map, "max_tombstones", context),
|
|
2391
|
+
maxRevisions: requiredU32(map, "max_revisions", context),
|
|
2392
|
+
maxGroupIdentities: requiredU32(map, "max_group_identities", context),
|
|
2393
|
+
maxCatalogBytes: field.optionalU64(map, "max_catalog_bytes", context) ?? DEFAULT_MAX_CATALOG_BYTES
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
function decodeRevisionRef(value, context) {
|
|
2397
|
+
const map = expectMap(value, context);
|
|
2398
|
+
return {
|
|
2399
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2400
|
+
revision: requiredU32(map, "revision", context),
|
|
2401
|
+
digest: requiredBytes(map, "digest", context)
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
function decodeFilterBinding(value, context) {
|
|
2405
|
+
const map = expectMap(value, context);
|
|
2406
|
+
return {
|
|
2407
|
+
group: decodeFilterGroupRef(map.get("group"), `${context}.group`),
|
|
2408
|
+
identity: decodeFilterGroupIdentity(map.get("identity"), `${context}.identity`),
|
|
2409
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2410
|
+
revision: requiredU32(map, "revision", context),
|
|
2411
|
+
digest: requiredBytes(map, "digest", context),
|
|
2412
|
+
boundAtMicros: field.requiredU64(map, "bound_at_micros", context)
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function decodeMutationResult(value, context) {
|
|
2416
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2417
|
+
switch (tag) {
|
|
2418
|
+
case "revision_state": {
|
|
2419
|
+
const map = expectMap(inner, context);
|
|
2420
|
+
return {
|
|
2421
|
+
kind: tag,
|
|
2422
|
+
filterId: requiredU32(map, "filter_id", context),
|
|
2423
|
+
revision: requiredU32(map, "revision", context),
|
|
2424
|
+
enabled: field.requiredBoolean(map, "enabled", context)
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
case "registered":
|
|
2428
|
+
case "revised":
|
|
2429
|
+
return { kind: tag, revision: decodeRevisionRef(inner, `${context}.${tag}`) };
|
|
2430
|
+
case "described":
|
|
2431
|
+
case "archived":
|
|
2432
|
+
case "dropped":
|
|
2433
|
+
return {
|
|
2434
|
+
kind: tag,
|
|
2435
|
+
filterId: requiredU32(expectMap(inner, `${context}.${tag}`), "filter_id", context)
|
|
2436
|
+
};
|
|
2437
|
+
case "bound":
|
|
2438
|
+
case "unbound":
|
|
2439
|
+
return { kind: tag, binding: decodeFilterBinding(inner, `${context}.${tag}`) };
|
|
2440
|
+
default:
|
|
2441
|
+
throw new CodecError(`\`${tag}\` is not a recognized mutation result`, context, "result");
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function decodeMutationStatus(value, context) {
|
|
2445
|
+
if (value === "pending")
|
|
2446
|
+
return { kind: "pending" };
|
|
2447
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2448
|
+
if (tag === "applied") {
|
|
2449
|
+
return { kind: "applied", result: decodeMutationResult(inner, `${context}.applied`) };
|
|
2450
|
+
}
|
|
2451
|
+
if (tag === "rejected") {
|
|
2452
|
+
return { kind: "rejected", error: decodeFilterError(inner, `${context}.rejected`) };
|
|
2453
|
+
}
|
|
2454
|
+
throw new CodecError(`\`${tag}\` is not a recognized mutation status`, context, "status");
|
|
2455
|
+
}
|
|
2456
|
+
export function decodeFilterMutationOutcome(value, context) {
|
|
2457
|
+
const map = expectMap(value, context);
|
|
2458
|
+
return {
|
|
2459
|
+
v: requiredU32(map, "v", context),
|
|
2460
|
+
operationId: operationIdOf(map, context),
|
|
2461
|
+
status: decodeMutationStatus(map.get("status"), `${context}.status`)
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
function decodeRevisionInfo(value, context) {
|
|
2465
|
+
const map = expectMap(value, context);
|
|
2466
|
+
return {
|
|
2467
|
+
enabled: map.has("enabled") ? field.requiredBoolean(map, "enabled", context) : true,
|
|
2468
|
+
revision: requiredU32(map, "revision", context),
|
|
2469
|
+
digest: requiredBytes(map, "digest", context),
|
|
2470
|
+
filter: decodeConsumerFilter(map.get("filter"), `${context}.filter`),
|
|
2471
|
+
createdAtMicros: field.requiredU64(map, "created_at_micros", context)
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function decodeSummary(value, context) {
|
|
2475
|
+
const map = expectMap(value, context);
|
|
2476
|
+
return {
|
|
2477
|
+
id: requiredU32(map, "id", context),
|
|
2478
|
+
name: field.requiredString(map, "name", context),
|
|
2479
|
+
description: field.requiredString(map, "description", context),
|
|
2480
|
+
state: oneOf(map.get("state"), FILTER_STATES, `${context}.state`),
|
|
2481
|
+
latestRevision: requiredU32(map, "latest_revision", context),
|
|
2482
|
+
latestDigest: requiredBytes(map, "latest_digest", context),
|
|
2483
|
+
codec: decodeFilterCodec(map.get("codec"), `${context}.codec`),
|
|
2484
|
+
bindings: requiredU32(map, "bindings", context),
|
|
2485
|
+
createdAtMicros: field.requiredU64(map, "created_at_micros", context),
|
|
2486
|
+
updatedAtMicros: field.requiredU64(map, "updated_at_micros", context)
|
|
2487
|
+
};
|
|
2488
|
+
}
|
|
2489
|
+
function decodeCatalogOutcome(value, context) {
|
|
2490
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2491
|
+
const innerContext = `${context}.${tag}`;
|
|
2492
|
+
const map = expectMap(inner, innerContext);
|
|
2493
|
+
const pageOf = (decode) => ({
|
|
2494
|
+
items: field.requiredArray(map, "items", innerContext, (item, index) => decode(item, `${innerContext}.items[${String(index)}]`)),
|
|
2495
|
+
page: requiredU32(map, "page", innerContext),
|
|
2496
|
+
pageSize: requiredU32(map, "page_size", innerContext),
|
|
2497
|
+
total: requiredU32(map, "total", innerContext)
|
|
2498
|
+
});
|
|
2499
|
+
switch (tag) {
|
|
2500
|
+
case "filter":
|
|
2501
|
+
return {
|
|
2502
|
+
kind: "filter",
|
|
2503
|
+
detail: {
|
|
2504
|
+
summary: decodeSummary(map.get("summary"), `${innerContext}.summary`),
|
|
2505
|
+
latest: decodeRevisionInfo(map.get("latest"), `${innerContext}.latest`),
|
|
2506
|
+
bindings: field.requiredArray(map, "bindings", innerContext, (item, index) => decodeFilterBinding(item, `${innerContext}.bindings[${String(index)}]`))
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
case "filters":
|
|
2510
|
+
return { kind: "filters", page: pageOf(decodeSummary) };
|
|
2511
|
+
case "revisions":
|
|
2512
|
+
return {
|
|
2513
|
+
kind: "revisions",
|
|
2514
|
+
page: {
|
|
2515
|
+
filterId: requiredU32(map, "filter_id", innerContext),
|
|
2516
|
+
...pageOf(decodeRevisionInfo)
|
|
2517
|
+
}
|
|
2518
|
+
};
|
|
2519
|
+
case "binding":
|
|
2520
|
+
return { kind: "binding", binding: decodeFilterBinding(map, innerContext) };
|
|
2521
|
+
case "bindings":
|
|
2522
|
+
return { kind: "bindings", page: pageOf(decodeFilterBinding) };
|
|
2523
|
+
case "mutation":
|
|
2524
|
+
return { kind: "mutation", outcome: decodeFilterMutationOutcome(map, innerContext) };
|
|
2525
|
+
case "policy":
|
|
2526
|
+
return {
|
|
2527
|
+
kind: "policy",
|
|
2528
|
+
policy: {
|
|
2529
|
+
filterId: requiredU32(map, "filter_id", innerContext),
|
|
2530
|
+
revision: requiredU32(map, "revision", innerContext),
|
|
2531
|
+
digest: requiredBytes(map, "digest", innerContext),
|
|
2532
|
+
state: oneOf(map.get("state"), FILTER_STATES, `${innerContext}.state`),
|
|
2533
|
+
filter: decodeConsumerFilter(map.get("filter"), `${innerContext}.filter`)
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
default:
|
|
2537
|
+
throw new CodecError(`\`${tag}\` is not a recognized catalog outcome`, context, "outcome");
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
export function decodeFilterCatalogReply(value, context) {
|
|
2541
|
+
const [tag, inner] = singleVariantTag(value, context);
|
|
2542
|
+
if (tag === "ok")
|
|
2543
|
+
return { kind: "ok", outcome: decodeCatalogOutcome(inner, `${context}.ok`) };
|
|
2544
|
+
if (tag === "err")
|
|
2545
|
+
return { kind: "err", error: decodeFilterError(inner, `${context}.err`) };
|
|
2546
|
+
throw new CodecError(`\`${tag}\` is not a recognized catalog reply`, context, "reply");
|
|
2547
|
+
}
|
|
2548
|
+
// Canonical JSON, byte for byte what serde_json writes: struct fields in
|
|
2549
|
+
// declaration order, strings escaped like `JSON.stringify`, bytes as number
|
|
2550
|
+
// arrays, and doubles in Rust's shortest round-trip notation.
|
|
2551
|
+
function writeJson(value) {
|
|
2552
|
+
if (value instanceof JsonDouble)
|
|
2553
|
+
return rustDouble(value.value);
|
|
2554
|
+
if (value instanceof Map) {
|
|
2555
|
+
const members = [...value.entries()].map(([key, item]) => `${JSON.stringify(key)}:${writeJson(item)}`);
|
|
2556
|
+
return `{${members.join(",")}}`;
|
|
2557
|
+
}
|
|
2558
|
+
if (value instanceof Uint8Array)
|
|
2559
|
+
return `[${[...value].join(",")}]`;
|
|
2560
|
+
if (Array.isArray(value))
|
|
2561
|
+
return `[${value.map(writeJson).join(",")}]`;
|
|
2562
|
+
if (typeof value === "bigint")
|
|
2563
|
+
return value.toString();
|
|
2564
|
+
if (typeof value === "number")
|
|
2565
|
+
return String(value);
|
|
2566
|
+
if (typeof value === "string" || typeof value === "boolean" || value === null) {
|
|
2567
|
+
return JSON.stringify(value);
|
|
2568
|
+
}
|
|
2569
|
+
throw new InvalidError("the filter holds a value JSON cannot carry");
|
|
2570
|
+
}
|
|
2571
|
+
// Rust (ryu) notation of a finite double: the shortest round-trip digits, with
|
|
2572
|
+
// `.0` on integral values below 1e16 and a bare exponent (`1e21`, `1e-7`)
|
|
2573
|
+
// outside the plain-notation window.
|
|
2574
|
+
export function rustDouble(value) {
|
|
2575
|
+
if (!Number.isFinite(value))
|
|
2576
|
+
throw new InvalidError("a double must be finite");
|
|
2577
|
+
if (value === 0)
|
|
2578
|
+
return Object.is(value, -0) ? "-0.0" : "0.0";
|
|
2579
|
+
const [mantissa = "", exponentText = "0"] = Math.abs(value).toExponential().split("e");
|
|
2580
|
+
const digits = mantissa.replace(".", "");
|
|
2581
|
+
const length = digits.length;
|
|
2582
|
+
const kk = Number(exponentText) + 1;
|
|
2583
|
+
const k = kk - length;
|
|
2584
|
+
let text;
|
|
2585
|
+
if (k >= 0 && kk <= 16) {
|
|
2586
|
+
text = `${digits}${"0".repeat(k)}.0`;
|
|
2587
|
+
}
|
|
2588
|
+
else if (kk > 0 && kk <= 16) {
|
|
2589
|
+
text = `${digits.slice(0, kk)}.${digits.slice(kk)}`;
|
|
2590
|
+
}
|
|
2591
|
+
else if (kk > -5 && kk <= 0) {
|
|
2592
|
+
text = `0.${"0".repeat(-kk)}${digits}`;
|
|
2593
|
+
}
|
|
2594
|
+
else if (length === 1) {
|
|
2595
|
+
text = `${digits}e${String(kk - 1)}`;
|
|
2596
|
+
}
|
|
2597
|
+
else {
|
|
2598
|
+
text = `${digits.slice(0, 1)}.${digits.slice(1)}e${String(kk - 1)}`;
|
|
2599
|
+
}
|
|
2600
|
+
return value < 0 ? `-${text}` : text;
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* Parse JSON the way serde_json does, keeping integers exact. Objects become
|
|
2604
|
+
* maps with the last duplicate key winning. Throws on anything serde_json
|
|
2605
|
+
* rejects. `payload` mode decodes a record payload like the evaluator: only
|
|
2606
|
+
* integers that fit i64 or u64 stay bigints, any other number is a double.
|
|
2607
|
+
* `document` mode keeps every integer a bigint, for u128 fields.
|
|
2608
|
+
*/
|
|
2609
|
+
export function parseCanonicalJson(text, mode = "document") {
|
|
2610
|
+
const parser = new JsonParser(text, mode);
|
|
2611
|
+
const value = parser.value();
|
|
2612
|
+
parser.end();
|
|
2613
|
+
return value;
|
|
2614
|
+
}
|
|
2615
|
+
const U64_MAX = (1n << 64n) - 1n;
|
|
2616
|
+
class JsonParser {
|
|
2617
|
+
text;
|
|
2618
|
+
mode;
|
|
2619
|
+
position = 0;
|
|
2620
|
+
constructor(text, mode) {
|
|
2621
|
+
this.text = text;
|
|
2622
|
+
this.mode = mode;
|
|
2623
|
+
}
|
|
2624
|
+
value() {
|
|
2625
|
+
this.whitespace();
|
|
2626
|
+
const character = this.text[this.position];
|
|
2627
|
+
switch (character) {
|
|
2628
|
+
case "{":
|
|
2629
|
+
return this.object();
|
|
2630
|
+
case "[":
|
|
2631
|
+
return this.array();
|
|
2632
|
+
case '"':
|
|
2633
|
+
return this.string();
|
|
2634
|
+
case "t":
|
|
2635
|
+
return this.literal("true", true);
|
|
2636
|
+
case "f":
|
|
2637
|
+
return this.literal("false", false);
|
|
2638
|
+
case "n":
|
|
2639
|
+
return this.literal("null", null);
|
|
2640
|
+
case undefined:
|
|
2641
|
+
return this.fail("unexpected end of input");
|
|
2642
|
+
default:
|
|
2643
|
+
return this.number();
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
end() {
|
|
2647
|
+
this.whitespace();
|
|
2648
|
+
if (this.position !== this.text.length)
|
|
2649
|
+
this.fail("trailing characters");
|
|
2650
|
+
}
|
|
2651
|
+
object() {
|
|
2652
|
+
const map = new Map();
|
|
2653
|
+
this.position += 1;
|
|
2654
|
+
this.whitespace();
|
|
2655
|
+
if (this.text[this.position] === "}") {
|
|
2656
|
+
this.position += 1;
|
|
2657
|
+
return map;
|
|
2658
|
+
}
|
|
2659
|
+
for (;;) {
|
|
2660
|
+
this.whitespace();
|
|
2661
|
+
if (this.text[this.position] !== '"')
|
|
2662
|
+
this.fail("expected a key");
|
|
2663
|
+
const key = this.string();
|
|
2664
|
+
this.whitespace();
|
|
2665
|
+
if (this.text[this.position] !== ":")
|
|
2666
|
+
this.fail("expected `:`");
|
|
2667
|
+
this.position += 1;
|
|
2668
|
+
map.set(key, this.value());
|
|
2669
|
+
this.whitespace();
|
|
2670
|
+
const next = this.text[this.position];
|
|
2671
|
+
this.position += 1;
|
|
2672
|
+
if (next === "}")
|
|
2673
|
+
return map;
|
|
2674
|
+
if (next !== ",")
|
|
2675
|
+
this.fail("expected `,` or `}`");
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
array() {
|
|
2679
|
+
const items = [];
|
|
2680
|
+
this.position += 1;
|
|
2681
|
+
this.whitespace();
|
|
2682
|
+
if (this.text[this.position] === "]") {
|
|
2683
|
+
this.position += 1;
|
|
2684
|
+
return items;
|
|
2685
|
+
}
|
|
2686
|
+
for (;;) {
|
|
2687
|
+
items.push(this.value());
|
|
2688
|
+
this.whitespace();
|
|
2689
|
+
const next = this.text[this.position];
|
|
2690
|
+
this.position += 1;
|
|
2691
|
+
if (next === "]")
|
|
2692
|
+
return items;
|
|
2693
|
+
if (next !== ",")
|
|
2694
|
+
this.fail("expected `,` or `]`");
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
// Plain runs are copied as one slice each, so a long key or value is not
|
|
2698
|
+
// built one character at a time.
|
|
2699
|
+
string() {
|
|
2700
|
+
this.position += 1;
|
|
2701
|
+
let out = "";
|
|
2702
|
+
let run = this.position;
|
|
2703
|
+
for (;;) {
|
|
2704
|
+
const code = this.text.charCodeAt(this.position);
|
|
2705
|
+
if (Number.isNaN(code))
|
|
2706
|
+
this.fail("unterminated string");
|
|
2707
|
+
if (code === 0x22) {
|
|
2708
|
+
out += this.text.slice(run, this.position);
|
|
2709
|
+
this.position += 1;
|
|
2710
|
+
return out;
|
|
2711
|
+
}
|
|
2712
|
+
if (code < 0x20)
|
|
2713
|
+
this.fail("control character in string");
|
|
2714
|
+
if (code === 0x5c) {
|
|
2715
|
+
out += this.text.slice(run, this.position);
|
|
2716
|
+
const escape = this.text[this.position + 1];
|
|
2717
|
+
this.position += 2;
|
|
2718
|
+
switch (escape) {
|
|
2719
|
+
case '"':
|
|
2720
|
+
case "\\":
|
|
2721
|
+
case "/":
|
|
2722
|
+
out += escape;
|
|
2723
|
+
break;
|
|
2724
|
+
case "b":
|
|
2725
|
+
out += "\b";
|
|
2726
|
+
break;
|
|
2727
|
+
case "f":
|
|
2728
|
+
out += "\f";
|
|
2729
|
+
break;
|
|
2730
|
+
case "n":
|
|
2731
|
+
out += "\n";
|
|
2732
|
+
break;
|
|
2733
|
+
case "r":
|
|
2734
|
+
out += "\r";
|
|
2735
|
+
break;
|
|
2736
|
+
case "t":
|
|
2737
|
+
out += "\t";
|
|
2738
|
+
break;
|
|
2739
|
+
case "u":
|
|
2740
|
+
out += this.unicodeEscape();
|
|
2741
|
+
break;
|
|
2742
|
+
case undefined:
|
|
2743
|
+
this.fail("unterminated escape");
|
|
2744
|
+
break;
|
|
2745
|
+
default:
|
|
2746
|
+
this.fail("invalid escape");
|
|
2747
|
+
}
|
|
2748
|
+
run = this.position;
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
if (code >= 0xd800 && code <= 0xdfff) {
|
|
2752
|
+
const low = this.text.charCodeAt(this.position + 1);
|
|
2753
|
+
if (code > 0xdbff || !(low >= 0xdc00 && low <= 0xdfff))
|
|
2754
|
+
this.fail("lone surrogate");
|
|
2755
|
+
this.position += 2;
|
|
2756
|
+
continue;
|
|
2757
|
+
}
|
|
2758
|
+
this.position += 1;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
unicodeEscape() {
|
|
2762
|
+
const high = this.hex4();
|
|
2763
|
+
if (high >= 0xdc00 && high <= 0xdfff)
|
|
2764
|
+
this.fail("lone trailing surrogate");
|
|
2765
|
+
if (high < 0xd800 || high > 0xdbff)
|
|
2766
|
+
return String.fromCharCode(high);
|
|
2767
|
+
if (this.text[this.position] !== "\\" || this.text[this.position + 1] !== "u") {
|
|
2768
|
+
this.fail("lone leading surrogate");
|
|
2769
|
+
}
|
|
2770
|
+
this.position += 2;
|
|
2771
|
+
const low = this.hex4();
|
|
2772
|
+
if (low < 0xdc00 || low > 0xdfff)
|
|
2773
|
+
this.fail("invalid surrogate pair");
|
|
2774
|
+
return String.fromCharCode(high, low);
|
|
2775
|
+
}
|
|
2776
|
+
hex4() {
|
|
2777
|
+
let value = 0;
|
|
2778
|
+
for (let index = 0; index < 4; index += 1) {
|
|
2779
|
+
const code = this.text.charCodeAt(this.position + index);
|
|
2780
|
+
const digit = code >= 48 && code <= 57
|
|
2781
|
+
? code - 48
|
|
2782
|
+
: code >= 65 && code <= 70
|
|
2783
|
+
? code - 55
|
|
2784
|
+
: code >= 97 && code <= 102
|
|
2785
|
+
? code - 87
|
|
2786
|
+
: -1;
|
|
2787
|
+
if (digit < 0)
|
|
2788
|
+
this.fail("invalid unicode escape");
|
|
2789
|
+
value = value * 16 + digit;
|
|
2790
|
+
}
|
|
2791
|
+
this.position += 4;
|
|
2792
|
+
return value;
|
|
2793
|
+
}
|
|
2794
|
+
literal(word, value) {
|
|
2795
|
+
if (!this.text.startsWith(word, this.position))
|
|
2796
|
+
this.fail("invalid literal");
|
|
2797
|
+
this.position += word.length;
|
|
2798
|
+
return value;
|
|
2799
|
+
}
|
|
2800
|
+
// Scanned by character code, with no slice or match array until the token
|
|
2801
|
+
// is known. A bare `-0` is the double negative zero, as the server's parser
|
|
2802
|
+
// reads it.
|
|
2803
|
+
number() {
|
|
2804
|
+
const start = this.position;
|
|
2805
|
+
const digitAt = (index) => {
|
|
2806
|
+
const code = this.text.charCodeAt(index);
|
|
2807
|
+
return code >= 0x30 && code <= 0x39;
|
|
2808
|
+
};
|
|
2809
|
+
let at = start;
|
|
2810
|
+
if (this.text.charCodeAt(at) === 0x2d)
|
|
2811
|
+
at += 1;
|
|
2812
|
+
if (this.text.charCodeAt(at) === 0x30) {
|
|
2813
|
+
at += 1;
|
|
2814
|
+
}
|
|
2815
|
+
else if (digitAt(at)) {
|
|
2816
|
+
while (digitAt(at))
|
|
2817
|
+
at += 1;
|
|
2818
|
+
}
|
|
2819
|
+
else {
|
|
2820
|
+
this.fail("invalid value");
|
|
2821
|
+
}
|
|
2822
|
+
let integral = true;
|
|
2823
|
+
if (this.text.charCodeAt(at) === 0x2e) {
|
|
2824
|
+
integral = false;
|
|
2825
|
+
at += 1;
|
|
2826
|
+
if (!digitAt(at))
|
|
2827
|
+
this.fail("invalid value");
|
|
2828
|
+
while (digitAt(at))
|
|
2829
|
+
at += 1;
|
|
2830
|
+
}
|
|
2831
|
+
const exponent = this.text.charCodeAt(at);
|
|
2832
|
+
if (exponent === 0x65 || exponent === 0x45) {
|
|
2833
|
+
integral = false;
|
|
2834
|
+
at += 1;
|
|
2835
|
+
const sign = this.text.charCodeAt(at);
|
|
2836
|
+
if (sign === 0x2b || sign === 0x2d)
|
|
2837
|
+
at += 1;
|
|
2838
|
+
if (!digitAt(at))
|
|
2839
|
+
this.fail("invalid value");
|
|
2840
|
+
while (digitAt(at))
|
|
2841
|
+
at += 1;
|
|
2842
|
+
}
|
|
2843
|
+
const token = this.text.slice(start, at);
|
|
2844
|
+
this.position = at;
|
|
2845
|
+
if (integral) {
|
|
2846
|
+
if (token === "-0" && this.mode === "payload")
|
|
2847
|
+
return -0;
|
|
2848
|
+
const integer = BigInt(token);
|
|
2849
|
+
if (this.mode === "document" || (integer >= I64_MIN && integer <= U64_MAX))
|
|
2850
|
+
return integer;
|
|
2851
|
+
}
|
|
2852
|
+
const float = Number(token);
|
|
2853
|
+
if (!Number.isFinite(float))
|
|
2854
|
+
this.fail("number out of range");
|
|
2855
|
+
return float;
|
|
2856
|
+
}
|
|
2857
|
+
whitespace() {
|
|
2858
|
+
for (;;) {
|
|
2859
|
+
const code = this.text.charCodeAt(this.position);
|
|
2860
|
+
if (code !== 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d)
|
|
2861
|
+
return;
|
|
2862
|
+
this.position += 1;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
fail(reason) {
|
|
2866
|
+
throw new CodecError(`invalid JSON at ${String(this.position)}: ${reason}`, "json", "value");
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
//# sourceMappingURL=filter.js.map
|