Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## [0.12.1] - 2024-11-05

### Changed

- laxed parsing rule for requests with header 'content-type: application/json'
now covers all 3 methods: GET, DELETE, and HEAD
- doc updated in README

## [0.12.0] - 2024-11-05

### Changed
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ deno add @oak/oak @dklab/oak-routing-ctrl
```ts
// main.ts

import { Application } from "@oak/oak/application";
import { Application } from "jsr:@oak/oak/application";
import {
Controller,
ControllerMethodArgs,
Get,
useOakServer,
} from "@dklab/oak-routing-ctrl";
} from "jsr:@dklab/oak-routing-ctrl";

const app = new Application();

Expand Down Expand Up @@ -96,13 +96,13 @@ curl localhost:1993/v1/hello/world # prints: hello, world
<summary>View Example</summary>

```ts
import { Application } from "@oak/oak/application";
import { Application } from "jsr:@oak/oak/application";
import {
Controller,
ControllerMethodArgs,
Post,
useOakServer,
} from "@dklab/oak-routing-ctrl";
} from "jsr:@dklab/oak-routing-ctrl";

@Controller("/v1")
class MyController {
Expand Down Expand Up @@ -133,13 +133,13 @@ curl -H"Content-Type: application/json" localhost:1993/v1/tell/alice -d'{"messag
<summary>View Example</summary>

```ts
import { Application } from "@oak/oak/application";
import { Application } from "jsr:@oak/oak/application";
import {
Controller,
ControllerMethodArgs,
Get,
useOakServer,
} from "@dklab/oak-routing-ctrl";
} from "jsr:@dklab/oak-routing-ctrl";

@Controller("/v1")
class MyController {
Expand Down Expand Up @@ -170,8 +170,8 @@ curl localhost:1993/v1/books/thriller\?page=2
<summary>View Example</summary>

```ts
import { Application } from "@oak/oak/application";
import { Controller, Get, useOakServer } from "@dklab/oak-routing-ctrl";
import { Application } from "jsr:@oak/oak/application";
import { Controller, Get, useOakServer } from "jsr:@dklab/oak-routing-ctrl";

@Controller()
class MyController {
Expand Down
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dklab/oak-routing-ctrl",
"version": "0.12.0",
"version": "0.12.1",
"exports": {
".": "./mod.ts",
"./mod": "./mod.ts"
Expand Down
8 changes: 5 additions & 3 deletions src/ControllerMethodArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ function getEnhancedHandler(
parsedReqBody = await _internal.parseOakReqBody(ctx);
} catch (e) {
if (
ctx.request.method === "GET" &&
["GET", "DELETE", "HEAD"].includes(ctx.request.method) &&
ctx.request.headers.get("Content-Type") === "application/json" &&
(e as Error).message?.includes("Unexpected end of JSON input")
) {
// we ignore this parsing error because the client was sending
// a weird combination of method & content-type header
// a weird combination of method & content-type header, but here to
// the "Japanese engineering mindset":
// https://www.500eboard.co/forums/threads/engineers-japanese-vs-german.14695/
} else {
// for other case, we trigger the error back to userland
// for other scenarios, we trigger the error back to userland
return ctx.throw(
400,
`Unable to parse request body: ${(e as Error).message}`,
Expand Down
63 changes: 40 additions & 23 deletions src/ControllerMethodArgs_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,30 +636,47 @@ Deno.test("getEnhancedHandler with a faulty ctx.request.body", async () => {
spyParseOakRequestBody.restore();
});

Deno.test("getEnhancedHandler with a faulty request method and content-type combination", async () => {
Deno.test("getEnhancedHandler with a non-conventional request method and content-type combination", async (t) => {
const methodsToTest = ["GET", "HEAD", "DELETE"];
const spyParseOakRequestBody = spy(_internal, "parseOakReqBody");
function testHandler() {
return "weird.method.content-type.combination.handled";
}
// deno-lint-ignore ban-types
const enhancedHandler: Function = _internal.getEnhancedHandler(testHandler);
const ctx = createMockContext({
method: "GET",
headers: [["Content-Type", "application/json"]],
});
Object.defineProperty(ctx.request, "body", {
get: () => createMockRequestBody("json", "Unexpected end of JSON input"),
});
const spyCtxThrow = spy();
Object.defineProperty(ctx, "throw", {
value: (errorStatus: unknown, message?: string, props?: unknown) => {
spyCtxThrow(errorStatus, message, props);
},
});
const retVal = await enhancedHandler(ctx);
assertSpyCalls(spyCtxThrow, 0);
assertSpyCalls(spyParseOakRequestBody, 1);
assertEquals(retVal, "weird.method.content-type.combination.handled");
await Promise.all(methodsToTest.map((method) =>
t.step({
name: `testing content-type: application/json and ${method} request`,
fn: async () => {
function testHandler() {
return `method ${method} and Content-Type: application/json handled`;
}
// deno-lint-ignore ban-types
const enhancedHandler: Function = _internal.getEnhancedHandler(
testHandler,
);
const ctx = createMockContext({
method, // GET | HEAD | DELETE
headers: [["Content-Type", "application/json"]],
});
Object.defineProperty(ctx.request, "body", {
get: () =>
createMockRequestBody("json", "Unexpected end of JSON input"),
});
const spyCtxThrow = spy();
Object.defineProperty(ctx, "throw", {
value: (errorStatus: unknown, message?: string, props?: unknown) => {
spyCtxThrow(errorStatus, message, props);
},
});
const retVal = await enhancedHandler(ctx);
assertSpyCalls(spyCtxThrow, 0);
assertEquals(
retVal,
`method ${method} and Content-Type: application/json handled`,
);
},
sanitizeOps: false,
sanitizeResources: false,
sanitizeExit: false,
})
));
assertSpyCalls(spyParseOakRequestBody, methodsToTest.length);
spyParseOakRequestBody.restore();
});

Expand Down