Skip to content

Commit

Permalink
feat: add support for chained route level middleware (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
davemooreuws authored Feb 15, 2023
2 parents b2a1954 + cdb03e4 commit ecc1843
Showing 1 changed file with 120 additions and 49 deletions.
169 changes: 120 additions & 49 deletions src/resources/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,28 @@ import { fromGrpcError } from '../api/errors';
import resourceClient from './client';
import { HttpMethod } from '../types';
import { make, newer, Resource as Base } from './common';
import path from "path"
import path from 'path';

export class ApiWorkerOptions{
export class ApiWorkerOptions {
public readonly api: string;
public readonly route: string;
public readonly methods: HttpMethod[];
public readonly opts: MethodOptions<string>;

constructor(api: string, route: string, methods: HttpMethod[], opts: MethodOptions<string> = {}) {
constructor(
api: string,
route: string,
methods: HttpMethod[],
opts: MethodOptions<string> = {}
) {
this.api = api;
this.route = route;
this.methods = methods;
this.opts = opts;
}
}

interface MethodOptions<SecurityDefs extends string> {
interface MethodOptions<SecurityDefs extends string> {
/**
* Optional security definitions for this method
*/
Expand All @@ -65,7 +70,9 @@ class Method<SecurityDefs extends string> {
) {
this.route = route;
this.methods = methods;
this.faas = new Faas(new ApiWorkerOptions(route.api.name, route.path, methods, opts));
this.faas = new Faas(
new ApiWorkerOptions(route.api.name, route.path, methods, opts)
);
this.faas.http(...middleware);
}

Expand Down Expand Up @@ -111,42 +118,60 @@ class Route<SecurityDefs extends string> {
/**
* Register a handler function for GET requests to this route
*/
async get(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async get(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['GET'], opts, ...middleware);
}

/**
* Register a handler function for POST requests to this route
*/
async post(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async post(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['POST'], opts, ...middleware);
}

/**
* Register a handler function for PUT requests to this route
*/
async put(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async put(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['PUT'], opts, ...middleware);
}

/**
* Register a handler function for PATCH requests to this route
*/
async patch(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async patch(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['PATCH'], opts, ...middleware);
}

/**
* Register a handler function for DELETE requests to this route
*/
async delete(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async delete(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['DELETE'], opts, ...middleware);
}

/**
* Register a handler function for OPTIONS requests to this route
*/
async options(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async options(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(['OPTIONS'], opts, ...middleware);
}

Expand All @@ -155,7 +180,10 @@ class Route<SecurityDefs extends string> {
*
* Most useful when routing isn't important or you're doing you own internal routing.
*/
async all(opts: MethodOptions<SecurityDefs>, ...middleware: HttpMiddleware[]): Promise<void> {
async all(
opts: MethodOptions<SecurityDefs>,
...middleware: HttpMiddleware[]
): Promise<void> {
return this.method(
['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'OPTIONS'],
opts,
Expand All @@ -165,10 +193,10 @@ class Route<SecurityDefs extends string> {
}

interface BaseSecurityDefinition<T extends string> {
kind: T
kind: T;
}

interface JwtSecurityDefinition extends BaseSecurityDefinition<"jwt"> {
interface JwtSecurityDefinition extends BaseSecurityDefinition<'jwt'> {
issuer: string;
audiences: string[];
}
Expand Down Expand Up @@ -214,12 +242,20 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
public readonly path: string;
public readonly middleware?: HttpMiddleware[];
private readonly routes: Route<SecurityDefs>[];
private readonly securityDefinitions?: Record<SecurityDefs, SecurityDefinition>;
private readonly securityDefinitions?: Record<
SecurityDefs,
SecurityDefinition
>;
private readonly security?: Record<SecurityDefs, string[]>;

constructor(name: string, options: ApiOpts<SecurityDefs> = {}) {
super(name);
const { middleware, path = '/', securityDefinitions = null, security = {} as Record<SecurityDefs, string[]> } = options;
const {
middleware,
path = '/',
securityDefinitions = null,
security = {} as Record<SecurityDefs, string[]>,
} = options;
// prepend / to path if its not there
this.path = path.replace(/^\/?/, '/');
this.middleware = Array.isArray(middleware) ? middleware : [middleware];
Expand Down Expand Up @@ -267,9 +303,14 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to GET
* @returns {Promise} that resolves when the handler terminates.
*/
async get(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async get(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.get(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.get(opts, ...m);
}

/**
Expand All @@ -278,9 +319,14 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to POST
* @returns {Promise} that resolves when the handler terminates.
*/
async post(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async post(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.post(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.post(opts, ...m);
}

/**
Expand All @@ -289,9 +335,14 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to PUT
* @returns {Promise} that resolves when the handler terminates.
*/
async put(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async put(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.put(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.put(opts, ...m);
}

/**
Expand All @@ -300,9 +351,14 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to PATCH
* @returns {Promise} that resolves when the handler terminates.
*/
async patch(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async patch(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.patch(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.patch(opts, ...m);
}

/**
Expand All @@ -311,9 +367,14 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to DELETE
* @returns {Promise} that resolves when the handler terminates.
*/
async delete(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async delete(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.delete(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.delete(opts, ...m);
}

/**
Expand All @@ -322,17 +383,24 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param middleware the middleware/handler to register for calls to DELETE
* @returns {Promise} that resolves when the handler terminates.
*/
async options(match: string, middleware: HttpMiddleware, opts?: MethodOptions<SecurityDefs>): Promise<void> {
async options(
match: string,
middleware: HttpMiddleware | HttpMiddleware[],
opts?: MethodOptions<SecurityDefs>
): Promise<void> {
const r = this.route(match);
return r.options(opts, middleware);
const m = Array.isArray(middleware) ? middleware : [middleware];
return r.options(opts, ...m);
}

/**
* Retrieves the Invocation URL of this API at runtime
* @returns {Promise} that contains the url of this API
*/
async url(): Promise<string> {
const { details: { url } } = await this.details();
const {
details: { url },
} = await this.details();

return url;
}
Expand All @@ -344,51 +412,51 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
protected unwrapDetails(resp: ResourceDetailsResponse): ApiDetails {
if (resp.hasApi()) {
return {
url: resp.getApi().getUrl()
}
url: resp.getApi().getUrl(),
};
}

throw new Error("Unexpected details in response. Expected API details")
throw new Error('Unexpected details in response. Expected API details');
}

/**
* Register this api as a required resource for the calling function/container
* @returns a promise that resolves when the registration is complete
*/
protected async register(): Promise<Resource> {
protected async register(): Promise<Resource> {
const req = new ResourceDeclareRequest();
const resource = new Resource();
const apiResource = new ApiResource();
const { security, securityDefinitions } = this;

if (security) {
Object.keys(security).forEach(k => {
Object.keys(security).forEach((k) => {
const scopes = new ApiScopes();
scopes.setScopesList(security[k]);
apiResource.getSecurityMap().set(k, scopes);
})
});
}

resource.setName(this.name);
resource.setType(ResourceType.API);

if (securityDefinitions) {
Object.keys(securityDefinitions).forEach(k => {
Object.keys(securityDefinitions).forEach((k) => {
const def = securityDefinitions[k] as SecurityDefinition;
const definition = new ApiSecurityDefinition();
if (def.kind === "jwt") {
const definition = new ApiSecurityDefinition();

if (def.kind === 'jwt') {
// Set it to a JWT definition
const secDef = new ApiSecurityDefinitionJwt();
secDef.setIssuer(def.issuer);
secDef.setAudiencesList(def.audiences);
definition.setJwt(secDef);
}

apiResource.getSecurityDefinitionsMap().set(k, definition);
});
}

req.setApi(apiResource);
req.setResource(resource);

Expand All @@ -407,8 +475,6 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
}
}



/**
* Register a new API Resource.
*
Expand All @@ -419,12 +485,17 @@ class Api<SecurityDefs extends string> extends Base<ApiDetails> {
* @param options
* @returns
*/
export const api:<Defs extends string>(name: string, options?: ApiOpts<Defs>) => Api<Defs> = make(Api);
export const api: <Defs extends string>(
name: string,
options?: ApiOpts<Defs>
) => Api<Defs> = make(Api);

/**
* Create a jwt security definition
* @returns
* @returns
*/
export const jwt = (opts: Omit<JwtSecurityDefinition, "kind">): JwtSecurityDefinition => {
return { kind: "jwt", issuer: opts.issuer, audiences: opts.audiences };
}
export const jwt = (
opts: Omit<JwtSecurityDefinition, 'kind'>
): JwtSecurityDefinition => {
return { kind: 'jwt', issuer: opts.issuer, audiences: opts.audiences };
};

0 comments on commit ecc1843

Please sign in to comment.