mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-11 10:33:08 -06:00
Add runtime type checking to structs (#1253)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user