feat: map
This commit is contained in:
parent
15ca775611
commit
9fe10408f7
|
@ -53,6 +53,16 @@ export default class Schema {
|
|||
}
|
||||
return value;
|
||||
}
|
||||
case 'map': {
|
||||
const length = view.getUint32(offset.value, true);
|
||||
offset.value += 4;
|
||||
const value = {};
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const key = this.deserialize(view, offset, {type: 'string'});
|
||||
value[key] = this.deserialize(view, offset, specification.value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
case 'object': {
|
||||
const value = {};
|
||||
for (const key in specification.properties) {
|
||||
|
@ -92,6 +102,9 @@ export default class Schema {
|
|||
case 'array': {
|
||||
return [];
|
||||
}
|
||||
case 'map': {
|
||||
return {};
|
||||
}
|
||||
case 'object': {
|
||||
const object = {};
|
||||
for (const key in specification.properties) {
|
||||
|
@ -109,10 +122,6 @@ export default class Schema {
|
|||
return this.constructor.defaultValue(this.specification);
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return key in this.specification;
|
||||
}
|
||||
|
||||
static normalize(specification) {
|
||||
let normalized = specification;
|
||||
switch (specification.type) {
|
||||
|
@ -120,6 +129,10 @@ export default class Schema {
|
|||
normalized.subtype = this.normalize(specification.subtype);
|
||||
break;
|
||||
}
|
||||
case 'map': {
|
||||
normalized.value = this.normalize(specification.value);
|
||||
break;
|
||||
}
|
||||
case 'object': {
|
||||
for (const key in specification.properties) {
|
||||
normalized.properties[key] = this.normalize(specification.properties[key])
|
||||
|
@ -139,6 +152,7 @@ export default class Schema {
|
|||
case 'string': {
|
||||
break;
|
||||
}
|
||||
/* v8 ignore next 2 */
|
||||
default:
|
||||
throw new TypeError(`invalid specification: ${JSON.stringify(specification)}`);
|
||||
}
|
||||
|
@ -154,8 +168,7 @@ export default class Schema {
|
|||
switch (specification.type) {
|
||||
case 'array': {
|
||||
view.setUint32(offset, source.length, true);
|
||||
offset += 4;
|
||||
let arraySize = 0;
|
||||
let arraySize = 4;
|
||||
for (const element of source) {
|
||||
arraySize += this.serialize(
|
||||
element,
|
||||
|
@ -164,7 +177,27 @@ export default class Schema {
|
|||
specification.subtype,
|
||||
);
|
||||
}
|
||||
return 4 + arraySize;
|
||||
return arraySize;
|
||||
}
|
||||
case 'map': {
|
||||
const keys = Object.keys(source);
|
||||
view.setUint32(offset, keys.length, true);
|
||||
let mapSize = 4;
|
||||
for (const key of keys) {
|
||||
mapSize += this.serialize(
|
||||
key,
|
||||
view,
|
||||
offset + mapSize,
|
||||
{type: 'string'},
|
||||
);
|
||||
mapSize += this.serialize(
|
||||
source[key],
|
||||
view,
|
||||
offset + mapSize,
|
||||
specification.value,
|
||||
);
|
||||
}
|
||||
return mapSize;
|
||||
}
|
||||
case 'object': {
|
||||
let objectSize = 0;
|
||||
|
@ -210,6 +243,14 @@ export default class Schema {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'map': {
|
||||
fullSize += 4;
|
||||
for (const key in concrete) {
|
||||
fullSize += this.sizeOf(key, {type: 'string'});
|
||||
fullSize += this.sizeOf(concrete[key], specification.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'object': {
|
||||
for (const key in specification.properties) {
|
||||
fullSize += this.sizeOf(concrete[key], specification.properties[key]);
|
||||
|
@ -231,6 +272,7 @@ export default class Schema {
|
|||
static size(specification) {
|
||||
switch (specification.type) {
|
||||
case 'array': return 0;
|
||||
case 'map': return 0; // TODO could be fixed-size w/ fixed-size key
|
||||
case 'object': {
|
||||
let size = 0;
|
||||
for (const key in specification.properties) {
|
||||
|
@ -258,8 +300,4 @@ export default class Schema {
|
|||
}
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.constructor.size(this.specification);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,14 @@ import {expect, test} from 'vitest';
|
|||
import Schema from './schema.js';
|
||||
|
||||
test('defaults values', () => {
|
||||
const compare = (specification, value) => {
|
||||
expect(Schema.defaultValue(specification))
|
||||
.to.deep.equal(value);
|
||||
expect(new Schema(specification).specification.defaultValue)
|
||||
.to.deep.equal(value);
|
||||
expect(new Schema(specification).defaultValue())
|
||||
.to.deep.equal(value);
|
||||
};
|
||||
[
|
||||
'uint8',
|
||||
'int8',
|
||||
|
@ -15,37 +23,35 @@ test('defaults values', () => {
|
|||
'float32',
|
||||
'float64',
|
||||
].forEach((type) => {
|
||||
expect(Schema.defaultValue({type}))
|
||||
.to.equal(0);
|
||||
expect(new Schema({type}).specification.defaultValue)
|
||||
.to.equal(0);
|
||||
compare({type}, 0);
|
||||
});
|
||||
expect(Schema.defaultValue({type: 'string'}))
|
||||
.to.equal('');
|
||||
expect(new Schema({type: 'string'}).specification.defaultValue)
|
||||
.to.equal('');
|
||||
expect(Schema.defaultValue({type: 'array', subtype: {type: 'string'}}))
|
||||
.to.deep.equal([]);
|
||||
expect(new Schema({type: 'array', subtype: {type: 'string'}}).specification.defaultValue)
|
||||
.to.deep.equal([]);
|
||||
expect(Schema.defaultValue({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
compare({type: 'string'}, '');
|
||||
compare({type: 'array', subtype: {type: 'string'}}, []);
|
||||
compare(
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
},
|
||||
},
|
||||
}))
|
||||
.to.deep.equal({foo: 0, bar: '', baz: {blah: []}});
|
||||
expect(new Schema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
{foo: 0, bar: '', baz: {blah: []}},
|
||||
);
|
||||
compare(
|
||||
{
|
||||
type: 'map',
|
||||
value: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).specification.defaultValue)
|
||||
.to.deep.equal({foo: 0, bar: '', baz: {blah: []}});
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
test('validates a schema', () => {
|
||||
|
@ -66,6 +72,7 @@ test('validates a schema', () => {
|
|||
new Schema({type});
|
||||
new Schema({type: 'array', subtype: {type}});
|
||||
new Schema({type: 'object', properties: {foo: {type}}});
|
||||
new Schema({type: 'map', value: {type}});
|
||||
})
|
||||
.to.not.throw();
|
||||
});
|
||||
|
@ -77,14 +84,16 @@ test('calculates the size of concrete instances', () => {
|
|||
.sizeOf('hi')
|
||||
)
|
||||
.to.equal(4 + (new TextEncoder().encode('hi')).length);
|
||||
|
||||
expect(
|
||||
(new Schema(
|
||||
{type: 'object', properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'uint32'},
|
||||
baz: {type: 'string'},
|
||||
}}
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'uint32'},
|
||||
baz: {type: 'string'},
|
||||
},
|
||||
},
|
||||
))
|
||||
.sizeOf({foo: 69, bar: 420, baz: 'aα'})
|
||||
)
|
||||
|
@ -102,6 +111,36 @@ test('calculates the size of concrete instances', () => {
|
|||
+ 4 + (new TextEncoder().encode('hallo')).length
|
||||
+ 4 + (new TextEncoder().encode('hαllo')).length
|
||||
);
|
||||
expect(
|
||||
(new Schema(
|
||||
{
|
||||
type: 'map',
|
||||
value: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'uint32'},
|
||||
baz: {type: 'string'},
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
.sizeOf({
|
||||
foo: {foo: 69, bar: 420, baz: 'aα'},
|
||||
'aα': {foo: 69, bar: 420, baz: 'meow'},
|
||||
})
|
||||
)
|
||||
.to.equal(
|
||||
4
|
||||
+ 4 + (new TextEncoder().encode('foo')).length
|
||||
+ 1
|
||||
+ 4
|
||||
+ 4 + (new TextEncoder().encode('aα')).length
|
||||
+ 4 + (new TextEncoder().encode('aα')).length
|
||||
+ 1
|
||||
+ 4
|
||||
+ 4 + (new TextEncoder().encode('meow')).length
|
||||
);
|
||||
});
|
||||
|
||||
test('encodes and decodes', () => {
|
||||
|
@ -124,7 +163,9 @@ test('encodes and decodes', () => {
|
|||
[{type: 'string'}, 'α'],
|
||||
[{type: 'array', subtype: {type: 'uint8'}}, [1, 2, 3, 4]],
|
||||
[{type: 'array', subtype: {type: 'string'}}, ['one', 'two', 'three', 'four']],
|
||||
[{type: 'map', value: {type: 'object', properties: {foo: {type: 'uint8'}, bar: {type: 'string'}}}}, {one: {foo: 64, bar: 'baz'}, two: {foo: 128, bar: 'baw'}}],
|
||||
[{type: 'object', properties: {foo: {type: 'uint8'}, bar: {type: 'string'}}}, {foo: 64, bar: 'baz'}],
|
||||
[{type: 'object', properties: {foo: {type: 'uint8'}}}, {foo: 64}],
|
||||
];
|
||||
entries.forEach(([specification, concrete]) => {
|
||||
const schema = new Schema(specification);
|
||||
|
|
Loading…
Reference in New Issue
Block a user