style(http): #resolveRouting
MODIFY
packages/http/src/error/http.error.model.ts
packages/http/src/error/http.error.model.ts
@@ -36,13 +36,8 @@ export const isHttpError = (error: Error | undefined): error is HttpError =>
export const isHttpRequestError = (error: Error | undefined): error is HttpRequestError =>
error?.name === HttpErrorType.HTTP_REQUEST_ERROR;
-export const unexpectedErrorWhileSendingErrorFactory = (error: Error): CoreError => {
- const message = `An unexpected error ${chalk.red(`"${error.message}"`)} occured while sending an error response. Please check your error effect.`;
- return coreErrorFactory(message, { printStacktrace: false });
-};
-
-export const unexpectedErrorWhileSendingOutputFactory = (error: Error): CoreError => {
- const message = `An unexpected error ${chalk.red(`"${error.message}"`)} occured while sending a response. Please check your output effect.`;
+export const unexpectedErrorWhileSendingResponseFactory = (error: Error): CoreError => {
+ const message = `An unexpected error ${chalk.red(`"${error.message}"`)} occured while sending a response. Please check your output/error effect.`;
return coreErrorFactory(message, { printStacktrace: false });
};

MODIFY
packages/http/src/http.interface.ts
packages/http/src/http.interface.ts
@@ -29,6 +29,13 @@ export interface QueryParameters {
}
export interface HttpResponse extends http.ServerResponse {
+ /**
+ * Send HTTP response
+ *
+ * @param response `HttpEffectResponse`
+ * @returns `Observable<boolean>` (indicates whether the response was sent or not)
+ * @since 1.0.0
+ */
send: (response: HttpEffectResponse) => Observable<boolean>;
}

RENAME
packages/http/src/router/http.router.resolver.v2.ts
packages/http/src/router/http.router.resolver.ts
import { Observable, Subject, fromEvent, merge } from 'rxjs';
import { takeUntil, share, take, mergeMap, map, catchError } from 'rxjs/operators';
-import { pipe } from 'fp-ts/lib/function';
+import { flow, pipe } from 'fp-ts/lib/function';
import { EffectContext, useContext, LoggerToken, LoggerTag, LoggerLevel } from '@marblejs/core';
+import { throwException } from '@marblejs/core/dist/+internal/utils';
import { HttpServer, HttpRequest, HttpStatus } from '../http.interface';
import { defaultError$ } from '../error/http.error.effect';
import { HttpEffectResponse, HttpErrorEffect, HttpOutputEffect } from '../effects/http.effects.interface';
import {
- unexpectedErrorWhileSendingErrorFactory,
- unexpectedErrorWhileSendingOutputFactory,
+ unexpectedErrorWhileSendingResponseFactory,
errorNotBoundToRequestErrorFactory,
responseNotBoundToRequestErrorFactory,
isHttpRequestError,
@@ -23,19 +23,23 @@ import { ROUTE_NOT_FOUND_ERROR } from './http.router.effects';
import { decorateEffect } from './http.router.helpers';
import { combineRouteMiddlewares } from './http.router.combiner';
-export const resolveRouting = (config: {
+type ResolveRoutingConfig = Readonly<{
routing: Routing,
ctx: EffectContext<HttpServer>,
output$?: HttpOutputEffect,
error$?: HttpErrorEffect,
-}) => {
+}>
+
+export const resolveRouting = (config: ResolveRoutingConfig) => {
const environmentConfig = provideConfig();
const requestBus = useContext(HttpRequestBusToken)(config.ctx.ask);
const logger = useContext(LoggerToken)(config.ctx.ask);
-
const outputSubject = new Subject<{ res: HttpEffectResponse; req: HttpRequest}>();
const errorSubject = new Subject<{ error: Error; req: HttpRequest }>();
+ /**
+ * Server close stream (closes all active streams)
+ */
const close$ = pipe(
fromEvent(config.ctx.client, 'close'),
take(1),
@@ -61,22 +65,22 @@ export const resolveRouting = (config: {
takeUntil(close$),
);
+ /**
+ * Subscribe to all outgoing HTTP responses and trigger side effect
+ * @param stream$ incoming `{ res, req }` pair
+ * @returns `Subscription`
+ */
const subscribeResponse = (stream$: Observable<{ res: HttpEffectResponse, req: HttpRequest }>) =>
stream$
.pipe(mergeMap(({ res, req }) => req.response.send(res)))
.subscribe({
- error: err => { throw unexpectedErrorWhileSendingOutputFactory(err); },
- });
-
- const subscribeError = (stream$: Observable<{ res: HttpEffectResponse, req: HttpRequest }>) =>
- stream$
- .pipe(mergeMap(({ res, req }) => req.response.send(res)))
- .subscribe({
- error: err => { throw unexpectedErrorWhileSendingErrorFactory(err); },
+ error: flow(
+ unexpectedErrorWhileSendingResponseFactory,
+ throwException),
});
subscribeResponse(response$);
- subscribeError(error$);
+ subscribeResponse(error$);
const bootstrappedRrouting: BootstrappedRoutingItem[] = config.routing.map(item => ({
...item,
@@ -94,21 +98,21 @@ export const resolveRouting = (config: {
message: `Effect mapped: ${item.path || '/'} ${method}`,
})();
+ const processError = (error: any, originStream$: Observable<any>): Observable<any> => {
+ if (!error.request) throw errorNotBoundToRequestErrorFactory(error);
+ errorSubject.next({ error, req: error.request });
+ return originStream$;
+ };
+
const output$ = pipe(
subject.asObservable(),
e$ => middleware(e$, config.ctx),
e$ => decorate ? decorateEffect(e$, errorSubject) : e$,
e$ => effect(e$, config.ctx),
- catchError((error, stream) => processError(stream)(error)),
+ catchError(processError),
takeUntil(close$),
);
- const processError = (originStream$: Observable<any>) => (error: any) => {
- if (!error.request) throw errorNotBoundToRequestErrorFactory(error);
- errorSubject.next({ error, req: error.request });
- return originStream$;
- };
-
const subscribe = (stream$: Observable<HttpEffectResponse>) =>
stream$.subscribe({
next: res => {
@@ -138,6 +142,11 @@ export const resolveRouting = (config: {
const find = matchRoute(bootstrappedRrouting);
+ /**
+ * Resolve incoming request
+ * @param req `HttpRequest`
+ * @returns `void`
+ */
const resolve = (req: HttpRequest) => {
const [urlPath, urlQuery] = req.url.split('?');

RENAME
packages/http/src/router/specs/http.router.resolver.v2.spec.ts
packages/http/src/router/specs/http.router.resolver.spec.ts
@@ -5,7 +5,7 @@ import { mapTo, take, toArray, delay, mergeMap, map, first } from 'rxjs/operator
import { createMockEffectContext, createHttpResponse, createHttpRequest, createTestRoute } from '../../+internal/testing.util';
import { HttpEffect, HttpErrorEffect, HttpOutputEffect } from '../../effects/http.effects.interface';
import { Routing } from '../http.router.interface';
-import { resolveRouting } from '../http.router.resolver.v2';
+import { resolveRouting } from '../http.router.resolver';
import { factorizeRegExpWithParams } from '../http.router.params.factory';
import { HttpError } from '../../error/http.error.model';
import { HttpStatus } from '../../http.interface';

MODIFY
packages/http/src/server/http.server.listener.ts
packages/http/src/server/http.server.listener.ts
@@ -4,7 +4,7 @@ import { HttpMiddlewareEffect, HttpErrorEffect, HttpOutputEffect } from '../effe
import { HttpRequest, HttpResponse } from '../http.interface';
import { handleResponse } from '../response/http.responseHandler';
import { RouteEffect, RouteEffectGroup, Routing } from '../router/http.router.interface';
-import { resolveRouting } from '../router/http.router.resolver.v2';
+import { resolveRouting } from '../router/http.router.resolver';
import { factorizeRoutingWithDefaults } from '../router/http.router.factory';
import { HttpServerClientToken } from './internal-dependencies/httpServerClient.reader';

style(http): #resolveRouting