Skip to content

Commit

Permalink
WIP: types for endpoint(options)
Browse files Browse the repository at this point in the history
  • Loading branch information
gr2m committed Apr 19, 2020
1 parent 17acf10 commit 5f51108
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 65 deletions.
65 changes: 60 additions & 5 deletions src/EndpointInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,75 @@ import { EndpointDefaults } from "./EndpointDefaults";
import { RequestOptions } from "./RequestOptions";
import { RequestParameters } from "./RequestParameters";
import { Route } from "./Route";
import { RequestMethod } from "./RequestMethod";

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

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;

type EndpointsByUrlAndMethod = UnionToIntersection<
{
[K in keyof Endpoints]: {
[TUrl in Endpoints[K]["request"]["url"]]: {
[TMethod in Endpoints[K]["request"]["method"]]: {
route: {
url: TUrl;
method: TMethod;
};
options: Endpoints[K]["parameters"] & {
url: TUrl;
method: TMethod;
};
request: Endpoints[K]["request"];
};
};
};
}[keyof Endpoints]
>;

type UnknownEndpointParameters = RequestParameters & {
method?: RequestMethod;
url: string;
};

type KnownOrUnknownEndpointParameters<
T extends UnknownEndpointParameters
> = T["url"] extends keyof EndpointsByUrlAndMethod
? T["method"] extends keyof EndpointsByUrlAndMethod[T["url"]]
? EndpointsByUrlAndMethod[T["url"]][T["method"]] extends {
parameters: infer TOpt;
}
? TOpt
: never
: never
: UnknownEndpointParameters;

// https://stackoverflow.com/a/61281317/206879
type KnownOptions<T> = T extends {
[k in keyof T]: {
[k: string]: infer OptionValue;
};
}
? OptionValue
: never;

type KnownEndpoints = KnownOptions<EndpointsByUrlAndMethod>["route"];

export interface EndpointInterface<D extends object = object> {
/**
* Transforms a GitHub REST API endpoint into generic request options
*
* @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`.
*/
<O extends RequestParameters = RequestParameters>(
options: O & { method?: string } & ("url" extends keyof D
? { url?: string }
: { url: string })
): RequestOptions & Pick<D & O, keyof RequestOptions>;
<O extends KnownEndpoints | UnknownEndpointParameters>(
options: O & KnownOrUnknownEndpointParameters<O>
): O extends KnownEndpoints
? EndpointsByUrlAndMethod[O["url"]][O["method"]]["request"]
: RequestOptions;

/**
* Transforms a GitHub REST API endpoint into generic request options
Expand Down
139 changes: 79 additions & 60 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ import { EndpointInterface, RequestMethod, RequestInterface } from "./src";

const endpoint = null as EndpointInterface;

endpoint({
url: "/app/installations/:installation_id",
method: "DELETE",
mediaType: {
previews: ["machine-man"],
},

// installation_id: 123,
});

endpoint({
url: "/app/installations/:installation_id",
method: "DELETE",
// installation_id: 123,
// mediaType: {
// previews: ["machine-man"],
// },
});

const fooOptions = { foo: "bar" };
const bazOptions = { baz: "daz" };
const overrideOptions = { foo: "newbar" };
Expand All @@ -22,69 +41,69 @@ assertString(test2.DEFAULTS.baz);
assertString(test3.DEFAULTS.foo);
assertRequestMethod(test4.DEFAULTS.method);

const result4 = test4({ method: "PUT", url: "/funk", headers: { foo: "bar" } });
assertString(result4.headers.foo);
// "url" parameter is required
// endpoint({});
// "url" is optional if set on defaults
assertString(test4({}).url);

const test5 = test4.defaults({
method: "PUT",
url: "/funk",
headers: { foo: "bar" },
});
// const result4 = test4({ method: "PUT", url: "/funk", headers: { foo: "bar" } });
// assertString(result4.headers.foo);
// // "url" parameter is required
// // endpoint({});
// // "url" is optional if set on defaults
// assertString(test4({}).url);

assertRequestMethod(test5({}).method);
assertString(test5({}).url);
assertString(test5({}).headers.foo);
// const test5 = test4.defaults({
// method: "PUT",
// url: "/funk",
// headers: { foo: "bar" },
// });

const createIssueOptions = {
owner: "octocat",
repo: "hello-world",
title: "My new issue!",
headers: {
"x-foo": "bar",
},
};
const result5 = test5("POST /repos/:owner/:repo/issues", createIssueOptions);
const result5merge = test5.merge(
"POST /repos/:owner/:repo/issues",
createIssueOptions
);
const result5merge2 = test5.merge(createIssueOptions);

assertString(result5.headers["x-foo"]);
assertString(result5merge.title);
assertString(result5merge.headers["x-foo"]);
assertString(result5merge2.url);

const staticParseResult = endpoint.parse({
baseUrl: "https://api.github.com",
method: "GET",
url: "/funk",
mediaType: {
format: "",
previews: [],
},
headers: {
"user-agent": "MyApp/1.2.3",
accept: "foo",
"x-foo": "bar",
},
});
// assertRequestMethod(test5({}).method);
// assertString(test5({}).url);
// assertString(test5({}).headers.foo);

// const createIssueOptions = {
// owner: "octocat",
// repo: "hello-world",
// title: "My new issue!",
// headers: {
// "x-foo": "bar",
// },
// };
// const result5 = test5("POST /repos/:owner/:repo/issues", createIssueOptions);
// const result5merge = test5.merge(
// "POST /repos/:owner/:repo/issues",
// createIssueOptions
// );
// const result5merge2 = test5.merge(createIssueOptions);

// assertString(result5.headers["x-foo"]);
// assertString(result5merge.title);
// assertString(result5merge.headers["x-foo"]);
// assertString(result5merge2.url);

// const staticParseResult = endpoint.parse({
// baseUrl: "https://api.github.com",
// method: "GET",
// url: "/funk",
// mediaType: {
// format: "",
// previews: [],
// },
// headers: {
// "user-agent": "MyApp/1.2.3",
// accept: "foo",
// "x-foo": "bar",
// },
// });

assertString(staticParseResult.headers["x-foo"]);
// assertString(staticParseResult.headers["x-foo"]);

const request = null as RequestInterface;
// const request = null as RequestInterface;

const rtest = request.defaults(fooOptions);
const rtest2 = rtest.defaults(bazOptions);
const rtest3 = rtest2.defaults(overrideOptions);
const rtest4 = rtest3.defaults(routeOptions);
// const rtest = request.defaults(fooOptions);
// const rtest2 = rtest.defaults(bazOptions);
// const rtest3 = rtest2.defaults(overrideOptions);
// const rtest4 = rtest3.defaults(routeOptions);

assertString(rtest.endpoint.DEFAULTS.foo);
assertString(rtest2.endpoint.DEFAULTS.foo);
assertString(rtest2.endpoint.DEFAULTS.baz);
assertString(rtest3.endpoint.DEFAULTS.foo);
assertRequestMethod(rtest4.endpoint.DEFAULTS.method);
// assertString(rtest.endpoint.DEFAULTS.foo);
// assertString(rtest2.endpoint.DEFAULTS.foo);
// assertString(rtest2.endpoint.DEFAULTS.baz);
// assertString(rtest3.endpoint.DEFAULTS.foo);
// assertRequestMethod(rtest4.endpoint.DEFAULTS.method);

0 comments on commit 5f51108

Please sign in to comment.