Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#124 Adding API and implementation to specify end… #125

Merged
merged 2 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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