@f5-sales-demo/xcsh 22.8.2 → 22.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "22.8.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "22.8.3",
|
|
21
|
+
"commit": "5ecf98705c48d2de46a9673adadc8957bc32343b",
|
|
22
|
+
"shortCommit": "5ecf987",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v22.8.
|
|
25
|
-
"commitDate": "2026-10-
|
|
26
|
-
"buildDate": "2026-10-
|
|
24
|
+
"tag": "v22.8.3",
|
|
25
|
+
"commitDate": "2026-10-03T16:38:57Z",
|
|
26
|
+
"buildDate": "2026-10-03T17:29:00.510Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.8.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/5ecf98705c48d2de46a9673adadc8957bc32343b",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v22.8.3"
|
|
33
33
|
};
|
|
@@ -1,100 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface MapScope {
|
|
5
|
-
type?: string;
|
|
6
|
-
minLength?: number;
|
|
7
|
-
maxLength?: number;
|
|
8
|
-
pattern?: string;
|
|
9
|
-
format?: string;
|
|
10
|
-
minimum?: number;
|
|
11
|
-
maximum?: number;
|
|
12
|
-
ranges?: number[][];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface MapConstraints {
|
|
16
|
-
constraintType: string;
|
|
17
|
-
keys?: MapScope;
|
|
18
|
-
values?: MapScope;
|
|
19
|
-
cardinality?: { minProperties?: number; maxProperties?: number };
|
|
20
|
-
crossEntry?: { uniqueValues?: boolean };
|
|
21
|
-
originalRules?: Record<string, unknown>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function formatMapConstraints(constraints: MapConstraints): string {
|
|
25
|
-
const parts: string[] = [];
|
|
26
|
-
for (const scope of ["keys", "values", "cardinality", "crossEntry"] as const) {
|
|
27
|
-
for (const [key, value] of Object.entries(constraints[scope] ?? {})) {
|
|
28
|
-
parts.push(`${scope}.${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return parts.join(", ");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function validateText(value: unknown, scope: MapScope, label: string): string[] {
|
|
35
|
-
const errors: string[] = [];
|
|
36
|
-
if (typeof value !== "string") return [`${label} must be a string`];
|
|
37
|
-
const length = Array.from(value).length;
|
|
38
|
-
if (scope.minLength !== undefined && length < scope.minLength) errors.push(`${label} below minimum length`);
|
|
39
|
-
if (scope.maxLength !== undefined && length > scope.maxLength) errors.push(`${label} exceeds maximum length`);
|
|
40
|
-
if (scope.pattern && !new RegExp(scope.pattern).test(value)) errors.push(`${label} does not match pattern`);
|
|
41
|
-
if (scope.type === "uint32-string") {
|
|
42
|
-
const number = Number(value);
|
|
43
|
-
if (!/^\d+$/.test(value) || !Number.isSafeInteger(number) || number > 4294967295)
|
|
44
|
-
errors.push(`${label} must be uint32`);
|
|
45
|
-
if (scope.minimum !== undefined && number < scope.minimum) errors.push(`${label} below minimum`);
|
|
46
|
-
if (scope.maximum !== undefined && number > scope.maximum) errors.push(`${label} exceeds maximum`);
|
|
47
|
-
if (scope.ranges && !scope.ranges.some(([lo, hi]) => number >= lo! && number <= hi!))
|
|
48
|
-
errors.push(`${label} outside ranges`);
|
|
49
|
-
}
|
|
50
|
-
switch (scope.format) {
|
|
51
|
-
case "ipv4":
|
|
52
|
-
if (isIP(value) !== 4) errors.push(`${label} must be IPv4`);
|
|
53
|
-
break;
|
|
54
|
-
case "ipv6":
|
|
55
|
-
if (isIP(value) !== 6) errors.push(`${label} must be IPv6`);
|
|
56
|
-
break;
|
|
57
|
-
case "ip-address":
|
|
58
|
-
if (!isIP(value)) errors.push(`${label} must be IP`);
|
|
59
|
-
break;
|
|
60
|
-
case "mac-address":
|
|
61
|
-
if (!/^(?:[\da-f]{2}:){5}[\da-f]{2}$/i.test(value)) errors.push(`${label} must be MAC`);
|
|
62
|
-
break;
|
|
63
|
-
case "regex":
|
|
64
|
-
try {
|
|
65
|
-
new RegExp(value);
|
|
66
|
-
} catch {
|
|
67
|
-
errors.push(`${label} must be a valid regex`);
|
|
68
|
-
}
|
|
69
|
-
break;
|
|
70
|
-
case "k8s-label-value":
|
|
71
|
-
if (value.length > 63 || !/^(?:[A-Za-z0-9](?:[A-Za-z0-9_.-]*[A-Za-z0-9])?)?$/.test(value))
|
|
72
|
-
errors.push(`${label} must be a Kubernetes label value`);
|
|
73
|
-
break;
|
|
74
|
-
case "uri-reference":
|
|
75
|
-
if (/[^\x21-\x7e]|%(?![\da-f]{2})/i.test(value)) errors.push(`${label} must be a URI-reference`);
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
return errors;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function validateMapConstraints(value: unknown, constraints: MapConstraints): string[] {
|
|
82
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return ["Expected a map object"];
|
|
83
|
-
const entries = Object.entries(value);
|
|
84
|
-
const errors: string[] = [];
|
|
85
|
-
const cardinality = constraints.cardinality;
|
|
86
|
-
if (cardinality?.minProperties !== undefined && entries.length < cardinality.minProperties)
|
|
87
|
-
errors.push("Too few map pairs");
|
|
88
|
-
if (cardinality?.maxProperties !== undefined && entries.length > cardinality.maxProperties)
|
|
89
|
-
errors.push("Too many map pairs");
|
|
90
|
-
for (const [key, item] of entries) {
|
|
91
|
-
if (constraints.keys) errors.push(...validateText(key, constraints.keys, `key ${key}`));
|
|
92
|
-
if (constraints.values) errors.push(...validateText(item, constraints.values, `value at ${key}`));
|
|
93
|
-
}
|
|
94
|
-
if (
|
|
95
|
-
constraints.crossEntry?.uniqueValues &&
|
|
96
|
-
new Set(entries.map(([, item]) => JSON.stringify(item))).size !== entries.length
|
|
97
|
-
)
|
|
98
|
-
errors.push("Map values must be unique");
|
|
99
|
-
return errors;
|
|
100
|
-
}
|
|
1
|
+
export type { MapConstraints, MapScope } from "@f5-sales-demo/pi-resource-management";
|
|
2
|
+
export { formatMapConstraints, validateMapConstraints } from "@f5-sales-demo/pi-resource-management";
|
|
@@ -1,7 +1,65 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createKindResolver,
|
|
3
|
+
type MapConstraints,
|
|
4
|
+
type ValidationError,
|
|
5
|
+
validateMapConstraints,
|
|
6
|
+
} from "@f5-sales-demo/pi-resource-management";
|
|
7
|
+
import { API_CATALOG_DATA } from "../internal-urls/api-catalog-index.generated";
|
|
2
8
|
import { API_SPEC_INDEX, API_VALIDATION_DATA } from "../internal-urls/api-spec-index.generated";
|
|
3
9
|
|
|
4
|
-
|
|
10
|
+
const baseResolver = createKindResolver(API_SPEC_INDEX, API_VALIDATION_DATA);
|
|
11
|
+
const normalizePath = (path: string) =>
|
|
12
|
+
path.replace(/\{metadata\.namespace\}/g, "{namespace}").replace(/\{metadata\.name\}/g, "{name}");
|
|
13
|
+
|
|
14
|
+
function valuesAtPath(object: unknown, path: string): unknown[] {
|
|
15
|
+
if (!path) return [object];
|
|
16
|
+
const [part, ...remaining] = path.split(".");
|
|
17
|
+
const name = part!.replace(/(?:\[\]|\{\})$/, "");
|
|
18
|
+
if (!object || typeof object !== "object") return [];
|
|
19
|
+
const child = (object as Record<string, unknown>)[name];
|
|
20
|
+
if (child === undefined || child === null) return [];
|
|
21
|
+
const rest = remaining.join(".");
|
|
22
|
+
if (part!.endsWith("[]")) return Array.isArray(child) ? child.flatMap(item => valuesAtPath(item, rest)) : [];
|
|
23
|
+
if (part!.endsWith("{}"))
|
|
24
|
+
return typeof child === "object" ? Object.values(child).flatMap(item => valuesAtPath(item, rest)) : [];
|
|
25
|
+
return valuesAtPath(child, rest);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const kindResolver = {
|
|
29
|
+
...baseResolver,
|
|
30
|
+
resolveKind(kind: string) {
|
|
31
|
+
const resolved = baseResolver.resolveKind(kind);
|
|
32
|
+
return {
|
|
33
|
+
...resolved,
|
|
34
|
+
validateFields(object: Record<string, unknown>, operation: "create" | "update"): ValidationError[] {
|
|
35
|
+
const method = operation === "update" ? "put" : "post";
|
|
36
|
+
const errors: ValidationError[] = [];
|
|
37
|
+
const seen = new Set<string>();
|
|
38
|
+
for (const category of Object.values(API_CATALOG_DATA)) {
|
|
39
|
+
for (const entry of category.operations) {
|
|
40
|
+
if (entry.method.toLowerCase() !== method || normalizePath(entry.path) !== resolved.paths[operation])
|
|
41
|
+
continue;
|
|
42
|
+
for (const [field, metadata] of Object.entries(entry.fieldMetadata ?? {})) {
|
|
43
|
+
if (metadata.constraints?.constraintType !== "map") continue;
|
|
44
|
+
const fingerprint = `${field}:${JSON.stringify(metadata.constraints)}`;
|
|
45
|
+
if (seen.has(fingerprint)) continue;
|
|
46
|
+
seen.add(fingerprint);
|
|
47
|
+
for (const value of valuesAtPath(object, field)) {
|
|
48
|
+
for (const message of validateMapConstraints(
|
|
49
|
+
value,
|
|
50
|
+
metadata.constraints as unknown as MapConstraints,
|
|
51
|
+
)) {
|
|
52
|
+
errors.push({ path: field, message, code: "INVALID_MAP" });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return errors;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
5
63
|
|
|
6
64
|
export type {
|
|
7
65
|
ApiSpecDomainEntry,
|