Skip to content

Commit 7c517b2

Browse files
committed
v2.0.0-rc.6 🚀 Refactor client & Rest.rpc
1 parent 62d544f commit 7c517b2

File tree

13 files changed

+824
-812
lines changed

13 files changed

+824
-812
lines changed

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ let getPosts = Rest.route(() => {
4040
})
4141
```
4242

43-
Set an endpoint your fetch calls should use:
43+
In the same file set an endpoint your fetch calls should use:
4444

4545
```rescript
4646
// Contract.res
@@ -98,6 +98,7 @@ let _ = app->Fastify.listen({port: 3000})
9898
- [Table of Contents](#table-of-contents)
9999
- [Install](#install)
100100
- [Route Definition](#route-definition)
101+
- [RPC-like abstraction](#rpc-like-abstraction)
101102
- [Path Parameters](#path-parameters)
102103
- [Query Parameters](#query-parameters)
103104
- [Request Headers](#request-headers)
@@ -106,6 +107,8 @@ let _ = app->Fastify.listen({port: 3000})
106107
- [Responses](#responses)
107108
- [Response Headers](#response-headers)
108109
- [Temporary Redirect](#temporary-redirect)
110+
- [Fetch & Client](#fetch--client)
111+
- [API Fetcher](#api-fetcher)
109112
- [Client-side Integrations](#client-side-integrations)
110113
- [SWR](#swr)
111114
- [Polling](#polling)
@@ -142,6 +145,30 @@ Routes are the main building block of the library and a perfect way to describe
142145

143146
For every route you can describe how the HTTP transport will look like, the `'input` and `'output` types, as well as add additional metadata to use for OpenAPI.
144147

148+
### RPC-like abstraction
149+
150+
Alternatively if you use ReScript Rest both on client and server and you don't care about how the data is transfered, there's a helper built on top of `Rest.route`. Just define input and output schemas and done:
151+
152+
```rescript
153+
let getPosts = Rest.rpc(() => {
154+
input: S.sceham(s => {
155+
"skip": s.matches(S.int),
156+
"take": s.matches(S.int),
157+
"page": s.matches(S.option(S.int)),
158+
}),
159+
output: S.array(postSchema),
160+
})
161+
162+
let result = await Contract.getPosts->Rest.fetch(
163+
{"skip": 0, "take": 10, "page": Some(1)}
164+
)
165+
// ℹ️ It'll do a POST request to http://localhost:3000/getPosts with the `{"skip": 0, "take": 10, "page": 1}` body and application/json Content Type
166+
```
167+
168+
This is a code snipped from the super simple example above. Note how I only changed the route definition, but the fetching call stayed untouched. The same goes for the server implementation - if the input and output types of the route don't change there's no need to rewrite any logic.
169+
170+
> 🧠 The path for the route is either taken from `operationId` or the name of the route variable.
171+
145172
### Path Parameters
146173

147174
You can define path parameters by adding them to the `path` strin with a curly brace `{}` including the parameter name. Then each parameter must be defined in `input` with the `s.param` method.
@@ -429,7 +456,7 @@ let fetch = Rest.fetch(~client, ...)
429456

430457
### API Fetcher
431458

432-
You can override the client fetching logic by passing the `~apiFetcher` param.
459+
You can override the client fetching logic by passing the `~fetcher` param.
433460

434461
## Client-side Integrations
435462

__tests__/Rest_test.res

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ let bigint: S.t<bigint> = S.custom("BigInt", s => {
607607
})
608608

609609
asyncTest("Test query params encoding to path", async t => {
610-
let getHeight = Rest.route(() => {
610+
let routeFn = (): Rest.definition<'i, 'o> => {
611611
path: "/height",
612612
method: Get,
613613
input: s =>
@@ -630,12 +630,11 @@ asyncTest("Test query params encoding to path", async t => {
630630
"trueString": s.query("trueString", S.literal("true")),
631631
"nested": s.query(
632632
"nested",
633-
S.object(
634-
s =>
635-
{
636-
"unit": s.field("unit", S.unit),
637-
"nestedNested": s.nested("nestedNested").field("field", S.string),
638-
},
633+
S.object(s =>
634+
{
635+
"unit": s.field("unit", S.unit),
636+
"nestedNested": s.nested("nestedNested").field("field", S.string),
637+
}
639638
),
640639
),
641640
},
@@ -645,7 +644,9 @@ asyncTest("Test query params encoding to path", async t => {
645644
s.data(S.bool)
646645
},
647646
],
648-
})
647+
}
648+
649+
let getHeight = Rest.route(routeFn)
649650

650651
let input = {
651652
"string": "abc",
@@ -685,7 +686,7 @@ asyncTest("Test query params encoding to path", async t => {
685686
t->Assert.deepEqual(await Rest.fetch(~client, getHeight, input), true)
686687

687688
let getHeight = Rest.route(() => {
688-
...(getHeight->Rest.params).definition,
689+
...routeFn(),
689690
jsonQuery: true,
690691
})
691692

@@ -712,7 +713,7 @@ asyncTest("Test query params encoding to path", async t => {
712713
})
713714

714715
asyncTest("Test query params support by Fastify", async t => {
715-
let getHeight = Rest.route(() => {
716+
let routeFn = (): Rest.definition<'i, 'o> => {
716717
path: "/height",
717718
method: Get,
718719
input: s =>
@@ -749,7 +750,8 @@ asyncTest("Test query params support by Fastify", async t => {
749750
s.data(S.bool)
750751
},
751752
],
752-
})
753+
}
754+
let getHeight = Rest.route(routeFn)
753755

754756
let input = {
755757
"string": "abc",
@@ -792,7 +794,7 @@ asyncTest("Test query params support by Fastify", async t => {
792794
t->Assert.deepEqual(await Rest.fetch(~client, getHeight, input), true)
793795

794796
let getHeight = Rest.route(() => {
795-
...(getHeight->Rest.params).definition,
797+
...routeFn(),
796798
jsonQuery: true,
797799
})
798800

@@ -1211,6 +1213,30 @@ asyncTest("Fails with an invalid response data", async t => {
12111213
)
12121214
})
12131215

1216+
asyncTest("Test Rest.rpc", async t => {
1217+
let client = Rest.client("http://localhost:3000", ~fetcher=async (
1218+
data
1219+
): Rest.ApiFetcher.response => {
1220+
t->Assert.deepEqual(
1221+
data,
1222+
{
1223+
path: "http://localhost:3000/getHeight",
1224+
method: "POST",
1225+
body: Some("false"->Obj.magic),
1226+
headers: Some(Js.Dict.fromArray([("content-type", "application/json"->Obj.magic)])),
1227+
},
1228+
)
1229+
{data: false->Obj.magic, status: 200, headers: Js.Dict.empty()}
1230+
})
1231+
1232+
let getHeight = Rest.rpc(() => {
1233+
input: S.bool,
1234+
output: S.bool,
1235+
})
1236+
1237+
t->Assert.deepEqual(await getHeight->Rest.fetch(~client, false), false)
1238+
})
1239+
12141240
asyncTest("Test POST request with rawBody", async t => {
12151241
let createGame = Rest.route(() => {
12161242
path: "/game",

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rescript-rest",
3-
"version": "2.0.0-rc.5",
3+
"version": "2.0.0-rc.6",
44
"description": "😴 ReScript RPC-like client, contract, and server implementation for a pure REST API",
55
"keywords": [
66
"rest",
@@ -44,7 +44,7 @@
4444
"@dzakh/rescript-ava": "3.1.0",
4545
"@fastify/swagger": "9.2.0",
4646
"@scalar/fastify-api-reference": "1.25.60",
47-
"ava": "6.2.0",
47+
"ava": "5.3.1",
4848
"c8": "10.1.2",
4949
"fastify": "5.1.0",
5050
"swr": "2.3.2",

0 commit comments

Comments
 (0)