Skip to content

Commit

Permalink
feat!: introduce safe method returning Result (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
fraxken authored Jul 8, 2023
1 parent 7faa6c0 commit c6a02cb
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 8 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The package is inspired by lukeed [httpie](https://github.com/lukeed/httpie) (Th
- Able to automatically detect domains and paths to assign the right Agent (use a LRU cache to avoid repetitive computation).
- Allows to use an accurate rate-limiter like `p-ratelimit` with the `limit` option.
- Built-in retry mechanism with **custom policies**.
- Safe error handling with Rust-like [Result](https://github.com/OpenAlly/npm-packages/tree/main/src/result).

Thanks to undici:

Expand Down Expand Up @@ -92,6 +93,23 @@ catch (error) {
}
```

Since v2.0.0 you can also use the `safe` prefix API to get a `Promise<Result<T, E>>`

```ts
import * as httpie from "@myunisoft/httpie";

const response = (await httpie.safePost("https://jsonplaceholder.typicode.com/posts", {
body: {
title: "foo",
body: "bar",
userId: 1
}
}))
.map((response) => response.data)
.mapErr((error) => new Error("a message here!", { cause: error.data }));
.unwrap();
```

> 👀 For more examples of use please look at the root folder **examples**.
## 📜 API
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@openally/result": "^1.2.0",
"content-type": "^1.0.5",
"lru-cache": "^10.0.0",
"statuses": "^2.0.1",
Expand Down
35 changes: 32 additions & 3 deletions src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { URLSearchParams } from "url";

// Import Third-party Dependencies
import * as undici from "undici";
import { Result } from "@openally/result";
import status from "statuses";

// Import Internal Dependencies
Expand All @@ -14,7 +15,14 @@ export type WebDavMethod = "MKCOL" | "COPY" | "MOVE" | "LOCK" | "UNLOCK" | "PROP
export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH" ;
export type InlineCallbackAction = <T>(fn: () => Promise<T>) => Promise<T>;

export interface ReqOptions {
export interface RequestError<E> extends Error {
statusMessage: string;
statusCode: number;
headers: IncomingHttpHeaders;
data: E;
}

export interface RequestOptions {
/** Default: 0 */
maxRedirections?: number;
/** Default: { "user-agent": "httpie" } */
Expand Down Expand Up @@ -46,7 +54,7 @@ export interface RequestResponse<T> {
export async function request<T>(
method: HttpMethod | WebDavMethod,
uri: string | URL,
options: ReqOptions = {}
options: RequestOptions = {}
): Promise<RequestResponse<T>> {
const { maxRedirections = 0 } = options;

Expand Down Expand Up @@ -87,10 +95,31 @@ export async function request<T>(
return RequestResponse;
}

export type RequestCallback = <T>(uri: string | URL, options?: ReqOptions) => Promise<RequestResponse<T>>;
export async function safeRequest<T, E>(
method: HttpMethod | WebDavMethod,
uri: string | URL,
options: RequestOptions = {}
): Promise<Result<RequestResponse<T>, RequestError<E>>> {
return Result.wrapAsync<RequestResponse<T>, RequestError<E>>(
() => request(method, uri, options)
);
}

export type RequestCallback = <T>(
uri: string | URL, options?: RequestOptions
) => Promise<RequestResponse<T>>;
export type SafeRequestCallback = <T, E>(
uri: string | URL, options?: RequestOptions
) => Promise<Result<RequestResponse<T>, RequestError<E>>>;

export const get = request.bind(null, "GET") as RequestCallback;
export const post = request.bind(null, "POST") as RequestCallback;
export const put = request.bind(null, "PUT") as RequestCallback;
export const del = request.bind(null, "DELETE") as RequestCallback;
export const patch = request.bind(null, "PATCH") as RequestCallback;

export const safeGet = safeRequest.bind(null, "GET") as SafeRequestCallback;
export const safePost = safeRequest.bind(null, "POST") as SafeRequestCallback;
export const safePut = safeRequest.bind(null, "PUT") as SafeRequestCallback;
export const safeDel = safeRequest.bind(null, "DELETE") as SafeRequestCallback;
export const safePatch = safeRequest.bind(null, "PATCH") as SafeRequestCallback;
4 changes: 2 additions & 2 deletions src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { Duplex, Writable } from "stream";
import * as undici from "undici";

// Import Internal Dependencies
import { ReqOptions, HttpMethod, WebDavMethod } from "./request";
import { RequestOptions, HttpMethod, WebDavMethod } from "./request";
import { computeURI } from "./agents";
import * as Utils from "./utils";

export type StreamOptions = Omit<ReqOptions, "limit">;
export type StreamOptions = Omit<RequestOptions, "limit">;

export function pipeline(
method: HttpMethod | WebDavMethod,
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as contentType from "content-type";
import { Dispatcher } from "undici";

// Import Internal Dependencies
import { RequestResponse, ReqOptions } from "./request";
import { RequestResponse, RequestOptions } from "./request";

// CONSTANTS
const kDefaultMimeType = "text/plain";
Expand Down Expand Up @@ -69,7 +69,7 @@ export async function parseUndiciResponse<T>(response: Dispatcher.ResponseData):
* - User-agent
* - Authorization
*/
export function createHeaders(options: Partial<Pick<ReqOptions, "headers" | "authorization">>): IncomingHttpHeaders {
export function createHeaders(options: Partial<Pick<RequestOptions, "headers" | "authorization">>): IncomingHttpHeaders {
const headers = Object.assign(options.headers ?? {}, DEFAULT_HEADER);
if (options.authorization) {
headers.Authorization = createAuthorizationHeader(options.authorization);
Expand Down
13 changes: 13 additions & 0 deletions test/__snapshots__/request.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ exports[`http.get should throw a 404 Not Found error because the path is not kno
</body></html>
"
`;
exports[`http.safeGet should throw a 404 Not Found error because the path is not known 1`] = `
"<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.54 (Debian) Server at ws-dev.myunisoft.fr Port 443</address>
</body></html>
"
`;
27 changes: 26 additions & 1 deletion test/request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { FastifyInstance } from "fastify";

// Import Internal Dependencies
import { get, post, put, patch, del } from "../src/index";
import { get, post, put, patch, del, safeGet } from "../src/index";

// Helpers and mock
import { createServer } from "./server/index";
Expand Down Expand Up @@ -161,3 +161,28 @@ describe("http.del", () => {
expect(statusCode).toStrictEqual(200);
});
});

describe("http.safeGet", () => {
it("should GET uptime from local fastify server", async() => {
const result = await safeGet<{ uptime: number }, any>("/local/");

expect(result.ok).toStrictEqual(true);
const { data } = result.unwrap();
expect("uptime" in data).toStrictEqual(true);
expect(typeof data.uptime).toStrictEqual("number");
});

it("should throw a 404 Not Found error because the path is not known", async() => {
const result = await safeGet<string, any>("/windev/hlkezcjcke");
expect(result.err).toStrictEqual(true);

if (result.err) {
const error = result.val;

expect(error.name).toStrictEqual("Error");
expect(error.statusCode).toStrictEqual(404);
expect(error.statusMessage).toStrictEqual("Not Found");
expect(error.data).toMatchSnapshot();
}
});
});

0 comments on commit c6a02cb

Please sign in to comment.