mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
refactor(gateway): one owner for the submitted-config validation pipeline (#124331)
config.set and config.patch each hand-rolled the same normalize ->
raw-validate -> plugin-validate -> respond-invalid pipeline (including the
duplicated errorShape/details blocks the round-5 error-text fix touched in
both places). validateSubmittedConfigOrRespond now owns it; both handlers
consume { validationCandidate, config }.
Behavior-neutral: same validators in the same order, same error shapes
(pinned by the existing 26 config tests including the round-5 regressions).
Net -11 production LOC and one place to evolve validation error content.
This commit is contained in:
committed by
GitHub
parent
1fda883530
commit
9c6539c975
@@ -516,42 +516,18 @@ function parseValidateConfigFromRawOrRespond(
|
||||
createMergePatch(snapshot.config, restored.result),
|
||||
)
|
||||
: restored.result;
|
||||
const validationCandidate = normalizeSubmittedConfigModelRefs(
|
||||
stripBundledProviderRuntimeDefaults({
|
||||
candidate: projectedValidationCandidate,
|
||||
sourceConfig: snapshot.sourceConfig,
|
||||
}) as OpenClawConfig,
|
||||
const validatedSubmission = validateSubmittedConfigOrRespond({
|
||||
candidate: projectedValidationCandidate,
|
||||
sourceConfig: snapshot.sourceConfig,
|
||||
modelIdNormalizationPolicies,
|
||||
);
|
||||
const sourceValidated = validateConfigObjectRawWithPlugins(validationCandidate);
|
||||
if (!sourceValidated.ok) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
summarizeConfigValidationIssues(sourceValidated.issues),
|
||||
{
|
||||
details: { issues: sourceValidated.issues },
|
||||
},
|
||||
),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(validationCandidate);
|
||||
if (!validated.ok) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, summarizeConfigValidationIssues(validated.issues), {
|
||||
details: { issues: validated.issues },
|
||||
}),
|
||||
);
|
||||
respond,
|
||||
});
|
||||
if (!validatedSubmission) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
config: validated.config,
|
||||
writeConfig: validationCandidate as OpenClawConfig,
|
||||
config: validatedSubmission.config,
|
||||
writeConfig: validatedSubmission.validationCandidate,
|
||||
schema,
|
||||
};
|
||||
}
|
||||
@@ -595,6 +571,42 @@ function rejectDroppedAgentRosterEntries(params: {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Shared normalize -> raw-validate -> plugin-validate pipeline for submitted configs; responds on failure. */
|
||||
function validateSubmittedConfigOrRespond(params: {
|
||||
candidate: unknown;
|
||||
sourceConfig: OpenClawConfig | undefined;
|
||||
modelIdNormalizationPolicies: Parameters<typeof normalizeSubmittedConfigModelRefs>[1];
|
||||
respond: RespondFn;
|
||||
}): { validationCandidate: OpenClawConfig; config: OpenClawConfig } | null {
|
||||
const validationCandidate = normalizeSubmittedConfigModelRefs(
|
||||
stripBundledProviderRuntimeDefaults({
|
||||
candidate: params.candidate,
|
||||
sourceConfig: params.sourceConfig,
|
||||
}) as OpenClawConfig,
|
||||
params.modelIdNormalizationPolicies,
|
||||
);
|
||||
const respondInvalid = (issues: ReadonlyArray<ConfigValidationIssue>) => {
|
||||
params.respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, summarizeConfigValidationIssues(issues), {
|
||||
details: { issues },
|
||||
}),
|
||||
);
|
||||
};
|
||||
const sourceValidated = validateConfigObjectRawWithPlugins(validationCandidate);
|
||||
if (!sourceValidated.ok) {
|
||||
respondInvalid(sourceValidated.issues);
|
||||
return null;
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(validationCandidate);
|
||||
if (!validated.ok) {
|
||||
respondInvalid(validated.issues);
|
||||
return null;
|
||||
}
|
||||
return { validationCandidate: validationCandidate as OpenClawConfig, config: validated.config };
|
||||
}
|
||||
|
||||
function summarizeConfigValidationIssues(issues: ReadonlyArray<ConfigValidationIssue>): string {
|
||||
const trimmed = issues.slice(0, MAX_CONFIG_ISSUES_IN_ERROR_MESSAGE);
|
||||
const lines = normalizeStringEntries(
|
||||
@@ -1080,48 +1092,25 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const validationCandidate = normalizeSubmittedConfigModelRefs(
|
||||
stripBundledProviderRuntimeDefaults({
|
||||
candidate: restoredMerge.result,
|
||||
sourceConfig: snapshot.sourceConfig,
|
||||
}) as OpenClawConfig,
|
||||
const validatedSubmission = validateSubmittedConfigOrRespond({
|
||||
candidate: restoredMerge.result,
|
||||
sourceConfig: snapshot.sourceConfig,
|
||||
modelIdNormalizationPolicies,
|
||||
);
|
||||
const sourceValidated = validateConfigObjectRawWithPlugins(validationCandidate);
|
||||
if (!sourceValidated.ok) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
summarizeConfigValidationIssues(sourceValidated.issues),
|
||||
{
|
||||
details: { issues: sourceValidated.issues },
|
||||
},
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const writeConfig = validationCandidate as OpenClawConfig;
|
||||
const validated = validateConfigObjectWithPlugins(validationCandidate);
|
||||
if (!validated.ok) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, summarizeConfigValidationIssues(validated.issues), {
|
||||
details: { issues: validated.issues },
|
||||
}),
|
||||
);
|
||||
respond,
|
||||
});
|
||||
if (!validatedSubmission) {
|
||||
return;
|
||||
}
|
||||
const writeConfig = validatedSubmission.validationCandidate;
|
||||
const validatedConfig = validatedSubmission.config;
|
||||
const preparedSecretsSnapshot = await ensureResolvableSecretRefsOrRespond({
|
||||
config: validated.config,
|
||||
config: validatedConfig,
|
||||
respond,
|
||||
});
|
||||
if (!preparedSecretsSnapshot) {
|
||||
return;
|
||||
}
|
||||
const changedPaths = diffConfigPaths(snapshot.config, validated.config);
|
||||
const changedPaths = diffConfigPaths(snapshot.config, validatedConfig);
|
||||
|
||||
// No-op: if the validated config is identical to the current config,
|
||||
// skip the file write and SIGUSR1 restart entirely. This avoids a full
|
||||
@@ -1130,7 +1119,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
if (changedPaths.length === 0) {
|
||||
respondConfigPatchNoop({
|
||||
snapshot,
|
||||
config: validated.config,
|
||||
config: validatedConfig,
|
||||
uiHints: schemaPatch.uiHints,
|
||||
actor,
|
||||
context,
|
||||
@@ -1147,7 +1136,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
const disconnectSharedAuthClients = shouldDisconnectSharedAuthClientsForConfigWrite({
|
||||
prevConfig: snapshot.config,
|
||||
prevSourceConfig: snapshot.sourceConfig,
|
||||
nextConfig: validated.config,
|
||||
nextConfig: validatedConfig,
|
||||
preparedSecretsSnapshot,
|
||||
});
|
||||
const writeResult = await commitGatewayConfigWriteOrRespond({
|
||||
|
||||
Reference in New Issue
Block a user