Skip to content

Commit

Permalink
Merge pull request #61 from UCNot/schema-id
Browse files Browse the repository at this point in the history
Proper schema identifiers
  • Loading branch information
surol authored Jul 17, 2023
2 parents d91beb3 + 8e8c314 commit 004fda2
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 83 deletions.
1 change: 0 additions & 1 deletion src/compiler/deserialization/list.ucrx.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export class ListUcrxClass<
configure: options => {
compiler
.processModel(schema.item)
.useUcrxClass('list', (lib, schema: UcList.Schema) => new this(lib, schema))
.useUcrxClass(schema, (lib, schema: UcList.Schema) => new this(lib, schema, options));
},
};
Expand Down
18 changes: 14 additions & 4 deletions src/compiler/deserialization/map.ucrx.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ export class MapUcrxClass<

return {
configure: variant => {
compiler
.useUcrxClass('map', (lib, schema: UcMap.Schema) => new this(lib, schema))
.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant));
compiler.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant));
for (const entrySchema of Object.values(entries)) {
compiler.processModel(entrySchema);
}
Expand All @@ -59,6 +57,7 @@ export class MapUcrxClass<
readonly #collector: MapUcrxClass$Collector;
readonly #store: MapUcrxStore;
readonly #slot: EsFieldHandle;
readonly #rxClasses = new Map<UcrxClass, UcrxClass>();

