mirror of
https://github.com/xodio/xod.git
synced 2026-03-24 17:46:56 +01:00
refactor(xod-project): enforce type checks with def-hm
This commit is contained in:
@@ -204,7 +204,7 @@ const convertLinksInPatches = R.curry(
|
||||
const copyImpls = R.compose(
|
||||
R.map(R.assoc('impls')),
|
||||
maybeEmpty,
|
||||
R.prop('impl')
|
||||
R.propOr({}, 'impl')
|
||||
);
|
||||
|
||||
// :: ProjectOld -> Maybe Function fn
|
||||
|
||||
@@ -369,6 +369,7 @@ const getPinType = R.curry((patchTuples, nodes, idGetter, keyGetter, link) =>
|
||||
const createCastNode = R.curry((patchTuples, nodes, link) => R.compose(
|
||||
R.chain(type => ({
|
||||
id: `${Link.getLinkOutputNodeId(link)}-to-${Link.getLinkInputNodeId(link)}`,
|
||||
position: { x: 0, y: 0 },
|
||||
type,
|
||||
})),
|
||||
// Link -> Maybe String
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as Link from './link';
|
||||
import * as Pin from './pin';
|
||||
import * as Utils from './utils';
|
||||
import { sortGraph } from './gmath';
|
||||
import { def } from './types';
|
||||
|
||||
/**
|
||||
* An object representing single patch in a project
|
||||
@@ -26,6 +27,8 @@ import { sortGraph } from './gmath';
|
||||
export const createPatch = () => ({
|
||||
nodes: {},
|
||||
links: {},
|
||||
impls: {},
|
||||
pins: {},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -382,10 +385,9 @@ export const listLinksByPin = R.curry(
|
||||
* @param {Patch} patch - a patch to operate on
|
||||
* @returns {Either<Error|Link>} validation errors or valid {@link Link}
|
||||
*/
|
||||
export const validateLink = R.curry(
|
||||
(link, patch) => Link.validateLinkId(link)
|
||||
.chain(Link.validateLinkInput)
|
||||
.chain(Link.validateLinkOutput)
|
||||
export const validateLink = def(
|
||||
'validateLink :: Link -> Patch -> Either Error Link',
|
||||
(link, patch) => Either.of(link)
|
||||
.chain(() => Tools.errOnNothing(
|
||||
CONST.ERROR.LINK_INPUT_NODE_NOT_FOUND,
|
||||
getNodeById(Link.getLinkInputNodeId(link), patch)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
import R from 'ramda';
|
||||
import RF from 'ramda-fantasy';
|
||||
import $ from 'sanctuary-def';
|
||||
import HMDef from 'hm-def';
|
||||
|
||||
@@ -41,6 +42,20 @@ const AliasType = (typeName, type) => NullaryType(
|
||||
hasType(type)
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Fantasy land types
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
export const $Either = $.BinaryType(
|
||||
'ramda-fantasy/Either',
|
||||
'https://github.com/ramda/ramda-fantasy/blob/master/docs/Either.md',
|
||||
R.is(RF.Either),
|
||||
either => either.isLeft ? [either.value] : [],
|
||||
either => either.isRight ? [either.value] : []
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//
|
||||
// Domain types
|
||||
@@ -49,11 +64,14 @@ const AliasType = (typeName, type) => NullaryType(
|
||||
|
||||
const ObjectWithId = NullaryType('ObjectWithId', R.has('id'));
|
||||
|
||||
export const Label = AliasType('Label', $.String);
|
||||
export const Source = AliasType('Source', $.String);
|
||||
export const ShortId = AliasType('ShortId', $.String);
|
||||
export const LinkId = AliasType('LinkId', ShortId);
|
||||
export const NodeId = AliasType('NodeId', ShortId);
|
||||
export const PinKey = AliasType('PinKey', $.String);
|
||||
export const PatchPath = AliasType('PatchPath', $.String);
|
||||
export const Pin = AliasType('Pin', $.Object); // TODO: enforce model
|
||||
|
||||
export const NodePosition = Model('NodePosition', {
|
||||
x: $.Number,
|
||||
@@ -77,6 +95,14 @@ export const Link = Model('Link', {
|
||||
output: PinRef,
|
||||
});
|
||||
|
||||
export const Patch = Model('Patch', {
|
||||
nodes: $.StrMap(Node),
|
||||
links: $.StrMap(Link),
|
||||
impls: $.StrMap(Source),
|
||||
pins: $.StrMap(Pin),
|
||||
//label: Label,
|
||||
});
|
||||
|
||||
export const NodeOrId = OneOfType('NodeOrId', [NodeId, ObjectWithId]);
|
||||
export const LinkOrId = OneOfType('LinkOrId', [LinkId, ObjectWithId]);
|
||||
|
||||
@@ -85,15 +111,18 @@ export const LinkOrId = OneOfType('LinkOrId', [LinkId, ObjectWithId]);
|
||||
// Environment
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
export const env = $.env.concat([
|
||||
$Either,
|
||||
Link,
|
||||
PinRef,
|
||||
PinKey,
|
||||
NodeId,
|
||||
LinkId,
|
||||
ShortId,
|
||||
NodeOrId,
|
||||
LinkOrId,
|
||||
NodeId,
|
||||
NodeOrId,
|
||||
Patch,
|
||||
PinKey,
|
||||
PinRef,
|
||||
ShortId,
|
||||
]);
|
||||
|
||||
export const def = HMDef.create({ checkTypes: true, env });
|
||||
|
||||
@@ -11,7 +11,7 @@ chai.use(dirtyChai);
|
||||
|
||||
describe('Flatten', () => {
|
||||
describe('trivial', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -63,7 +63,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should return error if implementation not found', () => {
|
||||
const flatProject = flatten(project, '@/main', ['cpp']);
|
||||
@@ -118,7 +118,7 @@ describe('Flatten', () => {
|
||||
});
|
||||
|
||||
describe('recursive', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -270,7 +270,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should ignore not referred patches', () => {
|
||||
const flatProject = flatten(project, '@/foo', ['js']);
|
||||
@@ -374,7 +374,7 @@ describe('Flatten', () => {
|
||||
};
|
||||
|
||||
describe('no links to terminal', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -432,7 +432,7 @@ describe('Flatten', () => {
|
||||
impls: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should return patches without cast patch', () => {
|
||||
const flatProject = flatten(project, '@/main', ['js']);
|
||||
@@ -465,7 +465,7 @@ describe('Flatten', () => {
|
||||
describe('through output terminal', () => {
|
||||
const createCastOutputTest = (typeIn, typeOut) => {
|
||||
it(`${typeIn} -> ${getCastPatchPath(typeIn, typeOut)} -> ${typeOut}`, () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -581,7 +581,7 @@ describe('Flatten', () => {
|
||||
impls: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (typeOut === typeIn) {
|
||||
project.patches[`xod/core/${typeOut}`].pins = {
|
||||
@@ -622,7 +622,7 @@ describe('Flatten', () => {
|
||||
describe('through input terminal', () => {
|
||||
const createCastInputTest = (typeIn, typeOut) => {
|
||||
it(`${typeIn} -> ${getCastPatchPath(typeIn, typeOut)} -> ${typeOut}`, () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -738,7 +738,7 @@ describe('Flatten', () => {
|
||||
impls: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (typeOut === typeIn) {
|
||||
project.patches[`xod/core/${typeOut}`].pins = {
|
||||
@@ -788,7 +788,7 @@ describe('Flatten', () => {
|
||||
describe('three different types', () => {});
|
||||
|
||||
describe('with same types', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -878,7 +878,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should return patches without cast patch', () => {
|
||||
const flatProject = flatten(project, '@/main', ['js']);
|
||||
@@ -933,7 +933,7 @@ describe('Flatten', () => {
|
||||
});
|
||||
|
||||
describe('needed, but missing in the project', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -996,7 +996,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it(`should return Either.Left with error "${CONST.ERROR.CAST_PATCH_NOT_FOUND}"`, () => {
|
||||
const flatProject = flatten(project, '@/main', ['js']);
|
||||
@@ -1008,7 +1008,7 @@ describe('Flatten', () => {
|
||||
});
|
||||
|
||||
describe('injected pins', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -1241,7 +1241,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should return node b~b with injected pin a2', () => {
|
||||
const flatProject = flatten(project, '@/main', ['js']);
|
||||
@@ -1264,7 +1264,7 @@ describe('Flatten', () => {
|
||||
});
|
||||
|
||||
describe('implementations', () => {
|
||||
const project = {
|
||||
const project = Helper.defaultizeProject({
|
||||
patches: {
|
||||
'@/main': {
|
||||
nodes: {
|
||||
@@ -1296,7 +1296,7 @@ describe('Flatten', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('single', () => {
|
||||
it('defined implementation exists', () => {
|
||||
|
||||
@@ -84,3 +84,43 @@ export const expectOptionalNumberSetter = R.curry((expect, method, propName) =>
|
||||
.to.have.property(propName).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Defaultizers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
export const defaultizeLink = R.merge({
|
||||
id: '$$defaultLinkId',
|
||||
input: { nodeId: '$$defaultInputNodeId', pinKey: '$$defaultInputPin' },
|
||||
output: { nodeId: '$$defaultOutputNodeId', pinKey: '$$defaultOutputPin' },
|
||||
});
|
||||
|
||||
export const defaultizeNode = R.merge({
|
||||
id: '$$defaultNodeId',
|
||||
position: { x: 0, y: 0 },
|
||||
type: '@/defaultType',
|
||||
});
|
||||
|
||||
export const defaultizePatch = R.compose(
|
||||
R.evolve({
|
||||
nodes: R.map(defaultizeNode),
|
||||
links: R.map(defaultizeLink),
|
||||
impls: R.identity,
|
||||
pins: R.identity,
|
||||
}),
|
||||
R.merge({
|
||||
nodes: {},
|
||||
links: {},
|
||||
impls: {},
|
||||
pins: {},
|
||||
})
|
||||
);
|
||||
|
||||
export const defaultizeProject = R.compose(
|
||||
R.evolve({
|
||||
patches: R.map(defaultizePatch),
|
||||
}),
|
||||
R.merge({
|
||||
patches: {},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -737,12 +737,12 @@ describe('Patch', () => {
|
||||
});
|
||||
});
|
||||
describe('validateLink', () => {
|
||||
const patch = {
|
||||
const patch = Helper.defaultizePatch({
|
||||
nodes: {
|
||||
out: { id: 'out' },
|
||||
in: { id: 'in' },
|
||||
},
|
||||
};
|
||||
});
|
||||
const linkId = '1';
|
||||
const validInput = {
|
||||
nodeId: 'in',
|
||||
@@ -753,26 +753,12 @@ describe('Patch', () => {
|
||||
pinKey: 'out',
|
||||
};
|
||||
|
||||
it('should return Either.Left for link without id', () => {
|
||||
const err = Patch.validateLink({}, {});
|
||||
expect(err.isLeft).to.be.true();
|
||||
});
|
||||
it('should return Either.Left if input property is not exist or invalid', () => {
|
||||
const link = { id: linkId };
|
||||
const err = Patch.validateLink(link, patch);
|
||||
expect(err.isLeft).to.be.true();
|
||||
});
|
||||
it('should return Either.Left for non-existent input node in the patch', () => {
|
||||
const link = { id: linkId, input: { nodeId: 'non-existent', pinKey: 'a' }, output: validOutput };
|
||||
const err = Patch.validateLink(link, patch);
|
||||
expect(err.isLeft).to.be.true();
|
||||
Helper.expectErrorMessage(expect, err, CONST.ERROR.LINK_INPUT_NODE_NOT_FOUND);
|
||||
});
|
||||
it('should return Either.Left if output property is not exist or invalid', () => {
|
||||
const link = { id: linkId, input: validInput };
|
||||
const err = Patch.validateLink(link, patch);
|
||||
expect(err.isLeft).to.be.true();
|
||||
});
|
||||
it('should return Either.Left for non-existent output node in the patch', () => {
|
||||
const link = { id: linkId, input: validInput, output: { nodeId: 'non-existent', pinKey: 'a' } };
|
||||
const err = Patch.validateLink(link, patch);
|
||||
|
||||
Reference in New Issue
Block a user