Skip to content

Instantly share code, notes, and snippets.

@leandro-manifesto
Last active August 7, 2020 23:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leandro-manifesto/cac78114030756383462b6c7b641aaf2 to your computer and use it in GitHub Desktop.
Save leandro-manifesto/cac78114030756383462b6c7b641aaf2 to your computer and use it in GitHub Desktop.
Custom type converter with @marcj/marshal
import { DateTime } from 'luxon';
import { PropertyCompilerSchema } from '@marcj/marshal';
import { CustomConverterCompiler, registerCustomConverterCompiler } from './register';
class DateTimeConverter implements CustomConverterCompiler {
public check(accessor: string, schema: PropertyCompilerSchema, utils: {
reserveVariable: () => string;
raise: (code: string, message: string) => string;
}) {
const variableName = utils.reserveVariable();
return {
template: `
if (typeof ${accessor} === 'string') {
const ${variableName} = DateTime.fromISO(${accessor});
if (!${variableName}.isValid) {
${utils.raise('invalid-datetime', 'DateTime must be in ISO format')}
}
} else {
${utils.raise('invalid-datetime', 'DateTime must be a string in ISO format')}
}
`,
context: { DateTime },
};
}
public to(setter: string, accessor: string, schema: PropertyCompilerSchema, reserveVariable: () => string) {
const variableName = reserveVariable();
return {
template: `
if (typeof ${accessor} === 'string') {
const ${variableName} = DateTime.fromISO(${accessor});
if (${variableName}.isValid) {
${setter} = ${variableName};
} else {
throw new Error('Invalid ISO DateTime given in property ${schema.name}: ' + value.invalidExplanation);
}
} else {
throw new Error('Invalid ISO DateTime given in property ${schema.name}: got a ' + (typeof ${accessor}));
}
`,
context: { DateTime },
};
}
public from(setter: string, accessor: string) {
return `${setter} = ${accessor}.toISO();`;
}
}
registerCustomConverterCompiler(DateTime, DateTimeConverter);
import {
compilerConvertClassToX,
compilerXToClass,
jitValidate,
registerCheckerCompiler,
registerConverterCompiler,
TypeCheckerCompiler,
TypeConverterCompiler,
} from '@marcj/marshal';
export interface AbstractType<T> {
prototype: T;
}
export interface CustomConverterCompiler {
check: TypeCheckerCompiler;
to: TypeConverterCompiler;
from: TypeConverterCompiler;
}
export interface CustomConverterCompilerType extends AbstractType<CustomConverterCompiler> {
new (): CustomConverterCompiler;
}
const COMPILERS = new WeakMap<AbstractType<unknown>, CustomConverterCompiler>();
export function registerCustomConverterCompiler(type: AbstractType<unknown>, compilerType: CustomConverterCompilerType): void {
COMPILERS.set(type, new compilerType());
}
registerCheckerCompiler('class', (accessor, schema, utils) => {
const { resolveClassType } = schema;
if (resolveClassType != null) {
const converter = COMPILERS.get(resolveClassType);
if (converter != null) {
return converter.check(accessor, schema, utils);
}
}
// fallback to default implementation
// copied from marshal source: https://github.com/super-hornet/super-hornet.ts/blob/cb358d5d3d7764e3defff9fc744ee9bd6a819b1d/packages/marshal/src/jit-validation-templates.ts#L103
const classType = utils.reserveVariable();
return {
template: `
if ('object' === typeof ${accessor} && 'function' !== typeof ${accessor}.slice) {
jitValidate(${classType})(${accessor}, ${utils.path}, _errors);
} else {
${utils.raise('invalid_type', 'Type is not an object')};
}
`,
context: {
[classType]: resolveClassType,
jitValidate,
},
};
});
const compilerPlainToClass = compilerXToClass('plain');
registerConverterCompiler('plain', 'class', 'class', (setter, accessor, schema, reserveVariable, context) => {
const { resolveClassType } = schema;
if (resolveClassType != null) {
const converter = COMPILERS.get(resolveClassType);
if (converter != null) {
return converter.to(setter, accessor, schema, reserveVariable, context);
}
}
// fallback to default implementation
return compilerPlainToClass(setter, accessor, schema, reserveVariable);
});
const compilerClassToPlain = compilerConvertClassToX('plain');
registerConverterCompiler('class', 'plain', 'class', (setter, accessor, schema, reserveVariable, context) => {
const { resolveClassType } = schema;
if (resolveClassType != null) {
const compiler = COMPILERS.get(resolveClassType);
if (compiler != null) {
return compiler.from(setter, accessor, schema, reserveVariable, context);
}
}
// fallback to default implementation
return compilerClassToPlain(setter, accessor, schema, reserveVariable);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment