Skip to content

Commit 5f51108

Browse files
committed
WIP: types for endpoint(options)
1 parent 17acf10 commit 5f51108

File tree

2 files changed

+139
-65
lines changed

2 files changed

+139
-65
lines changed

src/EndpointInterface.ts

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,75 @@ import { EndpointDefaults } from "./EndpointDefaults";
22
import { RequestOptions } from "./RequestOptions";
33
import { RequestParameters } from "./RequestParameters";
44
import { Route } from "./Route";
5+
import { RequestMethod } from "./RequestMethod";
56

67
import { Endpoints } from "./generated/Endpoints";
78

9+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
10+
k: infer I
11+
) => void
12+
? I
13+
: never;
14+
15+
type EndpointsByUrlAndMethod = UnionToIntersection<
16+
{
17+
[K in keyof Endpoints]: {
18+
[TUrl in Endpoints[K]["request"]["url"]]: {
19+
[TMethod in Endpoints[K]["request"]["method"]]: {
20+
route: {
21+
url: TUrl;
22+
method: TMethod;
23+
};
24+
options: Endpoints[K]["parameters"] & {
25+
url: TUrl;
26+
method: TMethod;
27+
};
28+
request: Endpoints[K]["request"];
29+
};
30+
};
31+
};
32+
}[keyof Endpoints]
33+
>;
34+
35+
type UnknownEndpointParameters = RequestParameters & {
36+
method?: RequestMethod;
37+
url: string;
38+
};
39+
40+
type KnownOrUnknownEndpointParameters<
41+
T extends UnknownEndpointParameters
42+
> = T["url"] extends keyof EndpointsByUrlAndMethod
43+
? T["method"] extends keyof EndpointsByUrlAndMethod[T["url"]]
44+
? EndpointsByUrlAndMethod[T["url"]][T["method"]] extends {
45+
parameters: infer TOpt;
46+
}
47+
? TOpt
48+
: never
49+
: never
50+
: UnknownEndpointParameters;
51+
52+
// https://stackoverflow.com/a/61281317/206879
53+
type KnownOptions<T> = T extends {
54+
[k in keyof T]: {
55+
[k: string]: infer OptionValue;
56+
};
57+
}
58+
? OptionValue
59+
: never;
60+
61+
type KnownEndpoints = KnownOptions<EndpointsByUrlAndMethod>["route"];
62+
863
export interface EndpointInterface<D extends object = object> {
964
/**
1065
* Transforms a GitHub REST API endpoint into generic request options
1166
*
1267
* @param {object} endpoint Must set `url` unless it's set defaults. Plus URL, query or body parameters, as well as `headers`, `mediaType.{format|previews}`, `request`, or `baseUrl`.
1368
*/
14-
<O extends RequestParameters = RequestParameters>(
15-
options: O & { method?: string } & ("url" extends keyof D
16-
? { url?: string }
17-
: { url: string })
18-
): RequestOptions & Pick<D & O, keyof RequestOptions>;
69+
<O extends KnownEndpoints | UnknownEndpointParameters>(
70+
options: O & KnownOrUnknownEndpointParameters<O>
71+
): O extends KnownEndpoints
72+
? EndpointsByUrlAndMethod[O["url"]][O["method"]]["request"]
73+
: RequestOptions;
1974

2075
/**
2176
* Transforms a GitHub REST API endpoint into generic request options

test.ts

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ import { EndpointInterface, RequestMethod, RequestInterface } from "./src";
33

44
const endpoint = null as EndpointInterface;
55

6+
endpoint({
7+
url: "/app/installations/:installation_id",
8+
method: "DELETE",
9+
mediaType: {
10+
previews: ["machine-man"],
11+
},
12+
13+
// installation_id: 123,
14+
});
15+
16+
endpoint({
17+
url: "/app/installations/:installation_id",
18+
method: "DELETE",
19+
// installation_id: 123,
20+
// mediaType: {
21+
// previews: ["machine-man"],
22+
// },
23+
});
24+
625
const fooOptions = { foo: "bar" };
726
const bazOptions = { baz: "daz" };
827
const overrideOptions = { foo: "newbar" };
@@ -22,69 +41,69 @@ assertString(test2.DEFAULTS.baz);
2241
assertString(test3.DEFAULTS.foo);
2342
assertRequestMethod(test4.DEFAULTS.method);
2443

25-
const result4 = test4({ method: "PUT", url: "/funk", headers: { foo: "bar" } });
26-
assertString(result4.headers.foo);
27-
// "url" parameter is required
28-
// endpoint({});
29-
// "url" is optional if set on defaults
30-
assertString(test4({}).url);
31-
32-
const test5 = test4.defaults({
33-
method: "PUT",
34-
url: "/funk",
35-
headers: { foo: "bar" },
36-
});
44+
// const result4 = test4({ method: "PUT", url: "/funk", headers: { foo: "bar" } });
45+
// assertString(result4.headers.foo);
46+
// // "url" parameter is required
47+
// // endpoint({});
48+
// // "url" is optional if set on defaults
49+
// assertString(test4({}).url);
3750

38-
assertRequestMethod(test5({}).method);
39-
assertString(test5({}).url);
40-
assertString(test5({}).headers.foo);
51+
// const test5 = test4.defaults({
52+
// method: "PUT",
53+
// url: "/funk",
54+
// headers: { foo: "bar" },
55+
// });
4156

42-
const createIssueOptions = {
43-
owner: "octocat",
44-
repo: "hello-world",
45-
title: "My new issue!",
46-
headers: {
47-
"x-foo": "bar",
48-
},
49-
};
50-
const result5 = test5("POST /repos/:owner/:repo/issues", createIssueOptions);
51-
const result5merge = test5.merge(
52-
"POST /repos/:owner/:repo/issues",
53-
createIssueOptions
54-
);
55-
const result5merge2 = test5.merge(createIssueOptions);
56-
57-
assertString(result5.headers["x-foo"]);
58-
assertString(result5merge.title);
59-
assertString(result5merge.headers["x-foo"]);
60-
assertString(result5merge2.url);
61-
62-
const staticParseResult = endpoint.parse({
63-
baseUrl: "https://api.github.com",
64-
method: "GET",
65-
url: "/funk",
66-
mediaType: {
67-
format: "",
68-
previews: [],
69-
},
70-
headers: {
71-
"user-agent": "MyApp/1.2.3",
72-
accept: "foo",
73-
"x-foo": "bar",
74-
},
75-
});
57+
// assertRequestMethod(test5({}).method);
58+
// assertString(test5({}).url);
59+
// assertString(test5({}).headers.foo);
60+
61+
// const createIssueOptions = {
62+
// owner: "octocat",
63+
// repo: "hello-world",
64+
// title: "My new issue!",
65+
// headers: {
66+
// "x-foo": "bar",
67+
// },
68+
// };
69+
// const result5 = test5("POST /repos/:owner/:repo/issues", createIssueOptions);
70+
// const result5merge = test5.merge(
71+
// "POST /repos/:owner/:repo/issues",
72+
// createIssueOptions
73+
// );
74+
// const result5merge2 = test5.merge(createIssueOptions);
75+
76+
// assertString(result5.headers["x-foo"]);
77+
// assertString(result5merge.title);
78+
// assertString(result5merge.headers["x-foo"]);
79+
// assertString(result5merge2.url);
80+
81+
// const staticParseResult = endpoint.parse({
82+
// baseUrl: "https://api.github.com",
83+
// method: "GET",
84+
// url: "/funk",
85+
// mediaType: {
86+
// format: "",
87+
// previews: [],
88+
// },
89+
// headers: {
90+
// "user-agent": "MyApp/1.2.3",
91+
// accept: "foo",
92+
// "x-foo": "bar",
93+
// },
94+
// });
7695

77-
assertString(staticParseResult.headers["x-foo"]);
96+
// assertString(staticParseResult.headers["x-foo"]);
7897

79-
const request = null as RequestInterface;
98+
// const request = null as RequestInterface;
8099

81-
const rtest = request.defaults(fooOptions);
82-
const rtest2 = rtest.defaults(bazOptions);
83-
const rtest3 = rtest2.defaults(overrideOptions);
84-
const rtest4 = rtest3.defaults(routeOptions);
100+
// const rtest = request.defaults(fooOptions);
101+
// const rtest2 = rtest.defaults(bazOptions);
102+
// const rtest3 = rtest2.defaults(overrideOptions);
103+
// const rtest4 = rtest3.defaults(routeOptions);
85104

86-
assertString(rtest.endpoint.DEFAULTS.foo);
87-
assertString(rtest2.endpoint.DEFAULTS.foo);
88-
assertString(rtest2.endpoint.DEFAULTS.baz);
89-
assertString(rtest3.endpoint.DEFAULTS.foo);
90-
assertRequestMethod(rtest4.endpoint.DEFAULTS.method);
105+
// assertString(rtest.endpoint.DEFAULTS.foo);
106+
// assertString(rtest2.endpoint.DEFAULTS.foo);
107+
// assertString(rtest2.endpoint.DEFAULTS.baz);
108+
// assertString(rtest3.endpoint.DEFAULTS.foo);
109+
// assertRequestMethod(rtest4.endpoint.DEFAULTS.method);

0 commit comments

Comments
 (0)