feat: ValueMatcher 支持 primitive 原始值简写,等价于 { equals: value }
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { isEmptyObject, isEqual, isNil, isPlainObject } from "es-toolkit";
|
||||
|
||||
import type { CheckFailure, JsonValue } from "../types";
|
||||
import type { ExpectResult, ValueMatcher } from "./types";
|
||||
import type { ExpectResult, ValueMatcher, ValueMatcherInput } from "./types";
|
||||
|
||||
import { mismatchFailure } from "./failure";
|
||||
|
||||
@@ -66,18 +66,19 @@ export function checkExpectValue(actual: unknown, expected: JsonValue | ValueMat
|
||||
|
||||
export function checkValueMatcher(
|
||||
actual: unknown,
|
||||
matcher: undefined | ValueMatcher,
|
||||
matcher: undefined | ValueMatcherInput,
|
||||
options: { message?: string; path: string; phase: CheckFailure["phase"]; stringifyNonString?: boolean },
|
||||
): ExpectResult {
|
||||
if (matcher === undefined) return { failure: null, matched: true };
|
||||
if (applyMatcher(actual, matcher, { stringifyNonString: options.stringifyNonString })) {
|
||||
const normalized = isValueMatcherObject(matcher) ? matcher : { equals: matcher };
|
||||
if (applyMatcher(actual, normalized, { stringifyNonString: options.stringifyNonString })) {
|
||||
return { failure: null, matched: true };
|
||||
}
|
||||
return {
|
||||
failure: mismatchFailure(
|
||||
options.phase,
|
||||
options.path,
|
||||
matcher,
|
||||
normalized,
|
||||
actual,
|
||||
options.message ?? `${options.path} mismatch`,
|
||||
),
|
||||
|
||||
22
src/server/checker/expect/normalize.ts
Normal file
22
src/server/checker/expect/normalize.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { ValueMatcherPrimitive } from "./types";
|
||||
|
||||
import { isValueMatcherObject } from "./matcher";
|
||||
|
||||
export function isValueMatcherPrimitive(value: unknown): value is ValueMatcherPrimitive {
|
||||
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
||||
}
|
||||
|
||||
export function normalizeExpectMatchers(expect: Record<string, unknown>, keys: string[]): void {
|
||||
for (const key of keys) {
|
||||
if (key in expect) {
|
||||
expect[key] = normalizeValueMatcher(expect[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeValueMatcher(value: unknown): unknown {
|
||||
if (value === undefined) return undefined;
|
||||
if (isValueMatcherObject(value)) return value;
|
||||
if (isValueMatcherPrimitive(value)) return { equals: value };
|
||||
return value;
|
||||
}
|
||||
@@ -39,3 +39,7 @@ export interface ValueMatcher {
|
||||
lte?: number;
|
||||
regex?: string;
|
||||
}
|
||||
|
||||
export type ValueMatcherInput = ValueMatcher | ValueMatcherPrimitive;
|
||||
|
||||
export type ValueMatcherPrimitive = boolean | null | number | string;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ConfigValidationIssue } from "../schema/issues";
|
||||
import type { JsonValue } from "../types";
|
||||
|
||||
import { issue, joinPath } from "../schema/issues";
|
||||
import { isValueMatcherPrimitive } from "./normalize";
|
||||
import { isUnsafeRegex } from "./redos";
|
||||
|
||||
export const MatcherKeys = ["contains", "empty", "equals", "exists", "gt", "gte", "lt", "lte", "regex"] as const;
|
||||
@@ -72,7 +73,9 @@ export function validateValueMatcher(
|
||||
options: { requireAtLeastOne?: boolean } = {},
|
||||
): ConfigValidationIssue[] {
|
||||
const requireAtLeastOne = options.requireAtLeastOne ?? true;
|
||||
if (!isPlainRecord(matcher)) return [issue("invalid-type", path, "必须为 matcher 对象", targetName)];
|
||||
if (isValueMatcherPrimitive(matcher)) return [];
|
||||
if (!isPlainRecord(matcher))
|
||||
return [issue("invalid-type", path, "必须为 primitive 原始值或 matcher 对象", targetName)];
|
||||
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
let found = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContentRules, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface CommandDefaultsConfig {
|
||||
@@ -7,7 +7,7 @@ export interface CommandDefaultsConfig {
|
||||
}
|
||||
|
||||
export interface CommandExpectConfig {
|
||||
durationMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
exitCode?: number[];
|
||||
stderr?: ContentRules;
|
||||
stdout?: ContentRules;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isNumber, isPlainObject, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import { validateContentRules, validateValueMatcher } from "../../expect/validate-matcher";
|
||||
import { issue, joinPath } from "../../schema/issues";
|
||||
import { parseSize } from "../../utils";
|
||||
@@ -41,6 +42,9 @@ function validateCommandExpect(target: Record<string, unknown>, path: string): C
|
||||
if (expect === undefined || expect === null || !isPlainObject(expect)) return [];
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs"]);
|
||||
|
||||
if (expect["stdout"] !== undefined) {
|
||||
issues.push(...validateContentRules(expect["stdout"], joinPath(expectPath, "stdout"), targetName));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { isPlainObject } from "es-toolkit";
|
||||
|
||||
import type { ExpectResult, KeyValueExpect, ValueMatcher } from "../../expect/types";
|
||||
import type { ExpectResult, KeyValueExpect, ValueMatcherInput } from "../../expect/types";
|
||||
|
||||
import { mismatchFailure } from "../../expect/failure";
|
||||
import { checkKeyValueExpect } from "../../expect/key-value";
|
||||
import { checkValueMatcher } from "../../expect/matcher";
|
||||
|
||||
export function checkRowCount(actual: number, matcher: ValueMatcher): ExpectResult {
|
||||
export function checkRowCount(actual: number, matcher: ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: `rowCount ${actual} 不满足条件`,
|
||||
path: "rowCount",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface DbExpectConfig {
|
||||
durationMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
result?: ContentRules;
|
||||
rowCount?: ValueMatcher;
|
||||
rowCount?: ValueMatcherInput;
|
||||
rows?: KeyValueExpect[];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isPlainObject, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import { validateContentRules, validateKeyValueExpect, validateValueMatcher } from "../../expect/validate-matcher";
|
||||
import { issue, joinPath } from "../../schema/issues";
|
||||
|
||||
@@ -44,6 +45,8 @@ function validateDbExpect(target: Record<string, unknown>, path: string): Config
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs", "rowCount"]);
|
||||
|
||||
if (expect["durationMs"] !== undefined) {
|
||||
issues.push(...validateValueMatcher(expect["durationMs"], joinPath(expectPath, "durationMs"), targetName));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { HttpExpectConfig, HttpTargetConfig, ResolvedHttpTarget } from "./t
|
||||
|
||||
import { checkContentRules } from "../../expect/content";
|
||||
import { errorFailure, mismatchFailure } from "../../expect/failure";
|
||||
import { checkValueMatcher } from "../../expect/matcher";
|
||||
import { checkValueMatcher, isValueMatcherObject } from "../../expect/matcher";
|
||||
import { parseSize } from "../../utils";
|
||||
import { checkHeaders, checkStatus } from "./expect";
|
||||
import { httpCheckerSchemas } from "./schema";
|
||||
@@ -198,7 +198,7 @@ function checkEarlyTimeout(
|
||||
start: number,
|
||||
durationMatcher: HttpExpectConfig["durationMs"] | undefined,
|
||||
): null | { elapsed: number; failure: CheckResult["failure"] } {
|
||||
if (durationMatcher === undefined) return null;
|
||||
if (!isValueMatcherObject(durationMatcher)) return null;
|
||||
const limit = Math.min(
|
||||
durationMatcher.lte ?? Number.POSITIVE_INFINITY,
|
||||
durationMatcher.lt ?? Number.POSITIVE_INFINITY,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface HttpDefaultsConfig {
|
||||
@@ -9,7 +9,7 @@ export interface HttpDefaultsConfig {
|
||||
|
||||
export interface HttpExpectConfig {
|
||||
body?: ContentRules;
|
||||
durationMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
headers?: KeyValueExpect;
|
||||
status?: Array<number | string>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isNumber, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import {
|
||||
isPlainRecord,
|
||||
validateContentRules,
|
||||
@@ -67,6 +68,8 @@ function validateHttpExpect(target: Record<string, unknown>, path: string): Conf
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs"]);
|
||||
|
||||
if (isPlainRecord(expect["headers"])) {
|
||||
issues.push(...validateKeyValueExpect(expect["headers"], joinPath(expectPath, "headers"), targetName));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ExpectResult, ValueMatcher } from "../../expect/types";
|
||||
import type { ExpectResult, ValueMatcherInput } from "../../expect/types";
|
||||
|
||||
import { mismatchFailure } from "../../expect/failure";
|
||||
import { checkValueMatcher } from "../../expect/matcher";
|
||||
@@ -17,7 +17,7 @@ export function checkAlive(actual: boolean, expected: boolean): ExpectResult {
|
||||
};
|
||||
}
|
||||
|
||||
export function checkAvgLatency(actual: null | number, matcher: undefined | ValueMatcher): ExpectResult {
|
||||
export function checkAvgLatency(actual: null | number, matcher: undefined | ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: "平均延迟不满足条件",
|
||||
path: "avgLatencyMs",
|
||||
@@ -25,7 +25,7 @@ export function checkAvgLatency(actual: null | number, matcher: undefined | Valu
|
||||
});
|
||||
}
|
||||
|
||||
export function checkMaxLatency(actual: null | number, matcher: undefined | ValueMatcher): ExpectResult {
|
||||
export function checkMaxLatency(actual: null | number, matcher: undefined | ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: "最大延迟不满足条件",
|
||||
path: "maxLatencyMs",
|
||||
@@ -33,7 +33,7 @@ export function checkMaxLatency(actual: null | number, matcher: undefined | Valu
|
||||
});
|
||||
}
|
||||
|
||||
export function checkPacketLoss(actual: number, matcher: undefined | ValueMatcher): ExpectResult {
|
||||
export function checkPacketLoss(actual: number, matcher: undefined | ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: "丢包率不满足条件",
|
||||
path: "packetLossPercent",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { ValueMatcher } from "../../expect/types";
|
||||
import type { ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface PingExpectConfig {
|
||||
alive?: boolean;
|
||||
avgLatencyMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcher;
|
||||
maxLatencyMs?: ValueMatcher;
|
||||
packetLossPercent?: ValueMatcher;
|
||||
avgLatencyMs?: ValueMatcherInput;
|
||||
durationMs?: ValueMatcherInput;
|
||||
maxLatencyMs?: ValueMatcherInput;
|
||||
packetLossPercent?: ValueMatcherInput;
|
||||
}
|
||||
|
||||
export interface PingStats {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isNumber, isPlainObject, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import { validateValueMatcher } from "../../expect/validate-matcher";
|
||||
import { issue, joinPath } from "../../schema/issues";
|
||||
|
||||
@@ -46,6 +47,8 @@ function validatePingExpect(target: Record<string, unknown>, path: string): Conf
|
||||
const targetName = getTargetName(target);
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["packetLossPercent", "avgLatencyMs", "maxLatencyMs", "durationMs"]);
|
||||
|
||||
if (expect["alive"] !== undefined && typeof expect["alive"] !== "boolean") {
|
||||
issues.push(issue("invalid-type", joinPath(expectPath, "alive"), "必须为布尔值", targetName));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { JSONObject } from "@ai-sdk/provider";
|
||||
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, KeyValueExpect, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface LlmCheckObservation {
|
||||
@@ -24,11 +24,11 @@ export interface LlmDefaultsConfig {
|
||||
}
|
||||
|
||||
export interface LlmExpectConfig {
|
||||
durationMs?: ValueMatcher;
|
||||
finishReason?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
finishReason?: ValueMatcherInput;
|
||||
headers?: KeyValueExpect;
|
||||
output?: ContentRules;
|
||||
rawFinishReason?: ValueMatcher;
|
||||
rawFinishReason?: ValueMatcherInput;
|
||||
status?: Array<number | string>;
|
||||
stream?: LlmStreamExpect;
|
||||
usage?: LlmUsageExpect;
|
||||
@@ -57,7 +57,7 @@ export type LlmProvider = "anthropic" | "openai" | "openai-responses";
|
||||
|
||||
export interface LlmStreamExpect {
|
||||
completed?: boolean;
|
||||
firstTokenMs?: ValueMatcher;
|
||||
firstTokenMs?: ValueMatcherInput;
|
||||
}
|
||||
|
||||
export interface LlmStreamObservation {
|
||||
@@ -80,9 +80,9 @@ export interface LlmTargetConfig {
|
||||
}
|
||||
|
||||
export interface LlmUsageExpect {
|
||||
inputTokens?: ValueMatcher;
|
||||
outputTokens?: ValueMatcher;
|
||||
totalTokens?: ValueMatcher;
|
||||
inputTokens?: ValueMatcherInput;
|
||||
outputTokens?: ValueMatcherInput;
|
||||
totalTokens?: ValueMatcherInput;
|
||||
}
|
||||
|
||||
export interface LlmUsageObservation {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isBoolean, isNumber, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import {
|
||||
isPlainRecord,
|
||||
validateContentRules,
|
||||
@@ -72,6 +73,8 @@ function validateLlmExpect(
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs", "finishReason", "rawFinishReason"]);
|
||||
|
||||
if (Array.isArray(expect["status"])) {
|
||||
issues.push(...validateStatusValues(expect["status"], joinPath(expectPath, "status"), targetName));
|
||||
}
|
||||
@@ -286,6 +289,8 @@ function validateStreamExpect(stream: unknown, path: string, targetName?: string
|
||||
if (!isPlainRecord(stream)) return [issue("invalid-type", path, "必须为对象", targetName)];
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
|
||||
normalizeExpectMatchers(stream, ["firstTokenMs"]);
|
||||
|
||||
if (stream["completed"] !== undefined && !isBoolean(stream["completed"])) {
|
||||
issues.push(issue("invalid-type", joinPath(path, "completed"), "必须为布尔值", targetName));
|
||||
}
|
||||
@@ -316,6 +321,8 @@ function validateUsageExpect(usage: unknown, path: string, targetName?: string):
|
||||
if (!isPlainRecord(usage)) return [issue("invalid-type", path, "必须为对象", targetName)];
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
|
||||
normalizeExpectMatchers(usage, ["inputTokens", "outputTokens", "totalTokens"]);
|
||||
|
||||
for (const key of ["inputTokens", "outputTokens", "totalTokens"]) {
|
||||
if (usage[key] !== undefined) {
|
||||
issues.push(...validateValueMatcher(usage[key], joinPath(path, key), targetName));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContentRules, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface ResolvedTcpConfig {
|
||||
@@ -27,7 +27,7 @@ export interface TcpDefaultsConfig {
|
||||
export interface TcpExpectConfig {
|
||||
banner?: ContentRules;
|
||||
connected?: boolean;
|
||||
durationMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
}
|
||||
|
||||
export interface TcpTargetConfig {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isNumber, isPlainObject, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import { validateContentRules, validateValueMatcher } from "../../expect/validate-matcher";
|
||||
import { issue, joinPath } from "../../schema/issues";
|
||||
|
||||
@@ -75,6 +76,8 @@ function validateTcpExpect(
|
||||
const issues: ConfigValidationIssue[] = [];
|
||||
const expectPath = joinPath(path, "expect");
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs"]);
|
||||
|
||||
if (expect["connected"] !== undefined && typeof expect["connected"] !== "boolean") {
|
||||
issues.push(issue("invalid-type", joinPath(expectPath, "connected"), "必须为布尔值", targetName));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContentRules, ExpectResult, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, ExpectResult, ValueMatcherInput } from "../../expect/types";
|
||||
|
||||
import { checkContentRules } from "../../expect/content";
|
||||
import { mismatchFailure } from "../../expect/failure";
|
||||
@@ -18,7 +18,7 @@ export function checkResponded(responded: boolean, expected: boolean): ExpectRes
|
||||
};
|
||||
}
|
||||
|
||||
export function checkResponseSize(size: number, matcher: ValueMatcher): ExpectResult {
|
||||
export function checkResponseSize(size: number, matcher: ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(size, matcher, {
|
||||
message: "响应大小不满足条件",
|
||||
path: "responseSize",
|
||||
@@ -30,7 +30,7 @@ export function checkResponseText(text: string, rules: ContentRules): ExpectResu
|
||||
return checkContentRules(text, rules, { path: "response", phase: "response" });
|
||||
}
|
||||
|
||||
export function checkSourceHost(actual: string, matcher: ValueMatcher): ExpectResult {
|
||||
export function checkSourceHost(actual: string, matcher: ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: "响应来源地址不满足条件",
|
||||
path: "sourceHost",
|
||||
@@ -38,7 +38,7 @@ export function checkSourceHost(actual: string, matcher: ValueMatcher): ExpectRe
|
||||
});
|
||||
}
|
||||
|
||||
export function checkSourcePort(actual: number, matcher: ValueMatcher): ExpectResult {
|
||||
export function checkSourcePort(actual: number, matcher: ValueMatcherInput): ExpectResult {
|
||||
return checkValueMatcher(actual, matcher, {
|
||||
message: "响应来源端口不满足条件",
|
||||
path: "sourcePort",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContentRules, ValueMatcher } from "../../expect/types";
|
||||
import type { ContentRules, ValueMatcherInput } from "../../expect/types";
|
||||
import type { ResolvedTargetBase } from "../../types";
|
||||
|
||||
export interface ResolvedUdpConfig {
|
||||
@@ -29,12 +29,12 @@ export interface UdpDefaultsConfig {
|
||||
export type UdpEncoding = "base64" | "hex" | "text";
|
||||
|
||||
export interface UdpExpectConfig {
|
||||
durationMs?: ValueMatcher;
|
||||
durationMs?: ValueMatcherInput;
|
||||
responded?: boolean;
|
||||
response?: ContentRules;
|
||||
responseSize?: ValueMatcher;
|
||||
sourceHost?: ValueMatcher;
|
||||
sourcePort?: ValueMatcher;
|
||||
responseSize?: ValueMatcherInput;
|
||||
sourceHost?: ValueMatcherInput;
|
||||
sourcePort?: ValueMatcherInput;
|
||||
}
|
||||
|
||||
export interface UdpTargetConfig {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { isNumber, isPlainObject, isString } from "es-toolkit";
|
||||
import type { ConfigValidationIssue } from "../../schema/issues";
|
||||
import type { CheckerValidationInput } from "../types";
|
||||
|
||||
import { normalizeExpectMatchers } from "../../expect/normalize";
|
||||
import { validateContentRules, validateValueMatcher } from "../../expect/validate-matcher";
|
||||
import { issue, joinPath } from "../../schema/issues";
|
||||
|
||||
@@ -75,6 +76,8 @@ function validateUdpExpect(target: Record<string, unknown>, path: string): Confi
|
||||
const expectPath = joinPath(path, "expect");
|
||||
const responded: unknown = expect["responded"];
|
||||
|
||||
normalizeExpectMatchers(expect, ["durationMs", "responseSize", "sourceHost", "sourcePort"]);
|
||||
|
||||
if (responded !== undefined && typeof responded !== "boolean") {
|
||||
issues.push(issue("invalid-type", joinPath(expectPath, "responded"), "必须为布尔值", targetName));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ export const jsonValueSchema = Type.Unsafe<JsonValue>({
|
||||
],
|
||||
});
|
||||
|
||||
export const primitiveValueSchema = Type.Unsafe<boolean | null | number | string>({
|
||||
anyOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }, { type: "null" }],
|
||||
});
|
||||
|
||||
export const sizeSchema = Type.Union([Type.String(), Type.Integer({ minimum: 0 })]);
|
||||
|
||||
export const variableValueSchema = Type.Union([Type.String(), Type.Number(), Type.Boolean()]);
|
||||
@@ -72,7 +76,9 @@ export function createKeyValueExpectSchema(): TSchema {
|
||||
}
|
||||
|
||||
export function createValueMatcherSchema(): TSchema {
|
||||
return Type.Object(matcherProperties(), { additionalProperties: false, minProperties: 1 });
|
||||
return Type.Unsafe({
|
||||
anyOf: [primitiveValueSchema, Type.Object(matcherProperties(), { additionalProperties: false, minProperties: 1 })],
|
||||
});
|
||||
}
|
||||
|
||||
export function matcherProperties(): Record<string, TSchema> {
|
||||
|
||||
Reference in New Issue
Block a user