Skip to content

Commit

Permalink
fix(routing): compute APIContext.params when rewriting using next (
Browse files Browse the repository at this point in the history
…#12031)

* fix(routing): compute `APIContext.params` when rewriting using `next`

* fix(routing): compute `APIContext.params` when rewriting using `next`
  • Loading branch information
ematipico committed Sep 19, 2024
1 parent d3bd673 commit 8c0cae6
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 37 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-worms-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes a bug where the rewrite via `next(/*..*/)` inside a middleware didn't compute the new `APIContext.params`
6 changes: 1 addition & 5 deletions packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,7 @@ export class AppPipeline extends Pipeline {
return module.page();
}

async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,
Expand Down
7 changes: 1 addition & 6 deletions packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,8 @@ export abstract class Pipeline {
*
* @param {RewritePayload} rewritePayload The payload provided by the user
* @param {Request} request The original request
* @param {RouteData} sourceRoute The original `RouteData`
*/
abstract tryRewrite(
rewritePayload: RewritePayload,
request: Request,
sourceRoute: RouteData,
): Promise<TryRewriteResult>;
abstract tryRewrite(rewritePayload: RewritePayload, request: Request): Promise<TryRewriteResult>;

/**
* Tells the pipeline how to retrieve a component give a `RouteData`
Expand Down
10 changes: 5 additions & 5 deletions packages/astro/src/core/build/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export class BuildPipeline extends Pipeline {
);
}

getRoutes(): RouteData[] {
return this.options.manifest.routes;
}

static create({
internals,
manifest,
Expand Down Expand Up @@ -286,11 +290,7 @@ export class BuildPipeline extends Pipeline {
return module.page();
}

async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,
Expand Down
11 changes: 9 additions & 2 deletions packages/astro/src/core/middleware/sequence.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { APIContext, MiddlewareHandler, RewritePayload } from '../../@types/astro.js';
import { AstroCookies } from '../cookies/cookies.js';
import { defineMiddleware } from './index.js';
import { getParams, type Pipeline } from '../render/index.js';
import { apiContextRoutesSymbol } from '../render-context.js';

// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
/**
Expand All @@ -15,7 +17,6 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
return next();
});
}

return defineMiddleware((context, next) => {
/**
* This variable is used to carry the rerouting payload across middleware functions.
Expand All @@ -28,7 +29,7 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
// @ts-expect-error
// SAFETY: Usually `next` always returns something in user land, but in `sequence` we are actually
// doing a loop over all the `next` functions, and eventually we call the last `next` that returns the `Response`.
const result = handle(handleContext, async (payload: RewritePayload) => {
const result = handle(handleContext, async (payload?: RewritePayload) => {
if (i < length - 1) {
if (payload) {
let newRequest;
Expand All @@ -42,10 +43,16 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
handleContext.request,
);
}
const pipeline: Pipeline = Reflect.get(handleContext, apiContextRoutesSymbol);
const { routeData, pathname } = await pipeline.tryRewrite(
payload,
handleContext.request,
);
carriedPayload = payload;
handleContext.request = newRequest;
handleContext.url = new URL(newRequest.url);
handleContext.cookies = new AstroCookies(newRequest);
handleContext.params = getParams(routeData, pathname);
}
return applyHandle(i + 1, handleContext);
} else {
Expand Down
31 changes: 19 additions & 12 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,13 @@ import { sequence } from './middleware/index.js';
import { renderRedirect } from './redirects/render.js';
import { type Pipeline, Slots, getParams, getProps } from './render/index.js';

export const apiContextRoutesSymbol = Symbol.for('context.routes');

/**
* Each request is rendered using a `RenderContext`.
* It contains data unique to each request. It is responsible for executing middleware, calling endpoints, and rendering the page by gathering necessary data from a `Pipeline`.
*/
export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;

private constructor(
readonly pipeline: Pipeline,
public locals: App.Locals,
Expand All @@ -57,9 +56,7 @@ export class RenderContext {
public params = getParams(routeData, pathname),
protected url = new URL(request.url),
public props: Props = {},
) {
this.originalRoute = routeData;
}
) {}

/**
* A flag that tells the render content if the rewriting was triggered
Expand Down Expand Up @@ -142,14 +139,24 @@ export class RenderContext {
if (payload) {
pipeline.logger.debug('router', 'Called rewriting to:', payload);
// we intentionally let the error bubble up
const { routeData, componentInstance: newComponent } = await pipeline.tryRewrite(
payload,
this.request,
this.originalRoute,
);
const {
routeData,
componentInstance: newComponent,
pathname,
newUrl,
} = await pipeline.tryRewrite(payload, this.request);
this.routeData = routeData;
componentInstance = newComponent;
if (payload instanceof Request) {
this.request = payload;
} else {
this.request = this.#copyRequest(newUrl, this.request);
}
this.isRewriting = true;
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, pathname);
this.pathname = pathname;
this.status = 200;
}
let response: Response;
Expand Down Expand Up @@ -218,6 +225,7 @@ export class RenderContext {
const context = this.createActionAPIContext();
const redirect = (path: string, status = 302) =>
new Response(null, { status, headers: { Location: path } });
Reflect.set(context, apiContextRoutesSymbol, this.pipeline);

return Object.assign(context, {
props,
Expand All @@ -237,7 +245,6 @@ export class RenderContext {
const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute,
);
this.routeData = routeData;
if (reroutePayload instanceof Request) {
Expand Down
6 changes: 1 addition & 5 deletions packages/astro/src/vite-plugin-astro-server/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,7 @@ export class DevPipeline extends Pipeline {
}
}

async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
if (!this.manifestData) {
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
}
Expand Down
12 changes: 10 additions & 2 deletions packages/astro/test/fixtures/reroute/src/middleware.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sequence } from 'astro:middleware';
import {defineMiddleware} from "astro/middleware";

let contextReroute = false;

Expand All @@ -22,16 +23,23 @@ export const second = async (context, next) => {
if (context.url.pathname.includes('/auth/params')) {
return next('/?foo=bar');
}

if (context.url.pathname.includes('/auth/astro-params')) {
return next('/auth/1234');
}
}
return next();
};

export const third = async (context, next) => {
export const third = defineMiddleware(async (context, next) => {
// just making sure that we are testing the change in context coming from `next()`
if (context.url.pathname.startsWith('/') && contextReroute === false) {
context.locals.auth = 'Third function called';
}
if (context.params?.id === '1234') {
context.locals.auth = 'Params changed'
}
return next();
};
});

export const onRequest = sequence(first, second, third);
23 changes: 23 additions & 0 deletions packages/astro/test/fixtures/reroute/src/pages/auth/[id].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
export function getStaticPaths( ) {
return [{
params: {
id: "1234"
}
}]
}
const { id } = Astro.params;
const auth = Astro.locals.auth;
---
<html>
<head>
<title>Index with params</title>
</head>
<body>
<h1>Index with params</h1>
<p id="params">Param: {id}</p>
<p id="locals">Locals: {auth}</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
---
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
---
const { id } = Astro.params;
const auth = Astro.locals.auth;
---
<html>
<head>
<title>Index with params</title>
</head>
<body>
<h1>Index with params</h1>
<p id="params">Param: {id}</p>
<p id="locals">Locals: {auth}</p>
</body>
</html>
9 changes: 9 additions & 0 deletions packages/astro/test/rewrite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,15 @@ describe('Middleware', () => {

assert.match($('h1').text(), /Index/);
});

it('should render correctly compute the new params next("/auth/1234")', async () => {
const html = await fixture.fetch('/auth/astro-params').then((res) => res.text());
const $ = cheerioLoad(html);

assert.match($('h1').text(), /Index with params/);
assert.match($('#params').text(), /Param: 1234/);
assert.match($('#locals').text(), /Locals: Params changed/);
});
});

describe('Middleware with custom 404.astro and 500.astro', () => {
Expand Down

0 comments on commit 8c0cae6

Please sign in to comment.