constructor(
lib: UcrxLib,
Expand Down Expand Up @@ -415,7 +414,18 @@ export class MapUcrxClass<
entryUcrxFor(key: string | null, schema: UcSchema): UcrxClass {
const entryClass = MapUcrxEntry.ucrxClass(this.#lib, this.schema, key, schema);

return this.#collector.rxs ? new MultiEntryUcrxClass(entryClass) : entryClass;
if (!this.#collector.rxs) {
return entryClass;
}

let rxClass = this.#rxClasses.get(entryClass);

if (!rxClass) {
rxClass = new MultiEntryUcrxClass(entryClass);
this.#rxClasses.set(entryClass, rxClass);
}

return rxClass;
}

createEntry(key: string | null, schema: UcSchema): MapUcrxEntry {
Expand Down
34 changes: 26 additions & 8 deletions src/compiler/impl/uri-charge.compiler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EsCode, EsSignature, EsSnippet, esStringLiteral, esline } from 'esgen';
import { UcList } from '../../schema/list/uc-list.js';
import { UcMap } from '../../schema/map/uc-map.js';
import { COMPILER_MODULE } from '../../impl/module-names.js';
import { UcList, ucList } from '../../schema/list/uc-list.js';
import { UcMap, ucMap } from '../../schema/map/uc-map.js';
import { UcNullable } from '../../schema/uc-nullable.js';
import { UcSchema } from '../../schema/uc-schema.js';
import { ucUnknown } from '../../schema/unknown/uc-unknown.js';
import { URICharge } from '../../schema/uri-charge/uri-charge.js';
import { ListUcrxClass } from '../deserialization/list.ucrx.class.js';
import { MapUcrxClass, MapUcrxStore } from '../deserialization/map.ucrx.class.js';
Expand All @@ -27,20 +28,20 @@ export class URIChargeCompiler extends UcdCompiler<{

constructor() {
super({
models: { parseURICharge: ['sync', ucUnknown() as UcSchema<URICharge>] },
models: { parseURICharge: ['sync', URICharge$Schema] },
features(compiler) {
return {
configure: () => {
compiler
.enable(ucdSupportDefaults)
.useUcrxClass('unknown', (lib, schema) => new URIChargeUcrxClass(lib, schema))
.useUcrxClass(URICharge$Schema, (lib, schema) => new URIChargeUcrxClass(lib, schema))
.useUcrxClass(
'list',
ucList(URICharge$Schema),
(lib, schema: UcList.Schema) => new URIChargeListUcrxClass(lib, schema),
)
.useUcrxClass(
'map',
(lib, schema: UcMap.Schema) => new URIChargeMapUcrxClass(lib, schema),
ucMap({}, { extra: URICharge$Schema }),
(lib, schema) => new URIChargeMapUcrxClass(lib, schema as unknown as UcMap.Schema),
);
},
};
Expand All @@ -61,6 +62,23 @@ export class URIChargeCompiler extends UcdCompiler<{

}

const URICharge$Schema: UcNullable<URICharge> = {
type: 'unknown',
nullable: true,
where: {
deserializer: {
use: 'UnknownUcrxClass',
from: COMPILER_MODULE,
with: 'charge',
},
serializer: {
use: 'ucsSupportUnknown',
from: COMPILER_MODULE,
with: 'charge',
},
},
};

class URIChargeListUcrxClass extends ListUcrxClass {

protected override createNullItem(cx: EsSnippet): EsSnippet {
Expand Down
37 changes: 28 additions & 9 deletions src/compiler/processor/ucc-schema-index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UcProcessorName } from '../../schema/uc-constraints.js';
import { asArray } from '@proc7ts/primitives';
import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js';
import { UcDataType, UcSchema } from '../../schema/uc-schema.js';
import { ucSchemaVariant } from '../impl/uc-schema-variant.js';

Expand Down Expand Up @@ -30,14 +31,31 @@ export class UccSchemaIndex {
}

#createSchemaId(schema: UcSchema): string {
const typeEntry = this.#typeEntry(schema);
const typeId = `${typeEntry.prefix}${ucSchemaVariant(schema)}`;
let { prefix: fullId } = this.#typeEntry(schema);
const variant = ucSchemaVariant(schema);

if (this.processors.some(name => schema.where?.[name])) {
return `${typeId}#${++typeEntry.counter}`;
if (variant) {
fullId += `,${variant}`;
}

return typeId;
const { where = {} } = schema;

return this.processors.reduce((fullId, processorName) => {
const constraints = where[processorName];

if (!constraints) {
return fullId;
}

return asArray(constraints).reduce((fullId, constraint): string => {
const { use, from } = constraint;
const id = constraint.id
? constraint.id(schema, schema => this.schemaId(schema))
: UcsSchemaIndex$defaultConstraintId(constraint);

return fullId + `,${use}@${from}` + (id ? `(${id})` : '');
}, fullId);
}, fullId);
}

#typeEntry({ type }: UcSchema): UccSchemaIndex$TypeEntry {
Expand All @@ -54,14 +72,12 @@ export class UccSchemaIndex {
return this.#addEntry({
type,
prefix: `${prefix}#${++this.#typeCounter}`,
counter: 0,
});
}

return this.#addEntry({
type,
prefix,
counter: 0,
});
}

Expand All @@ -77,5 +93,8 @@ export class UccSchemaIndex {
interface UccSchemaIndex$TypeEntry {
readonly type: string | UcDataType;
readonly prefix: string;
counter: number;
}

function UcsSchemaIndex$defaultConstraintId({ with: options }: UcFeatureConstraint): string {
return options !== undefined ? JSON.stringify(options) : '';
}
10 changes: 10 additions & 0 deletions src/schema/list/uc-list.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ export function createUcListSchema<TItem, TItemSchema extends UcSchema<TItem> =
use: 'ListUcrxClass',
from: COMPILER_MODULE,
with: options,
id: UcList$id,
},
serializer: {
use: 'ucsSupportList',
from: COMPILER_MODULE,
with: options,
id: UcList$id,
},
},
item,
Expand All @@ -45,3 +47,11 @@ export function createUcListSchema<TItem, TItemSchema extends UcSchema<TItem> =
options,
);
}

function UcList$id(
this: { with: UccListOptions },
{ item }: UcList.Schema,
schemaId: (schema: UcSchema) => string,
): string {
return `item:${schemaId(ucSchema(item))},single:${this.with.single}`;
}
29 changes: 29 additions & 0 deletions src/schema/map/uc-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,12 @@ export function ucMap<TEntriesModel extends UcMap.EntriesModel, TExtraModel exte
use: 'MapUcrxClass',
from: COMPILER_MODULE,
with: variant,
id: UcMap$id,
},
serializer: {
use: 'ucsSupportMap',
from: COMPILER_MODULE,
id: UcMap$id,
},
},
entries: Object.fromEntries(entries) as UcMap.Entries<TEntriesModel>,
Expand Down Expand Up @@ -220,3 +222,30 @@ export function ucMap<TEntriesModel extends UcMap.EntriesModel, TExtraModel exte
| undefined,
);
}

