File

projects/rest/src/lib/request-builder.ts

Index

Properties

Properties

headers
headers: HttpHeaders
Type : HttpHeaders
params
params: HttpParams
Type : HttpParams
withCredentials
withCredentials: boolean
Type : boolean
import { ClientInstance, HTTP_CLIENT, BASE_URL, GUARDS, ClientConstructor,
          CLIENT_GUARDS, BODIES, INJECTOR, HANDLERS, CLIENT_HANDLERS, HandlersOf,
          ERROR_HANDLER, RequestMethod, PARAM_HEADERS, HeadersParam, HeadersInjector,
          HeadersObject, HEADERS, CLIENT_HEADERS, HeadersClientParam, WITH_CREDENTIALS,
          CLIENT_WITH_CREDENTIALS, PATHS, QUERIES } from './types';
import { HttpRequest, HttpResponse, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { REST_HANDLERS, BASE_HEADERS, BASE_WITH_CREDENTIALS } from './tokens';

type RestPropertyDecorator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;

class GuardForbid extends Error {
  constructor(
    public request: HttpRequest<unknown>
  ) {
    super('A guard function forbad the request');
  }
}

export function requestBuilder(type: RequestMethod): (path?: string) => RestPropertyDecorator {
  return function (path?: string): RestPropertyDecorator {
    return function (target: ClientConstructor, methodName: string, descriptor: PropertyDescriptor): PropertyDescriptor {
      descriptor.value = async function(this: ClientInstance, ...args: any[]) {
        // > Configure endpoint
        // _____________________________________________________________________________
        let endpoint = path !== undefined ? path : methodName;

        for (const [param, index] of Object.entries((target.constructor[PATHS] || {})[methodName] || {})) {
          endpoint = endpoint.replace(':' + param, args[index]);
        }

        // > Configure request body
        // _____________________________________________________________________________
        const bodyParamIndex = (target.constructor[BODIES] || {})[methodName];
        let body: any = null;

        if (typeof bodyParamIndex === 'number') {
          body = args[bodyParamIndex];
        }

        // > Configure Queries
        // _____________________________________________________________________________
        let query = new HttpParams();

        for (const [param, index] of Object.entries((target.constructor[QUERIES] || {})[methodName] || {})) {
          query = query.append(param, args[index]);
        }

        // > Configure Headers
        // _____________________________________________________________________________
        let headers = new HttpHeaders();

        // >> Base Headers
        const baseHeaders: HeadersParam[] = <HeadersParam[]> this[INJECTOR].get(BASE_HEADERS);

        for (const set of baseHeaders) {
          for (const header of set) {
            let _headers: HeadersObject = <HeadersObject> header;

            if (typeof header === 'function') {
              const instance: HeadersInjector = this[INJECTOR].get(header);
              _headers = await instance.inject();
            }

            for (const key of Object.keys(_headers)) {
              headers = headers.append(key, _headers[key]);
            }
          }
        }

        // >> Client Headers & Method Headers
        const clientHeaders: HeadersClientParam<any> = (target.constructor[HEADERS] || {})[CLIENT_HEADERS] || [];
        const methodHeaders: HeadersClientParam<any> = (target.constructor[HEADERS] || {})[methodName] || [];

        for (let header of [...clientHeaders, ...methodHeaders]) {
          if (typeof header === 'function') {
            const instance: HeadersInjector = this[INJECTOR].get(header);
            header = await instance.inject();
          }

          if (typeof header === 'string') {
            header = await this[header]();
          }

          for (const key of Object.keys(header)) {
            headers = headers.append(key, header[key]);
          }
        }

        // >> Parameter Headers
        for (const [name, [replace, index]] of Object.entries((target.constructor[PARAM_HEADERS] || {})[methodName] || {})) {
          const method: 'set' | 'append' = replace ? 'set' : 'append';

          headers = headers[method](name, args[index]);
        }

        // > With Credentials
        // _____________________________________________________________________________
        let withCredentials: boolean = <boolean> this[INJECTOR].get(BASE_WITH_CREDENTIALS);

        if (target.constructor[WITH_CREDENTIALS]) {
          if (typeof target.constructor[WITH_CREDENTIALS][CLIENT_WITH_CREDENTIALS] !== 'undefined') {
            withCredentials = target.constructor[WITH_CREDENTIALS][CLIENT_WITH_CREDENTIALS];
          }

          if (typeof target.constructor[WITH_CREDENTIALS][methodName] !== 'undefined') {
            withCredentials = target.constructor[WITH_CREDENTIALS][methodName];
          }
        }

        // > Create request object
        // _____________________________________________________________________________
        const request = requestFactory(type as any, `${this[BASE_URL]}/${endpoint}`, { body, headers, withCredentials, params: query });

        // > Run guard process
        // _____________________________________________________________________________
        const guardsPromise = startGuardCheck(target, methodName, request, this)
          .then(result => {
            if (!result) { throw false; }
          })
          .catch(error => {
            if (error === false) {
              throw new GuardForbid(request);
            }

            throw error;
          });

        // > Handlers
        // _____________________________________________________________________________
        const globalHandlers: HandlersOf<any> = (<any[]>this[INJECTOR].get(REST_HANDLERS)).reduce((prev, next) => [...prev, ...next], []);
        const clientHandlers: HandlersOf<any> = target.constructor[HANDLERS][CLIENT_HANDLERS];
        const methodHandlers: HandlersOf<any> = target.constructor[HANDLERS][methodName] || [];

        // > Result
        // _____________________________________________________________________________
        return await chainHandlers(
          [...globalHandlers, ...clientHandlers, ...methodHandlers],
          this,
          guardsPromise.then(() => <Promise<HttpResponse<any>>>this[HTTP_CLIENT].request(request).toPromise())
        );
      };

      return descriptor;
    };
  };
}

interface RequestConfig {
  headers: HttpHeaders;
  withCredentials: boolean;
  params: HttpParams;
}

function requestFactory<T = unknown>(
  method: RequestMethod.POST | RequestMethod.PUT | RequestMethod.PATCH,
  url: string,
  config: RequestConfig
): HttpRequest<T>;
function requestFactory<T = unknown>(
  method: RequestMethod.GET | RequestMethod.DELETE | RequestMethod.HEAD | RequestMethod.JSONP | RequestMethod.OPTIONS,
  url: string,
  config: RequestConfig & { body?: T }
): HttpRequest<T>;
function requestFactory<T = unknown>(
  method: RequestMethod,
  url: string,
  {body, ...rest}: RequestConfig & { body?: T }
): HttpRequest<T> {
  switch (method) {
    case RequestMethod.POST:
    case RequestMethod.PUT:
    case RequestMethod.PATCH:
      return new HttpRequest<T>(method, url, body, rest);
    default:
      return new HttpRequest<T>(<'GET'>method, url, rest);
  }
}

async function startGuardCheck(
  target: ClientConstructor,
  methodName: string,
  request: HttpRequest<unknown>,
  context: ClientInstance
): Promise<boolean> {
  const allGuards = [...target.constructor[GUARDS][CLIENT_GUARDS], ...(target.constructor[GUARDS][methodName] || [])];

  return await allGuards.reduce((prev, next) => {
    return prev.then(passed => {
      let result;

      if (!passed) {
        throw false;
      }

      if (typeof next === 'function') {
        if (next.prototype && 'canSend' in next.prototype) {
          result = context[INJECTOR].get(next).canSend(request);
        } else {
          result = next(request);
        }
      } else {
        result = (<any>context)[next](request);
      }

      if (result instanceof Observable) {
        result = result.toPromise();
      }

      return result;
    });
  }, Promise.resolve(true));
}

function chainHandlers<T>(
  handlers: HandlersOf<any>,
  context: ClientInstance,
  source: Promise<HttpResponse<any>>
): Promise<T> {
  let original: HttpResponse<any>;

  source.then(res => {
    original = res;
  }, error => {
    original = error;
  });

  return handlers.reduce((prev: PromiseLike<any>, next) => {
    let handler: Function;
    let method: 'then' | 'catch';

    if (typeof next === 'string') {
      method = context[next][ERROR_HANDLER] ? 'catch' : 'then';
      handler = (<Function>context[next]).bind(context);
    } else if (next.prototype && 'handle' in next.prototype) {
      const injectable = context[INJECTOR].get(next);
      method = injectable.handle[ERROR_HANDLER] ? 'catch' : 'then';
      handler = injectable.handle.bind(injectable);
    } else {
      method = next[ERROR_HANDLER] ? 'catch' : 'then';
      handler = next;
    }

    return prev[method](res => handler(original, res));
  }, source);
}

result-matching ""

    No results matching ""