Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/schema-utf16-json-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Reject JSON Schema generation for unsupported UTF-16 string-length checks instead of emitting misleading code-point bounds. Runtime validation and array-length mappings are unchanged. Use `isMinCodePoints`, `isMaxCodePoints`, or `isBetweenCodePoints` for code-point semantics, or provide an explicit `toJsonSchema` check annotation to preserve a custom mapping.
61 changes: 45 additions & 16 deletions packages/effect/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8019,9 +8019,11 @@
*
* JSON Schema:
*
* For arrays, this check corresponds to `minItems`. For strings, it corresponds
* to `minLength`. JavaScript counts UTF-16 code units while JSON Schema counts
* Unicode code points, so the two validations can differ for some strings.
* For arrays, this check corresponds to `minItems`. For strings, bounds of zero
* and one correspond to `minLength`. Other string bounds throw during JSON Schema
* generation because UTF-16 code units differ from Unicode code points. Use
* {@link isMinCodePoints} for code-point semantics, or supply a `toJsonSchema`
* check annotation to define an explicit mapping.
*
* Arbitrary:
*
Expand Down Expand Up @@ -8053,7 +8055,14 @@
id: "effect/schema/isMinLength",
payload: { minLength }
},
toJsonSchema: ({ type }) => type === "array" ? { minItems: minLength } : { minLength },
toJsonSchema: ({ type }) => {
if (type === "string" && minLength !== 0 && minLength !== 1) {
throw new globalThis.Error(

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes InvalidToolResultError failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toolJsonSchema src/ai/McpServer.ts:1935:12

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes ToolResultEncodingError failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toolJsonSchema src/ai/McpServer.ts:1935:12

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toolJsonSchema src/ai/McpServer.ts:1935:12

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toolJsonSchema src/ai/McpServer.ts:1935:12

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes InvalidToolResultError failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes ToolResultEncodingError failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes InvalidToolResultError failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes ToolResultEncodingError failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in return mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8060 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/McpServer/McpServer.test.ts > McpServer > registerToolkit > distinguishes handler failures from decoding failures in error mode

Error: Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8060:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23
"Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation."
)
}
return type === "array" ? { minItems: minLength } : { minLength }
},
toCode: () => ({ runtime: `Schema.isMinLength(${minLength})` }),
[InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true,
arbitraryConstraint: {
Expand Down Expand Up @@ -8093,8 +8102,11 @@
*
* JSON Schema:
*
* This check corresponds to the `maxLength` constraint for strings or the
* `maxItems` constraint for arrays in JSON Schema.
* For arrays, this check corresponds to `maxItems`. A string bound of zero
* corresponds to `maxLength: 0`. Other string bounds throw during JSON Schema
* generation because UTF-16 code units differ from Unicode code points. Use
* {@link isMaxCodePoints} for code-point semantics, or supply a `toJsonSchema`
* check annotation to define an explicit mapping.
*
* Arbitrary:
*
Expand All @@ -8115,7 +8127,14 @@
id: "effect/schema/isMaxLength",
payload: { maxLength }
},
toJsonSchema: ({ type }) => type === "array" ? { maxItems: maxLength } : { maxLength },
toJsonSchema: ({ type }) => {
if (type === "string" && maxLength !== 0) {
throw new globalThis.Error(

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/AnthropicStructuredOutput.test.ts > toCodecAnthropic > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toCodecAnthropic src/ai/AnthropicStructuredOutput.ts:63:37 ❯ assertJsonSchema test/ai/AnthropicStructuredOutput.test.ts:8:26 ❯ test/ai/AnthropicStructuredOutput.test.ts:109:7

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Bun)

[effect] test/ai/OpenAiStructuredOutput.test.ts > toCodecOpenAI > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:11 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ toCodecOpenAI src/ai/OpenAiStructuredOutput.ts:68:12

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/AnthropicStructuredOutput.test.ts > toCodecAnthropic > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ Module.toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ Module.toJsonSchemaDocument src/Schema.ts:15291:39

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Deno)

[effect] test/ai/OpenAiStructuredOutput.test.ts > toCodecOpenAI > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/OpenAiStructuredOutput.test.ts > toCodecOpenAI > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ on src/internal/schema/toJsonSchemaDocument.ts:428:28 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:307:18 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23

Check failure on line 8132 in packages/effect/src/Schema.ts

View workflow job for this annotation

GitHub Actions / Test (Node)

[effect] test/ai/AnthropicStructuredOutput.test.ts > toCodecAnthropic > String > String + isMinLength + isMaxLength

Error: Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation. ❯ toJsonSchema src/Schema.ts:8132:17 ❯ compileCheck src/internal/schema/toJsonSchemaDocument.ts:275:25 ❯ recur src/internal/schema/toJsonSchemaDocument.ts:314:21 ❯ src/internal/schema/toJsonSchemaDocument.ts:187:51 ❯ src/Array.ts:3594:89 ❯ Module.<anonymous> src/Function.ts:128:18 ❯ compileJsonSchema src/internal/schema/toJsonSchemaDocument.ts:185:23 ❯ Module.toJsonSchemaDocument src/internal/schema/toJsonSchemaDocument.ts:632:18 ❯ Module.toJsonSchemaDocument src/Schema.ts:15291:39
"Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation."
)
}
return type === "array" ? { maxItems: maxLength } : { maxLength }
},
toCode: () => ({ runtime: `Schema.isMaxLength(${maxLength})` }),
[InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true,
arbitraryConstraint: {
Expand All @@ -8134,9 +8153,11 @@
* JSON Schema:
*
* For arrays, this check corresponds to `minItems` and `maxItems`. For strings,
* it corresponds to `minLength` and `maxLength`. JavaScript counts UTF-16 code
* units while JSON Schema counts Unicode code points, so the two validations
* can differ for some strings.
* only a maximum of zero with a minimum of zero or one is exported directly.
* Other string bounds throw during JSON Schema generation because UTF-16 code
* units differ from Unicode code points. Use {@link isBetweenCodePoints} for
* code-point semantics, or supply a `toJsonSchema` check annotation to define
* an explicit mapping.
*
* Arbitrary:
*
Expand All @@ -8161,10 +8182,16 @@
id: "effect/schema/isBetweenLength",
payload: { minimum, maximum }
},
toJsonSchema: ({ type }) =>
type === "array"
toJsonSchema: ({ type }) => {
if (type === "string" && (maximum !== 0 || (minimum !== 0 && minimum !== 1))) {
throw new globalThis.Error(
"Cannot export Schema.isBetweenLength as JSON Schema length bounds: UTF-16 code units and Unicode code points differ. Use Schema.isBetweenCodePoints or provide a toJsonSchema annotation."
)
}
return type === "array"
? { allOf: [{ minItems: minimum }, { maxItems: maximum }] }
: { allOf: [{ minLength: minimum }, { maxLength: maximum }] },
: { allOf: [{ minLength: minimum }, { maxLength: maximum }] }
},
toCode: () => ({ runtime: `Schema.isBetweenLength(${minimum}, ${maximum})` }),
[InternalAnnotations.STRUCTURAL_ANNOTATION_KEY]: true,
arbitraryConstraint: {
Expand Down Expand Up @@ -15230,9 +15257,11 @@
*
* **Gotchas**
*
* JSON Schema generation is best-effort. String length uses Unicode code points
* in JSON Schema and UTF-16 code units in Effect. A generated `pattern` cannot
* retain JavaScript RegExp flags. Object property checks apply to the original
* JSON Schema generation is best-effort. UTF-16 string-length checks throw when
* their bounds cannot be exported directly as equivalent code-point bounds.
* Use code-point checks or an explicit `toJsonSchema` check annotation instead.
* A generated `pattern` cannot retain JavaScript RegExp flags.
* Object property checks apply to the original
* input in JSON Schema but to the decoded object in Effect. `oneOf` can also
* reject values accepted by overlapping Effect union members. Custom
* `toJsonSchema` annotations are the annotation author's responsibility. When
Expand Down
4 changes: 3 additions & 1 deletion packages/effect/src/SchemaRepresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2450,7 +2450,9 @@ export function toMultiDocument(document: Document): MultiDocument {
*
* - Reference allocation is already fixed in the input `Document`. The inherited `referencePolicy` option has no effect
* here; pass it to {@link toRepresentation} when creating the document.
* - String length, RegExp flags, decoded-object property checks, and `oneOf` can differ from Effect validation.
* - UTF-16 string-length checks throw when their bounds have no supported equivalent JSON Schema mapping.
* Use code-point checks or an explicit `toJsonSchema` check annotation instead.
* - RegExp flags, decoded-object property checks, and `oneOf` can differ from Effect validation.
* - Opaque declarations are represented by an unconstrained JSON Schema.
* - Check callback results are used directly, and exceptions raised by a callback pass through unchanged. Callbacks
* must treat their input schemas as immutable. Each returned value must be a valid JSON Schema object graph and must
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe("Schema.toJsonSchemaDocument", () => {
})

it("preserves output, references and generation options", () => {
const shared = Schema.String.check(Schema.isMinLength(2)).annotate({
const shared = Schema.String.check(Schema.isMinCodePoints(2)).annotate({
identifier: "Shared",
description: "shared text",
"x-consumer": "kept"
Expand Down
56 changes: 19 additions & 37 deletions packages/effect/test/schema/toJsonSchemaDocument.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ describe("toJsonSchemaDocument", () => {

it("String & check", () => {
assertJsonSchemaDocument(
Schema.String.check(Schema.isMinLength(2)),
Schema.String.check(Schema.isMinCodePoints(2)),
{
schema: {
"type": "string",
Expand All @@ -910,7 +910,7 @@ describe("toJsonSchemaDocument", () => {

it("String & annotate & check", () => {
assertJsonSchemaDocument(
Schema.String.annotate({ description: "a" }).check(Schema.isMinLength(2)),
Schema.String.annotate({ description: "a" }).check(Schema.isMinCodePoints(2)),
{
schema: {
"type": "string",
Expand All @@ -923,7 +923,7 @@ describe("toJsonSchemaDocument", () => {

it("String & check & annotate", () => {
assertJsonSchemaDocument(
Schema.String.check(Schema.isMinLength(2)).annotate({
Schema.String.check(Schema.isMinCodePoints(2)).annotate({
description: "a"
}),
{
Expand All @@ -938,7 +938,7 @@ describe("toJsonSchemaDocument", () => {

it("String & check & check", () => {
assertJsonSchemaDocument(
Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(3)),
Schema.String.check(Schema.isMinCodePoints(2), Schema.isMaxCodePoints(3)),
{
schema: {
"type": "string",
Expand All @@ -951,7 +951,7 @@ describe("toJsonSchemaDocument", () => {

it("String & annotate & check & check", () => {
assertJsonSchemaDocument(
Schema.String.annotate({ description: "a" }).check(Schema.isMinLength(2), Schema.isMaxLength(3)),
Schema.String.annotate({ description: "a" }).check(Schema.isMinCodePoints(2), Schema.isMaxCodePoints(3)),
{
schema: {
"type": "string",
Expand All @@ -965,7 +965,7 @@ describe("toJsonSchemaDocument", () => {

it("String & check & check & annotate", () => {
assertJsonSchemaDocument(
Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(3)).annotate({
Schema.String.check(Schema.isMinCodePoints(2), Schema.isMaxCodePoints(3)).annotate({
description: "a"
}),
{
Expand All @@ -982,8 +982,8 @@ describe("toJsonSchemaDocument", () => {
it("String & annotate & check & check & annotate", () => {
assertJsonSchemaDocument(
Schema.String.annotate({ description: "a" }).check(
Schema.isMinLength(2),
Schema.isMaxLength(3, { description: "c" })
Schema.isMinCodePoints(2),
Schema.isMaxCodePoints(3, { description: "c" })
),
{
schema: {
Expand All @@ -1004,8 +1004,8 @@ describe("toJsonSchemaDocument", () => {
it("String & check & annotations & check & annotations", () => {
assertJsonSchemaDocument(
Schema.String.check(
Schema.isMinLength(2, { description: "b" }),
Schema.isMaxLength(3, { description: "c" })
Schema.isMinCodePoints(2, { description: "b" }),
Schema.isMaxCodePoints(3, { description: "c" })
),
{
schema: {
Expand All @@ -1026,8 +1026,8 @@ describe("toJsonSchemaDocument", () => {
it("String & annotations & check & annotations & check & annotations", () => {
assertJsonSchemaDocument(
Schema.String.annotate({ description: "a" }).check(
Schema.isMinLength(2, { description: "b" }),
Schema.isMaxLength(3, { description: "c" })
Schema.isMinCodePoints(2, { description: "b" }),
Schema.isMaxCodePoints(3, { description: "c" })
),
{
schema: {
Expand Down Expand Up @@ -1133,17 +1133,9 @@ describe("toJsonSchemaDocument", () => {

describe("isBetweenLength", () => {
it("String", () => {
assertJsonSchemaDocument(
assertUnsupportedSchema(
Schema.String.check(Schema.isBetweenLength(2, 2)),
{
schema: {
"type": "string",
"allOf": [
{ "minLength": 2 },
{ "maxLength": 2 }
]
}
}
"Cannot export Schema.isBetweenLength as JSON Schema length bounds: UTF-16 code units and Unicode code points differ. Use Schema.isBetweenCodePoints or provide a toJsonSchema annotation."
)
})

Expand Down Expand Up @@ -1208,14 +1200,9 @@ describe("toJsonSchemaDocument", () => {

describe("isMinLength", () => {
it("String", () => {
assertJsonSchemaDocument(
assertUnsupportedSchema(
Schema.String.check(Schema.isMinLength(2)),
{
schema: {
"type": "string",
"minLength": 2
}
}
"Cannot export Schema.isMinLength as JSON Schema minLength: UTF-16 code units and Unicode code points differ. Use Schema.isMinCodePoints or provide a toJsonSchema annotation."
)
})

Expand Down Expand Up @@ -1258,14 +1245,9 @@ describe("toJsonSchemaDocument", () => {

describe("isMaxLength", () => {
it("String", () => {
assertJsonSchemaDocument(
assertUnsupportedSchema(
Schema.String.check(Schema.isMaxLength(2)),
{
schema: {
"type": "string",
"maxLength": 2
}
}
"Cannot export Schema.isMaxLength as JSON Schema maxLength: UTF-16 code units and Unicode code points differ. Use Schema.isMaxCodePoints or provide a toJsonSchema annotation."
)
})

Expand Down Expand Up @@ -2937,7 +2919,7 @@ describe("toJsonSchemaDocument", () => {

it("does not use a partial pattern as an index selector", () => {
const schema = Schema.Record(
Schema.String.check(Schema.isStartingWith("x"), Schema.isMinLength(3)),
Schema.String.check(Schema.isStartingWith("x"), Schema.isMinCodePoints(3)),
Schema.Finite
)
assertJsonSchemaDocument(
Expand Down
71 changes: 71 additions & 0 deletions packages/effect/test/schema/toJsonSchemaStringLength.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { assert, describe, it } from "@effect/vitest"
import { Schema, SchemaRepresentation } from "effect"

describe("JSON Schema exports of UTF-16 string lengths", () => {
it("rejects misleading bounds without changing runtime validation", () => {
const cases = [
[Schema.isMinLength(2), true, "isMinCodePoints"],
[Schema.isMaxLength(1), false, "isMaxCodePoints"],
[Schema.isBetweenLength(1, 1), false, "isBetweenCodePoints"]
] as const

for (const [check, acceptsEmoji, alternative] of cases) {
const schema = Schema.String.check(check)
assert.strictEqual(Schema.is(schema)("😀"), acceptsEmoji)
assert.throws(() => Schema.toJsonSchemaDocument(schema), alternative)
assert.throws(
() => SchemaRepresentation.toJsonSchemaDocument(Schema.toRepresentation(schema)),
alternative
)
}
})

it("preserves equivalent empty and non-empty string bounds", () => {
for (
const [check, keywords] of [
[Schema.isMinLength(0), { minLength: 0 }],
[Schema.isMinLength(1), { minLength: 1 }],
[Schema.isMaxLength(0), { maxLength: 0 }],
[Schema.isBetweenLength(0, 0), { allOf: [{ minLength: 0 }, { maxLength: 0 }] }]
] as const
) {
assert.deepStrictEqual(Schema.toJsonSchemaDocument(Schema.String.check(check)).schema, {
type: "string",
...keywords
})
}
assert.deepStrictEqual(Schema.toJsonSchemaDocument(Schema.NonEmptyString).schema, {
type: "string",
minLength: 1
})
})

it("allows an explicit check annotation to describe UTF-16 semantics", () => {
const schema = Schema.String.check(Schema.isMaxLength(1, {
toJsonSchema: () => ({ pattern: "^[\\u0000-\\uFFFF]?(?![\\s\\S])" })
}))
assert.deepStrictEqual(Schema.toJsonSchemaDocument(schema).schema, {
type: "string",
pattern: "^[\\u0000-\\uFFFF]?(?![\\s\\S])"
})
// oxlint-disable-next-line no-control-regex -- The range covers all UTF-16 code units, including controls.
const validate = new RegExp("^[\\u0000-\\uFFFF]?(?![\\s\\S])", "u")
for (const input of ["", "a", "é", "😀", "e\u0301", "\uD800", "\uDC00", "\n", "a\n"]) {
assert.strictEqual(validate.test(input), Schema.is(schema)(input))
}
})

it("reports unsupported bounds inside a property or definition", () => {
const value = Schema.String.check(Schema.isMaxLength(2)).annotate({ identifier: "Label" })
assert.throws(
() => Schema.toJsonSchemaDocument(Schema.Struct({ value })),
"isMaxCodePoints"
)
})

it("preserves Char validation while rejecting its UTF-16 length export", () => {
assert.isTrue(Schema.is(Schema.Char)("a"))
assert.isFalse(Schema.is(Schema.Char)("😀"))
assert.throws(() => Schema.toJsonSchemaDocument(Schema.Char), "isBetweenCodePoints")
})
})
Loading