Skip to content

Commit

Permalink
feat: add formFile
Browse files Browse the repository at this point in the history
  • Loading branch information
aaydin-tr committed Aug 8, 2024
1 parent 95d2a84 commit 76d4ada
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 16 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,18 @@ ikari provides a `Context` object to access ikari context methods and properties
```
</details>

- `ctx.formFile()` Returns the file of the specified form field.
<details>
<summary>Example</summary>

```typescript
@Post("/users")
async create(ctx: Context) {
// curl -X POST -F "file=@/path/to/file" http://localhost:3000/users
const file = await ctx.formFile("file");
}
```

- `ctx.cookie()` Returns the value of the specified cookie.
<details>
<summary>Example</summary>
Expand Down
44 changes: 33 additions & 11 deletions __test__/ikari.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect, test, describe } from "bun:test";
import { expect, test, describe, afterAll } from "bun:test";
import {
HTTPMethod,
NotFound,
Expand Down Expand Up @@ -111,7 +111,6 @@ describe("ServeValidator", () => {
new ServeValidator({}).validate();
}).toThrow("Either groups or controllers must be provided");
});

});

test("Controller Decorator", () => {
Expand Down Expand Up @@ -678,13 +677,8 @@ test("Context", async () => {

@Post("/post-body-urlencoded")
public async postBodyUrlencoded(ctx: Context) {
const urlencoded = (await ctx.body()) as URLSearchParams;
const fields: Record<string, string> = {};
for (const [key, value] of urlencoded) {
fields[key] = value.toString();
}

return ctx.json(fields);
const urlencoded = await ctx.body<Record<string, string>>();
return ctx.json(urlencoded);
}

@Post("/post-body-raw")
Expand All @@ -698,6 +692,12 @@ test("Context", async () => {
const buffer = Buffer.from(stream);
return ctx.json({ test: buffer.toString() });
}

@Post("/post-from-file")
public async postFromFile(ctx: Context) {
const file = await ctx.formFile("file");
return ctx.json({ name: file?.name });
}
}

const config: Config = {
Expand Down Expand Up @@ -991,6 +991,14 @@ test("Context", async () => {
body: { test: "test raw body" },
streamBody: "test raw body",
},
{
path: "/post-from-file",
method: HTTPMethod.POST,
bodyType: "json",
statusCode: StatusCode.OK,
body: { name: "test.txt" },
fileName: "test.txt",
},
];

for (const expected of expectedValues) {
Expand All @@ -1016,9 +1024,21 @@ test("Context", async () => {
await Bun.write(filePath, expected.streamBody);
const f = Bun.file(filePath);
streamBody = f.stream();
setTimeout(() => {
afterAll(() => {
unlinkSync(filePath);
}, 0);
});
}

let formFile: FormData | undefined;
if (expected.fileName) {
formFile = new FormData();
const filePath = expected.fileName;
await Bun.write(filePath, "test");
const f = Bun.file(filePath);
formFile.append("file", f);
afterAll(() => {
unlinkSync(filePath);
});
}

let reqBody;
Expand All @@ -1030,6 +1050,8 @@ test("Context", async () => {
reqBody = formData;
} else if (expected.streamBody) {
reqBody = streamBody;
} else if (expected.fileName) {
reqBody = formFile;
}

const res = await fetch(serve.hostname + ":" + serve.port + expected.path, {
Expand Down
40 changes: 35 additions & 5 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class Context {
private _locals: Local | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _body: any | FormData | string | null = null;
private _formFiles: Map<string, File> | null = null;
private _cookies: Map<string, string> | null = null;

constructor(
Expand All @@ -17,7 +18,7 @@ export class Context {
* The all path parameters of the request.
*/
public params: Record<string, string>,
private _url: URL,
private _url: string,
private routes: Routes,
public res: Response = new Response()
) {}
Expand Down Expand Up @@ -204,7 +205,10 @@ export class Context {
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async body<T = FormData | string | ArrayBuffer>(): Promise<T | null> {
public async body<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
T = FormData | string | ArrayBuffer | Record<string, any>
>(): Promise<T | null> {
if (this.method === HTTPMethod.GET || this.method === HTTPMethod.HEAD)
return null;
if (this.req.bodyUsed) return this._body;
Expand All @@ -216,10 +220,18 @@ export class Context {
this._body = this.req.json();
break;
case "application/x-www-form-urlencoded":
this._body = this.req.formData();
this._body = parse(await this.req.text());
break;
case "multipart/form-data":
this._body = this.req.formData();
this._body = await this.req.formData();
for (const [key, value] of this._body.entries()) {
if (value instanceof File) {
if (!this._formFiles) {
this._formFiles = new Map<string, File>();
}
this._formFiles.set(key, value);
}
}
break;
case "text/plain":
this._body = this.req.text();
Expand All @@ -235,6 +247,24 @@ export class Context {
return this._body;
}

/**
* Returns the specified form file from the request.
* @example
* ```ts
* const file = await ctx.formFile("file");
* console.log(file);
* ```
* @param name
* @returns File | null
* */
public async formFile(name: string): Promise<File | null> {
if (!this._formFiles) {
await this.body();
}

return this._formFiles?.get(name) || null;
}

/**
* Redirects to the specified URL with the specified status code. If the status code is not specified it will default to 302.
*
Expand Down Expand Up @@ -379,7 +409,7 @@ export class Context {
* Return the URL string of the request.
*/
public get url(): URL {
return this._url;
return new URL(this._url);
}

/**
Expand Down

0 comments on commit 76d4ada

Please sign in to comment.