Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions rest/nodejs/src/api/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
PaymentDataSchema,
type PostalAddress,
} from "../models";
import { type IdParamContext } from "../utils/validation";

/**
* Schema for the request body when completing a checkout session.
Expand Down Expand Up @@ -494,8 +495,8 @@ export class CheckoutService {
}
};

getCheckout = async (c: Context) => {
const id = c.req.param("id");
getCheckout = async (c: IdParamContext) => {
const { id } = c.req.valid("param");

// Log Request
logRequest("GET", `/checkout-sessions/${id}`, id, {});
Expand All @@ -507,8 +508,8 @@ export class CheckoutService {
return c.json(checkout, 200);
};

updateCheckout = async (c: Context) => {
const id = c.req.param("id");
updateCheckout = async (c: IdParamContext) => {
const { id } = c.req.valid("param");
const idempotencyKey = c.req.header("Idempotency-Key");
const ucpAgent = c.req.header("UCP-Agent");
const updateRequest = await c.req.json<ExtendedCheckoutUpdateRequest>();
Expand Down Expand Up @@ -627,8 +628,8 @@ export class CheckoutService {
}
};

completeCheckout = async (c: Context) => {
const id = c.req.param("id");
completeCheckout = async (c: IdParamContext) => {
const { id } = c.req.valid("param");
const idempotencyKey = c.req.header("Idempotency-Key");
const rawBody = await c.req.json<CompleteCheckoutRequest>();
let requestHash = "";
Expand Down Expand Up @@ -863,8 +864,8 @@ export class CheckoutService {
}
};

cancelCheckout = async (c: Context) => {
const id = c.req.param("id");
cancelCheckout = async (c: IdParamContext) => {
const { id } = c.req.valid("param");
const idempotencyKey = c.req.header("Idempotency-Key");
const rawBody = {}; // Empty body for cancel usually
let requestHash = "";
Expand Down
10 changes: 5 additions & 5 deletions rest/nodejs/src/api/order.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { type Context } from "hono";
import { getOrder, logRequest, saveOrder } from "../data";
import { type Order } from "../models";
import { type IdParamContext } from "../utils/validation";

/**
* Service for managing orders.
*/
export class OrderService {
getOrder = async (c: Context) => {
const id = c.req.param("id");
getOrder = async (c: IdParamContext) => {
const { id } = c.req.valid("param");

// Log Request
logRequest("GET", `/orders/${id}`, undefined, {});
Expand All @@ -19,8 +19,8 @@ export class OrderService {
return c.json(order, 200);
};

updateOrder = async (c: Context) => {
const id = c.req.param("id");
updateOrder = async (c: IdParamContext) => {
const { id } = c.req.valid("param");
const updateRequest = await c.req.json<Order>();

// Log Request
Expand Down
7 changes: 3 additions & 4 deletions rest/nodejs/src/api/testing.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { type Context } from "hono";

import { type IdParamContext } from "../utils/validation";
import { CheckoutService } from "./checkout";

export class TestingService {
constructor(private readonly checkoutService: CheckoutService) {}

shipOrder = async (c: Context) => {
shipOrder = async (c: IdParamContext) => {
const secret = c.req.header("Simulation-Secret");
const expectedSecret =
process.env.SIMULATION_SECRET || "super-secret-sim-key";
Expand All @@ -14,7 +13,7 @@ export class TestingService {
return c.json({ detail: "Invalid Simulation Secret" }, 403);
}

const id = c.req.param("id");
const { id } = c.req.valid("param");
try {
await this.checkoutService.shipOrder(id);
return c.json({ status: "shipped" }, 200);
Expand Down
11 changes: 10 additions & 1 deletion rest/nodejs/src/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Context } from "hono";
import { type Context, type Env } from "hono";
import * as z from "zod";

/**
Expand Down Expand Up @@ -36,3 +36,12 @@ export function prettyValidation<T>(
export const IdParamSchema = z.object({
id: z.string(),
});

export type IdParamContext = Context<
Env,
string,
{
in: { param: z.input<typeof IdParamSchema> };
out: { param: z.output<typeof IdParamSchema> };
}
>;
19 changes: 19 additions & 0 deletions rest/nodejs/test/validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import { test } from "node:test";

import { IdParamSchema } from "../src/utils/validation";

test("accepts a required route ID", () => {
const result = IdParamSchema.safeParse({ id: "item-123" });

assert.equal(result.success, true);
if (result.success) {
assert.equal(result.data.id, "item-123");
}
});

test("rejects a missing route ID", () => {
const result = IdParamSchema.safeParse({});

assert.equal(result.success, false);
});
Loading