function UcMap$id(
this: { with: UcMap.Variant | undefined },
{ entries, extra }: UcMap.Schema,
schemaId: (schema: UcSchema) => string,
): string {
const entryIds = Object.entries(entries)
.map(
([entryName, entryModel]) => esQuoteKey(entryName) + '(' + schemaId(ucSchema(entryModel)) + ')',
)
.join(';');
let id = `entries{${entryIds}}`;

if (extra) {
id += ',extra(' + schemaId(ucSchema(extra)) + ')';
}

const options = this.with;

if (options) {
const { duplicates = 'overwrite' } = options;

id += `,duplicates:${duplicates}`;
}

return id;
}
6 changes: 6 additions & 0 deletions src/schema/numeric/uc-numeric-range.validator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { esEscapeString } from 'esgen';
import type { UcvNumericRange } from '../../compiler/validation/ucv-support-numeric-range.js';
import { COMPILER_MODULE } from '../../impl/module-names.js';
import { UcConstraints } from '../uc-constraints.js';
Expand Down Expand Up @@ -74,6 +75,11 @@ function ucValidateNumericRange(
use: 'ucvSupportNumericRange',
from: COMPILER_MODULE,
with: options,
id(): string {
const [constraint, than, or] = options;

return `${constraint}:${than}` + (or != null ? `,or:${esEscapeString(or)}` : '');
},
},
};
}
14 changes: 14 additions & 0 deletions src/schema/uc-constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ export interface UcFeatureConstraint {
* The format is specific to the feature.
*/
readonly with?: unknown;

/**
* Builds unique identifier based on {@link with additional options}.
*
* This identifier will be appended to full schema identifier.
*
* When omitted, the schema identifier will be built based on JSON representation of options.
*
* @param schema - Target schema.
* @param schemaId - Builds unique schema identifier.
*
* @returns Part of schema identifier.
*/
id?(schema: UcSchema, schemaId: (schema: UcSchema) => string): string;
}

/**
Expand Down
29 changes: 0 additions & 29 deletions src/schema/unknown/parse-uc-value.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,35 +367,6 @@ describe('parseUcValue', () => {
});
});

it('merges maps', () => {
expect(parseUcValue('foo(bar(baz(1)))foo(bar(baz(-)))foo(bar(baz(2)test))')).toEqual({
foo: { bar: { baz: 2, test: '' } },
});
});
it('overrides list', () => {
expect(parseUcValue('foo(bar,baz)foo(bar1,baz1)foo(bar2,baz2)')).toEqual({
foo: ['bar2', 'baz2'],
});
});
it('replaces value with map', () => {
expect(parseUcValue('foo(bar(test))foo(bar(baz(1)test))')).toEqual({
foo: { bar: { baz: 1, test: '' } },
});
});
it('concatenates maps', () => {
expect(parseUcValue('foo(bar(test,test2),bar(baz(1)test(!)),bar(baz(2)test(-)))')).toEqual({
foo: [
{ bar: ['test', 'test2'] },
{ bar: { baz: 1, test: true } },
{ bar: { baz: 2, test: false } },
],
});
});
it('concatenates map and value', () => {
expect(parseUcValue('foo(bar(baz(1),test))')).toEqual({
foo: { bar: [{ baz: 1 }, 'test'] },
});
});
it('stops simple value parsing at closing parent', () => {
expect(parseUcValue('foo)')).toBe('foo');
});
Expand Down
32 changes: 0 additions & 32 deletions src/schema/uri-charge/parse-uri-charge.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,36 +308,4 @@ describe('parseURICharge', () => {
expect(map.toString()).toBe(input);
});
});

it('merges maps', () => {
expect(parse('foo(bar(baz(1)))foo(bar(baz(-)))foo(bar(baz(2)test))')).toHaveURIChargeItems({
foo: { bar: { baz: 2, test: '' } },
});
});
it('overrides list', () => {
expect(parse('foo(bar,baz)foo(bar1,baz1)foo(bar2,baz2)')).toHaveURIChargeItems({
foo: ['bar2', 'baz2'],
});
});
it('replaces value with map', () => {
expect(parse('foo(bar(test))foo(bar(baz(1)test))')).toHaveURIChargeItems({
foo: { bar: { baz: 1, test: '' } },
});
});
it('concatenates maps', () => {
expect(
parse('foo(bar(test,test2),bar(baz(1)test(!)),bar(baz(2)test(-)))'),
).toHaveURIChargeItems({
foo: [
{ bar: ['test', 'test2'] },
{ bar: { baz: 1, test: true } },
{ bar: { baz: 2, test: false } },
],
});
});
it('concatenates map and value', () => {
expect(parse('foo(bar(baz(1),test))')).toHaveURIChargeItems({
foo: { bar: [{ baz: 1 }, 'test'] },
});
});
});

0 comments on commit 004fda2

Please sign in to comment.