Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proper schema identifiers #61

Merged
merged 2 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'] },
});
});
});