Skip to content

Commit

Permalink
feat(core): implements service errors as arrays; removes references c…
Browse files Browse the repository at this point in the history
…reate function; adds item create
  • Loading branch information
rafamel committed Nov 12, 2019
1 parent 92dc97c commit 867f579
Show file tree
Hide file tree
Showing 17 changed files with 112 additions and 131 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/application/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { createDefaults, defaultMap } from './defaults';
import { mergeFallback } from './helpers/merge-fallback';
import { removeChildren } from './helpers/remove-children';

// TODO: routes from camel/pascal case to _ or - separated + implement for REST
// TODO: routes from camel/pascal case to _ or - separated + validate + implement for REST
/**
* Validates and prepares a collection to be used:
* - Merges service intercepts into each route resolver.
Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/application/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApplicationCreateOptions } from './types';
import { query, error } from '~/create';
import { query, error, item } from '~/create';
import { PublicError } from '~/errors';
import {
ServiceImplementation,
Expand All @@ -13,9 +13,7 @@ export function createDefaults(): Required<ApplicationCreateOptions> {
validate: true,
children: true,
fallback: query({
errors: {
NotFoundError: error({ label: 'ClientNotFound' })
},
errors: [item('NotFoundError', error({ label: 'ClientNotFound' }))],
async resolve() {
throw new PublicError(
'NotFoundError',
Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/application/helpers/intercept-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ export function addInterceptResponse(
tree,
[
intercept({
errors: Object.keys(errors).reduce(
(acc, key) => Object.assign(acc, { [key]: key }),
{}
),
errors: Object.keys(errors),
factory: () => (data, context, info, next) => {
return next(data).pipe(
map((value) => (value === undefined ? null : value)),
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/create/implement/intercepts/intercepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function intercept<I, O>(
): InterceptImplementation<I, O> {
return {
kind: 'intercept',
errors: intercept.errors || {},
errors: intercept.errors || [],
factory(...args: any) {
const fn = intercept.factory.apply(this, args);
return function(data, context, info, next) {
Expand All @@ -70,7 +70,7 @@ export function before<T>(
): InterceptImplementation<T, any> {
return {
kind: 'intercept',
errors: hook.errors || {},
errors: hook.errors || [],
factory(...args: any) {
const fn = hook.factory.apply(this, args);
return function(data, context, info, next) {
Expand All @@ -91,7 +91,7 @@ export function after<T>(
): InterceptImplementation<any, T> {
return {
kind: 'intercept',
errors: hook.errors || {},
errors: hook.errors || [],
factory(...args: any) {
const fn = hook.factory.apply(this, args);
return function(data, context, info, next) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/create/implement/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function subscription<I = any, O = any, C = any>(

function getDefaults(): Omit<ServiceImplementation, 'kind' | 'resolve'> {
return {
errors: {},
errors: [],
request: { type: 'object' },
response: { type: 'null' },
intercepts: []
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/create/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './implement';
export * from './collections';
export * from './item';
export * from './schema';
export * from './scopes';
export * from './references';
export * from './reference';
14 changes: 14 additions & 0 deletions packages/core/src/create/item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Element, ElementItem } from '~/types';

/**
* Returns an *Element* `element` as an `ElementItem` with `name`.
*/
export function item<N extends string, T extends Element>(
name: N,
element: T
): ElementItem<T, N> {
return {
name,
item: element
};
}
31 changes: 31 additions & 0 deletions packages/core/src/create/reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CollectionTree } from '~/types';

export function reference<T extends CollectionTree, K extends keyof T['types']>(
collection: T,
name: K
): K;
export function reference<T extends CollectionTree, K extends keyof T['types']>(
collection: T,
name: K[]
): K[];

/**
* Returns `name`, as a *string* or a *string array*, while ensuring
* types with `name`s exist on `collection`.
* A helper to be used for type safety when referencing types on service creation.
*/
export function reference(
collection: CollectionTree,
name: string | string[]
): string | string[] {
const isArray = Array.isArray(name);
const names = isArray ? (name as string[]) : [name as string];

for (const name of names) {
if (!Object.hasOwnProperty.call(collection.types, name)) {
throw Error(`Can't reference "${name}" on collection`);
}
}

return isArray ? names : names[0];
}
49 changes: 0 additions & 49 deletions packages/core/src/create/references.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/core/src/helpers/empty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function emptyScope(): ScopeTree {
export function emptyIntercept(): InterceptImplementation {
return {
kind: 'intercept',
errors: {},
errors: [],
factory: () => (data, context, info, next) => next(data)
};
}
29 changes: 15 additions & 14 deletions packages/core/src/helpers/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
TreeServices,
ScopeTree,
CollectionTree,
ServiceErrors
ServiceErrors,
ErrorType,
ElementItem
} from '~/types';

