From bb908868c2a9b456d8e929ab3f3d31f1689a1873 Mon Sep 17 00:00:00 2001 From: Jonathan Meeks Date: Fri, 11 Apr 2025 13:46:38 -0500 Subject: [PATCH 1/3] validation check for each concrete resource type, wip --- packages/validators/src/resources/cloud-v1.ts | 19 +++++++++-- .../validators/src/resources/kubernetes-v1.ts | 19 +++++++++-- packages/validators/src/resources/util.ts | 32 +++++++++++++++++++ packages/validators/src/resources/validate.ts | 18 +++++++++++ packages/validators/src/resources/vm-v1.ts | 19 +++++++++-- 5 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 packages/validators/src/resources/util.ts create mode 100644 packages/validators/src/resources/validate.ts diff --git a/packages/validators/src/resources/cloud-v1.ts b/packages/validators/src/resources/cloud-v1.ts index 021acadd3..f434ace55 100644 --- a/packages/validators/src/resources/cloud-v1.ts +++ b/packages/validators/src/resources/cloud-v1.ts @@ -1,4 +1,6 @@ import { z } from "zod"; +import type { Identifiable } from "./util"; +import { isResourceAPI } from "./util.js"; const subnet = z.object({ name: z.string(), @@ -19,9 +21,12 @@ const subnet = z.object({ .optional(), }); +const kind = "VPC"; +const version = "cloud/v1"; + export const cloudVpcV1 = z.object({ - version: z.literal("cloud/v1"), - kind: z.literal("VPC"), + version: z.literal(version), + kind: z.literal(kind), identifier: z.string(), name: z.string(), config: z.object({ @@ -43,3 +48,13 @@ export const cloudVpcV1 = z.object({ export type CloudVPCV1 = z.infer; export type CloudSubnetV1 = z.infer; + +export const isCloudVpcAPIV1 = ( + obj: object, +): obj is CloudVPCV1 => + isResourceAPI( + obj, + (identifiable: Identifiable) => + identifiable.kind === kind && identifiable.version === version, + cloudVpcV1, + ); diff --git a/packages/validators/src/resources/kubernetes-v1.ts b/packages/validators/src/resources/kubernetes-v1.ts index 7d2edb442..bbe3330fa 100644 --- a/packages/validators/src/resources/kubernetes-v1.ts +++ b/packages/validators/src/resources/kubernetes-v1.ts @@ -1,4 +1,6 @@ import { z } from "zod"; +import type { Identifiable } from "./util"; +import { isResourceAPI } from "./util.js"; const clusterConfig = z.object({ name: z.string(), @@ -54,9 +56,12 @@ const clusterConfig = z.object({ ]), }); +const version = "kubernetes/v1"; +const kind = "ClusterAPI"; + export const kubernetesClusterApiV1 = z.object({ - version: z.literal("kubernetes/v1"), - kind: z.literal("ClusterAPI"), + version: z.literal(version), + kind: z.literal(kind), identifier: z.string(), name: z.string(), config: clusterConfig, @@ -92,3 +97,13 @@ export const kubernetesNamespaceV1 = z.object({ }); export type KubernetesNamespaceV1 = z.infer; + +export const isKubernetesClusterAPIV1 = ( + obj: object, +): obj is KubernetesClusterAPIV1 => + isResourceAPI( + obj, + (identifiable: Identifiable) => + identifiable.kind === kind && identifiable.version === version, + kubernetesClusterApiV1, + ); diff --git a/packages/validators/src/resources/util.ts b/packages/validators/src/resources/util.ts new file mode 100644 index 000000000..fca589397 --- /dev/null +++ b/packages/validators/src/resources/util.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +export const identifiable = z.object({ + version: z.string(), + kind: z.string(), +}); + +export type Identifiable = z.infer; + +export const isIdentifiable = ( + obj: object, +): obj is Identifiable => { + return identifiable.safeParse(obj).success; +}; + +export const isResourceAPI = ( + obj: object, + matcher: (identifiable: Identifiable) => boolean, + schema: S, +): obj is z.infer => { + if (isIdentifiable(obj) && matcher(obj)) { + // If the object is identifiable and matches the kind and version, validate it against the schema + const parseResult = schema.safeParse(obj); + if (parseResult.success) { + return true; + } + // If validation fails, log and throw the error + console.error("Validation failed:", parseResult.error); + throw parseResult.error; + } + return false; +}; diff --git a/packages/validators/src/resources/validate.ts b/packages/validators/src/resources/validate.ts new file mode 100644 index 000000000..3028d0340 --- /dev/null +++ b/packages/validators/src/resources/validate.ts @@ -0,0 +1,18 @@ +import { isCloudVpcAPIV1 } from "./cloud-v1.js"; +import { isKubernetesClusterAPIV1 } from "./kubernetes-v1.js"; +import { isIdentifiable } from "./util.js"; +import { isVmV1 } from "./vm-v1.js"; + +export const validateResource = (obj: object) => { + // The object must be Identifable to be a valid resource + if (!isIdentifiable(obj)) { + throw new Error("Resource must have a version and kind"); + } + + // If each of these do not throw an error, it means one of the following: + // 1. It could be a valid resource of one of the types below: correct kind/version AND with validated schema + // 2. It is not a valid resource of any of the types below and will be treated as a generic resource + isKubernetesClusterAPIV1(obj); + isCloudVpcAPIV1(obj); + isVmV1(obj); +}; diff --git a/packages/validators/src/resources/vm-v1.ts b/packages/validators/src/resources/vm-v1.ts index 31f35914c..5fafcae5c 100644 --- a/packages/validators/src/resources/vm-v1.ts +++ b/packages/validators/src/resources/vm-v1.ts @@ -1,4 +1,6 @@ import { z } from "zod"; +import type { Identifiable } from "./util"; +import { isResourceAPI } from "./util.js"; const diskV1 = z.object({ name: z.string(), @@ -7,11 +9,14 @@ const diskV1 = z.object({ encrypted: z.boolean(), }); +const version = "vm/v1"; +const kind = "VM"; + export const vmV1 = z.object({ workspaceId: z.string(), providerId: z.string(), - version: z.literal("vm/v1"), - kind: z.literal("VM"), + version: z.literal(version), + kind: z.literal(kind), identifier: z.string(), name: z.string(), config: z @@ -34,3 +39,13 @@ export const vmV1 = z.object({ }); export type VmV1 = z.infer; + +export const isVmV1 = ( + obj: object, +): obj is VmV1 => + isResourceAPI( + obj, + (identifiable: Identifiable) => + identifiable.kind === kind && identifiable.version === version, + vmV1, + ); From 583a78db538b82ed735255d2e66d40a659e2b4ae Mon Sep 17 00:00:00 2001 From: Jonathan Meeks Date: Fri, 11 Apr 2025 17:31:07 -0500 Subject: [PATCH 2/3] correctly filters non-matching schemas for matching version/kind resources --- .../[providerId]/set/route.ts | 93 ++++++++++++------- packages/validators/src/resources/cloud-v1.ts | 9 +- packages/validators/src/resources/index.ts | 1 + .../validators/src/resources/kubernetes-v1.ts | 9 +- packages/validators/src/resources/util.ts | 26 ++++-- packages/validators/src/resources/validate.ts | 45 ++++++--- packages/validators/src/resources/vm-v1.ts | 9 +- 7 files changed, 122 insertions(+), 70 deletions(-) diff --git a/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts b/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts index 26167be01..e32badf6c 100644 --- a/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts +++ b/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts @@ -6,41 +6,48 @@ import { eq, takeFirstOrNull } from "@ctrlplane/db"; import { db } from "@ctrlplane/db/client"; import { createResource, + Resource, resourceProvider, workspace, } from "@ctrlplane/db/schema"; +import type { ResourceToInsert } from "@ctrlplane/job-dispatch"; import { handleResourceProviderScan } from "@ctrlplane/job-dispatch"; +import { partitionForSchemaErrors } from "@ctrlplane/validators/resources"; import { Permission } from "@ctrlplane/validators/auth"; import { authn, authz } from "~/app/api/v1/auth"; import { parseBody } from "../../../body-parser"; import { request } from "../../../middleware"; +const bodyResource = createResource + .omit({ lockedAt: true, providerId: true, workspaceId: true }) + .extend({ + metadata: z.record(z.string()).optional(), + variables: z + .array( + z.object({ + key: z.string(), + value: z.union([z.string(), z.number(), z.boolean(), z.null()]), + sensitive: z.boolean(), + }), + ) + .optional() + .refine( + (vars) => + vars == null || + new Set(vars.map((v) => v.key)).size === vars.length, + "Duplicate variable keys are not allowed", + ), + }); + +type BodyResource = z.infer; + const bodySchema = z.object({ - resources: z.array( - createResource - .omit({ lockedAt: true, providerId: true, workspaceId: true }) - .extend({ - metadata: z.record(z.string()).optional(), - variables: z - .array( - z.object({ - key: z.string(), - value: z.union([z.string(), z.number(), z.boolean(), z.null()]), - sensitive: z.boolean(), - }), - ) - .optional() - .refine( - (vars) => - vars == null || - new Set(vars.map((v) => v.key)).size === vars.length, - "Duplicate variable keys are not allowed", - ), - }), - ), + resources: z.array(bodyResource), }); +type BodySchema = z.infer; + export const PATCH = request() .use(authn) .use(parseBody(bodySchema)) @@ -48,11 +55,11 @@ export const PATCH = request() authz(({ can, extra }) => can .perform(Permission.ResourceUpdate) - .on({ type: "resourceProvider", id: extra.params.providerId }), + .on({ type: "resourceProvider", id: extra.params.providerId }) ), ) .handle< - { body: z.infer }, + { body: BodySchema }, { params: { providerId: string } } >(async (ctx, { params }) => { const { body } = ctx; @@ -65,11 +72,12 @@ export const PATCH = request() .then(takeFirstOrNull); const provider = query?.resource_provider; - if (!provider) + if (!provider) { return NextResponse.json( { error: "Provider not found" }, { status: 404 }, ); + } const resourcesToInsert = body.resources.map((r) => ({ ...r, @@ -77,16 +85,31 @@ export const PATCH = request() workspaceId: provider.workspaceId, })); - const resources = await handleResourceProviderScan( - db, - resourcesToInsert.map((r) => ({ - ...r, - variables: r.variables?.map((v) => ({ - ...v, - value: v.value ?? null, - })), - })), + const { valid, errors } = partitionForSchemaErrors( + resourcesToInsert, ); - return NextResponse.json({ resources }); + if (valid.length > 0) { + const resources = await handleResourceProviderScan( + db, + valid.map((r) => ({ + ...r, + variables: r.variables?.map((v) => ({ + ...v, + value: v.value ?? null, + })), + })), + ); + + return NextResponse.json({ resources }); + } + + if (errors.length > 0) { + return NextResponse.json( + { error: "Validation errors", issues: errors }, + { status: 400 }, + ); + } + + return NextResponse.json([]); }); diff --git a/packages/validators/src/resources/cloud-v1.ts b/packages/validators/src/resources/cloud-v1.ts index f434ace55..06ecaab29 100644 --- a/packages/validators/src/resources/cloud-v1.ts +++ b/packages/validators/src/resources/cloud-v1.ts @@ -1,6 +1,7 @@ +import type { ZodError } from "zod"; import { z } from "zod"; import type { Identifiable } from "./util"; -import { isResourceAPI } from "./util.js"; +import { getSchemaParseError } from "./util.js"; const subnet = z.object({ name: z.string(), @@ -49,10 +50,10 @@ export const cloudVpcV1 = z.object({ export type CloudVPCV1 = z.infer; export type CloudSubnetV1 = z.infer; -export const isCloudVpcAPIV1 = ( +export const getCloudVpcV1SchemaParserError = ( obj: object, -): obj is CloudVPCV1 => - isResourceAPI( +): ZodError | undefined => + getSchemaParseError( obj, (identifiable: Identifiable) => identifiable.kind === kind && identifiable.version === version, diff --git a/packages/validators/src/resources/index.ts b/packages/validators/src/resources/index.ts index 435b7cede..39799b82f 100644 --- a/packages/validators/src/resources/index.ts +++ b/packages/validators/src/resources/index.ts @@ -3,3 +3,4 @@ export * from "./conditions/index.js"; export * from "./cloud-v1.js"; export * from "./vm-v1.js"; export * from "./cloud-geo.js"; +export * from "./validate.js"; diff --git a/packages/validators/src/resources/kubernetes-v1.ts b/packages/validators/src/resources/kubernetes-v1.ts index bbe3330fa..4cd9c78f6 100644 --- a/packages/validators/src/resources/kubernetes-v1.ts +++ b/packages/validators/src/resources/kubernetes-v1.ts @@ -1,6 +1,7 @@ +import type { ZodError } from "zod"; import { z } from "zod"; import type { Identifiable } from "./util"; -import { isResourceAPI } from "./util.js"; +import { getSchemaParseError } from "./util.js"; const clusterConfig = z.object({ name: z.string(), @@ -98,10 +99,10 @@ export const kubernetesNamespaceV1 = z.object({ export type KubernetesNamespaceV1 = z.infer; -export const isKubernetesClusterAPIV1 = ( +export const getKubernetesClusterAPIV1SchemaParseError = ( obj: object, -): obj is KubernetesClusterAPIV1 => - isResourceAPI( +): ZodError | undefined => + getSchemaParseError( obj, (identifiable: Identifiable) => identifiable.kind === kind && identifiable.version === version, diff --git a/packages/validators/src/resources/util.ts b/packages/validators/src/resources/util.ts index fca589397..b6f737152 100644 --- a/packages/validators/src/resources/util.ts +++ b/packages/validators/src/resources/util.ts @@ -13,20 +13,28 @@ export const isIdentifiable = ( return identifiable.safeParse(obj).success; }; -export const isResourceAPI = ( +export const getIdentifiableSchemaParseError = ( + obj: object, +): z.ZodError | undefined => { + return identifiable.safeParse(obj).error; +}; + +/** + * getSchemaParseError will return a ZodError if the object has expected kind and version + * @param obj incoming object to have it's schema validated, if identifiable based on its kind and version + * @param matcher impl to check the object's kind and version + * @param schema schema to validate the object against + * @returns ZodError if the object is has expected kind and version + */ +export const getSchemaParseError = ( obj: object, matcher: (identifiable: Identifiable) => boolean, schema: S, -): obj is z.infer => { +): z.ZodError | undefined => { if (isIdentifiable(obj) && matcher(obj)) { // If the object is identifiable and matches the kind and version, validate it against the schema const parseResult = schema.safeParse(obj); - if (parseResult.success) { - return true; - } - // If validation fails, log and throw the error - console.error("Validation failed:", parseResult.error); - throw parseResult.error; + return parseResult.error; } - return false; + return undefined; }; diff --git a/packages/validators/src/resources/validate.ts b/packages/validators/src/resources/validate.ts index 3028d0340..04514060d 100644 --- a/packages/validators/src/resources/validate.ts +++ b/packages/validators/src/resources/validate.ts @@ -1,18 +1,35 @@ -import { isCloudVpcAPIV1 } from "./cloud-v1.js"; -import { isKubernetesClusterAPIV1 } from "./kubernetes-v1.js"; -import { isIdentifiable } from "./util.js"; -import { isVmV1 } from "./vm-v1.js"; +import type { ZodError } from "zod"; +import { getIdentifiableSchemaParseError } from "./util.js"; +import { getCloudVpcV1SchemaParserError } from "./cloud-v1.js"; +import { getKubernetesClusterAPIV1SchemaParseError } from "./kubernetes-v1.js"; +import { getVmV1SchemaParseError } from "./vm-v1.js"; -export const validateResource = (obj: object) => { - // The object must be Identifable to be a valid resource - if (!isIdentifiable(obj)) { - throw new Error("Resource must have a version and kind"); +export const anySchemaError = (obj: object): ZodError | undefined => { + return getIdentifiableSchemaParseError(obj) ?? + getCloudVpcV1SchemaParserError(obj) ?? + getKubernetesClusterAPIV1SchemaParseError(obj) ?? + getVmV1SchemaParseError(obj); +}; + +interface ValidatedObjects { + valid: T[]; + errors: ZodError[]; +} + +export const partitionForSchemaErrors = ( + objs: T[], +): ValidatedObjects => { + const errors: ZodError[] = []; + const valid: T[] = []; + + for (const obj of objs) { + const error = anySchemaError(obj); + if (error) { + errors.push(error); + } else { + valid.push(obj); + } } - // If each of these do not throw an error, it means one of the following: - // 1. It could be a valid resource of one of the types below: correct kind/version AND with validated schema - // 2. It is not a valid resource of any of the types below and will be treated as a generic resource - isKubernetesClusterAPIV1(obj); - isCloudVpcAPIV1(obj); - isVmV1(obj); + return { valid, errors }; }; diff --git a/packages/validators/src/resources/vm-v1.ts b/packages/validators/src/resources/vm-v1.ts index 5fafcae5c..5453842bd 100644 --- a/packages/validators/src/resources/vm-v1.ts +++ b/packages/validators/src/resources/vm-v1.ts @@ -1,6 +1,7 @@ +import type { ZodError } from "zod"; import { z } from "zod"; import type { Identifiable } from "./util"; -import { isResourceAPI } from "./util.js"; +import { getSchemaParseError } from "./util.js"; const diskV1 = z.object({ name: z.string(), @@ -40,10 +41,10 @@ export const vmV1 = z.object({ export type VmV1 = z.infer; -export const isVmV1 = ( +export const getVmV1SchemaParseError = ( obj: object, -): obj is VmV1 => - isResourceAPI( +): ZodError | undefined => + getSchemaParseError( obj, (identifiable: Identifiable) => identifiable.kind === kind && identifiable.version === version, From b102af788b447bd5702d887584b2294b6a49f6e6 Mon Sep 17 00:00:00 2001 From: Jonathan Meeks Date: Fri, 11 Apr 2025 17:37:27 -0500 Subject: [PATCH 3/3] format, lint fixes --- .../[providerId]/set/route.ts | 102 +++++++++--------- packages/validators/src/resources/cloud-v1.ts | 1 + .../validators/src/resources/kubernetes-v1.ts | 1 + packages/validators/src/resources/util.ts | 32 +++--- packages/validators/src/resources/validate.ts | 39 +++---- packages/validators/src/resources/vm-v1.ts | 5 +- 6 files changed, 88 insertions(+), 92 deletions(-) diff --git a/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts b/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts index e32badf6c..c3bced6c6 100644 --- a/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts +++ b/apps/webservice/src/app/api/v1/resource-providers/[providerId]/set/route.ts @@ -1,3 +1,4 @@ +import type { ResourceToInsert } from "@ctrlplane/job-dispatch"; import { NextResponse } from "next/server"; import _ from "lodash"; import { z } from "zod"; @@ -6,14 +7,12 @@ import { eq, takeFirstOrNull } from "@ctrlplane/db"; import { db } from "@ctrlplane/db/client"; import { createResource, - Resource, resourceProvider, workspace, } from "@ctrlplane/db/schema"; -import type { ResourceToInsert } from "@ctrlplane/job-dispatch"; import { handleResourceProviderScan } from "@ctrlplane/job-dispatch"; -import { partitionForSchemaErrors } from "@ctrlplane/validators/resources"; import { Permission } from "@ctrlplane/validators/auth"; +import { partitionForSchemaErrors } from "@ctrlplane/validators/resources"; import { authn, authz } from "~/app/api/v1/auth"; import { parseBody } from "../../../body-parser"; @@ -34,14 +33,11 @@ const bodyResource = createResource .optional() .refine( (vars) => - vars == null || - new Set(vars.map((v) => v.key)).size === vars.length, + vars == null || new Set(vars.map((v) => v.key)).size === vars.length, "Duplicate variable keys are not allowed", ), }); -type BodyResource = z.infer; - const bodySchema = z.object({ resources: z.array(bodyResource), }); @@ -55,61 +51,59 @@ export const PATCH = request() authz(({ can, extra }) => can .perform(Permission.ResourceUpdate) - .on({ type: "resourceProvider", id: extra.params.providerId }) + .on({ type: "resourceProvider", id: extra.params.providerId }), ), ) - .handle< - { body: BodySchema }, - { params: { providerId: string } } - >(async (ctx, { params }) => { - const { body } = ctx; + .handle<{ body: BodySchema }, { params: { providerId: string } }>( + async (ctx, { params }) => { + const { body } = ctx; - const query = await db - .select() - .from(resourceProvider) - .innerJoin(workspace, eq(workspace.id, resourceProvider.workspaceId)) - .where(eq(resourceProvider.id, params.providerId)) - .then(takeFirstOrNull); + const query = await db + .select() + .from(resourceProvider) + .innerJoin(workspace, eq(workspace.id, resourceProvider.workspaceId)) + .where(eq(resourceProvider.id, params.providerId)) + .then(takeFirstOrNull); - const provider = query?.resource_provider; - if (!provider) { - return NextResponse.json( - { error: "Provider not found" }, - { status: 404 }, - ); - } + const provider = query?.resource_provider; + if (!provider) { + return NextResponse.json( + { error: "Provider not found" }, + { status: 404 }, + ); + } - const resourcesToInsert = body.resources.map((r) => ({ - ...r, - providerId: provider.id, - workspaceId: provider.workspaceId, - })); + const resourcesToInsert = body.resources.map((r) => ({ + ...r, + providerId: provider.id, + workspaceId: provider.workspaceId, + })); - const { valid, errors } = partitionForSchemaErrors( - resourcesToInsert, - ); + const { valid, errors } = + partitionForSchemaErrors(resourcesToInsert); - if (valid.length > 0) { - const resources = await handleResourceProviderScan( - db, - valid.map((r) => ({ - ...r, - variables: r.variables?.map((v) => ({ - ...v, - value: v.value ?? null, + if (valid.length > 0) { + const resources = await handleResourceProviderScan( + db, + valid.map((r) => ({ + ...r, + variables: r.variables?.map((v) => ({ + ...v, + value: v.value ?? null, + })), })), - })), - ); + ); - return NextResponse.json({ resources }); - } + return NextResponse.json({ resources }); + } - if (errors.length > 0) { - return NextResponse.json( - { error: "Validation errors", issues: errors }, - { status: 400 }, - ); - } + if (errors.length > 0) { + return NextResponse.json( + { error: "Validation errors", issues: errors }, + { status: 400 }, + ); + } - return NextResponse.json([]); - }); + return NextResponse.json([]); + }, + ); diff --git a/packages/validators/src/resources/cloud-v1.ts b/packages/validators/src/resources/cloud-v1.ts index 06ecaab29..afbdb936c 100644 --- a/packages/validators/src/resources/cloud-v1.ts +++ b/packages/validators/src/resources/cloud-v1.ts @@ -1,5 +1,6 @@ import type { ZodError } from "zod"; import { z } from "zod"; + import type { Identifiable } from "./util"; import { getSchemaParseError } from "./util.js"; diff --git a/packages/validators/src/resources/kubernetes-v1.ts b/packages/validators/src/resources/kubernetes-v1.ts index 4cd9c78f6..66d7ccd6e 100644 --- a/packages/validators/src/resources/kubernetes-v1.ts +++ b/packages/validators/src/resources/kubernetes-v1.ts @@ -1,5 +1,6 @@ import type { ZodError } from "zod"; import { z } from "zod"; + import type { Identifiable } from "./util"; import { getSchemaParseError } from "./util.js"; diff --git a/packages/validators/src/resources/util.ts b/packages/validators/src/resources/util.ts index b6f737152..b580a2c07 100644 --- a/packages/validators/src/resources/util.ts +++ b/packages/validators/src/resources/util.ts @@ -1,22 +1,20 @@ import { z } from "zod"; export const identifiable = z.object({ - version: z.string(), - kind: z.string(), + version: z.string(), + kind: z.string(), }); export type Identifiable = z.infer; -export const isIdentifiable = ( - obj: object, -): obj is Identifiable => { - return identifiable.safeParse(obj).success; +export const isIdentifiable = (obj: object): obj is Identifiable => { + return identifiable.safeParse(obj).success; }; export const getIdentifiableSchemaParseError = ( - obj: object, + obj: object, ): z.ZodError | undefined => { - return identifiable.safeParse(obj).error; + return identifiable.safeParse(obj).error; }; /** @@ -27,14 +25,14 @@ export const getIdentifiableSchemaParseError = ( * @returns ZodError if the object is has expected kind and version */ export const getSchemaParseError = ( - obj: object, - matcher: (identifiable: Identifiable) => boolean, - schema: S, + obj: object, + matcher: (identifiable: Identifiable) => boolean, + schema: S, ): z.ZodError | undefined => { - if (isIdentifiable(obj) && matcher(obj)) { - // If the object is identifiable and matches the kind and version, validate it against the schema - const parseResult = schema.safeParse(obj); - return parseResult.error; - } - return undefined; + if (isIdentifiable(obj) && matcher(obj)) { + // If the object is identifiable and matches the kind and version, validate it against the schema + const parseResult = schema.safeParse(obj); + return parseResult.error; + } + return undefined; }; diff --git a/packages/validators/src/resources/validate.ts b/packages/validators/src/resources/validate.ts index 04514060d..19a27b3e5 100644 --- a/packages/validators/src/resources/validate.ts +++ b/packages/validators/src/resources/validate.ts @@ -1,35 +1,38 @@ import type { ZodError } from "zod"; -import { getIdentifiableSchemaParseError } from "./util.js"; + import { getCloudVpcV1SchemaParserError } from "./cloud-v1.js"; import { getKubernetesClusterAPIV1SchemaParseError } from "./kubernetes-v1.js"; +import { getIdentifiableSchemaParseError } from "./util.js"; import { getVmV1SchemaParseError } from "./vm-v1.js"; export const anySchemaError = (obj: object): ZodError | undefined => { - return getIdentifiableSchemaParseError(obj) ?? - getCloudVpcV1SchemaParserError(obj) ?? - getKubernetesClusterAPIV1SchemaParseError(obj) ?? - getVmV1SchemaParseError(obj); + return ( + getIdentifiableSchemaParseError(obj) ?? + getCloudVpcV1SchemaParserError(obj) ?? + getKubernetesClusterAPIV1SchemaParseError(obj) ?? + getVmV1SchemaParseError(obj) + ); }; interface ValidatedObjects { - valid: T[]; - errors: ZodError[]; + valid: T[]; + errors: ZodError[]; } export const partitionForSchemaErrors = ( - objs: T[], + objs: T[], ): ValidatedObjects => { - const errors: ZodError[] = []; - const valid: T[] = []; + const errors: ZodError[] = []; + const valid: T[] = []; - for (const obj of objs) { - const error = anySchemaError(obj); - if (error) { - errors.push(error); - } else { - valid.push(obj); - } + for (const obj of objs) { + const error = anySchemaError(obj); + if (error) { + errors.push(error); + } else { + valid.push(obj); } + } - return { valid, errors }; + return { valid, errors }; }; diff --git a/packages/validators/src/resources/vm-v1.ts b/packages/validators/src/resources/vm-v1.ts index 5453842bd..a1d5d1382 100644 --- a/packages/validators/src/resources/vm-v1.ts +++ b/packages/validators/src/resources/vm-v1.ts @@ -1,5 +1,6 @@ import type { ZodError } from "zod"; import { z } from "zod"; + import type { Identifiable } from "./util"; import { getSchemaParseError } from "./util.js"; @@ -41,9 +42,7 @@ export const vmV1 = z.object({ export type VmV1 = z.infer; -export const getVmV1SchemaParseError = ( - obj: object, -): ZodError | undefined => +export const getVmV1SchemaParseError = (obj: object): ZodError | undefined => getSchemaParseError( obj, (identifiable: Identifiable) =>