Add runtime type checking to structs (#1253)

This commit is contained in:
Erik Arvidsson
2016-04-15 09:25:25 -07:00
parent 14f7bdeb11
commit 71ee644d36
6 changed files with 73 additions and 29 deletions

View File

@@ -48,14 +48,15 @@ function getEmptyCommitMap(): Promise<NomsMap<string, RefValue<Commit>>> {
let datasTypes: DatasTypes;
export function getDatasTypes(): DatasTypes {
if (!datasTypes) {
const commitTypeDef = makeStructType('Commit', [
new Field('value', valueType, false),
new Field('parents', makeCompoundType(Kind.Set,
makeCompoundType(Kind.Ref, makeType(emptyRef, 0))), false),
const datasPackage = new Package([
makeStructType('Commit', [
new Field('value', valueType, false),
new Field('parents', makeCompoundType(Kind.Set,
makeCompoundType(Kind.Ref, makeType(emptyRef, 0))), false),
], []),
], []);
const datasPackage = new Package([commitTypeDef], []);
registerPackage(datasPackage);
const [commitTypeDef] = datasPackage.types;
const commitType = makeType(datasPackage.ref, 0);
const refOfCommitType = makeCompoundType(Kind.Ref, commitType);

View File

@@ -440,15 +440,16 @@ suite('Decode', () => {
test('test read struct with', async () => {
const ms = new MemoryStore();
const ds = new DataStore(ms);
let tr = makeStructType('A1', [
new Field('x', int16Type, false),
new Field('e', makeType(emptyRef, 1), false),
new Field('b', boolType, false),
const pkg = new Package([
makeStructType('A1', [
new Field('x', int16Type, false),
new Field('e', makeType(emptyRef, 1), false),
new Field('b', boolType, false),
], []),
makeEnumType('E', ['a', 'b', 'c']),
], []);
const enumTref = makeEnumType('E', ['a', 'b', 'c']);
const pkg = new Package([tr, enumTref], []);
registerPackage(pkg);
tr = pkg.types[0];
const tr = pkg.types[0];
const a = [Kind.Unresolved, pkg.ref.toString(), '0', '42', '1', true];
const r = new JsonArrayReader(a, ds);

View File

@@ -43,18 +43,17 @@ export class Package extends ValueBase {
}
}
const packageRegistry: { [key: string]: Package } = Object.create(null);
const packageRegistry: {[key: string]: Package} = Object.create(null);
export function lookupPackage(r: Ref): ?Package {
return packageRegistry[r.toString()];
}
// TODO: Compute ref rather than setting
export function registerPackage(p: Package) {
packageRegistry[p.ref.toString()] = p;
}
const pendingPackages: { [key: string]: Promise<Package> } = Object.create(null);
const pendingPackages: {[key: string]: Promise<Package>} = Object.create(null);
export function readPackage(r: Ref, ds: DataStore): Promise<Package> {
const refStr = r.toString();

View File

@@ -242,23 +242,24 @@ suite('Struct', () => {
});
test('named union', () => {
let typeDef, typeDefA, typeDefD;
const pkg = new Package([
typeDef = makeStructType('StructWithUnions', [
makeStructType('StructWithUnions', [
new Field('a', makeType(emptyRef, 1), false),
new Field('d', makeType(emptyRef, 2), false),
], []),
typeDefA = makeStructType('', [], [
makeStructType('', [], [
new Field('b', float64Type, false),
new Field('c', stringType, false),
]),
typeDefD = makeStructType('', [], [
makeStructType('', [], [
new Field('e', float64Type, false),
new Field('f', stringType, false),
]),
], []);
registerPackage(pkg);
const pkgRef = pkg.ref;
const [typeDef, typeDefA, typeDefD] = pkg.types;
const type = makeType(pkgRef, 0);
const typeA = makeType(pkgRef, 1);
const typeD = makeType(pkgRef, 2);
@@ -288,4 +289,26 @@ suite('Struct', () => {
d: new D({f: 'bye'}),
})));
});
test('type validation', () => {
const typeDef = makeStructType('S1', [
new Field('x', boolType, false),
new Field('o', stringType, true),
], []);
const pkg = new Package([typeDef], []);
registerPackage(pkg);
const pkgRef = pkg.ref;
const type = makeType(pkgRef, 0);
assert.throws(() => {
newStruct(type, typeDef, {x: 1});
});
assert.throws(() => {
newStruct(type, typeDef, {o: 1});
});
newStruct(type, typeDef, {x: true, o: undefined});
newStruct(type, typeDef, {x: true});
});
});

View File

@@ -8,6 +8,7 @@ import {invariant} from './assert.js';
import {isPrimitive} from './primitives.js';
import {Kind} from './noms-kind.js';
import {ValueBase} from './value.js';
import validateType from './validate-type.js';
type StructData = {[key: string]: ?valueOrPrimitive};
@@ -90,9 +91,17 @@ function validate(typeDef: Type, data: StructData): void {
let dataCount = Object.keys(data).length;
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
invariant(data[field.name] !== undefined || field.optional);
if (field.name in data) {
const value = data[field.name];
if (field.optional) {
if (field.name in data) {
dataCount--;
}
if (value !== undefined) {
validateType(field.t, value);
}
} else {
dataCount--;
validateType(field.t, value);
}
}
@@ -101,7 +110,9 @@ function validate(typeDef: Type, data: StructData): void {
invariant(dataCount === 1);
for (let i = 0; i < union.length; i++) {
const field = union[i];
if (data[field.name] !== undefined) {
const value = data[field.name];
if (value !== undefined) {
validateType(field.t, value);
return;
}
}

View File

@@ -4,7 +4,8 @@ import {Kind, kindToString} from './noms-kind.js';
import {CompoundDesc} from './type.js';
import type {Type} from './type.js';
import {ValueBase} from './value.js';
import {invariant} from './assert.js';
import {notNull, invariant} from './assert.js';
import {lookupPackage} from './package.js';
export default function validateType(t: Type, v: any): void {
switch (t.kind) {
@@ -36,23 +37,31 @@ export default function validateType(t: Type, v: any): void {
return;
}
case Kind.Enum:
assertTypeof(v, 'number', t);
// TODO: Validate value.
case Kind.Unresolved: {
// Struct or Enum.
const pkg = lookupPackage(t.packageRef);
const t2 = notNull(pkg).types[t.ordinal];
if (t2.kind === Kind.Enum) {
assertTypeof(v, 'number', t);
// TODO: Validate value.
} else {
assertSubtype(v, t);
}
return;
}
case Kind.List:
case Kind.Map:
case Kind.Ref:
case Kind.Set:
case Kind.Blob:
case Kind.Struct:
case Kind.Type:
case Kind.Package:
case Kind.Unresolved:
assertSubtype(v, t);
return;
case Kind.Enum:
case Kind.Struct:
default:
throw new Error('unreachable');
}