export function mergeCollection<
Expand Down Expand Up @@ -103,17 +105,16 @@ export function mergeServiceErrors(
a: ServiceErrors,
b: ServiceErrors
): ServiceErrors {
return Object.entries(a)
.concat(Object.entries(b))
.reduce((acc: ServiceErrors, [key, value]) => {
if (
Object.hasOwnProperty.call(a, key) &&
Object.hasOwnProperty.call(b, key) &&
a[key] !== b[key]
) {
throw Error(`Intercepts error collition: ${key}`);
}
acc[key] = value;
return acc;
}, {});
const hash: { [key: string]: string | ElementItem<ErrorType> } = {};

const errors = a.concat(b);
for (const error of errors) {
const name = typeof error === 'string' ? error : error.name;
if (Object.hasOwnProperty.call(hash, name) && hash[name] !== error) {
throw Error(`Service error name collition: ${name}`);
}
hash[name] = error;
}

return Object.values(hash);
}
15 changes: 5 additions & 10 deletions packages/core/src/transform/lift/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,14 @@ export function liftErrors(
types: { source: TreeTypes; lift: TreeTypes },
options: Required<LiftTransformOptions>
): ServiceErrors {
const result: ServiceErrors = {};
for (const [key, error] of Object.entries(errors)) {
const result: ServiceErrors = [];
for (const error of errors) {
if (typeof error === 'string') {
checkSourceType('error', error, types, options);
if (key !== error) {
checkServiceType('error', types.source[error]);
liftServiceType(key, 'error', types.source[error], types);
result[key] = key;
}
} else {
checkServiceType('error', error);
liftServiceType(key, 'error', error, types);
result[key] = key;
checkServiceType('error', error.item);
liftServiceType(error.name, 'error', error.item, types);
result.push(error.name);
}
}
return result;
Expand Down
20 changes: 8 additions & 12 deletions packages/core/src/transform/replace/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
ScopeTree,
ResponseTypeChildren,
Element,
ElementInfo
ElementInfo,
ServiceErrors
} from '~/types';
import {
isTreeCollection,
Expand Down Expand Up @@ -107,20 +108,15 @@ export function nextService<E extends Service>(
info: ElementInfo,
cb: ReplaceTransformFn
): E {
service = {
...service,
errors: {
...service.errors
}
};
const errors: ServiceErrors = [];

for (const [name, error] of Object.entries(service.errors)) {
for (const error of service.errors) {
if (typeof error !== 'string') {
const path = info.path.concat(['errors', name]);
const route = info.route.concat(['errors', name]);
service.errors[name] = next(error, { path, route }, cb);
const path = info.path.concat(['errors', error.name]);
const route = info.route.concat(['errors', error.name]);
errors.push({ ...error, item: next(error.item, { path, route }, cb) });
}
}

return service;
return { ...service, errors };
}
10 changes: 5 additions & 5 deletions packages/core/src/types/collection/abstract.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ErrorLabel, Schema } from '../types';
import { ErrorLabel, Schema, ElementItem } from '../types';
import {
CollectionTreeKind,
ScopeTreeKind,
Expand Down Expand Up @@ -112,6 +112,10 @@ export interface AbstractGenericService {
response: string | Schema;
}

export type AbstractServiceErrors = Array<
string | ElementItem<AbstractErrorType>
>;

export interface AbstractQueryService extends AbstractGenericService {
kind: QueryServiceKind;
}
Expand All @@ -124,10 +128,6 @@ export interface AbstractSubscriptionService extends AbstractGenericService {
kind: SubscriptionServiceKind;
}

export interface AbstractServiceErrors {
[key: string]: AbstractErrorType | string;
}

// Types
export interface AbstractErrorType<L extends ErrorLabel = ErrorLabel> {
kind: ErrorTypeKind;
Expand Down
41 changes: 19 additions & 22 deletions packages/intercepts/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
ServiceErrorsImplementation,
PublicError,
ElementItem,
ErrorType
ErrorType,
ErrorTypeImplementation,
item
} from '@karmic/core';
import Ajv from 'ajv';
import draft04 from 'ajv/lib/refs/json-schema-draft-04.json';
Expand Down Expand Up @@ -40,37 +42,32 @@ export function validation(
): InterceptImplementation<any, any, any> {
const opts = Object.assign({ request: true, response: false }, options);

const requestError: ElementItem<ErrorType> | null = opts.request
const requestError: ElementItem<ErrorTypeImplementation> | null = opts.request
? opts.request === true
? {
name: 'RequestValidationError',
item: error({ label: 'ClientInvalid' })
}
? item('RequestValidationError', error({ label: 'ClientInvalid' }))
: opts.request
: null;
const responseError: ElementItem<ErrorType> | null = opts.response
const responseError: ElementItem<
ErrorTypeImplementation
> | null = opts.response
? opts.response === true
? {
name: 'ResponseValidationError',
item: error({ label: 'ServerError' })
}
? item('ResponseValidationError', error({ label: 'ServerError' }))
: opts.response
: null;

if (requestError && requestError.item.label !== 'ClientInvalid') {
throw Error(
`Request error must have label ClientInvalid: ${requestError.item.label}`
);
const errors: ServiceErrorsImplementation = [];
if (requestError) {
if (requestError.item.label !== 'ClientInvalid') {
throw Error(
`Request error must have label ClientInvalid: ${requestError.item.label}`
);
}
errors.push(requestError);
}
if (responseError) errors.push(responseError);

return intercept({
errors: [requestError, responseError].reduce(
(acc: ServiceErrorsImplementation, error) => {
if (error) acc[error.name] = error.item;
return acc;
},
{}
),
errors,
factory: (schemas) => {
const validateRequest = ajv.compile(schemas.request);
const validateResponse = ajv.compile(schemas.response);
Expand Down

0 comments on commit 867f579

Please sign in to comment.