WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
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
42 changes: 42 additions & 0 deletions e2e/react-start/server-functions/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { Route as RedirectTestSsrTargetRouteImport } from './routes/redirect-tes
import { Route as MiddlewareSendServerFnRouteImport } from './routes/middleware/send-serverFn'
import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middleware/request-middleware'
import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router'
import { Route as MethodNotAllowedPostRouteImport } from './routes/method-not-allowed/post'
import { Route as MethodNotAllowedGetRouteImport } from './routes/method-not-allowed/get'
import { Route as CookiesSetRouteImport } from './routes/cookies/set'
import { Route as FormdataRedirectTargetNameRouteImport } from './routes/formdata-redirect/target.$name'

Expand Down Expand Up @@ -170,6 +172,16 @@ const MiddlewareClientMiddlewareRouterRoute =
path: '/middleware/client-middleware-router',
getParentRoute: () => rootRouteImport,
} as any)
const MethodNotAllowedPostRoute = MethodNotAllowedPostRouteImport.update({
id: '/method-not-allowed/post',
path: '/method-not-allowed/post',
getParentRoute: () => rootRouteImport,
} as any)
const MethodNotAllowedGetRoute = MethodNotAllowedGetRouteImport.update({
id: '/method-not-allowed/get',
path: '/method-not-allowed/get',
getParentRoute: () => rootRouteImport,
} as any)
const CookiesSetRoute = CookiesSetRouteImport.update({
id: '/cookies/set',
path: '/cookies/set',
Expand Down Expand Up @@ -198,6 +210,8 @@ export interface FileRoutesByFullPath {
'/status': typeof StatusRoute
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/cookies/set': typeof CookiesSetRoute
'/method-not-allowed/get': typeof MethodNotAllowedGetRoute
'/method-not-allowed/post': typeof MethodNotAllowedPostRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute
'/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute
Expand Down Expand Up @@ -228,6 +242,8 @@ export interface FileRoutesByTo {
'/status': typeof StatusRoute
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/cookies/set': typeof CookiesSetRoute
'/method-not-allowed/get': typeof MethodNotAllowedGetRoute
'/method-not-allowed/post': typeof MethodNotAllowedPostRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute
'/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute
Expand Down Expand Up @@ -259,6 +275,8 @@ export interface FileRoutesById {
'/status': typeof StatusRoute
'/submit-post-formdata': typeof SubmitPostFormdataRoute
'/cookies/set': typeof CookiesSetRoute
'/method-not-allowed/get': typeof MethodNotAllowedGetRoute
'/method-not-allowed/post': typeof MethodNotAllowedPostRoute
'/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute
'/middleware/request-middleware': typeof MiddlewareRequestMiddlewareRoute
'/middleware/send-serverFn': typeof MiddlewareSendServerFnRoute
Expand Down Expand Up @@ -291,6 +309,8 @@ export interface FileRouteTypes {
| '/status'
| '/submit-post-formdata'
| '/cookies/set'
| '/method-not-allowed/get'
| '/method-not-allowed/post'
| '/middleware/client-middleware-router'
| '/middleware/request-middleware'
| '/middleware/send-serverFn'
Expand Down Expand Up @@ -321,6 +341,8 @@ export interface FileRouteTypes {
| '/status'
| '/submit-post-formdata'
| '/cookies/set'
| '/method-not-allowed/get'
| '/method-not-allowed/post'
| '/middleware/client-middleware-router'
| '/middleware/request-middleware'
| '/middleware/send-serverFn'
Expand Down Expand Up @@ -351,6 +373,8 @@ export interface FileRouteTypes {
| '/status'
| '/submit-post-formdata'
| '/cookies/set'
| '/method-not-allowed/get'
| '/method-not-allowed/post'
| '/middleware/client-middleware-router'
| '/middleware/request-middleware'
| '/middleware/send-serverFn'
Expand Down Expand Up @@ -382,6 +406,8 @@ export interface RootRouteChildren {
StatusRoute: typeof StatusRoute
SubmitPostFormdataRoute: typeof SubmitPostFormdataRoute
CookiesSetRoute: typeof CookiesSetRoute
MethodNotAllowedGetRoute: typeof MethodNotAllowedGetRoute
MethodNotAllowedPostRoute: typeof MethodNotAllowedPostRoute
MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute
MiddlewareRequestMiddlewareRoute: typeof MiddlewareRequestMiddlewareRoute
MiddlewareSendServerFnRoute: typeof MiddlewareSendServerFnRoute
Expand Down Expand Up @@ -581,6 +607,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport
parentRoute: typeof rootRouteImport
}
'/method-not-allowed/post': {
id: '/method-not-allowed/post'
path: '/method-not-allowed/post'
fullPath: '/method-not-allowed/post'
preLoaderRoute: typeof MethodNotAllowedPostRouteImport
parentRoute: typeof rootRouteImport
}
'/method-not-allowed/get': {
id: '/method-not-allowed/get'
path: '/method-not-allowed/get'
fullPath: '/method-not-allowed/get'
preLoaderRoute: typeof MethodNotAllowedGetRouteImport
parentRoute: typeof rootRouteImport
}
'/cookies/set': {
id: '/cookies/set'
path: '/cookies/set'
Expand Down Expand Up @@ -614,6 +654,8 @@ const rootRouteChildren: RootRouteChildren = {
StatusRoute: StatusRoute,
SubmitPostFormdataRoute: SubmitPostFormdataRoute,
CookiesSetRoute: CookiesSetRoute,
MethodNotAllowedGetRoute: MethodNotAllowedGetRoute,
MethodNotAllowedPostRoute: MethodNotAllowedPostRoute,
MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute,
MiddlewareRequestMiddlewareRoute: MiddlewareRequestMiddlewareRoute,
MiddlewareSendServerFnRoute: MiddlewareSendServerFnRoute,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { useState } from 'react'

export const Route = createFileRoute('/method-not-allowed/get')({
component: MethodNotAllowedFn,
})

export const getableServerFn = createServerFn({ method: 'GET' }).handler(() => {
return new Response('Hello, World!')
})

const fetchFn = async (method: string) => {
const response = await fetch('/_serverFn/constant_id_2?createServerFn', {
method,
})
return [response.status, await response.text()] as const
}

function MethodNotAllowedFn() {
const [fetchResult, setFetchResult] = useState<
readonly [number, string] | null
>(null)
return (
<div className="flex flex-col gap-2">
<h1>Method Not Allowed GET</h1>

<button
data-testid="get-button"
onClick={() => fetchFn('GET').then(setFetchResult)}
>
Fetch GET
</button>
<button
data-testid="post-button"
onClick={() => fetchFn('POST').then(setFetchResult)}
>
Fetch POST
</button>
<button
data-testid="put-button"
onClick={() => fetchFn('PUT').then(setFetchResult)}
>
Fetch PUT
</button>
<button
data-testid="options-button"
onClick={() => fetchFn('OPTIONS').then(setFetchResult)}
>
Fetch OPTIONS
</button>

<pre data-testid="fetch-result">{JSON.stringify(fetchResult)}</pre>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { useState } from 'react'

export const Route = createFileRoute('/method-not-allowed/post')({
component: MethodNotAllowedFn,
})

export const postableServerFn = createServerFn({ method: 'POST' }).handler(
() => {
return new Response('Hello, World!')
},
)

const fetchFn = async (method: string) => {
const response = await fetch('/_serverFn/constant_id_3?createServerFn', {
method,
})
return [response.status, await response.text()] as const
}

function MethodNotAllowedFn() {
const [fetchResult, setFetchResult] = useState<
readonly [number, string] | null
>(null)
return (
<div className="flex flex-col gap-2">
<h1>Method Not Allowed POST</h1>

<button
data-testid="get-button"
onClick={() => fetchFn('GET').then(setFetchResult)}
>
Fetch GET
</button>
<button
data-testid="post-button"
onClick={() => fetchFn('POST').then(setFetchResult)}
>
Fetch POST
</button>
<button
data-testid="put-button"
onClick={() => fetchFn('PUT').then(setFetchResult)}
>
Fetch PUT
</button>
<button
data-testid="options-button"
onClick={() => fetchFn('OPTIONS').then(setFetchResult)}
>
Fetch OPTIONS
</button>

<pre data-testid="fetch-result">{JSON.stringify(fetchResult)}</pre>
</div>
)
}
62 changes: 62 additions & 0 deletions e2e/react-start/server-functions/tests/server-functions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,65 @@ test('redirect in server function called in query during SSR', async ({
await expect(page.getByTestId('redirect-target-ssr')).toBeVisible()
expect(page.url()).toContain('/redirect-test-ssr/target')
})

test.describe('server function returns 405 when method is not allowed', () => {
test('serverFn defined with GET method', async ({ page }) => {
await page.goto('/method-not-allowed/get')

await page.waitForLoadState('networkidle')

await page.getByTestId('get-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[200,"Hello, World!"]',
)

await page.getByTestId('post-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected GET method. Got POST"]',
)

await page.getByTestId('put-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected GET method. Got PUT"]',
)

await page.getByTestId('options-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected GET method. Got OPTIONS"]',
)
})

test('serverFn defined with POST method', async ({ page }) => {
await page.goto('/method-not-allowed/post')

await page.waitForLoadState('networkidle')

await page.getByTestId('get-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected POST method. Got GET"]',
)

await page.getByTestId('post-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[200,"Hello, World!"]',
)

await page.getByTestId('put-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected POST method. Got PUT"]',
)

await page.getByTestId('options-button').click()
await page.waitForLoadState('networkidle')
await expect(page.getByTestId('fetch-result')).toContainText(
'[405,"expected POST method. Got OPTIONS"]',
)
})
})
2 changes: 2 additions & 0 deletions e2e/react-start/server-functions/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import viteReact from '@vitejs/plugin-react'
const FUNCTIONS_WITH_CONSTANT_ID = [
'src/routes/submit-post-formdata.tsx/greetUser_createServerFn_handler',
'src/routes/formdata-redirect/index.tsx/greetUser_createServerFn_handler',
'src/routes/method-not-allowed/get.tsx/getableServerFn_createServerFn_handler',
'src/routes/method-not-allowed/post.tsx/postableServerFn_createServerFn_handler',
]

export default defineConfig({
Expand Down
11 changes: 10 additions & 1 deletion packages/start-server-core/src/server-functions-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import { getServerFnById } from './getServerFnById'

let regex: RegExp | undefined = undefined

const methodNotAllowed = (expectedMethod: string, actualMethod: string) => {
throw new Response(`expected ${expectedMethod} method. Got ${actualMethod}`, {
status: 405,
headers: {
Allow: expectedMethod,
},
})
}

export const handleServerAction = async ({
request,
context,
Expand Down Expand Up @@ -119,7 +128,7 @@ export const handleServerAction = async ({
}

if (method.toLowerCase() !== 'post') {
throw new Error('expected POST method')
throw methodNotAllowed('POST', method)
}

let jsonPayload
Expand Down
Loading