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.
This commit is contained in:
parent
e6c8b946aa
commit
3a121bc8fc
2 changed files with 116 additions and 10 deletions
|
|
@ -345,8 +345,11 @@ describe("JsonSchemaType elicitation field support", () => {
|
|||
},
|
||||
role: {
|
||||
type: "string",
|
||||
enum: ["admin", "user", "guest"],
|
||||
enumNames: ["Administrator", "User", "Guest"],
|
||||
oneOf: [
|
||||
{ const: "admin", title: "Administrator" },
|
||||
{ const: "user", title: "User" },
|
||||
{ const: "guest", title: "Guest" },
|
||||
],
|
||||
},
|
||||
},
|
||||
required: ["name", "email"],
|
||||
|
|
@ -372,15 +375,17 @@ describe("JsonSchemaType elicitation field support", () => {
|
|||
);
|
||||
});
|
||||
|
||||
test("should handle enum and enumNames fields", () => {
|
||||
test("should handle oneOf with const and title fields", () => {
|
||||
const schema = {
|
||||
type: "string" as const,
|
||||
enum: ["option1", "option2"],
|
||||
enumNames: ["Option 1", "Option 2"],
|
||||
type: "string",
|
||||
oneOf: [
|
||||
{ const: "option1", title: "Option 1" },
|
||||
{ const: "option2", title: "Option 2" },
|
||||
],
|
||||
};
|
||||
|
||||
expect(getValueAtPath(schema, ["enum", "0"])).toBe("option1");
|
||||
expect(getValueAtPath(schema, ["enumNames", "1"])).toBe("Option 2");
|
||||
expect(getValueAtPath(schema, ["oneOf", "0", "const"])).toBe("option1");
|
||||
expect(getValueAtPath(schema, ["oneOf", "1", "title"])).toBe("Option 2");
|
||||
});
|
||||
|
||||
test("should handle validation constraints", () => {
|
||||
|
|
@ -425,4 +430,97 @@ describe("JsonSchemaType elicitation field support", () => {
|
|||
"Do you accept the terms and conditions?",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle JSON Schema spec compliant oneOf with const for labeled enums", () => {
|
||||
// Example from JSON Schema spec: labeled enums using oneOf with const
|
||||
const trafficLightSchema = {
|
||||
type: "string" as const,
|
||||
title: "Traffic Light",
|
||||
description: "Select a traffic light color",
|
||||
oneOf: [
|
||||
{ const: "red", title: "Stop" },
|
||||
{ const: "amber", title: "Caution" },
|
||||
{ const: "green", title: "Go" },
|
||||
],
|
||||
};
|
||||
|
||||
// Verify the schema structure
|
||||
expect(trafficLightSchema.type).toBe("string");
|
||||
expect(trafficLightSchema.oneOf).toHaveLength(3);
|
||||
|
||||
// Verify each oneOf option has const and title
|
||||
expect(trafficLightSchema.oneOf[0].const).toBe("red");
|
||||
expect(trafficLightSchema.oneOf[0].title).toBe("Stop");
|
||||
|
||||
expect(trafficLightSchema.oneOf[1].const).toBe("amber");
|
||||
expect(trafficLightSchema.oneOf[1].title).toBe("Caution");
|
||||
|
||||
expect(trafficLightSchema.oneOf[2].const).toBe("green");
|
||||
expect(trafficLightSchema.oneOf[2].title).toBe("Go");
|
||||
|
||||
// Test with JsonValue operations
|
||||
const schemaAsJsonValue = trafficLightSchema as JsonValue;
|
||||
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "0", "const"])).toBe(
|
||||
"red",
|
||||
);
|
||||
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "1", "title"])).toBe(
|
||||
"Caution",
|
||||
);
|
||||
expect(getValueAtPath(schemaAsJsonValue, ["oneOf", "2", "const"])).toBe(
|
||||
"green",
|
||||
);
|
||||
});
|
||||
|
||||
test("should handle complex oneOf scenarios with mixed schema types", () => {
|
||||
const complexSchema = {
|
||||
type: "object" as const,
|
||||
title: "User Preference",
|
||||
properties: {
|
||||
theme: {
|
||||
type: "string" as const,
|
||||
oneOf: [
|
||||
{ const: "light", title: "Light Mode" },
|
||||
{ const: "dark", title: "Dark Mode" },
|
||||
{ const: "auto", title: "Auto (System)" },
|
||||
],
|
||||
},
|
||||
notifications: {
|
||||
type: "string" as const,
|
||||
oneOf: [
|
||||
{ const: "all", title: "All Notifications" },
|
||||
{ const: "important", title: "Important Only" },
|
||||
{ const: "none", title: "None" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
getValueAtPath(complexSchema, [
|
||||
"properties",
|
||||
"theme",
|
||||
"oneOf",
|
||||
"0",
|
||||
"const",
|
||||
]),
|
||||
).toBe("light");
|
||||
expect(
|
||||
getValueAtPath(complexSchema, [
|
||||
"properties",
|
||||
"theme",
|
||||
"oneOf",
|
||||
"1",
|
||||
"title",
|
||||
]),
|
||||
).toBe("Dark Mode");
|
||||
expect(
|
||||
getValueAtPath(complexSchema, [
|
||||
"properties",
|
||||
"notifications",
|
||||
"oneOf",
|
||||
"2",
|
||||
"const",
|
||||
]),
|
||||
).toBe("none");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,8 +7,14 @@ export type JsonValue =
|
|||
| JsonValue[]
|
||||
| { [key: string]: JsonValue };
|
||||
|
||||
export type JsonSchemaConst = {
|
||||
const: JsonValue;
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type JsonSchemaType = {
|
||||
type:
|
||||
type?:
|
||||
| "string"
|
||||
| "number"
|
||||
| "integer"
|
||||
|
|
@ -29,7 +35,9 @@ export type JsonSchemaType = {
|
|||
pattern?: string;
|
||||
format?: string;
|
||||
enum?: string[];
|
||||
enumNames?: string[];
|
||||
const?: JsonValue;
|
||||
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||
};
|
||||
|
||||
export type JsonObject = { [key: string]: JsonValue };
|
||||
|
|
|
|||
Loading…
Reference in a new issue