Skip to content

Commit

Permalink
#124 Adding API and implementation to specify end…
Browse files Browse the repository at this point in the history
…points without decorators.
  • Loading branch information
stazz committed Jan 20, 2024
1 parent 5671bca commit ec52215
Show file tree
Hide file tree
Showing 7 changed files with 447 additions and 83 deletions.
2 changes: 1 addition & 1 deletion endpoint-spec/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ty-ras/endpoint-spec",
"version": "2.0.1",
"version": "2.1.0",
"author": {
"name": "Stanislav Muhametsin",
"email": "346799+stazz@users.noreply.github.com",
Expand Down
18 changes: 18 additions & 0 deletions endpoint-spec/src/__test__/inline.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @file This file contains tests for the endpoint builder using definitions in "./inline.ts" file.
*/

import test from "ava";
import * as epValidation from "./endpoint-validation";
import inlineEndpoints, { SEEN_ARGS } from "./inline";

test("Test that decorator-based builder works on class with instance methods", async (c) => {
c.plan(6);
const { endpoints } = inlineEndpoints;
c.deepEqual(
endpoints.length,
1,
"There must be exactly one endpoint created by application builder.",
);
await epValidation.validateEndpoint(c, endpoints[0], () => SEEN_ARGS);
});
61 changes: 61 additions & 0 deletions endpoint-spec/src/__test__/inline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @file This file contains the inline implementation for TyRAS-powered app, without using decorators.
*/

import type * as spec from "..";
import * as mp from "./missing-parts";
import * as protocol from "./protocol";

/* eslint-disable jsdoc/require-jsdoc */

export const app = mp.newBuilder({});
type StateSpecBase = spec.StateSpecBaseOfAppBuilder<typeof app>;

const withURL = app.url`/something/${mp.urlParameter(
"urlParam",
protocol.urlParam,
)}`({});
const stateSpec = {
userId: false,
} as const satisfies StateSpecBase;

const endpoint = withURL.endpoint<protocol.SomeEndpoint>({})(
{
method: "GET",
responseBody: mp.responseBody(protocol.responseBody),
query: mp.query({
queryParam: {
decoder: protocol.queryParam,
required: false,
},
}),
responseHeaders: mp.responseHeaders({
responseHeader: {
encoder: protocol.resHeader,
required: true,
},
}),
requestBody: app.requestBody(protocol.requestBody),
state: stateSpec,
},
(args) => {
SEEN_ARGS.push(args);
return {
body: "responseBody",
headers: {
responseHeader: "resHeader",
},
} as const;
},
);

export const SEEN_ARGS: Array<
spec.GetMethodArgs<protocol.SomeEndpoint, typeof withURL, typeof stateSpec>
> = [];

export default app.createEndpoints(
{},
{
"/api": endpoint,
},
);
32 changes: 20 additions & 12 deletions endpoint-spec/src/api.types/app.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,7 @@ export interface ApplicationBuilderGeneric<
* @param args The non-string portions of the template literal.
* @returns A new {@link url.ApplicationEndpointsForURLFactory} to be used to decorate class methods using ES decorators.
*/
url: <
TArgs extends Array<dataBE.URLParameterInfo<string, any, TValidatorHKT>>,
>(
url: <TArgs extends TURLTemplateLiteralArgsBase<TValidatorHKT>>(
this: void,
fragments: TemplateStringsArray,
...args: TArgs
Expand All @@ -64,7 +62,7 @@ export interface ApplicationBuilderGeneric<
TDefaultRequestBodyContentType,
TDefaultResponseBodyContentType,
TEndpointSpecAdditionalDataHKT,
TArgs extends [] ? undefined : URLParameterReducer<TArgs>
GetURLData<TValidatorHKT, TArgs>
>;

/**
Expand Down Expand Up @@ -160,6 +158,23 @@ export interface ApplicationBuilderGeneric<
>;
}

/**
* This is the base type for he arguments given to template literal function.
* @see ApplicationBuilderGeneric.url
*/
export type TURLTemplateLiteralArgsBase<
TValidatorHKT extends data.ValidatorHKTBase,
> = Array<dataBE.URLParameterInfo<string, any, TValidatorHKT>>;

/**
* Helper type to extract the shape of the URL parameters from the arguments given to template literal function.
* @see ApplicationBuilderGeneric.url
*/
export type GetURLData<
TValidatorHKT extends data.ValidatorHKTBase,
TArgs extends TURLTemplateLiteralArgsBase<TValidatorHKT>,
> = TArgs extends [] ? undefined : URLParameterReducer<TArgs>;

/**
* Helper type to extract final type of URL parameters, given an array of {@link URLParameterInfo} objects.
* Modified from [StackOverflow](https://stackoverflow.com/questions/69085499/typescript-convert-tuple-type-to-object).
Expand Down Expand Up @@ -267,14 +282,7 @@ export type EndpointCreationArg =
* @see EndpointCreationArgLeafSingle
*/
export type EndpointCreationArgLeaf =
data.OneOrMany<EndpointCreationArgLeafSingle>;

/**
* This is single atom of {@link EndpointCreationArgLeaf} type.
* It represents either class, or objects created from classes using `new` operator.
* Since TypeScript does not allow easily to describe such nuances, it is for now just `object` type, to avoid at least the most obvious compilation errors.
*/
export type EndpointCreationArgLeafSingle = object;
data.OneOrMany<common.EndpointCreationArgLeafSingle>;

/**
* This type is part of {@link EndpointCreationArg}.
Expand Down
7 changes: 7 additions & 0 deletions endpoint-spec/src/api.types/common.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,10 @@ export type MaterializeEndpointSpecAdditionalData<
readonly _argStateSpec: TStateSpec;
})["_getAdditionalEndpointSpecData"]
: never;

/**
* This is single atom of {@link EndpointCreationArgLeaf} type.
* It represents either class, or objects created from classes using `new` operator.
* Since TypeScript does not allow easily to describe such nuances, it is for now just `object` type, to avoid at least the most obvious compilation errors.
*/
export type EndpointCreationArgLeafSingle = object;
115 changes: 106 additions & 9 deletions endpoint-spec/src/api.types/url.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,14 @@ export interface ApplicationEndpointsForURL<
TRequestBodyContentType extends TAllRequestBodyContentTypes = TDefaultRequestBodyContentType,
>(
this: void,
mdArgs: {
[P in keyof TMetadataProviders]: md.MaterializeParameterWhenSpecifyingEndpoint<
TMetadataProviders[P],
TProtoEncodedHKT,
TProtocolSpec,
TRequestBodyContentType,
TResponseBodyContentType
>;
},
mdArgs: GetEndpointMetadataArgs<
TProtoEncodedHKT,
TMetadataProviders,
TURLData,
TProtocolSpec,
TRequestBodyContentType,
TResponseBodyContentType
>,
): ClassMethodDecoratorFactory<
TProtoEncodedHKT,
TValidatorHKT,
Expand All @@ -112,8 +111,106 @@ export interface ApplicationEndpointsForURL<
TEndpointSpecAdditionalData,
TProtocolSpec
>;

/**
* Allows endpoint to be added without using decorators.
* Useful when the toolchain does not support decorator functionality, and/or additional scope provided by the classes (e.g. with properties) is not required.
*
* The returned {@link AddEndpointAsInline} will need further invocation to provide actual implementation.
* This is in order to avoid specifying generic parameter twice.
* @param this The `this` parameter is `void` to prevent using "this" in implementations.
* @param mdArgs Parameters for each of the metadata providers. Each parameter is related to this specific BE endpoint.
* @returns The {@link AddEndpointAsInline} to continue adding the endpoint.
*/
endpoint: <
TProtocolSpec extends GetProtocolBaseForURLData<TURLData>,
TResponseBodyContentType extends TAllResponseBodyContentTypes = TDefaultResponseBodyContentType,
TRequestBodyContentType extends TAllRequestBodyContentTypes = TDefaultRequestBodyContentType,
>(
this: void,
mdArgs: GetEndpointMetadataArgs<
TProtoEncodedHKT,
TMetadataProviders,
TURLData,
TProtocolSpec,
TRequestBodyContentType,
TResponseBodyContentType
>,
) => AddEndpointAsInline<
TProtoEncodedHKT,
TValidatorHKT,
TStateHKT,
TServerContext,
TRequestBodyContentType,
TResponseBodyContentType,
TEndpointSpecAdditionalData,
TProtocolSpec
>;
}

/**
* Helper type to define metadata arguments for one specific endpoint (url pattern + method combination).
*/
export type GetEndpointMetadataArgs<
TProtoEncodedHKT extends protocol.EncodedHKTBase,
TMetadataProviders extends common.TMetadataProvidersBase,
TURLData,
TProtocolSpec extends GetProtocolBaseForURLData<TURLData>,
TRequestBodyContentType extends string,
TResponseBodyContentType extends string,
> = {
[P in keyof TMetadataProviders]: md.MaterializeParameterWhenSpecifyingEndpoint<
TMetadataProviders[P],
TProtoEncodedHKT,
TProtocolSpec,
TRequestBodyContentType,
TResponseBodyContentType
>;
};

/**
* This type exposes functionality to add endpoint implementation without decorators.
*/
export interface AddEndpointAsInline<
TProtoEncodedHKT extends protocol.EncodedHKTBase,
TValidatorHKT extends data.ValidatorHKTBase,
TStateHKT extends dataBE.StateHKTBase,
TServerContext,
TRequestBodyContentType extends string,
TResponseBodyContentType extends string,
TEndpointSpecAdditionalData extends common.EndpointSpecAdditionalDataHKTBase,
TProtocolSpec extends protocol.ProtocolSpecCore<protocol.HttpMethod, unknown>,
> {
/**
* Registers the endpoint to the application.
* @param this The `this` parameter is `void` to prevent using "this" in implementations.
* @param spec The endpoint specification.
* @param implementation The endpoint implementation.
* @returns Nothing.
*/
<TStateSpec extends dataBE.MaterializeStateSpecBase<TStateHKT>>(
this: void,
spec: GetEndpointSpec<
TProtoEncodedHKT,
TValidatorHKT,
TRequestBodyContentType,
TResponseBodyContentType,
TEndpointSpecAdditionalData,
TProtocolSpec,
TStateSpec
>,
implementation: MethodForEndpoint<
GetMethodArgsGeneric<
TStateHKT,
TServerContext,
TProtocolSpec,
TStateSpec
>,
void,
GetMethodReturnType<TProtocolSpec>
>,
): common.EndpointCreationArgLeafSingle;
}
/**
* This is function interface to create the decorator for class methods acting as BE endpoints.
*
Expand Down
Loading

0 comments on commit ec52215

Please sign in to comment.