Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new fields to API route context #4986

Merged
merged 19 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions .changeset/fifty-ads-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Add `site`, `generator`, `url`, and `clientAddress` fields to API route context
52 changes: 40 additions & 12 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface BuildConfig {
*
* [Astro reference](https://docs.astro.build/reference/api-reference/#astro-global)
*/
export interface AstroGlobal extends AstroGlobalPartial {
export interface AstroGlobal extends AstroGlobalPartial, AstroSharedContext {
/**
* Canonical URL of the current page.
* @deprecated Use `Astro.url` instead.
Expand All @@ -106,21 +106,13 @@ export interface AstroGlobal extends AstroGlobalPartial {
* ```
*/
canonicalURL: URL;
/** The address (usually IP address) of the user. Used with SSR only.
*
*/
clientAddress: string;
/**
* A full URL object of the request URL.
* Equivalent to: `new URL(Astro.request.url)`
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#url)
*/
/**
* Utility for getting and setting cookies values.
*/
cookies: AstroCookies;
url: URL;
url: AstroSharedContext['url'];
/** Parameters passed to a dynamic page generated using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths)
*
* Example usage:
Expand All @@ -139,7 +131,7 @@ export interface AstroGlobal extends AstroGlobalPartial {
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#params)
*/
params: Params;
params: AstroSharedContext['params'];
/** List of props passed to this component
*
* A common way to get specific props is through [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), ex:
Expand Down Expand Up @@ -1095,10 +1087,46 @@ export interface AstroAdapter {

type Body = string;

export interface APIContext {
// Shared types between `Astro` global and API context object
interface AstroSharedContext {
/**
* The address (usually IP address) of the user. Used with SSR only.
*/
clientAddress: string;
/**
* Utility for getting and setting cookies values.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
cookies: AstroCookies;
/**
* Information about the current request. This is a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
*/
request: Request;
/**
* A full URL object of the request URL.
*/
url: URL;
/**
* Parameters passed to a dynamic page generated.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
params: Params;
}

export interface APIContext extends AstroSharedContext {
site: URL | undefined;
generator: string;
/**
* A full URL object of the request URL.
* Equivalent to: `new URL(request.url)`
*/
url: AstroSharedContext['url'];
}

interface AstroSharedContext {
cookies: AstroCookies;
request: Request;
clientAddress: string;
url: URL;
params: Params;
}

export interface EndpointOutput {
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/endpoint/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export async function call(ssrOpts: SSROptions) {
return await callEndpoint(mod as unknown as EndpointHandler, {
...ssrOpts,
ssr: ssrOpts.settings.config.output === 'server',
site: ssrOpts.settings.config.site,
adapterName: ssrOpts.settings.config.adapter?.name,
});
}
41 changes: 39 additions & 2 deletions packages/astro/src/core/endpoint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { RenderOptions } from '../render/core';
import { renderEndpoint } from '../../runtime/server/index.js';
import { AstroCookies, attachToResponse } from '../cookies/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
import { ASTRO_VERSION } from '../util.js';

const clientAddressSymbol = Symbol.for('astro.clientAddress');

export type EndpointOptions = Pick<
RenderOptions,
Expand All @@ -17,6 +20,7 @@ export type EndpointOptions = Pick<
| 'site'
| 'ssr'
| 'status'
| 'adapterName'
>;

type EndpointCallResult =
Expand All @@ -30,11 +34,39 @@ type EndpointCallResult =
response: Response;
};

function createAPIContext(request: Request, params: Params): APIContext {
function createAPIContext({
request,
params,
site,
adapterName,
}: {
request: Request;
params: Params;
site?: string;
adapterName?: string;
}): APIContext {
return {
cookies: new AstroCookies(request),
request,
params,
site: site ? new URL(site) : undefined,
generator: `Astro v${ASTRO_VERSION}`,
url: new URL(request.url),
get clientAddress() {
if (!(clientAddressSymbol in request)) {
if (adapterName) {
throw new Error(
`Astro.clientAddress is not available in the ${args.adapterName} adapter. File an issue with the adapter to add support.`
);
} else {
throw new Error(
`Astro.clientAddress is not available in your environment. Ensure that you are using an SSR adapter that supports this feature.`
);
}
}

return Reflect.get(request, clientAddressSymbol);
},
};
}

Expand All @@ -51,7 +83,12 @@ export async function call(
}
const [params] = paramsAndPropsResp;

const context = createAPIContext(opts.request, params);
const context = createAPIContext({
request: opts.request,
params,
site: opts.site,
adapterName: opts.adapterName,
});
const response = await renderEndpoint(mod, context, opts.ssr);

if (response instanceof Response) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/render/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function getParamsAndProps(
}

export interface RenderOptions {
adapterName: string | undefined;
adapterName?: string;
logging: LogOptions;
links: Set<SSRElement>;
styles?: Set<SSRElement>;
Expand Down
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/ssr-api-route/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

// https://astro.build/config
export default defineConfig({
output: 'server',
adapter: node(),
});
1 change: 1 addition & 0 deletions packages/astro/test/fixtures/ssr-api-route/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^1.1.0",
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* @param {import('astro').APIContext} api
*/
export function get(ctx) {
return {
body: JSON.stringify({
cookiesExist: !!ctx.cookies,
requestExist: !!ctx.request,
params: ctx.params,
site: ctx.site?.toString(),
generator: ctx.generator,
url: ctx.url.toString(),
clientAddress: ctx.clientAddress,
})
};
}
27 changes: 27 additions & 0 deletions packages/astro/test/ssr-api-route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ describe('API routes in SSR', () => {
expect(body.length).to.equal(3);
});

it('Has valid api context', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/context/any');
const response = await app.render(request);
expect(response.status).to.equal(200);
const data = await response.json();
expect(data.cookiesExist).to.equal(true);
expect(data.requestExist).to.equal(true);
expect(data.params).to.deep.equal({ param: 'any' });
expect(data.generator).to.match(/^Astro v/);
expect(data.url).to.equal('http://example.com/context/any');
expect(data.clientAddress).to.equal('0.0.0.0');
});

describe('API Routes - Dev', () => {
let devServer;
before(async () => {
Expand Down Expand Up @@ -87,3 +101,16 @@ describe('API routes in SSR', () => {
});
});
});

describe('API context in SSR', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;

before(async () => {
fixture = await loadFixture({
root: './fixtures/ssr-api-route/',
output: 'server',
});
await fixture.build();
});
});
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.