Skip to content

Commit 7cbc2f0

Browse files
authored
Merge branch 'master' into make-v28
2 parents 9afb519 + 63697a0 commit 7cbc2f0

4 files changed

Lines changed: 72 additions & 24 deletions

File tree

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export default tsPlugin.config(
210210
name: "source/ez",
211211
files: ["express-zod-api/src/*.ts"],
212212
rules: {
213-
complexity: ["error", 18],
213+
complexity: ["error", 16],
214214
"allowed/dependencies": ["error", { packageDir: ezDir }],
215215
"no-restricted-syntax": [
216216
"warn",

express-zod-api/src/documentation-helpers.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,26 +295,25 @@ export const depictRequestParams = ({
295295
const isQueryEnabled = inputSources.includes("query");
296296
const areParamsEnabled = inputSources.includes("params");
297297
const areHeadersEnabled = inputSources.includes("headers");
298-
const isPathParam = (name: string) =>
299-
areParamsEnabled && pathParams.includes(name);
300298
const securityHeaders = R.chain(
301299
R.filter((entry: Security) => entry.type === "header"),
302300
security ?? [],
303301
).map(({ name }) => name);
304-
const isHeaderParam = (name: string) =>
305-
areHeadersEnabled &&
306-
(isHeader?.(name, method, path) ?? defaultIsHeader(name, securityHeaders));
302+
303+
const getLocation = (name: string) => {
304+
if (areParamsEnabled && pathParams.includes(name)) return "path";
305+
if (
306+
areHeadersEnabled &&
307+
(isHeader?.(name, method, path) ?? defaultIsHeader(name, securityHeaders))
308+
)
309+
return "header";
310+
if (isQueryEnabled) return "query";
311+
};
307312

308313
return Object.entries(flat.properties).reduce<ParameterObject[]>(
309314
(acc, [name, jsonSchema]) => {
310315
if (!isObject(jsonSchema)) return acc;
311-
const location = isPathParam(name)
312-
? "path"
313-
: isHeaderParam(name)
314-
? "header"
315-
: isQueryEnabled
316-
? "query"
317-
: undefined;
316+
const location = getLocation(name);
318317
if (!location) return acc;
319318
const depicted = asOAS(jsonSchema);
320319
const result =

express-zod-api/src/json-schema-helpers.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ export const processPropertyNames = (
7979
if (!isOptional) requiredKeys.push(...keys);
8080
};
8181

82+
/** @internal */
83+
export const mergeExamples = (
84+
target: FlattenObjectSchema,
85+
entry: z.core.JSONSchema.BaseSchema,
86+
isOptional: boolean,
87+
) => {
88+
if (!entry.examples?.length) return;
89+
if (isOptional) {
90+
target.examples = R.concat(target.examples || [], entry.examples);
91+
} else {
92+
target.examples = combinations(
93+
target.examples?.filter(isObject) || [],
94+
entry.examples.filter(isObject),
95+
([a, b]) => R.mergeDeepRight(a, b),
96+
);
97+
}
98+
};
99+
82100
export const flattenIO = (
83101
jsonSchema: z.core.JSONSchema.BaseSchema,
84102
mode: MergeMode = "coerce",
@@ -91,17 +109,7 @@ export const flattenIO = (
91109
if (entry.description) flat.description ??= entry.description;
92110
stack.push(...processAllOf(entry, mode, isOptional));
93111
stack.push(...processVariants(entry));
94-
if (entry.examples?.length) {
95-
if (isOptional) {
96-
flat.examples = R.concat(flat.examples || [], entry.examples);
97-
} else {
98-
flat.examples = combinations(
99-
flat.examples?.filter(isObject) || [],
100-
entry.examples.filter(isObject),
101-
([a, b]) => R.mergeDeepRight(a, b),
102-
);
103-
}
104-
}
112+
mergeExamples(flat, entry, isOptional);
105113
if (!isJsonObjectSchema(entry)) continue;
106114
stack.push([isOptional, { examples: pullRequestExamples(entry) }]);
107115
if (entry.properties) {

express-zod-api/tests/json-schema-helpers.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { z } from "zod";
22
import {
33
flattenIO,
44
isJsonObjectSchema,
5+
mergeExamples,
56
propsMerger,
67
canMerge,
78
nestOptional,
@@ -199,6 +200,46 @@ describe("JSON Schema helpers", () => {
199200
});
200201
});
201202

203+
describe("mergeExamples()", () => {
204+
test("should do nothing when entry has no examples", () => {
205+
const flat = { type: "object" as const, properties: {} };
206+
mergeExamples(flat, { type: "string" }, false);
207+
expect(flat).toEqual({ type: "object", properties: {} });
208+
});
209+
210+
test("should concatenate examples when optional", () => {
211+
const flat = {
212+
type: "object" as const,
213+
properties: {},
214+
examples: [{ a: 1 }],
215+
};
216+
mergeExamples(flat, { examples: [{ b: 2 }, { c: 3 }] }, true);
217+
expect(flat.examples).toEqual([{ a: 1 }, { b: 2 }, { c: 3 }]);
218+
});
219+
220+
test.each([true, false])(
221+
"should initialize examples when flat has none (isOptional=%s)",
222+
(isOptional) => {
223+
const flat = { type: "object" as const, properties: {} };
224+
mergeExamples(flat, { examples: [{ a: 1 }] }, isOptional);
225+
expect(flat).toHaveProperty("examples", [{ a: 1 }]);
226+
},
227+
);
228+
229+
test("should produce combinations when required", () => {
230+
const flat = {
231+
type: "object" as const,
232+
properties: {},
233+
examples: [{ a: 1 }],
234+
};
235+
mergeExamples(flat, { examples: [{ b: 2 }, { b: 3 }] }, false);
236+
expect(flat.examples).toEqual([
237+
{ a: 1, b: 2 },
238+
{ a: 1, b: 3 },
239+
]);
240+
});
241+
});
242+
202243
describe("pullRequestExamples()", () => {
203244
test("should return empty array for empty properties", () => {
204245
expect(pullRequestExamples({ type: "object", properties: {} })).toEqual(

0 commit comments

Comments
 (0)