|
1 | 1 | import { ApiRequest } from './api-request'
|
2 | 2 | import { ApiResponse } from './api-response'
|
3 | 3 | import { Benchmark, BenchmarkPreview, Result, Submission, SubmissionPreview, Test } from './interfaces'
|
4 |
| -import { Empty, json, ResourceId, StripLocator } from './utility-types' |
| 4 | +import { Empty, json, NoNever, ResourceId, StripLocator } from './utility-types' |
5 | 5 |
|
6 | 6 | /**
|
7 |
| - * Base interface for registered API GET endpoints. |
8 |
| - * @param Query Type of request query. Use `Empty` to indicate absence. |
9 |
| - * @param Response Type of response body. Use `null` or `Empty` to indicate absence. |
| 7 | + * Interface for registered enpoints. Types the request body, request query |
| 8 | + * and response. |
10 | 9 | */
|
11 |
| -export interface ApiGetEndpoint<Query, Response> { |
12 |
| - request: ApiRequest<Empty, Query> |
| 10 | +interface ApiEndpoint<Body = unknown, Query = unknown, Response = unknown> { |
| 11 | + request: ApiRequest<Body, Query> |
13 | 12 | response: ApiResponse<Response>
|
14 | 13 | }
|
15 | 14 |
|
16 | 15 | /**
|
17 |
| - * Base interface for registered API POST endpoints. |
18 |
| - * @param Body Type of request body. Use `Empty` to indicate absence. |
19 |
| - * @param Response Type of response body. Use `null` or `Empty` to indicate absence. |
| 16 | + * Registered endpoints per verb. |
| 17 | + * Pairs of `Verb : ApiEndpoint`. |
| 18 | + * @see {@link ApiEndpoint} |
20 | 19 | */
|
21 |
| -export interface ApiPostEndpoint<Body, Response> { |
22 |
| - request: ApiRequest<Body, Empty> |
23 |
| - response: ApiResponse<Response> |
| 20 | +interface ApiEndpointVerbs { |
| 21 | + GET?: ApiEndpoint |
| 22 | + POST?: ApiEndpoint |
| 23 | + PATCH?: ApiEndpoint |
24 | 24 | }
|
25 | 25 |
|
| 26 | +// There is no way in TypeScript to enforce the types of an interface's |
| 27 | +// properties a priori - instead, give dev freedom to make mistakes and filter |
| 28 | +// those endpoints that actually match the `path: ApiEndpointVerbs` pattern |
| 29 | +// below (see ApiEndpoints below), otherwise any mistake in the definitions |
| 30 | +// would cause hard-to-track-down type errors in controllers. |
26 | 31 | /**
|
27 |
| - * Base interface for registered API PATCH endpoints. |
28 |
| - * @param Body Type of request body. Use `Empty` to indicate absence. |
29 |
| - * @param Response Type of response body. Use `null` or `Empty` to indicate absence. |
| 32 | + * Registered API endpoints. |
| 33 | + * Pairs of `path : ApiEndpointVerbs` with `path` being a string starting with |
| 34 | + * `/`. Use `:<name>` syntax to set up named parameters. |
| 35 | + * @see {@link ApiEndpointVerbs} |
| 36 | + * @see {@link ApiEndpoint} |
30 | 37 | */
|
31 |
| -export interface ApiPatchEndpoint<Body, Response> { |
32 |
| - request: ApiRequest<Body, Empty> |
33 |
| - response: ApiResponse<Response> |
| 38 | +interface ApiEndpointDefinitions { |
| 39 | + '/mirror': { |
| 40 | + GET: ApiEndpoint<Empty, Empty, string> |
| 41 | + POST: ApiEndpoint<{ data: json }, Empty, { data: json }> |
| 42 | + } |
| 43 | + '/mirror/:id': { |
| 44 | + GET: ApiEndpoint<Empty, Empty, string> |
| 45 | + } |
| 46 | + '/dbsetup': { |
| 47 | + GET: ApiEndpoint<Empty, Empty, null> |
| 48 | + } |
| 49 | + '/ampq': { |
| 50 | + GET: ApiEndpoint<Empty, Empty, string> |
| 51 | + POST: ApiEndpoint<json, Empty, string> |
| 52 | + } |
| 53 | + '/whoami': { |
| 54 | + GET: ApiEndpoint<Empty, Empty, json> |
| 55 | + } |
| 56 | + '/benchmarks': { |
| 57 | + GET: ApiEndpoint<Empty, Empty, BenchmarkPreview[]> |
| 58 | + } |
| 59 | + '/benchmarks/:id': { |
| 60 | + GET: ApiEndpoint<Empty, Empty, Benchmark[]> |
| 61 | + } |
| 62 | + '/tests/:id': { |
| 63 | + GET: ApiEndpoint<Empty, Empty, Test[]> |
| 64 | + } |
| 65 | + '/submissions': { |
| 66 | + GET: ApiEndpoint<Empty, { benchmark?: ResourceId; uuid?: string; submitted_by?: string }, SubmissionPreview[]> |
| 67 | + POST: ApiEndpoint<StripLocator<Submission>, Empty, { uuid: string }> |
| 68 | + } |
| 69 | + '/submissions/:uuid': { |
| 70 | + GET: ApiEndpoint<Empty, Empty, Submission[]> |
| 71 | + } |
| 72 | + '/submissions/:uuid/results': { |
| 73 | + GET: ApiEndpoint<Empty, Empty, Result[]> |
| 74 | + } |
| 75 | + '/test': { |
| 76 | + GET: ApiEndpoint<Empty, Empty, Test> |
| 77 | + } |
| 78 | + '/result': { |
| 79 | + PATCH: ApiEndpoint<Partial<Result>, Empty, Result> |
| 80 | + } |
34 | 81 | }
|
35 | 82 |
|
| 83 | +/** |
| 84 | + * Type-saved version of `ApiEndpointDefinitions` with all non-conforming defs |
| 85 | + * removed. |
| 86 | + */ |
| 87 | +export type ApiEndpoints = NoNever<{ |
| 88 | + [E in keyof ApiEndpointDefinitions]: ApiEndpointDefinitions[E] extends ApiEndpointVerbs |
| 89 | + ? ApiEndpointDefinitions[E] |
| 90 | + : never |
| 91 | +}> |
| 92 | + |
| 93 | +/** |
| 94 | + * Registered API endpoints for given method. |
| 95 | + * Extracted from `ApiEndpoints` |
| 96 | + * @see {@link ApiEndpoints} |
| 97 | + */ |
| 98 | +export type ApiEndpointsOfVerb<V extends string> = NoNever<{ |
| 99 | + [EP in keyof ApiEndpoints]: ApiEndpoints[EP] extends Record<V, ApiEndpoint> ? ApiEndpoints[EP][V] : never |
| 100 | +}> |
| 101 | + |
36 | 102 | /**
|
37 | 103 | * Registered API endpoints for GET method.
|
38 |
| - * Pairs of `path : ApiGetEndpoint<Query, Response>` with `path` being a string |
39 |
| - * starting with `/`. Use `:<name>` syntax to set up named parameters. |
40 |
| - * @see {@link ApiGetEndpoint} |
| 104 | + * Extracted from `ApiEndpoints` |
| 105 | + * @see {@link ApiEndpoints} |
41 | 106 | */
|
42 |
| -// list endpoints return locators instead of full resources - dev.001 |
43 |
| -// resource endpoints always return an array of said resource - dev.002 |
44 |
| -export interface ApiGetEndpoints { |
45 |
| - '/mirror': ApiGetEndpoint<Empty, string> |
46 |
| - '/mirror/:id': ApiGetEndpoint<Empty, string> |
47 |
| - '/dbsetup': ApiGetEndpoint<Empty, unknown> |
48 |
| - '/ampq': ApiGetEndpoint<Empty, string> |
49 |
| - '/whoami': ApiGetEndpoint<Empty, json> |
50 |
| - '/benchmarks': ApiGetEndpoint<Empty, BenchmarkPreview[]> |
51 |
| - '/benchmarks/:id': ApiGetEndpoint<Empty, Benchmark[]> |
52 |
| - '/tests/:id': ApiGetEndpoint<Empty, Test[]> |
53 |
| - '/submissions': ApiGetEndpoint<{ benchmark?: ResourceId; uuid?: string; submitted_by?: string }, SubmissionPreview[]> |
54 |
| - '/submissions/:uuid': ApiGetEndpoint<Empty, Submission[]> |
55 |
| - '/submissions/:uuid/results': ApiGetEndpoint<Empty, Result[]> |
56 |
| - '/test': ApiGetEndpoint<Empty, Empty> |
57 |
| -} |
| 107 | +export type ApiGetEndpoints = ApiEndpointsOfVerb<'GET'> |
58 | 108 |
|
59 | 109 | /**
|
60 | 110 | * Registered API endpoints for POST method.
|
61 |
| - * Pairs of `path : ApiPostEndpoint<Body, Response>` with `path` being a string |
62 |
| - * starting with `/`. Use `:<name>` syntax to set up named parameters. |
63 |
| - * @see {@link ApiPostEndpoint} for syntax. |
| 111 | + * Extracted from `ApiEndpoints` |
| 112 | + * @see {@link ApiEndpoints} |
64 | 113 | */
|
65 |
| -export interface ApiPostEndpoints { |
66 |
| - '/mirror': ApiPostEndpoint<{ data: unknown }, unknown> |
67 |
| - '/ampq': ApiPostEndpoint<json, string> |
68 |
| - '/submissions': ApiPostEndpoint<StripLocator<Submission>, { uuid: string }> |
69 |
| -} |
| 114 | +export type ApiPostEndpoints = ApiEndpointsOfVerb<'POST'> |
70 | 115 |
|
71 | 116 | /**
|
72 | 117 | * Registered API endpoints for PATCH method.
|
73 |
| - * Pairs of `path : ApiPatchEndpoint<Body, Response>` with `path` being a string |
74 |
| - * starting with `/`. Use `:<name>` syntax to set up named parameters. |
75 |
| - * @see {@link ApiPatchEndpoints} for syntax. |
| 118 | + * Extracted from `ApiEndpoints` |
| 119 | + * @see {@link ApiEndpoints} |
76 | 120 | */
|
77 |
| -export interface ApiPatchEndpoints { |
78 |
| - '/result': ApiPatchEndpoint<Partial<Result>, Result> |
79 |
| -} |
| 121 | +export type ApiPatchEndpoints = ApiEndpointsOfVerb<'PATCH'> |
0 commit comments