@distilled.cloud/core 1.0.0-rc.11 → 1.0.0-rc.13
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/lib/api.d.ts +4 -4
- package/lib/api.d.ts.map +1 -1
- package/lib/api.js +1 -1
- package/lib/api.js.map +1 -1
- package/lib/category.js +1 -1
- package/lib/category.js.map +1 -1
- package/lib/codegen/boolean-string-enums.d.ts +8 -8
- package/lib/codegen/boolean-string-enums.d.ts.map +1 -1
- package/lib/codegen/boolean-string-enums.js +84 -27
- package/lib/codegen/boolean-string-enums.js.map +1 -1
- package/lib/codegen/boolean-string-enums.test.js +130 -0
- package/lib/codegen/boolean-string-enums.test.js.map +1 -1
- package/lib/codegen/cli.js +2 -2
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/generator.d.ts +20 -3
- package/lib/codegen/generator.d.ts.map +1 -1
- package/lib/codegen/generator.js +36 -9
- package/lib/codegen/generator.js.map +1 -1
- package/lib/codegen/generator.test.d.ts +2 -0
- package/lib/codegen/generator.test.d.ts.map +1 -0
- package/lib/codegen/generator.test.js +126 -0
- package/lib/codegen/generator.test.js.map +1 -0
- package/lib/codegen/graphql-client.d.ts +7 -1
- package/lib/codegen/graphql-client.d.ts.map +1 -1
- package/lib/codegen/graphql-client.js +262 -125
- package/lib/codegen/graphql-client.js.map +1 -1
- package/lib/codegen/graphql-client.test.js +104 -236
- package/lib/codegen/graphql-client.test.js.map +1 -1
- package/lib/codegen/openapi-binary.test.d.ts +2 -0
- package/lib/codegen/openapi-binary.test.d.ts.map +1 -0
- package/lib/codegen/openapi-binary.test.js +67 -0
- package/lib/codegen/openapi-binary.test.js.map +1 -0
- package/lib/codegen/openapi.d.ts +7 -1
- package/lib/codegen/openapi.d.ts.map +1 -1
- package/lib/codegen/openapi.js +15 -4
- package/lib/codegen/openapi.js.map +1 -1
- package/lib/codegen/patches.d.ts +16 -1
- package/lib/codegen/patches.d.ts.map +1 -1
- package/lib/codegen/patches.js +70 -4
- package/lib/codegen/patches.js.map +1 -1
- package/lib/codegen/patches.test.js +48 -2
- package/lib/codegen/patches.test.js.map +1 -1
- package/lib/codegen/untagged-unions.test.d.ts +2 -0
- package/lib/codegen/untagged-unions.test.d.ts.map +1 -0
- package/lib/codegen/untagged-unions.test.js +80 -0
- package/lib/codegen/untagged-unions.test.js.map +1 -0
- package/lib/errors.d.ts +2 -1
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +5 -2
- package/lib/errors.js.map +1 -1
- package/lib/fixtures/xml/upstream-validator.json +397 -0
- package/lib/graphql.d.ts +296 -205
- package/lib/graphql.d.ts.map +1 -1
- package/lib/graphql.js +968 -631
- package/lib/graphql.js.map +1 -1
- package/lib/graphql.test.js +134 -763
- package/lib/graphql.test.js.map +1 -1
- package/lib/graphql.types.d.ts +10 -0
- package/lib/graphql.types.d.ts.map +1 -1
- package/lib/graphql.types.js +4 -44
- package/lib/graphql.types.js.map +1 -1
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +27 -0
- package/lib/index.js.map +1 -0
- package/lib/pagination.d.ts +2 -1
- package/lib/pagination.d.ts.map +1 -1
- package/lib/pagination.js +24 -16
- package/lib/pagination.js.map +1 -1
- package/lib/pagination.test.d.ts +2 -0
- package/lib/pagination.test.d.ts.map +1 -0
- package/lib/pagination.test.js +134 -0
- package/lib/pagination.test.js.map +1 -0
- package/lib/protocol-http.d.ts +12 -17
- package/lib/protocol-http.d.ts.map +1 -1
- package/lib/protocol-http.js +100 -45
- package/lib/protocol-http.js.map +1 -1
- package/lib/protocol-http.test.js +130 -0
- package/lib/protocol-http.test.js.map +1 -1
- package/lib/protocol-rest.d.ts +18 -1
- package/lib/protocol-rest.d.ts.map +1 -1
- package/lib/protocol-rest.js +24 -4
- package/lib/protocol-rest.js.map +1 -1
- package/lib/query.d.ts +29 -0
- package/lib/query.d.ts.map +1 -0
- package/lib/query.js +27 -0
- package/lib/query.js.map +1 -0
- package/lib/response-validation.d.ts +67 -0
- package/lib/response-validation.d.ts.map +1 -0
- package/lib/response-validation.js +76 -0
- package/lib/response-validation.js.map +1 -0
- package/lib/response-validation.test.d.ts +2 -0
- package/lib/response-validation.test.d.ts.map +1 -0
- package/lib/response-validation.test.js +67 -0
- package/lib/response-validation.test.js.map +1 -0
- package/lib/testing.d.ts +39 -0
- package/lib/testing.d.ts.map +1 -0
- package/lib/testing.js +41 -0
- package/lib/testing.js.map +1 -0
- package/lib/trait.d.ts +29 -9
- package/lib/trait.d.ts.map +1 -1
- package/lib/trait.js +8 -0
- package/lib/trait.js.map +1 -1
- package/lib/xml.d.ts +44 -0
- package/lib/xml.d.ts.map +1 -0
- package/lib/xml.js +315 -0
- package/lib/xml.js.map +1 -0
- package/lib/xml.test.d.ts +2 -0
- package/lib/xml.test.d.ts.map +1 -0
- package/lib/xml.test.js +211 -0
- package/lib/xml.test.js.map +1 -0
- package/package.json +4 -4
- package/src/api.ts +4 -4
- package/src/category.ts +1 -1
- package/src/codegen/boolean-string-enums.test.ts +147 -0
- package/src/codegen/boolean-string-enums.ts +84 -27
- package/src/codegen/cli.ts +2 -2
- package/src/codegen/generator.test.ts +151 -0
- package/src/codegen/generator.ts +70 -12
- package/src/codegen/graphql-client.test.ts +116 -283
- package/src/codegen/graphql-client.ts +326 -169
- package/src/codegen/openapi-binary.test.ts +98 -0
- package/src/codegen/openapi.ts +26 -4
- package/src/codegen/patches.test.ts +66 -1
- package/src/codegen/patches.ts +84 -4
- package/src/codegen/untagged-unions.test.ts +89 -0
- package/src/errors.ts +5 -2
- package/src/fixtures/xml/upstream-validator.json +397 -0
- package/src/graphql.test.ts +174 -948
- package/src/graphql.ts +1592 -1167
- package/src/graphql.types.ts +92 -163
- package/src/index.ts +26 -0
- package/src/pagination.test.ts +191 -0
- package/src/pagination.ts +35 -25
- package/src/protocol-http.test.ts +163 -0
- package/src/protocol-http.ts +122 -46
- package/src/protocol-rest.ts +62 -13
- package/src/query.ts +39 -0
- package/src/response-validation.test.ts +95 -0
- package/src/response-validation.ts +116 -0
- package/src/testing.ts +78 -0
- package/src/trait.ts +39 -8
- package/src/xml.test.ts +269 -0
- package/src/xml.ts +339 -0
- package/lib/graphql.fixture.d.ts +0 -249
- package/lib/graphql.fixture.d.ts.map +0 -1
- package/lib/graphql.fixture.js +0 -240
- package/lib/graphql.fixture.js.map +0 -1
- package/src/graphql.fixture.ts +0 -371
package/src/codegen/openapi.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* across operations; anonymous nested objects synthesize names from the
|
|
24
24
|
* parent + member path
|
|
25
25
|
* • responses: 200 → 201 → 204 precedence (`successStatuses` overrides),
|
|
26
|
-
* `application/
|
|
26
|
+
* JSON or opt-in binary `application/octet-stream`; object
|
|
27
27
|
* results become `<Op>Response` structures (a sole `$ref` reuses the named
|
|
28
28
|
* shape); bare array/scalar results wrap in a structure whose single
|
|
29
29
|
* member carries `com.distilled.openapi#rawResponse` (the SdkSpec maps it
|
|
@@ -161,6 +161,12 @@ export interface OpenApiConvertOptions {
|
|
|
161
161
|
* ask for them.
|
|
162
162
|
*/
|
|
163
163
|
readonly headerParams?: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Convert `format: binary` strings to blobs and include binary
|
|
166
|
+
* `application/octet-stream` responses. Opt in only when the provider's
|
|
167
|
+
* generator and protocol support binary inputs and outputs. Default false.
|
|
168
|
+
*/
|
|
169
|
+
readonly binaryTypes?: boolean;
|
|
164
170
|
/**
|
|
165
171
|
* Response statuses to read the operation's output shape from, most
|
|
166
172
|
* preferred first. Default `["200", "201", "204"]`. Extend it for an API
|
|
@@ -226,6 +232,7 @@ export interface SmithyModel {
|
|
|
226
232
|
const PRELUDE = {
|
|
227
233
|
Unit: "smithy.api#Unit",
|
|
228
234
|
String: "smithy.api#String",
|
|
235
|
+
Blob: "smithy.api#Blob",
|
|
229
236
|
Boolean: "smithy.api#Boolean",
|
|
230
237
|
Double: "smithy.api#Double",
|
|
231
238
|
Integer: "smithy.api#Integer",
|
|
@@ -308,6 +315,7 @@ interface Ctx {
|
|
|
308
315
|
*/
|
|
309
316
|
readonly dirSensitiveRefs: Map<Dir, ReadonlySet<string>>;
|
|
310
317
|
readonly sensitivePatterns: readonly RegExp[];
|
|
318
|
+
readonly binaryTypes: boolean;
|
|
311
319
|
}
|
|
312
320
|
|
|
313
321
|
interface Converted {
|
|
@@ -913,7 +921,12 @@ const convertSchema = (
|
|
|
913
921
|
// --- scalars --------------------------------------------------------------
|
|
914
922
|
switch (t) {
|
|
915
923
|
case "string":
|
|
916
|
-
return inline(
|
|
924
|
+
return inline(
|
|
925
|
+
ctx.binaryTypes && def.format === "binary"
|
|
926
|
+
? PRELUDE.Blob
|
|
927
|
+
: PRELUDE.String,
|
|
928
|
+
nullable,
|
|
929
|
+
);
|
|
917
930
|
case "boolean":
|
|
918
931
|
return inline(PRELUDE.Boolean, nullable);
|
|
919
932
|
case "integer":
|
|
@@ -1104,7 +1117,7 @@ const opDoc = (op: any): string | undefined => {
|
|
|
1104
1117
|
// Responses
|
|
1105
1118
|
// ============================================================================
|
|
1106
1119
|
|
|
1107
|
-
/** First declared status
|
|
1120
|
+
/** First declared status wins; resolve response refs and preserve JSON or binary bodies. */
|
|
1108
1121
|
const successSchema = (
|
|
1109
1122
|
ctx: Ctx,
|
|
1110
1123
|
responses: any,
|
|
@@ -1118,7 +1131,15 @@ const successSchema = (
|
|
|
1118
1131
|
if (ctx.version === "2.0") {
|
|
1119
1132
|
return { schema: resp.schema };
|
|
1120
1133
|
}
|
|
1121
|
-
|
|
1134
|
+
const json = resp.content?.["application/json"]?.schema;
|
|
1135
|
+
const binary = resp.content?.["application/octet-stream"]?.schema;
|
|
1136
|
+
return {
|
|
1137
|
+
schema:
|
|
1138
|
+
json ??
|
|
1139
|
+
(ctx.binaryTypes && deref(ctx, binary)?.format === "binary"
|
|
1140
|
+
? binary
|
|
1141
|
+
: undefined),
|
|
1142
|
+
};
|
|
1122
1143
|
}
|
|
1123
1144
|
return { schema: undefined };
|
|
1124
1145
|
};
|
|
@@ -1269,6 +1290,7 @@ export const convertOpenApiToSmithy = (
|
|
|
1269
1290
|
refs: new Map(),
|
|
1270
1291
|
dirSensitiveRefs: new Map(),
|
|
1271
1292
|
sensitivePatterns: options.sensitivePatterns ?? SENSITIVE_FIELD_PATTERNS,
|
|
1293
|
+
binaryTypes: options.binaryTypes ?? false,
|
|
1272
1294
|
};
|
|
1273
1295
|
const statusToErrorClass =
|
|
1274
1296
|
options.statusToErrorClass ?? DEFAULT_STATUS_TO_ERROR_CLASS;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
FINALIZED_KEY,
|
|
7
|
+
SKIP_PATCHES_ENV,
|
|
8
|
+
applyRfc6902Files,
|
|
7
9
|
danglingTargets,
|
|
8
10
|
finalizeConvert,
|
|
11
|
+
listRfc6902PatchFiles,
|
|
9
12
|
syncServiceOperations,
|
|
10
13
|
} from "./patches.ts";
|
|
11
14
|
|
|
@@ -128,3 +131,65 @@ describe("syncServiceOperations", () => {
|
|
|
128
131
|
]);
|
|
129
132
|
});
|
|
130
133
|
});
|
|
134
|
+
|
|
135
|
+
describe(SKIP_PATCHES_ENV, () => {
|
|
136
|
+
afterEach(() => {
|
|
137
|
+
delete process.env[SKIP_PATCHES_ENV];
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const patchDir = () => {
|
|
141
|
+
const dir = mkdtempSync(join(tmpdir(), "skip-"));
|
|
142
|
+
mkdirSync(join(dir, "svc"));
|
|
143
|
+
writeFileSync(
|
|
144
|
+
join(dir, "svc", "a.json"),
|
|
145
|
+
JSON.stringify({
|
|
146
|
+
patches: [
|
|
147
|
+
{ op: "add", path: "/x", value: 1 },
|
|
148
|
+
{ op: "add", path: "/y", value: 2 },
|
|
149
|
+
],
|
|
150
|
+
}),
|
|
151
|
+
);
|
|
152
|
+
writeFileSync(
|
|
153
|
+
join(dir, "svc", "b.json"),
|
|
154
|
+
JSON.stringify({ patches: [{ op: "add", path: "/z", value: 3 }] }),
|
|
155
|
+
);
|
|
156
|
+
return join(dir, "svc");
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
test("unset applies everything", async () => {
|
|
160
|
+
const target: Record<string, unknown> = {};
|
|
161
|
+
const files = await listRfc6902PatchFiles(patchDir());
|
|
162
|
+
expect(files.map((f) => f.split("/").pop())).toEqual(["a.json", "b.json"]);
|
|
163
|
+
expect((await applyRfc6902Files(target, files)).applied).toBe(3);
|
|
164
|
+
expect(target).toEqual({ x: 1, y: 2, z: 3 });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("all lists no files", async () => {
|
|
168
|
+
process.env[SKIP_PATCHES_ENV] = "all";
|
|
169
|
+
expect(await listRfc6902PatchFiles(patchDir())).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a basename or a path suffix drops that file", async () => {
|
|
173
|
+
const dir = patchDir();
|
|
174
|
+
process.env[SKIP_PATCHES_ENV] = "a.json";
|
|
175
|
+
expect(
|
|
176
|
+
(await listRfc6902PatchFiles(dir)).map((f) => f.split("/").pop()),
|
|
177
|
+
).toEqual(["b.json"]);
|
|
178
|
+
process.env[SKIP_PATCHES_ENV] = "svc/b.json";
|
|
179
|
+
expect(
|
|
180
|
+
(await listRfc6902PatchFiles(dir)).map((f) => f.split("/").pop()),
|
|
181
|
+
).toEqual(["a.json"]);
|
|
182
|
+
process.env[SKIP_PATCHES_ENV] = "other/b.json";
|
|
183
|
+
expect((await listRfc6902PatchFiles(dir)).length).toBe(2);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("<file>:<index> drops one op and keeps the rest of the file", async () => {
|
|
187
|
+
const dir = patchDir();
|
|
188
|
+
const files = await listRfc6902PatchFiles(dir);
|
|
189
|
+
process.env[SKIP_PATCHES_ENV] = "a.json:1";
|
|
190
|
+
const target: Record<string, unknown> = {};
|
|
191
|
+
const result = await applyRfc6902Files(target, files);
|
|
192
|
+
expect(result.applied).toBe(2);
|
|
193
|
+
expect(target).toEqual({ x: 1, z: 3 });
|
|
194
|
+
});
|
|
195
|
+
});
|
package/src/codegen/patches.ts
CHANGED
|
@@ -40,12 +40,89 @@ const exists = async (p: string): Promise<boolean> => {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Leave patches out of one convert run (dev-time only; the seam
|
|
45
|
+
* `scripts/patches.ts audit` uses to find patches the spec no longer
|
|
46
|
+
* needs). Comma-separated entries:
|
|
47
|
+
*
|
|
48
|
+
* all skip every patch file
|
|
49
|
+
* <file> skip a file — its basename, or a path suffix
|
|
50
|
+
* (`svc/a.json`) when basenames repeat
|
|
51
|
+
* <file>:<index> skip one op of a file, by its index in `patches`
|
|
52
|
+
*
|
|
53
|
+
* Like `DISTILLED_SPECS_LOCAL`, it lives in one command's environment: a
|
|
54
|
+
* model written under it is announced on stderr and must not be committed.
|
|
55
|
+
*/
|
|
56
|
+
export const SKIP_PATCHES_ENV = "DISTILLED_SKIP_PATCHES";
|
|
57
|
+
|
|
58
|
+
interface SkipList {
|
|
59
|
+
readonly all: boolean;
|
|
60
|
+
readonly files: readonly string[];
|
|
61
|
+
readonly ops: ReadonlyMap<string, ReadonlySet<number>>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const parseSkipList = (): SkipList | undefined => {
|
|
65
|
+
const raw = (process.env[SKIP_PATCHES_ENV] ?? "").trim();
|
|
66
|
+
if (raw === "") return undefined;
|
|
67
|
+
const files: string[] = [];
|
|
68
|
+
const ops = new Map<string, Set<number>>();
|
|
69
|
+
for (const entry of raw.split(",")) {
|
|
70
|
+
const item = entry.trim();
|
|
71
|
+
if (item === "") continue;
|
|
72
|
+
if (item === "all") return { all: true, files: [], ops: new Map() };
|
|
73
|
+
const at = item.lastIndexOf(":");
|
|
74
|
+
const index = at === -1 ? NaN : Number(item.slice(at + 1));
|
|
75
|
+
if (Number.isInteger(index) && index >= 0) {
|
|
76
|
+
const file = item.slice(0, at);
|
|
77
|
+
ops.set(file, (ops.get(file) ?? new Set()).add(index));
|
|
78
|
+
} else {
|
|
79
|
+
files.push(item);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { all: false, files, ops };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const skipMatches = (file: string, entry: string): boolean =>
|
|
86
|
+
path.basename(file) === entry || file.endsWith(path.sep + entry);
|
|
87
|
+
|
|
88
|
+
let skipAnnounced = false;
|
|
89
|
+
|
|
90
|
+
const skipList = (): SkipList | undefined => {
|
|
91
|
+
const list = parseSkipList();
|
|
92
|
+
if (list && !skipAnnounced) {
|
|
93
|
+
skipAnnounced = true;
|
|
94
|
+
// stderr for the same reason as the DISTILLED_SPECS_LOCAL notice.
|
|
95
|
+
console.error(
|
|
96
|
+
`⚠ ${SKIP_PATCHES_ENV}=${process.env[SKIP_PATCHES_ENV]} — this model is missing patches; do not commit it.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return list;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const skipsFile = (list: SkipList | undefined, file: string): boolean =>
|
|
103
|
+
list !== undefined &&
|
|
104
|
+
(list.all || list.files.some((entry) => skipMatches(file, entry)));
|
|
105
|
+
|
|
106
|
+
const skipsOp = (
|
|
107
|
+
list: SkipList | undefined,
|
|
108
|
+
file: string,
|
|
109
|
+
index: number,
|
|
110
|
+
): boolean => {
|
|
111
|
+
if (list === undefined) return false;
|
|
112
|
+
for (const [entry, indices] of list.ops) {
|
|
113
|
+
if (indices.has(index) && skipMatches(file, entry)) return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
|
|
43
118
|
/**
|
|
44
119
|
* RFC-6902 files in `dir`: every `*.json`, `*.manual.json` last (those
|
|
45
|
-
* usually target post-rename shape names). Missing dir → `[]`.
|
|
120
|
+
* usually target post-rename shape names). Missing dir → `[]`. Files named
|
|
121
|
+
* by {@link SKIP_PATCHES_ENV} are left out.
|
|
46
122
|
*/
|
|
47
123
|
export const listRfc6902PatchFiles = async (dir: string): Promise<string[]> => {
|
|
48
124
|
if (!(await exists(dir))) return [];
|
|
125
|
+
const skip = skipList();
|
|
49
126
|
return (await fs.readdir(dir))
|
|
50
127
|
.filter((f) => f.endsWith(".json"))
|
|
51
128
|
.sort(
|
|
@@ -53,7 +130,8 @@ export const listRfc6902PatchFiles = async (dir: string): Promise<string[]> => {
|
|
|
53
130
|
Number(a.endsWith(".manual.json")) -
|
|
54
131
|
Number(b.endsWith(".manual.json")) || a.localeCompare(b),
|
|
55
132
|
)
|
|
56
|
-
.map((f) => path.join(dir, f))
|
|
133
|
+
.map((f) => path.join(dir, f))
|
|
134
|
+
.filter((f) => !skipsFile(skip, f));
|
|
57
135
|
};
|
|
58
136
|
|
|
59
137
|
export const applyRfc6902Files = async (
|
|
@@ -73,12 +151,14 @@ export const applyRfc6902Files = async (
|
|
|
73
151
|
stale: 0,
|
|
74
152
|
errors: [],
|
|
75
153
|
};
|
|
154
|
+
const skip = skipList();
|
|
76
155
|
for (const file of files) {
|
|
156
|
+
if (skipsFile(skip, file)) continue;
|
|
77
157
|
const parsed = JSON.parse(await fs.readFile(file, "utf8")) as PatchFile;
|
|
78
158
|
const label = opts.label?.(file) ?? path.basename(file);
|
|
79
159
|
result.files++;
|
|
80
|
-
for (const patchOp of parsed.patches ?? []) {
|
|
81
|
-
if (!include(patchOp)) continue;
|
|
160
|
+
for (const [index, patchOp] of (parsed.patches ?? []).entries()) {
|
|
161
|
+
if (!include(patchOp) || skipsOp(skip, file, index)) continue;
|
|
82
162
|
try {
|
|
83
163
|
applyOperation(target, patchOp);
|
|
84
164
|
result.applied++;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { generateService } from "./generator.ts";
|
|
3
|
+
|
|
4
|
+
const model = {
|
|
5
|
+
smithy: "2.0",
|
|
6
|
+
shapes: {
|
|
7
|
+
"com.example#Service": {
|
|
8
|
+
type: "service",
|
|
9
|
+
version: "1",
|
|
10
|
+
operations: [{ target: "com.example#GetValue" }],
|
|
11
|
+
},
|
|
12
|
+
"com.example#GetValue": {
|
|
13
|
+
type: "operation",
|
|
14
|
+
input: { target: "com.example#Input" },
|
|
15
|
+
output: { target: "com.example#Output" },
|
|
16
|
+
traits: {
|
|
17
|
+
"smithy.api#http": { method: "GET", uri: "/value", code: 200 },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"com.example#Input": { type: "structure", members: {} },
|
|
21
|
+
"com.example#Output": {
|
|
22
|
+
type: "structure",
|
|
23
|
+
members: { value: { target: "com.example#Choice" } },
|
|
24
|
+
},
|
|
25
|
+
"com.example#Choice": {
|
|
26
|
+
type: "union",
|
|
27
|
+
members: {
|
|
28
|
+
boolean: { target: "smithy.api#Boolean" },
|
|
29
|
+
paths: { target: "com.example#Paths" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
"com.example#Paths": {
|
|
33
|
+
type: "list",
|
|
34
|
+
member: { target: "smithy.api#String" },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const operationDecl = {
|
|
40
|
+
contextType: "ExampleOpContext",
|
|
41
|
+
commonErrorType: "ExampleOpError",
|
|
42
|
+
commonErrorClasses: [],
|
|
43
|
+
protocol: "ExampleProtocol",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
test("untagged unions validate primitive and collection alternatives", () => {
|
|
47
|
+
const { code } = generateService(model, {
|
|
48
|
+
unionStyle: "untagged",
|
|
49
|
+
operationDecl,
|
|
50
|
+
});
|
|
51
|
+
expect(code).toContain("export type Choice = boolean | Paths;");
|
|
52
|
+
expect(code).toContain("S.Union([S.Boolean, Paths])");
|
|
53
|
+
expect(code).not.toContain("S.Unknown as any as S.Schema<Choice>");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("untagged unions honor the configured schema type", () => {
|
|
57
|
+
for (const schemaType of ["Schema", "Codec"] as const) {
|
|
58
|
+
const { code } = generateService(model, {
|
|
59
|
+
unionStyle: "untagged",
|
|
60
|
+
schemaType,
|
|
61
|
+
operationDecl,
|
|
62
|
+
});
|
|
63
|
+
expect(code).toContain(
|
|
64
|
+
`S.Union([S.Boolean, Paths]) as any as S.${schemaType}<Choice>`,
|
|
65
|
+
);
|
|
66
|
+
expect(code).toContain(
|
|
67
|
+
`S.Array(S.String) as any as S.${schemaType}<Paths>`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("omitting a retry policy emits neither a retry import nor an operation setting", () => {
|
|
73
|
+
const { code } = generateService(model, {
|
|
74
|
+
unionStyle: "untagged",
|
|
75
|
+
operationDecl,
|
|
76
|
+
});
|
|
77
|
+
expect(code).not.toContain("../retry.ts");
|
|
78
|
+
expect(code).not.toContain("retry:");
|
|
79
|
+
expect(code).not.toContain('import { } from "../errors.ts"');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("an explicit retry policy retains the existing import and operation setting", () => {
|
|
83
|
+
const { code } = generateService(model, {
|
|
84
|
+
unionStyle: "untagged",
|
|
85
|
+
operationDecl: { ...operationDecl, retry: "Retry.Retry" },
|
|
86
|
+
});
|
|
87
|
+
expect(code).toContain('import * as Retry from "../retry.ts"');
|
|
88
|
+
expect(code).toContain("retry: Retry.Retry");
|
|
89
|
+
});
|
package/src/errors.ts
CHANGED
|
@@ -133,7 +133,7 @@ export class ServiceUnavailable extends Schema.TaggedError<ServiceUnavailable>()
|
|
|
133
133
|
).pipe(Category.withServerError, Category.withRetryable()) {}
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
* GatewayTimeout -
|
|
136
|
+
* GatewayTimeout - HTTP request or gateway timeout (408, 504).
|
|
137
137
|
*/
|
|
138
138
|
export class GatewayTimeout extends Schema.TaggedError<GatewayTimeout>()(
|
|
139
139
|
"GatewayTimeout",
|
|
@@ -163,6 +163,7 @@ export const HTTP_STATUS_MAP = {
|
|
|
163
163
|
401: Unauthorized,
|
|
164
164
|
403: Forbidden,
|
|
165
165
|
404: NotFound,
|
|
166
|
+
408: GatewayTimeout,
|
|
166
167
|
409: Conflict,
|
|
167
168
|
422: UnprocessableEntity,
|
|
168
169
|
423: Locked,
|
|
@@ -189,7 +190,9 @@ export const DEFAULT_ERROR_STATUSES = new Set([401, 429, 500, 502, 503, 504]);
|
|
|
189
190
|
* classes (BadRequest/Unauthorized/etc.) would silently retain it as a
|
|
190
191
|
* stale field on the instance and pollute serialized output.
|
|
191
192
|
*/
|
|
192
|
-
export const RETRYABLE_HTTP_STATUSES = new Set([
|
|
193
|
+
export const RETRYABLE_HTTP_STATUSES = new Set([
|
|
194
|
+
408, 423, 429, 500, 502, 503, 504,
|
|
195
|
+
]);
|
|
193
196
|
|
|
194
197
|
/**
|
|
195
198
|
* All common API error classes.
|