Gateway: add path-scoped config schema lookup (#37266)

Merged via squash.

Prepared head SHA: 0c4d187f6f
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana
2026-03-06 02:50:48 -05:00
committed by GitHub
parent c5828cbc08
commit ff97195500
18 changed files with 633 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
import { beforeAll, describe, expect, it } from "vitest";
import { buildConfigSchema } from "./schema.js";
import { buildConfigSchema, lookupConfigSchema } from "./schema.js";
import { applyDerivedTags, CONFIG_TAGS, deriveTagsForPath } from "./schema.tags.js";
describe("config schema", () => {
@@ -202,4 +202,101 @@ describe("config schema", () => {
}
}
});
it("looks up a config schema path with immediate child summaries", () => {
const lookup = lookupConfigSchema(baseSchema, "gateway.auth");
expect(lookup?.path).toBe("gateway.auth");
expect(lookup?.hintPath).toBe("gateway.auth");
expect(lookup?.children.some((child) => child.key === "token")).toBe(true);
const tokenChild = lookup?.children.find((child) => child.key === "token");
expect(tokenChild?.path).toBe("gateway.auth.token");
expect(tokenChild?.hint?.sensitive).toBe(true);
expect(tokenChild?.hintPath).toBe("gateway.auth.token");
const schema = lookup?.schema as { properties?: unknown } | undefined;
expect(schema?.properties).toBeUndefined();
});
it("returns a shallow lookup schema without nested composition keywords", () => {
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.runtime");
expect(lookup?.path).toBe("agents.list.0.runtime");
expect(lookup?.hintPath).toBe("agents.list[].runtime");
expect(lookup?.schema).toEqual({});
});
it("matches wildcard ui hints for concrete lookup paths", () => {
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.identity.avatar");
expect(lookup?.path).toBe("agents.list.0.identity.avatar");
expect(lookup?.hintPath).toBe("agents.list.*.identity.avatar");
expect(lookup?.hint?.help).toContain("workspace-relative path");
});
it("normalizes bracketed lookup paths", () => {
const lookup = lookupConfigSchema(baseSchema, "agents.list[0].identity.avatar");
expect(lookup?.path).toBe("agents.list.0.identity.avatar");
expect(lookup?.hintPath).toBe("agents.list.*.identity.avatar");
});
it("matches ui hints that use empty array brackets", () => {
const lookup = lookupConfigSchema(baseSchema, "agents.list.0.runtime");
expect(lookup?.path).toBe("agents.list.0.runtime");
expect(lookup?.hintPath).toBe("agents.list[].runtime");
expect(lookup?.hint?.label).toBe("Agent Runtime");
});
it("uses the indexed tuple item schema for positional array lookups", () => {
const tupleSchema = {
schema: {
type: "object",
properties: {
pair: {
type: "array",
items: [{ type: "string" }, { type: "number" }],
},
},
},
uiHints: {},
version: "test",
generatedAt: "test",
} as unknown as Parameters<typeof lookupConfigSchema>[0];
const lookup = lookupConfigSchema(tupleSchema, "pair.1");
expect(lookup?.path).toBe("pair.1");
expect(lookup?.schema).toMatchObject({ type: "number" });
expect((lookup?.schema as { items?: unknown } | undefined)?.items).toBeUndefined();
});
it("rejects prototype-chain lookup segments", () => {
expect(() => lookupConfigSchema(baseSchema, "constructor")).not.toThrow();
expect(lookupConfigSchema(baseSchema, "constructor")).toBeNull();
expect(lookupConfigSchema(baseSchema, "__proto__.polluted")).toBeNull();
});
it("rejects overly deep lookup paths", () => {
const buildNestedObjectSchema = (segments: string[]) => {
const [head, ...rest] = segments;
if (!head) {
return { type: "string" };
}
return {
type: "object",
properties: {
[head]: buildNestedObjectSchema(rest),
},
};
};
const deepPathSegments = Array.from({ length: 33 }, (_, index) => `a${index}`);
const deepSchema = {
schema: buildNestedObjectSchema(deepPathSegments),
uiHints: {},
version: "test",
generatedAt: "test",
} as unknown as Parameters<typeof lookupConfigSchema>[0];
expect(lookupConfigSchema(deepSchema, deepPathSegments.join("."))).toBeNull();
});
it("returns null for missing config schema paths", () => {
expect(lookupConfigSchema(baseSchema, "gateway.notReal.path")).toBeNull();
});
});