Skip to content
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
62 changes: 46 additions & 16 deletions packages/dds/tree/src/codec/versioned/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,31 +399,61 @@ export class ClientVersionDispatchingCodecBuilder<
*/
readonly writeVersion: TFormatVersion;
} {
const applied = this.applyOptions(options);
const [applied, decoder] = this.buildDecoderInternal(options);
const writeVersion = getWriteVersion(this.name, options, applied);
const fromFormatVersion = new Map<
FormatVersion,
EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>
>(applied.map((codec) => [codec.formatVersion, codec]));
return {
...decoder,
encode: (data: TDecoded, context: TContext): JsonCompatibleReadOnly => {
return writeVersion.codec.encode(data, context);
},
decode: (data: JsonCompatibleReadOnly, context: TContext): TDecoded => {
const versioned = data as Partial<Versioned>;
const codec = fromFormatVersion.get(versioned.version);
if (codec === undefined) {
throw new UsageError(
`Unsupported version ${versioned.version} encountered while decoding ${this.name} data. Supported versions for this data are: ${versionList(applied)}.
The client which encoded this data likely specified an "minVersionForCollab" value which corresponds to a version newer than the version of this client ("${pkgVersion}").`,
);
}
return codec.codec.decode(data, context);
},
writeVersion: writeVersion.formatVersion,
};
}

private buildDecoderInternal(
options: TBuildOptions,
): [
EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>[],
Pick<
IJsonCodec<TDecoded, JsonCompatibleReadOnly, JsonCompatibleReadOnly, TContext>,
"decode"
>,
] {
const applied = this.applyOptions(options);
const fromFormatVersion = new Map<
FormatVersion,
EvaluatedCodecVersion<TDecoded, TContext, TFormatVersion>
>(applied.map((codec) => [codec.formatVersion, codec]));
return [
applied,
{
decode: (data: JsonCompatibleReadOnly, context: TContext): TDecoded => {
const versioned = data as Partial<Versioned>;
const codec = fromFormatVersion.get(versioned.version);
if (codec === undefined) {
throw new UsageError(
`Unsupported version ${versioned.version} encountered while decoding ${this.name} data. Supported versions for this data are: ${versionList(applied)}.
The client which encoded this data likely specified an "minVersionForCollab" value which corresponds to a version newer than the version of this client ("${pkgVersion}").`,
);
}
return codec.codec.decode(data, context);
},
},
];
}

/**
* Produce a single codec which can read any supported format.
*/
public buildDecoder(
options: TBuildOptions,
): Pick<
IJsonCodec<TDecoded, JsonCompatibleReadOnly, JsonCompatibleReadOnly, TContext>,
"decode"
> {
return this.buildDecoderInternal(options)[1];
}

public getCodecTree(clientVersion: MinimumVersionForCollab): CodecTree {
// TODO: add support for children codecs.
const selected = getWriteVersionNoOverrides(this.registry, clientVersion);
Expand Down
23 changes: 5 additions & 18 deletions packages/dds/tree/src/shared-tree/independentView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,22 @@ import {
createIdCompressor,
} from "@fluidframework/id-compressor/internal";

import {
FluidClientVersion,
type CodecWriteOptions,
type ICodecOptions,
} from "../codec/index.js";
import type { CodecWriteOptions, ICodecOptions } from "../codec/index.js";
import {
type RevisionTag,
RevisionTagCodec,
SchemaFormatVersion,
TreeStoredSchemaRepository,
} from "../core/index.js";
import {
createNodeIdentifierManager,
fieldBatchCodecBuilder,
makeSchemaCodec,
type FieldBatchEncodingContext,
defaultSchemaPolicy,
TreeCompressionStrategy,
defaultIncrementalEncodingPolicy,
schemaCodecBuilder,
} from "../feature-libraries/index.js";
import { combineChunks } from "../feature-libraries/index.js";
// eslint-disable-next-line import-x/no-internal-modules
import type { Format } from "../feature-libraries/schema-index/formatV1.js";
import type {
TreeViewConfiguration,
ImplicitFieldSchema,
Expand Down Expand Up @@ -236,15 +229,9 @@ export function createIndependentTreeAlpha<const TSchema extends ImplicitFieldSc
});

