Skip to content

Commit

Permalink
feat(core/transform): adds normalize
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Oct 15, 2019
1 parent a8e6524 commit 1b0228e
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/core/src/transform/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './replace';
export * from './normalize';
export * from './routes';
124 changes: 124 additions & 0 deletions packages/core/src/transform/normalize/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
TreeTypes,
Service,
ServiceErrors,
Type,
QueryService,
SubscriptionService
} from '~/types';

export function normalizeServiceTypes(
name: string,
service: Service,
types: { source: TreeTypes; normal: TreeTypes },
transform: (str: string, isExplicit: boolean) => string
): Service {
service = { ...service, types: { ...service.types } };

for (const kind of ['request', 'response'] as ['request', 'response']) {
const type = service.types[kind];
if (typeof type === 'string') {
service.types[kind] = checkSourceType(kind, type, types, transform);
} else {
const pascal = name + transform('R' + kind.slice(1), false);
normalizeServiceType(kind, pascal, type, types, transform);
service.types[kind] = pascal;
}
}

const errors: ServiceErrors = {};
for (const [key, error] of Object.entries(service.types.errors)) {
if (typeof error === 'string') {
let id = checkSourceType('error', error, types, transform);
if (key !== error) {
id = transform(key, true);
normalizeServiceType(
'error',
id,
types.source[error],
types,
transform
);
}
errors[id] = id;
} else {
const id = transform(key, true);
normalizeServiceType('error', id, error, types, transform);
errors[id] = id;
}
}
service.types.errors = errors;

return service;
}

export function normalizeServiceType(
kind: string,
name: string,
type: Type,
types: { source: TreeTypes; normal: TreeTypes },
transform: (str: string, isExplicit: boolean) => string
): void {
if (type.kind !== kind) {
throw Error(`Invalid inline type kind: ${name}`);
}

switch (type.kind) {
case 'error':
case 'request': {
if (Object.hasOwnProperty.call(types.normal, name)) {
throw Error(`Inline type name collision: ${name}`);
}
types.normal[name] = type;
return;
}
case 'response': {
if (Object.hasOwnProperty.call(types.normal, name)) {
throw Error(`Inline type name collision: ${name}`);
}
if (!type.children) {
types.normal[name] = type;
return;
}

const item = {
...type,
children: { ...type.children }
};
for (const [key, service] of Object.entries(type.children)) {
item.children[key] = normalizeServiceTypes(
name + transform(key, false),
service,
types,
transform
) as QueryService | SubscriptionService;
}

types.normal[name] = item;
return;
}
default: {
throw Error(`Invalid kind for type: ${name}`);
}
}
}

export function checkSourceType(
kind: string,
name: string,
types: { source: TreeTypes; normal: TreeTypes },
transform: (str: string, isExplicit: boolean) => string
): string {
const pascal = transform(name, true);
if (
!Object.hasOwnProperty.call(types.source, name) ||
!Object.hasOwnProperty.call(types.normal, pascal)
) {
throw Error(`Collection lacks referenced type: ${name}`);
}
if (types.normal[pascal].kind !== kind) {
throw Error(`Invalid type kind reference: ${pascal}`);
}

return pascal;
}
86 changes: 86 additions & 0 deletions packages/core/src/transform/normalize/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
CollectionTree,
TreeTypes,
QueryService,
SubscriptionService,
CollectionTreeImplementation
} from '~/types';
import { isElementType, isElementService } from '~/inspect';
import camelcase from 'camelcase';
import { normalizeServiceTypes } from './helpers';
import { replace } from '../replace';

export type NormalizeTransform<
T extends CollectionTree
> = T extends CollectionTreeImplementation
? CollectionTreeImplementation
: CollectionTree;

/**
* Extracts all service inline types of a collection to its top level `CollectionTree.types`, naming them according to their scope, service, and kind. It additionally transforms all type names to pascal case. It will throw if a collection:
* - Produces conflicting type names.
* - Contains references to non existent types.
* - Contains types with an empty name or with non word characters.
* - Contains services with inline types or type references of the wrong kind.
*/
export function normalize<T extends CollectionTree>(
collection: T
): NormalizeTransform<T> {
const transform = (str: string, isExplicit: boolean) => {
return camelcase(str, { pascalCase: true });
};

const types = {
source: collection.types,
normal: Object.entries(collection.types).reduce(
(acc: TreeTypes, [name, type]) => {
const pascal = transform(name, true);
if (Object.hasOwnProperty.call(acc, pascal)) {
throw Error(`Type name collision: ${pascal}`);
}
acc[pascal] = type;
return acc;
},
{}
)
};

return {
...replace(
collection,
(element, { route }) => {
const name = transform(route[route.length - 1], true);

if (isElementType(element)) {
if (element.kind !== 'response' || !element.children) {
return element;
}

const response = { ...element, children: element.children };
for (const [key, service] of Object.entries(element.children)) {
response.children[key] = normalizeServiceTypes(
name + transform(key, false),
service,
types,
transform
) as QueryService | SubscriptionService;
}
return response;
} else if (isElementService(element)) {
return normalizeServiceTypes(
route.length > 1
? transform(route[route.length - 2], false) + name
: name,
element,
types,
transform
);
} else {
return element;
}
},
{ deep: true, children: false, inline: false }
),
types: types.normal
} as NormalizeTransform<T>;
}

0 comments on commit 1b0228e

Please sign in to comment.