fix(opencode): ignore unknown config fields (#41312)
This commit is contained in:
committed by
GitHub
parent
fe82a1b6ca
commit
38e10eb140
@@ -37,22 +37,11 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
|
|||||||
data: unknown,
|
data: unknown,
|
||||||
source: string,
|
source: string,
|
||||||
): DeepMutable<S["Type"]> {
|
): DeepMutable<S["Type"]> {
|
||||||
const extra = topLevelExtraKeys(schema, data)
|
const decoded = EffectSchema.decodeUnknownExit(schema)(data, {
|
||||||
if (extra.length) {
|
errors: "all",
|
||||||
throw new InvalidError({
|
onExcessProperty: "ignore",
|
||||||
path: source,
|
propertyOrder: "original",
|
||||||
issues: [
|
})
|
||||||
{
|
|
||||||
code: "unrecognized_keys",
|
|
||||||
keys: extra,
|
|
||||||
path: [],
|
|
||||||
message: `Unrecognized key${extra.length === 1 ? "" : "s"}: ${extra.join(", ")}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const decoded = EffectSchema.decodeUnknownExit(schema)(data, { errors: "all", propertyOrder: "original" })
|
|
||||||
if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable<S["Type"]>
|
if (Exit.isSuccess(decoded)) return decoded.value as DeepMutable<S["Type"]>
|
||||||
const error = Cause.squash(decoded.cause)
|
const error = Cause.squash(decoded.cause)
|
||||||
|
|
||||||
@@ -70,10 +59,3 @@ export function schema<S extends EffectSchema.Decoder<unknown, never>>(
|
|||||||
{ cause: error },
|
{ cause: error },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function topLevelExtraKeys(schema: EffectSchema.Top, data: unknown) {
|
|
||||||
if (typeof data !== "object" || data === null || Array.isArray(data)) return []
|
|
||||||
if (schema.ast._tag !== "Objects" || schema.ast.indexSignatures.length > 0) return []
|
|
||||||
const known = new Set(schema.ast.propertySignatures.map((item) => String(item.name)))
|
|
||||||
return Object.keys(data).filter((key) => !known.has(key))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -597,12 +597,12 @@ accountTokenIt.instance("resolves env templates in account config with account t
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.instance("validates config schema and throws on invalid fields", () =>
|
it.instance("validates config schema and throws on invalid values", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
yield* writeConfigEffect(test.directory, {
|
yield* writeConfigEffect(test.directory, {
|
||||||
$schema: "https://opencode.ai/config.json",
|
$schema: "https://opencode.ai/config.json",
|
||||||
invalid_field: "should cause error",
|
model: 42,
|
||||||
})
|
})
|
||||||
const exit = yield* Config.use.get().pipe(Effect.exit)
|
const exit = yield* Config.use.get().pipe(Effect.exit)
|
||||||
expect(Exit.isFailure(exit)).toBe(true)
|
expect(Exit.isFailure(exit)).toBe(true)
|
||||||
@@ -1331,7 +1331,7 @@ it.instance("permission config preserves user key order", () =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
test("config parser preserves permission order while rejecting unknown top-level keys", () => {
|
test("config parser preserves permission order while ignoring unknown top-level keys", () => {
|
||||||
const config = ConfigParse.schema(
|
const config = ConfigParse.schema(
|
||||||
ConfigV1.Info,
|
ConfigV1.Info,
|
||||||
{
|
{
|
||||||
@@ -1340,18 +1340,13 @@ test("config parser preserves permission order while rejecting unknown top-level
|
|||||||
"*": "deny",
|
"*": "deny",
|
||||||
edit: "ask",
|
edit: "ask",
|
||||||
},
|
},
|
||||||
|
plugins: ["example"],
|
||||||
},
|
},
|
||||||
"test",
|
"test",
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"])
|
expect(Object.keys(config.permission!)).toEqual(["bash", "*", "edit"])
|
||||||
try {
|
expect(config).not.toHaveProperty("plugins")
|
||||||
ConfigParse.schema(ConfigV1.Info, { invalid_field: true }, "test")
|
|
||||||
throw new Error("expected config parse to fail")
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as { data?: { issues?: Array<{ code?: string; keys?: string[]; path?: string[] }> } }
|
|
||||||
expect(error.data?.issues?.[0]).toMatchObject({ code: "unrecognized_keys", keys: ["invalid_field"], path: [] })
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// MCP config merging tests
|
// MCP config merging tests
|
||||||
|
|||||||
Reference in New Issue
Block a user