if (options?.content !== undefined) {
// Any version can be passed down to `makeSchemaCodec` and `fieldBatchCodecBuilder.build` here.
// We only use the decode part, which always dispatches to the correct codec based on the version in the data, not `minVersionForCollab`.
const writeOptions: CodecWriteOptions = {
...options,
minVersionForCollab: FluidClientVersion.v2_0,
};
const schemaCodec = makeSchemaCodec(writeOptions, SchemaFormatVersion.v1);
const fieldBatchCodec = fieldBatchCodecBuilder.build(writeOptions);
const newSchema = schemaCodec.decode(options.content.schema as Format);
const schemaCodec = schemaCodecBuilder.buildDecoder(options);
const fieldBatchCodec = fieldBatchCodecBuilder.buildDecoder(options);
const newSchema = schemaCodec.decode(options.content.schema);

const context: FieldBatchEncodingContext = {
encodeType: TreeCompressionStrategy.Compressed,
Expand Down
12 changes: 3 additions & 9 deletions packages/dds/tree/src/shared-tree/sharedTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ import {
makeTreeChunker,
type IncrementalEncodingPolicy,
} from "../feature-libraries/index.js";
// eslint-disable-next-line import-x/no-internal-modules
import { schemaCodecBuilder, type FormatV1 } from "../feature-libraries/schema-index/index.js";
import { schemaCodecBuilder } from "../feature-libraries/index.js";
import {
type BranchId,
clientVersionToEditManagerFormatVersion,
Expand Down Expand Up @@ -498,13 +497,8 @@ export function persistedToSimpleSchema(
persisted: JsonCompatible,
options: ICodecOptions,
): SimpleTreeSchema {
// Any version can be passed down to makeSchemaCodec here.
// We only use the decode part, which always dispatches to the correct codec based on the version in the data, not the version passed to `makeSchemaCodec`.
const schemaCodec = makeSchemaCodec({
...options,
minVersionForCollab: FluidClientVersion.v2_0,
});
const stored = schemaCodec.decode(persisted as FormatV1);
const schemaCodec = schemaCodecBuilder.buildDecoder(options);
const stored = schemaCodec.decode(persisted);
return exportSimpleSchema(stored);
}

Expand Down
22 changes: 4 additions & 18 deletions packages/dds/tree/src/simple-tree/api/storedSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,8 @@

import type { MinimumVersionForCollab } from "@fluidframework/runtime-definitions/internal";

import {
FluidClientVersion,
FormatValidatorNoOp,
type ICodecOptions,
} from "../../codec/index.js";
import { SchemaFormatVersion } from "../../core/index.js";
import { makeSchemaCodec } from "../../feature-libraries/index.js";
import type {
FormatV1,
// eslint-disable-next-line import-x/no-internal-modules
} from "../../feature-libraries/schema-index/index.js";
import { FormatValidatorNoOp, type ICodecOptions } from "../../codec/index.js";
import { makeSchemaCodec, schemaCodecBuilder } from "../../feature-libraries/index.js";
import type { JsonCompatible } from "../../util/index.js";
import type { SchemaUpgrade } from "../core/index.js";
import { normalizeFieldSchema, type ImplicitFieldSchema } from "../fieldSchema.js";
Expand Down Expand Up @@ -102,13 +93,8 @@ export function comparePersistedSchema(
view: ImplicitFieldSchema,
options: ICodecOptions,
): Omit<SchemaCompatibilityStatus, "canInitialize"> {
// Any version can be passed down to makeSchemaCodec here.
// We only use the decode part, which always dispatches to the correct codec based on the version in the data, not the version passed to `makeSchemaCodec`.
const schemaCodec = makeSchemaCodec(
{ ...options, minVersionForCollab: FluidClientVersion.v2_0 },
SchemaFormatVersion.v1,
);
const stored = schemaCodec.decode(persisted as FormatV1);
const schemaCodec = schemaCodecBuilder.buildDecoder(options);
const stored = schemaCodec.decode(persisted);
const config = new TreeViewConfigurationAlpha({
schema: normalizeFieldSchema(view),
});
Expand Down
20 changes: 20 additions & 0 deletions packages/dds/tree/src/test/codec/versioned/codec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,26 @@ The client which encoded this data likely specified an "minVersionForCollab" val
]);
});

describe("buildDecoder", () => {
it("decodes all supported format versions", () => {
const decoder = builder.buildDecoder({ jsonValidator: FormatValidatorBasic });
assert.equal(decoder.decode({ version: 1, value1: 42 }), 42);
assert.equal(decoder.decode({ version: 2, value2: 99 }), 99);
assert.equal(decoder.decode({ version: "X", valueX: 7 }), 7);
});

it("throws UsageError for unsupported version", () => {
const decoder = builder.buildDecoder({ jsonValidator: FormatValidatorBasic });
assert.throws(
() => decoder.decode({ version: 3, value2: 42 }),
validateUsageError(
`Unsupported version 3 encountered while decoding Test data. Supported versions for this data are: [1,2,"X"].
The client which encoded this data likely specified an "minVersionForCollab" value which corresponds to a version newer than the version of this client ("${pkgVersion}").`,
),
);
});
});

it("bad builds", () => {
// Build asserts are debugAsserts, so only test them when those are enabled.
if (nonProductionConditionalsIncluded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ describe("schemaSummarizer", () => {

for (const schemaFormat of schemaCodecBuilder.registry) {
const encode = (schema: TreeStoredSchema): JsonCompatibleReadOnly => {
assert(schemaFormat.minVersionForCollab !== undefined);
const codec = schemaFormat.codec({
jsonValidator: FormatValidatorBasic,
});
const codec = schemaFormat.codec({ jsonValidator: FormatValidatorBasic });
const result: JsonCompatibleReadOnly = codec.encode(schema);
return result;
};
Expand Down
7 changes: 1 addition & 6 deletions packages/dds/tree/src/test/snapshots/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* Licensed under the MIT License.
*/

import { strict as assert } from "node:assert";

import { FormatValidatorBasic } from "../../external-utilities/index.js";
// eslint-disable-next-line import-x/no-internal-modules
import { schemaCodecBuilder } from "../../feature-libraries/schema-index/codec.js";
Expand All @@ -18,11 +16,8 @@ describe("schema snapshots", () => {
for (const schemaFormat of schemaCodecBuilder.registry) {
for (const { name, schemaData } of testTrees) {
it(`${name} - schema v${schemaFormat.formatVersion}`, () => {
assert(schemaFormat.minVersionForCollab !== undefined);
const encoded = schemaFormat
.codec({
jsonValidator: FormatValidatorBasic,
})
.codec({ jsonValidator: FormatValidatorBasic })
.encode(schemaData);
takeJsonSnapshot(encoded);
});
Expand Down
Loading