Skip to content

Commit 3a121bc

Browse files
committed
fix: support JSON Schema spec compliant oneOf with const for labeled enums
- Make JsonSchemaType.type optional to support const-only schemas - Add JsonSchemaConst type for schema objects using const values - Update oneOf/anyOf to accept both full schemas and const schemas - Add comprehensive tests for oneOf with const and title patterns This fixes TypeScript compilation errors when using the JSON Schema specification's recommended approach for labeled enums: { "type": "string", "oneOf": [{ "const": "red", "title": "Stop" }] } Replaces deprecated enumNames pattern with spec-compliant implementation.
1 parent e6c8b94 commit 3a121bc

File tree

2 files changed

+116
-10
lines changed

2 files changed

+116
-10
lines changed

client/src/utils/__tests__/jsonUtils.test.ts

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,11 @@ describe("JsonSchemaType elicitation field support", () => {
345345
},
346346
role: {
347347
type: "string",
348-
enum: ["admin", "user", "guest"],
349-
enumNames: ["Administrator", "User", "Guest"],
348+
oneOf: [
349+
{ const: "admin", title: "Administrator" },
350+
{ const: "user", title: "User" },
351+
{ const: "guest", title: "Guest" },
352+
],
350353
},
351354
},
352355
required: ["name", "email"],
@@ -372,15 +375,17 @@ describe("JsonSchemaType elicitation field support", () => {
372375
);
373376
});
374377

375-
test("should handle enum and enumNames fields", () => {
378+
test("should handle oneOf with const and title fields", () => {
376379
const schema = {
377-
type: "string" as const,
378-
enum: ["option1", "option2"],
379-
enumNames: ["Option 1", "Option 2"],
380+
type: "string",
381+
oneOf: [
382+
{ const: "option1", title: "Option 1" },
383+
{ const: "option2", title: "Option 2" },
384+
],
380385
};
381386

382-
expect(getValueAtPath(schema, ["enum", "0"])).toBe("option1");
383-
expect(getValueAtPath(schema, ["enumNames", "1"])).toBe("Option 2");
387+
expect(getValueAtPath(schema, ["oneOf", "0", "const"])).toBe("option1");
388+
expect(getValueAtPath(schema, ["oneOf", "1", "title"])).toBe("Option 2");
384389
});
385390

386391
test("should handle validation constraints", () => {
@@ -425,4 +430,97 @@ describe("JsonSchemaType elicitation field support", () => {
425430
"Do you accept the terms and conditions?",
426431
);
427432
});
433+
434+
test("should handle JSON Schema spec compliant oneOf with const for labeled enums", () => {
435+
// Example from JSON Schema spec: labeled enums using oneOf with const
436+
const trafficLightSchema = {
437+
type: "string" as const,
438+
title: "Traffic Light",
439+
description: "Select a traffic light color",
440+
oneOf: [
441+
{ const: "red", title: "Stop" },
442+
{ const: "amber", title: "Caution" },
443+
{ const: "green", title: "Go" },
444+
],
445+
};
446+
447+
// Verify the schema structure
448+
expect(trafficLightSchema.type).toBe("string");
449+
expect(trafficLightSchema.oneOf).toHaveLength(3);
450+
451+
// Verify each oneOf option has const and title
452+
expect(trafficLightSchema.oneOf[0].const).toBe("red");
453+
expect(trafficLightSchema.oneOf[0].title).toBe("Stop");
454+
455+
expect(trafficLightSchema.oneOf[1].const).toBe("amber");
456+
expect(trafficLightSchema.oneOf[1].title).toBe("Caution");
457+
458+
expect(trafficLightSchema.oneOf[2].const).toBe("green");
459+
expect(trafficLightSchema.oneOf[2].title).toBe("Go");
460+
461+
// Test with JsonValue operations
462+
const schemaAsJsonValue = trafficLightSchema as JsonValue;
463+
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "0", "const"])).toBe(
464+
"red",
465+
);
466+
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "1", "title"])).toBe(
467+
"Caution",
468+
);
469+
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "2", "const"])).toBe(
470+
"green",
471+
);
472+
});
473+
474+
test("should handle complex oneOf scenarios with mixed schema types", () => {
475+
const complexSchema = {
476+
type: "object" as const,
477+
title: "User Preference",
478+
properties: {
479+
theme: {
480+
type: "string" as const,
481+
oneOf: [
482+
{ const: "light", title: "Light Mode" },
483+
{ const: "dark", title: "Dark Mode" },
484+
{ const: "auto", title: "Auto (System)" },
485+
],
486+
},
487+
notifications: {
488+
type: "string" as const,
489+
oneOf: [
490+
{ const: "all", title: "All Notifications" },
491+
{ const: "important", title: "Important Only" },
492+
{ const: "none", title: "None" },
493+
],
494+
},
495+
},
496+
};
497+
498+
expect(
499+
getValueAtPath(complexSchema, [
500+
"properties",
501+
"theme",
502+
"oneOf",
503+
"0",
504+
"const",
505+
]),
506+
).toBe("light");
507+
expect(
508+
getValueAtPath(complexSchema, [
509+
"properties",
510+
"theme",
511+
"oneOf",
512+
"1",
513+
"title",
514+
]),
515+
).toBe("Dark Mode");
516+
expect(
517+
getValueAtPath(complexSchema, [
518+
"properties",
519+
"notifications",
520+
"oneOf",
521+
"2",
522+
"const",
523+
]),
524+
).toBe("none");
525+
});
428526
});

client/src/utils/jsonUtils.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ export type JsonValue =
77
| JsonValue[]
88
| { [key: string]: JsonValue };
99

10+
export type JsonSchemaConst = {
11+
const: JsonValue;
12+
title?: string;
13+
description?: string;
14+
};
15+
1016
export type JsonSchemaType = {
11-
type:
17+
type?:
1218
| "string"
1319
| "number"
1420
| "integer"
@@ -29,7 +35,9 @@ export type JsonSchemaType = {
2935
pattern?: string;
3036
format?: string;
3137
enum?: string[];
32-
enumNames?: string[];
38+
const?: JsonValue;
39+
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
40+
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
3341
};
3442

3543
export type JsonObject = { [key: string]: JsonValue };

0 commit comments

Comments
 (0)