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: {
|
role: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: ["admin", "user", "guest"],
|
oneOf: [
|
||||||
enumNames: ["Administrator", "User", "Guest"],
|
{ const: "admin", title: "Administrator" },
|
||||||
|
{ const: "user", title: "User" },
|
||||||
|
{ const: "guest", title: "Guest" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["name", "email"],
|
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 = {
|
const schema = {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: ["option1", "option2"],
|
oneOf: [
|
||||||
enumNames: ["Option 1", "Option 2"],
|
{ const: "option1", title: "Option 1" },
|
||||||
|
{ const: "option2", title: "Option 2" },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getValueAtPath(schema, ["enum", "0"])).toBe("option1");
|
expect(getValueAtPath(schema, ["oneOf", "0", "const"])).toBe("option1");
|
||||||
expect(getValueAtPath(schema, ["enumNames", "1"])).toBe("Option 2");
|
expect(getValueAtPath(schema, ["oneOf", "1", "title"])).toBe("Option 2");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle validation constraints", () => {
|
test("should handle validation constraints", () => {
|
||||||
|
|
@ -425,4 +430,97 @@ describe("JsonSchemaType elicitation field support", () => {
|
||||||
"Do you accept the terms and conditions?",
|
"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[]
|
| JsonValue[]
|
||||||
| { [key: string]: JsonValue };
|
| { [key: string]: JsonValue };
|
||||||
|
|
||||||
|
export type JsonSchemaConst = {
|
||||||
|
const: JsonValue;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type JsonSchemaType = {
|
export type JsonSchemaType = {
|
||||||
type:
|
type?:
|
||||||
| "string"
|
| "string"
|
||||||
| "number"
|
| "number"
|
||||||
| "integer"
|
| "integer"
|
||||||
|
|
@ -29,7 +35,9 @@ export type JsonSchemaType = {
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
format?: string;
|
format?: string;
|
||||||
enum?: string[];
|
enum?: string[];
|
||||||
enumNames?: string[];
|
const?: JsonValue;
|
||||||
|
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||||
|
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JsonObject = { [key: string]: JsonValue };
|
export type JsonObject = { [key: string]: JsonValue };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue