Skip to content

Commit

Permalink
improvement(tree): getJsonSchema supports field schema input (micro…
Browse files Browse the repository at this point in the history
…soft#22579)

The API previously only supported `ImplicitAllowedTypes` input, but did
not allow for `ImplicitFieldSchema` input. This meant that it could not
be called via `getJsonSchema(node.schema)`.
  • Loading branch information
Josmithr authored Sep 26, 2024
1 parent 6fcdf7d commit 96bf7d1
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function getBranch(tree: ITree): TreeBranch;
export function getBranch(view: TreeView<ImplicitFieldSchema>): TreeBranch;

// @alpha
export function getJsonSchema(schema: ImplicitAllowedTypes): JsonTreeSchema;
export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema;

// @public
export type ImplicitAllowedTypes = AllowedTypes | TreeNodeSchema;
Expand Down
6 changes: 3 additions & 3 deletions packages/dds/tree/src/simple-tree/api/getJsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import type { JsonTreeSchema } from "./jsonSchema.js";
import type { ImplicitAllowedTypes } from "../schemaTypes.js";
import type { ImplicitFieldSchema } from "../schemaTypes.js";
import { toJsonSchema } from "./simpleSchemaToJsonSchema.js";
import { getSimpleSchema } from "./getSimpleSchema.js";
import { getOrCreate } from "../../util/index.js";
Expand Down Expand Up @@ -53,7 +53,7 @@ const jsonSchemaCache = new WeakMap<TreeNodeSchema, JsonTreeSchema>();
* "required": ["foo"],
* },
* },
* "anyOf": [ { "$ref": "#/$defs/com.myapp.MyObject" } ],
* "$ref": "#/$defs/com.myapp.MyObject",
* }
* ```
*
Expand All @@ -62,7 +62,7 @@ const jsonSchemaCache = new WeakMap<TreeNodeSchema, JsonTreeSchema>();
*
* @alpha
*/
export function getJsonSchema(schema: ImplicitAllowedTypes): JsonTreeSchema {
export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema {
return getOrCreate(jsonSchemaCache, schema, () => {
const simpleSchema = getSimpleSchema(schema);
return toJsonSchema(simpleSchema);
Expand Down
4 changes: 2 additions & 2 deletions packages/dds/tree/src/simple-tree/api/getSimpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { getOrCreate } from "../../util/index.js";
import type { TreeNodeSchema } from "../core/index.js";
import type { ImplicitAllowedTypes } from "../schemaTypes.js";
import type { ImplicitFieldSchema } from "../schemaTypes.js";
import type { SimpleTreeSchema } from "./simpleSchema.js";
import { toSimpleTreeSchema } from "./viewSchemaToSimpleSchema.js";

Expand Down Expand Up @@ -64,6 +64,6 @@ const simpleSchemaCache = new WeakMap<TreeNodeSchema, SimpleTreeSchema>();
* @privateRemarks In the future, we may wish to move this to a more discoverable API location.
* For now, while still an experimental API, it is surfaced as a free function.
*/
export function getSimpleSchema(schema: ImplicitAllowedTypes): SimpleTreeSchema {
export function getSimpleSchema(schema: ImplicitFieldSchema): SimpleTreeSchema {
return getOrCreate(simpleSchemaCache, schema, () => toSimpleTreeSchema(schema));
}
25 changes: 15 additions & 10 deletions packages/dds/tree/src/simple-tree/api/simpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export type SimpleNodeSchema =
*/
export interface SimpleFieldSchema {
/**
* The kind of object field.
* The kind of tree field.
*/
readonly kind: FieldKind;

Expand All @@ -120,24 +120,29 @@ export interface SimpleFieldSchema {
/**
* A simplified representation of a schema for a tree.
*
* @remarks Contains the complete set of schema {@link SimpleTreeSchema.definitions} required to resolve references
* by schema identifier.
* @remarks Contains the complete set of schema {@link SimpleTreeSchema.definitions} required to resolve references,
* which are represented inline with identifiers.
*
* @sealed
*/
export interface SimpleTreeSchema {
export interface SimpleTreeSchema extends SimpleFieldSchema {
/**
* The complete set of node schema definitions recursively referenced by the tree's {@link SimpleTreeSchema.allowedTypes}.
*
* @remarks the keys are the schemas' {@link TreeNodeSchemaCore.identifier | identifiers}.
* The kind of tree field representing the root of the tree.
*/
readonly definitions: ReadonlyMap<string, SimpleNodeSchema>;
readonly kind: FieldKind;

/**
* The types allowed under the root of the tree.
* The types allowed under the tree root.
*
* @remarks Refers to the types by identifier.
* {@link SimpleTreeSchema.definitions} can be used to resolve these identifiers to their associated schema definition.
* Can be resolved via {@link SimpleTreeSchema.definitions}.
*/
readonly allowedTypes: ReadonlySet<string>;

/**
* The complete set of node schema definitions recursively referenced by the tree's {@link SimpleTreeSchema.allowedTypes}.
*
* @remarks the keys are the schemas' {@link TreeNodeSchemaCore.identifier | identifiers}.
*/
readonly definitions: ReadonlyMap<string, SimpleNodeSchema>;
}
13 changes: 11 additions & 2 deletions packages/dds/tree/src/simple-tree/api/viewSchemaToSimpleSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
normalizeFieldSchema,
type FieldSchema,
type ImplicitAllowedTypes,
type ImplicitFieldSchema,
} from "../schemaTypes.js";
import type {
SimpleArrayNodeSchema,
Expand All @@ -26,18 +27,26 @@ import { NodeKind, type TreeNodeSchema } from "../core/index.js";
/**
* Converts a "view" schema to a "simple" schema representation.
*/
export function toSimpleTreeSchema(schema: ImplicitAllowedTypes): SimpleTreeSchema {
export function toSimpleTreeSchema(schema: ImplicitFieldSchema): SimpleTreeSchema {
const normalizedSchema = normalizeFieldSchema(schema);

const allowedTypes = allowedTypesFromFieldSchema(normalizedSchema);

const definitions = new Map<string, SimpleNodeSchema>();
populateSchemaDefinitionsForField(normalizedSchema, definitions);

return {
const output: Mutable<SimpleTreeSchema> = {
kind: normalizedSchema.kind,
allowedTypes,
definitions,
};

// Include the "description" property only if it's present on the input.
if (normalizedSchema.metadata?.description !== undefined) {
output.description = normalizedSchema.metadata.description;
}

return output;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ValueSchema } from "../../../core/index.js";
describe("simpleSchemaToJsonSchema", () => {
it("Leaf schema", async () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
["test.string", { leafKind: ValueSchema.String, kind: NodeKind.Leaf }],
]),
Expand Down Expand Up @@ -50,6 +51,7 @@ describe("simpleSchemaToJsonSchema", () => {
// Ensure the code throws if a handle is encountered.
it("Leaf node (Fluid Handle)", async () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
["test.handle", { leafKind: ValueSchema.FluidHandle, kind: NodeKind.Leaf }],
]),
Expand All @@ -61,6 +63,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Array schema", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
[
"test.array",
Expand Down Expand Up @@ -105,6 +108,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Map schema", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
["test.map", { kind: NodeKind.Map, allowedTypes: new Set<string>(["test.string"]) }],
["test.string", { leafKind: ValueSchema.String, kind: NodeKind.Leaf }],
Expand Down Expand Up @@ -158,6 +162,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Object schema", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
[
"test.object",
Expand Down Expand Up @@ -254,6 +259,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Object schema including an identifier field", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
[
"test.object",
Expand Down Expand Up @@ -297,6 +303,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Object schema including a union field", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
[
"test.object",
Expand Down Expand Up @@ -347,6 +354,7 @@ describe("simpleSchemaToJsonSchema", () => {

it("Recursive object schema", () => {
const input: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map<string, SimpleNodeSchema>([
[
"test.recursive-object",
Expand Down
31 changes: 31 additions & 0 deletions packages/dds/tree/src/test/simple-tree/getJsonSchema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,37 @@ import { hydrate } from "./utils.js";
import { getJsonValidator } from "./jsonSchemaUtilities.js";

describe("getJsonSchema", () => {
it("Field Schema", async () => {
const schemaFactory = new SchemaFactory("test");
const Schema = schemaFactory.optional(schemaFactory.string, {
metadata: { description: "An optional string." },
});

const actual = getJsonSchema(Schema);

const expected: JsonTreeSchema = {
$defs: {
"com.fluidframework.leaf.string": {
type: "string",
_treeNodeSchemaKind: NodeKind.Leaf,
},
},
$ref: "#/$defs/com.fluidframework.leaf.string",
};
assert.deepEqual(actual, expected);

// Verify that the generated schema is valid.
const validator = getJsonValidator(actual);

// Verify expected data validation behavior.
validator(hydrate(Schema, "Hello world"), true);
validator("Hello world", true);
validator(42, false);
validator({}, false);
validator([], false);
validator(null, false);
});

it("Leaf node", async () => {
const schemaFactory = new SchemaFactory("test");
const Schema = schemaFactory.string;
Expand Down
33 changes: 33 additions & 0 deletions packages/dds/tree/src/test/simple-tree/getSimpleSchema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,39 @@ import {
import { ValueSchema } from "../../core/index.js";

describe("getSimpleSchema", () => {
it("Field Schema", async () => {
const schemaFactory = new SchemaFactory("test");
const Schema = schemaFactory.optional(schemaFactory.string, {
metadata: { description: "An optional string." },
});

const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Optional,
definitions: new Map([
[
"com.fluidframework.leaf.string",
{
leafKind: ValueSchema.String,
kind: NodeKind.Leaf,
},
],
]),
allowedTypes: new Set(["com.fluidframework.leaf.string"]),
description: "An optional string.",
};
assert.deepEqual(actual, expected);
});

it("Leaf node", async () => {
const schemaFactory = new SchemaFactory("test");
const Schema = schemaFactory.string;

const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"com.fluidframework.leaf.string",
Expand All @@ -42,6 +68,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"com.fluidframework.leaf.number",
Expand Down Expand Up @@ -73,6 +100,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.array",
Expand Down Expand Up @@ -100,6 +128,7 @@ describe("getSimpleSchema", () => {

const actual = getSimpleSchema(Schema);
const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.map",
Expand Down Expand Up @@ -131,6 +160,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.object",
Expand Down Expand Up @@ -177,6 +207,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.object",
Expand Down Expand Up @@ -212,6 +243,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.object",
Expand Down Expand Up @@ -257,6 +289,7 @@ describe("getSimpleSchema", () => {
const actual = getSimpleSchema(Schema);

const expected: SimpleTreeSchema = {
kind: FieldKind.Required,
definitions: new Map([
[
"test.recursive-object",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export function getBranch(tree: ITree): TreeBranch;
export function getBranch(view: TreeView<ImplicitFieldSchema>): TreeBranch;

// @alpha
export function getJsonSchema(schema: ImplicitAllowedTypes): JsonTreeSchema;
export function getJsonSchema(schema: ImplicitFieldSchema): JsonTreeSchema;

// @public
export interface IConnection {
Expand Down

0 comments on commit 96bf7d1

Please sign in to comment.