refactor: migrate more boundary parsing to zod

This commit is contained in:
Peter Steinberger
2026-03-27 04:52:19 +00:00
parent 137a56194e
commit a1f995053e
17 changed files with 475 additions and 146 deletions

View File

@@ -6,6 +6,8 @@ import {
resolveMergedAccountConfig,
} from "openclaw/plugin-sdk/account-resolution";
import { isSecretRef, type OpenClawConfig } from "openclaw/plugin-sdk/core";
import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
import { z } from "zod";
import type { GoogleChatAccountConfig } from "./types.config.js";
export type GoogleChatCredentialSource = "file" | "inline" | "env" | "none";
@@ -22,6 +24,7 @@ export type ResolvedGoogleChatAccount = {
const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
const JsonRecordSchema = z.record(z.string(), z.unknown());
const {
listAccountIds: listGoogleChatAccountIds,
@@ -62,24 +65,19 @@ function mergeGoogleChatAccountConfig(
}
function parseServiceAccount(value: unknown): Record<string, unknown> | null {
if (value && typeof value === "object") {
if (isSecretRef(value)) {
if (isSecretRef(value)) {
return null;
}
if (typeof value === "string") {
const trimmed = value.trim();
if (!trimmed) {
return null;
}
return value as Record<string, unknown>;
}
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
if (!trimmed) {
return null;
}
try {
return JSON.parse(trimmed) as Record<string, unknown>;
} catch {
return null;
return safeParseJsonWithSchema(JsonRecordSchema, trimmed);
}
return safeParseWithSchema(JsonRecordSchema, value);
}
function resolveCredentialsFromConfig(params: {

View File

@@ -49,6 +49,7 @@ function buildAccount(): ResolvedGoogleChatAccount {
describe("googlechat setup", () => {
afterEach(() => {
vi.clearAllMocks();
vi.unstubAllEnvs();
});
it("rejects env auth for non-default accounts", () => {
@@ -186,6 +187,32 @@ describe("googlechat setup", () => {
});
describe("resolveGoogleChatAccount", () => {
it("parses default-account env JSON credentials only when they decode to an object", () => {
vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT", '{"client_email":"bot@example.com"}');
const resolved = resolveGoogleChatAccount({
cfg: { channels: { googlechat: {} } },
accountId: "default",
});
expect(resolved.credentialSource).toBe("env");
expect(resolved.credentials).toEqual({ client_email: "bot@example.com" });
});
it("ignores env JSON credentials when they decode to a non-object value", () => {
vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT", '["not","an","object"]');
vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT_FILE", "/tmp/googlechat.json");
const resolved = resolveGoogleChatAccount({
cfg: { channels: { googlechat: {} } },
accountId: "default",
});
expect(resolved.credentialSource).toBe("env");
expect(resolved.credentials).toBeUndefined();
expect(resolved.credentialsFile).toBe("/tmp/googlechat.json");
});
it("inherits shared defaults from accounts.default for named accounts", () => {
const cfg: OpenClawConfig = {
channels: {