diff --git a/app/forms/network-interface-create.tsx b/app/forms/network-interface-create.tsx index 45e1f0003b..edec5d5650 100644 --- a/app/forms/network-interface-create.tsx +++ b/app/forms/network-interface-create.tsx @@ -23,7 +23,7 @@ import { useForm, useProjectSelector } from 'app/hooks' const defaultValues: InstanceNetworkInterfaceCreate = { name: '', description: '', - ip: '', + ip: undefined, subnetName: '', vpcName: '', } diff --git a/libs/api/__generated__/msw-handlers.ts b/libs/api/__generated__/msw-handlers.ts index 69b01aa468..dace34bcf3 100644 --- a/libs/api/__generated__/msw-handlers.ts +++ b/libs/api/__generated__/msw-handlers.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - /** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -8,9 +6,15 @@ * Copyright Oxide Computer Company */ -import { http, HttpHandler, HttpResponse, PathParams, StrictResponse } from 'msw' +import { + http, + HttpResponse, + type HttpHandler, + type PathParams, + type StrictResponse, +} from 'msw' import type { Promisable, SnakeCasedPropertiesDeep as Snakify } from 'type-fest' -import { z, ZodSchema } from 'zod' +import { type ZodSchema } from 'zod' import type * as Api from './Api' import { snakeify } from './util' @@ -1069,13 +1073,6 @@ export interface MSWHandlers { }) => Promisable } -function validateBody(schema: S, body: unknown) { - const result = schema.transform(snakeify).safeParse(body) - if (result.success) { - return { body: result.data as Json> } - } - return { bodyErr: json(result.error.issues, { status: 400 }) } -} function validateParams( schema: S, req: Request, @@ -1127,15 +1124,19 @@ const handler = const { path, query } = params - const { body, bodyErr } = bodySchema - ? validateBody(bodySchema, await req.json()) - : { body: undefined, bodyErr: undefined } - if (bodyErr) return json(bodyErr, { status: 400 }) + let body = undefined + if (bodySchema) { + const rawBody = await req.json() + const result = bodySchema.transform(snakeify).safeParse(rawBody) + if (!result.success) return json(result.error.issues, { status: 400 }) + body = result.data + } try { // TypeScript can't narrow the handler down because there's not an explicit relationship between the schema // being present and the shape of the handler API. The type of this function could be resolved such that the // relevant schema is required if and only if the handler has a type that matches the inferred schema + // eslint-disable-next-line @typescript-eslint/no-explicit-any const result = await (handler as any).apply(null, [ { path, query, body, req, cookies }, ]) diff --git a/libs/api/__generated__/validate.ts b/libs/api/__generated__/validate.ts index 3ca5da16d6..cc47cfd499 100644 --- a/libs/api/__generated__/validate.ts +++ b/libs/api/__generated__/validate.ts @@ -117,7 +117,11 @@ export const AddressLot = z.preprocess( */ export const AddressLotBlock = z.preprocess( processResponseBody, - z.object({ firstAddress: z.string(), id: z.string().uuid(), lastAddress: z.string() }) + z.object({ + firstAddress: z.string().ip(), + id: z.string().uuid(), + lastAddress: z.string().ip(), + }) ) /** @@ -125,7 +129,7 @@ export const AddressLotBlock = z.preprocess( */ export const AddressLotBlockCreate = z.preprocess( processResponseBody, - z.object({ firstAddress: z.string(), lastAddress: z.string() }) + z.object({ firstAddress: z.string().ip(), lastAddress: z.string().ip() }) ) /** @@ -284,7 +288,7 @@ export const BgpImportedRouteIpv4 = z.preprocess( export const BgpPeerConfig = z.preprocess( processResponseBody, z.object({ - addr: z.string(), + addr: z.string().ip(), bgpAnnounceSet: NameOrId, bgpConfig: NameOrId, connectRetry: z.number().min(0).max(4294967295), @@ -318,7 +322,7 @@ export const BgpPeerState = z.preprocess( export const BgpPeerStatus = z.preprocess( processResponseBody, z.object({ - addr: z.string(), + addr: z.string().ip(), localAsn: z.number().min(0).max(4294967295), remoteAsn: z.number().min(0).max(4294967295), state: BgpPeerState, @@ -994,7 +998,7 @@ export const IpKind = z.preprocess(processResponseBody, z.enum(['ephemeral', 'fl export const ExternalIp = z.preprocess( processResponseBody, - z.object({ ip: z.string(), kind: IpKind }) + z.object({ ip: z.string().ip(), kind: IpKind }) ) /** @@ -1253,7 +1257,7 @@ export const InstanceNetworkInterfaceCreate = z.preprocess( processResponseBody, z.object({ description: z.string(), - ip: z.string().optional(), + ip: z.string().ip().optional(), name: Name, subnetName: Name, vpcName: Name, @@ -1324,7 +1328,7 @@ export const InstanceNetworkInterface = z.preprocess( description: z.string(), id: z.string().uuid(), instanceId: z.string().uuid(), - ip: z.string(), + ip: z.string().ip(), mac: MacAddr, name: Name, primary: SafeBoolean, @@ -1536,7 +1540,7 @@ export const LoopbackAddress = z.preprocess( export const LoopbackAddressCreate = z.preprocess( processResponseBody, z.object({ - address: z.string(), + address: z.string().ip(), addressLot: NameOrId, anycast: SafeBoolean, mask: z.number().min(0).max(255), @@ -1733,7 +1737,11 @@ export const RoleResultsPage = z.preprocess( */ export const Route = z.preprocess( processResponseBody, - z.object({ dst: IpNet, gw: z.string(), vid: z.number().min(0).max(65535).optional() }) + z.object({ + dst: IpNet, + gw: z.string().ip(), + vid: z.number().min(0).max(65535).optional(), + }) ) /** @@ -1752,7 +1760,7 @@ export const RouteConfig = z.preprocess( export const RouteDestination = z.preprocess( processResponseBody, z.union([ - z.object({ type: z.enum(['ip']), value: z.string() }), + z.object({ type: z.enum(['ip']), value: z.string().ip() }), z.object({ type: z.enum(['ip_net']), value: IpNet }), z.object({ type: z.enum(['vpc']), value: Name }), z.object({ type: z.enum(['subnet']), value: Name }), @@ -1765,7 +1773,7 @@ export const RouteDestination = z.preprocess( export const RouteTarget = z.preprocess( processResponseBody, z.union([ - z.object({ type: z.enum(['ip']), value: z.string() }), + z.object({ type: z.enum(['ip']), value: z.string().ip() }), z.object({ type: z.enum(['vpc']), value: Name }), z.object({ type: z.enum(['subnet']), value: Name }), z.object({ type: z.enum(['instance']), value: Name }), @@ -2155,7 +2163,7 @@ export const SwitchPortApplySettings = z.preprocess( export const SwitchPortBgpPeerConfig = z.preprocess( processResponseBody, z.object({ - addr: z.string(), + addr: z.string().ip(), bgpConfigId: z.string().uuid(), interfaceName: z.string(), portSettingsId: z.string().uuid(), @@ -2429,7 +2437,7 @@ export const VpcFirewallRuleHostFilter = z.preprocess( z.object({ type: z.enum(['vpc']), value: Name }), z.object({ type: z.enum(['subnet']), value: Name }), z.object({ type: z.enum(['instance']), value: Name }), - z.object({ type: z.enum(['ip']), value: z.string() }), + z.object({ type: z.enum(['ip']), value: z.string().ip() }), z.object({ type: z.enum(['ip_net']), value: IpNet }), ]) ) @@ -2468,7 +2476,7 @@ export const VpcFirewallRuleTarget = z.preprocess( z.object({ type: z.enum(['vpc']), value: Name }), z.object({ type: z.enum(['subnet']), value: Name }), z.object({ type: z.enum(['instance']), value: Name }), - z.object({ type: z.enum(['ip']), value: z.string() }), + z.object({ type: z.enum(['ip']), value: z.string().ip() }), z.object({ type: z.enum(['ip_net']), value: IpNet }), ]) ) @@ -3961,7 +3969,7 @@ export const NetworkingLoopbackAddressDeleteParams = z.preprocess( processResponseBody, z.object({ path: z.object({ - address: z.string(), + address: z.string().ip(), rackId: z.string().uuid(), subnetMask: z.number().min(0).max(255), switchLocation: Name, diff --git a/tools/generate_api_client.sh b/tools/generate_api_client.sh index 329a4d7955..fcaeaef03c 100755 --- a/tools/generate_api_client.sh +++ b/tools/generate_api_client.sh @@ -18,7 +18,7 @@ GEN_DIR="$PWD/libs/api/__generated__" # this will be less horrific when the package is published? or maybe not npm run --silent --prefix ../oxide.ts gen-from $OMICRON_SHA $GEN_DIR -npx prettier --write --loglevel error "$GEN_DIR" +npx prettier --write --log-level error "$GEN_DIR" # hack until we start pulling this from npm. we don't want this file rm "$GEN_DIR/type-test.ts"