import { InvalidStateError, LoginError, NotFoundError, NotLoggedInError, OptimisticConcurrencyException, ServiceUnavailableError, UnauthorizedError, ValidationError } from "effect-app/client/errors"
import * as AppSchema from "effect-app/Schema"
import { Class } from "effect-app/Schema/Class"
import { flattenNestedAnyOf, flattenSimpleAllOf, specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi"
import * as Option from "effect/Option"
import * as Predicate from "effect/Predicate"
import * as Schema from "effect/Schema"
import * as S from "effect/Schema"
import * as SchemaGetter from "effect/SchemaGetter"
import { describe, expect, it } from "vitest"
describe("Class", () => {
describe("strict", () => {
it("encoding doesnt accepts plain objects matching the struct (Fields argument)", () => {
class A extends Class("A")({ a: S.String }) {}
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
})
it("encoding rejects plain objects matching the struct (Struct argument)", () => {
class A extends Class("A")(S.Struct({ a: S.String })) {}
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
})
it("decoding still works normally", () => {
class A extends Class("A")({ a: S.String }) {}
const decoded = S.decodeUnknownSync(A)({ a: "hello" })
expect(decoded).toBeInstanceOf(A)
expect((decoded as A).a).toBe("hello")
expect(() => S.decodeUnknownSync(A)(null)).toThrow()
expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
})
it("S.is accepts class instances and not matching plain objects", () => {
class A extends Class("A")({ a: S.String }) {}
expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
expect(S.is(A)({ a: "world" })).toBe(false)
expect(S.is(A)({ a: 1 })).toBe(false)
expect(S.is(A)(null)).toBe(false)
})
it("rejects values that don't match the struct", () => {
class A extends Class("A")({ a: S.String }) {}
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
expect(() => S.encodeUnknownSync(A)("not an object")).toThrow()
})
it("returns a class constructor — new and instanceof work", () => {
class A extends Class("A")({ a: S.String }) {}
const instance = new A({ a: "hello" })
expect(instance).toBeInstanceOf(A)
expect(instance.a).toBe("hello")
})
it("preserves fields and identifier", () => {
class A extends Class("A")({ a: S.String, b: S.Number }) {}
expect(A.identifier).toBe("A")
expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
})
})
describe("non strict", () => {
it("encoding accepts plain objects matching the struct (Fields argument)", () => {
class A extends Class("A")({ a: S.String }, undefined, { strict: false }) {}
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
})
it("encoding accepts plain objects matching the struct (Struct argument)", () => {
class A extends Class("A")(S.Struct({ a: S.String }), undefined, { strict: false }) {}
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
})
it("S.is accepts class instances and matching plain objects", () => {
class A extends Class("A")({ a: S.String }, undefined, { strict: false }) {}
expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
expect(S.is(A)({ a: "world" })).toBe(true)
expect(S.is(A)({ a: 1 })).toBe(false)
expect(S.is(A)(null)).toBe(false)
})
})
})
describe("Class constructor", () => {
describe("strict", () => {
it("works as a base class — new, instanceof, encoding plain objects", () => {
class A extends Class("A")({ a: S.String }) {}
const instance = new A({ a: "hello" })
expect(instance).toBeInstanceOf(A)
expect(instance.a).toBe("hello")
expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
})
it("decoding works normally", () => {
class A extends Class("A")({ a: S.String }) {}
const decoded = S.decodeUnknownSync(A)({ a: "hello" })
expect(decoded).toBeInstanceOf(A)
expect((decoded as A).a).toBe("hello")
expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
})
it("exposes fields, identifier", () => {
class A extends Class("A")({ a: S.String, b: S.Number }) {}
expect(A.identifier).toBe("A")
expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
})
})
describe("non strict", () => {
it("works as a base class — new, instanceof, encoding plain objects", () => {
class A extends Class("A")({ a: S.String }, undefined, { strict: false }) {}
const instance = new A({ a: "hello" })
expect(instance).toBeInstanceOf(A)
expect(instance.a).toBe("hello")
expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
})
})
})
describe("TaggedStruct constructor", () => {
it("works with _tag, make, and encoding plain objects", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
const instance = Circle.make({ radius: 5 })
expect(instance).toEqual({ _tag: "Circle", radius: 5 })
expect(S.encodeUnknownSync(Circle)(instance)).toStrictEqual({ _tag: "Circle", radius: 5 })
expect(S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 10 })).toStrictEqual({ _tag: "Circle", radius: 10 })
expect(() => S.encodeUnknownSync(Circle)(null)).toThrow()
expect(() => S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: "nope" })).toThrow()
})
it("decoding works normally", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
const decoded = S.decodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
expect(decoded).toEqual({ _tag: "Circle", radius: 5 })
})
it("S.decodeSync(S.toType(X)) should report n length schema error", () => {
class X extends S.Opaque()(S.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
try {
S.decodeSync(S.toType(X))({ _tag: "X", n: "a" /* not length 3 */ })
expect.fail("expected decode to fail with a SchemaError")
} catch (error) {
expect(error).toBeInstanceOf(Error)
if (error instanceof Error) {
expect(error.message).toContain("n")
expect(error.message.toLowerCase()).toContain("length")
}
}
})
it("exposes fields", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
expect(Object.keys(Circle.fields)).toContain("_tag")
expect(Object.keys(Circle.fields)).toContain("radius")
})
})
describe("strict declaration option", () => {
it("Class strict: true keeps class-level expected errors", () => {
class X extends Class("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, { strict: true }) {}
expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
})
it("TaggedStruct preserves field-level expected errors", () => {
class X extends S.Opaque()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
})
it("Class with encoded override strict: true keeps class-level expected errors", () => {
class X extends Class("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, {
strict: true
}) {}
expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
})
it("TaggedStruct with opaque wrapper decodes tagged input", () => {
class X extends S.Opaque()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
})
})
describe("Class.copy", () => {
it("creates a new instance with updated fields", () => {
class A extends Class("A")({ a: S.String, b: S.Number }) {}
const instance = new A({ a: "hello", b: 1 })
const copied: A = A.copy(instance, { b: 2 })
expect(copied).toBeInstanceOf(A)
expect(copied.a).toBe("hello")
expect(copied.b).toBe(2)
})
it("accepts a function for updates", () => {
class A extends Class("A")({ a: S.String, b: S.Number }) {}
const instance = new A({ a: "hello", b: 1 })
const copied: A = A.copy(instance, (a) => ({ b: a.b + 1 }))
expect(copied).toBeInstanceOf(A)
expect(copied.b).toBe(2)
})
it("is pipeable", () => {
class A extends Class("A")({ a: S.String, b: S.Number }) {}
const instance = new A({ a: "hello", b: 1 })
const copied: A = A.copy({ b: 2 })(instance)
expect(copied).toBeInstanceOf(A)
expect(copied.b).toBe(2)
})
})
describe("TaggedStruct (opaque).copy", () => {
it("creates a new instance with updated fields", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
const instance = Circle.make({ radius: 5 })
const copied = Circle.copy(instance, { radius: 10 })
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
})
it("accepts a function for updates", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
const instance = Circle.make({ radius: 5 })
const copied = Circle.copy(instance, (c) => ({ radius: c.radius * 2 }))
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
})
})
describe("Struct.copy", () => {
it("creates a new value with updated fields", () => {
const A = AppSchema.Struct({ a: S.String, b: S.Number })
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, { b: 2 })
expect(copied).toEqual({ a: "hello", b: 2 })
expect(copied).not.toBe(instance)
})
it("accepts a function for updates", () => {
const A = AppSchema.Struct({ a: S.String, b: S.Number })
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, (a) => ({ b: a.b + 1 }))
expect(copied).toEqual({ a: "hello", b: 2 })
})
it("allows widening updates against the full struct type", () => {
const A = AppSchema.Struct({
name: S.String,
state: S.Union([
S.Struct({ _tag: S.tag("a"), a: S.String }),
S.Struct({ _tag: S.tag("b"), b: S.Number })
])
})
const instance = A.make({ name: "x", state: { _tag: "a", a: "a" } })
const copied = A.copy(instance, { state: { _tag: "b", b: 1 } })
expect(copied).toEqual({ name: "x", state: { _tag: "b", b: 1 } })
})
it("copy function is preserved when using .annotate()", () => {
const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotate({ title: "A" })
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, { b: 2 })
expect(copied).toEqual({ a: "hello", b: 2 })
expect(copied).not.toBe(instance)
})
it("copy function is preserved when using .annotateKey()", () => {
const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotateKey({ title: "A" })
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, { b: 2 })
expect(copied).toEqual({ a: "hello", b: 2 })
expect(copied).not.toBe(instance)
})
it("copy function is preserved when using .mapFields()", () => {
const A = AppSchema.Struct({ a: S.String, b: S.Number }).mapFields((f) => ({ ...f }))
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, { b: 2 })
expect(copied).toEqual({ a: "hello", b: 2 })
expect(copied).not.toBe(instance)
})
it("copy function is preserved through chained calls", () => {
const A = AppSchema
.Struct({ a: S.String, b: S.Number })
.annotate({ title: "A" })
.annotateKey({ description: "test" })
const instance = A.make({ a: "hello", b: 1 })
const copied = A.copy(instance, { b: 2 })
expect(copied).toEqual({ a: "hello", b: 2 })
expect(copied).not.toBe(instance)
})
})
describe("TaggedStruct.copy", () => {
it("creates a new tagged value with updated fields", () => {
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
const instance = Circle.make({ radius: 5 })
const copied = Circle.copy(instance, { radius: 10 })
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
expect(copied).not.toBe(instance)
})
})
describe("TaggedError", () => {
it("InvalidStateError toString includes the message", () => {
const error = new InvalidStateError("something went wrong")
expect(error.toString()).toContain("something went wrong")
})
it("NotFoundError toString includes the message", () => {
const error = new NotFoundError({ type: "User", id: "123" })
expect(error.toString()).toContain("Didn't find User")
expect(error.toString()).toContain("123")
})
it("ServiceUnavailableError toString includes the message", () => {
const error = new ServiceUnavailableError("service down")
expect(error.toString()).toContain("service down")
})
it("ValidationError toString includes the message", () => {
const error = new ValidationError({ errors: ["field required"] })
expect(error.toString()).toContain("Validation failed")
expect(error.toString()).toContain("field required")
})
it("NotLoggedInError toString includes the message", () => {
const error = new NotLoggedInError("not logged in")
expect(error.toString()).toContain("not logged in")
})
it("LoginError toString includes the message", () => {
const error = new LoginError("login failed")
expect(error.toString()).toContain("login failed")
})
it("UnauthorizedError toString includes the message", () => {
const error = new UnauthorizedError("forbidden")
expect(error.toString()).toContain("forbidden")
})
it("OptimisticConcurrencyException toString includes the message", () => {
const error = new OptimisticConcurrencyException({ message: "conflict" })
expect(error.toString()).toContain("conflict")
})
it("OptimisticConcurrencyException from details toString includes the message", () => {
const error = new OptimisticConcurrencyException({ type: "User", id: "123", code: 409 })
expect(error.toString()).toContain("Existing User 123 record changed")
})
})
describe("SpecialJsonSchema", () => {
it("nullable to optional — from NullOr", () => {
const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => {
const isNullableSchema = "members" in schema
&& globalThis.Array.isArray((schema as any).members)
&& (schema as any).members.length === 2
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined
const innerSchema = nullableMembers
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
: schema
const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema)
return nullableSchema.pipe(
Schema.encodeTo(Schema.optionalKey(innerSchema), {
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
})
)
}
const fromNullOr = nullableDecodedUndefinedEncoded(Schema.NullOr(Schema.String))
const structFromNullOr = Schema.Struct({ status: fromNullOr })
const encode = Schema.encodeUnknownSync(structFromNullOr as any)
const encodedNull = encode({ status: null }) as any
expect("status" in encodedNull).toBe(false)
expect(encode({ status: "test" })).toStrictEqual({ status: "test" })
const decode = Schema.decodeUnknownSync(structFromNullOr as any)
expect(decode({})).toStrictEqual({ status: null })
expect(decode({ status: "test" })).toStrictEqual({ status: "test" })
const doc = specialJsonSchemaDocument(structFromNullOr)
expect(doc).toStrictEqual({
dialect: "draft-2020-12",
schema: {
"type": "object",
"properties": {
"status": { "type": "string" }
}
},
definitions: {}
})
})
it("identifies X universally — deduplicates same-fingerprint references", () => {
const X = Schema.String.annotate({ title: "X", identifier: "X" })
const s = Schema.Struct({
a: Schema.NullOr(X).pipe(
Schema.encodeTo(Schema.optionalKey(X), {
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
})
),
b: Schema.NullOr(X).pipe(
Schema.encodeTo(Schema.optionalKey(X), {
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
})
),
c: Schema.NullOr(X),
d: X,
e: X.pipe(Schema.optionalKey)
})
const doc = specialJsonSchemaDocument(s)
expect(doc).toStrictEqual({
dialect: "draft-2020-12",
schema: {
"type": "object",
"properties": {
"a": { "$ref": "#/$defs/X" },
"b": { "$ref": "#/$defs/X" },
"c": {
"anyOf": [
{ "$ref": "#/$defs/X" },
{ "type": "null" }
]
},
"d": { "$ref": "#/$defs/X" },
"e": { "$ref": "#/$defs/X" }
},
"required": ["c", "d"]
},
definitions: {
X: {
"type": "string",
"title": "X"
}
}
})
})
it("shared annotated schema via helper — deduplicates", () => {
const X = Schema.String.annotate({ title: "X", identifier: "X" })
const cache = new WeakMap()
const nullableDecodedUndefinedEncoded = (schema: Schema.Top) => {
const isNullableSchema = "members" in schema
&& globalThis.Array.isArray((schema as any).members)
&& (schema as any).members.length === 2
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray : undefined
const innerSchema = nullableMembers
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
: schema
const cached = cache.get(innerSchema.ast)
if (cached !== undefined) return cached
const nullableSchema = isNullableSchema ? schema : Schema.NullOr(schema)
const out = nullableSchema.pipe(
Schema.encodeTo(Schema.optionalKey(innerSchema), {
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
})
)
cache.set(innerSchema.ast, out)
return out
}
const structWithShared = Schema.Struct({
a: nullableDecodedUndefinedEncoded(X),
b: nullableDecodedUndefinedEncoded(Schema.NullOr(X)),
c: Schema.NullOr(X),
d: X,
e: X.pipe(Schema.optionalKey)
})
const doc = specialJsonSchemaDocument(structWithShared)
expect(doc).toStrictEqual({
dialect: "draft-2020-12",
schema: {
"type": "object",
"properties": {
"a": { "$ref": "#/$defs/X" },
"b": { "$ref": "#/$defs/X" },
"c": {
"anyOf": [
{ "$ref": "#/$defs/X" },
{ "type": "null" }
]
},
"d": { "$ref": "#/$defs/X" },
"e": { "$ref": "#/$defs/X" }
},
"required": ["c", "d"]
},
definitions: {
X: {
"type": "string",
"title": "X"
}
}
})
})
})
describe("SpecialOpenApi", () => {
it("deduplicates identical components.schemas entries with same base identifier", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {
"/foo": {
get: {
responses: {
200: {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/X" }
}
}
}
}
}
},
"/bar": {
get: {
responses: {
200: {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/X1" }
}
}
}
}
}
}
},
components: {
schemas: {
X: { type: "string", title: "X" },
X1: { type: "string", title: "X" }
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
// X1 should be removed, and $ref to X1 rewritten to X
expect(result.components.schemas).toStrictEqual({
X: { type: "string", title: "X" }
})
expect(
result.paths["/bar"].get.responses[200].content["application/json"].schema
)
.toStrictEqual({ $ref: "#/components/schemas/X" })
})
it("does not deduplicate entries with different representations", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
X: { type: "string", title: "X" },
X1: { type: "number", title: "X" }
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
// Both should remain since they have different representations
expect(result.components.schemas).toStrictEqual({
X: { type: "string", title: "X" },
X1: { type: "number", title: "X" }
})
})
it("returns spec unchanged when no duplicates exist", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
Foo: { type: "string" },
Bar: { type: "number" }
}
}
}
const result = deduplicateOpenApiSchemas(spec)
expect(result).toStrictEqual(spec)
})
it("rewrites nested $ref pointers in allOf/anyOf/oneOf", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {
"/baz": {
post: {
requestBody: {
content: {
"application/json": {
schema: {
anyOf: [
{ $ref: "#/components/schemas/Y1" },
{ type: "null" }
]
}
}
}
}
}
}
},
components: {
schemas: {
Y: { type: "object", properties: { name: { type: "string" } } },
Y1: { type: "object", properties: { name: { type: "string" } } }
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
expect(result.components.schemas).toStrictEqual({
Y: { type: "object", properties: { name: { type: "string" } } }
})
expect(
result.paths["/baz"].post.requestBody.content["application/json"].schema.anyOf[0]
)
.toStrictEqual({ $ref: "#/components/schemas/Y" })
})
it("rewrites $ref pointers inside definitions themselves", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
Inner: { type: "string" },
Inner1: { type: "string" },
Outer: {
type: "object",
properties: {
field: { $ref: "#/components/schemas/Inner1" }
}
}
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
expect(Object.keys(result.components.schemas)).toStrictEqual(["Inner", "Outer"])
expect(result.components.schemas.Outer.properties.field).toStrictEqual({
$ref: "#/components/schemas/Inner"
})
})
it("handles spec without components gracefully", () => {
const spec = { openapi: "3.1.0", info: { title: "Test", version: "1.0" }, paths: {} }
const result = deduplicateOpenApiSchemas(spec)
expect(result).toStrictEqual(spec)
})
it("flattens allOf in components.schemas", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
PositiveInt: {
type: "integer",
allOf: [{ exclusiveMinimum: 0, title: "PositiveInt" }]
}
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
expect(result.components.schemas.PositiveInt).toStrictEqual({
type: "integer",
exclusiveMinimum: 0,
title: "PositiveInt"
})
})
it("does not flatten allOf containing $ref entries", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
Composed: {
type: "object",
allOf: [{ $ref: "#/components/schemas/Base" }]
},
Base: { type: "object", properties: { id: { type: "string" } } }
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
expect(result.components.schemas.Composed).toStrictEqual({
type: "object",
allOf: [{ $ref: "#/components/schemas/Base" }]
})
})
it("does not flatten allOf entries that define their own type", () => {
const spec = {
openapi: "3.1.0",
info: { title: "Test", version: "1.0" },
paths: {},
components: {
schemas: {
Mixed: {
type: "object",
allOf: [{ type: "string", minLength: 1 }]
}
}
}
}
const result = deduplicateOpenApiSchemas(spec) as any
expect(result.components.schemas.Mixed).toStrictEqual({
type: "object",
allOf: [{ type: "string", minLength: 1 }]
})
})
})
describe("flattenSimpleAllOf", () => {
it("flattens constraint-only allOf into parent with type", () => {
const input = {
type: "integer",
allOf: [{ exclusiveMinimum: 0, title: "PositiveInt" }]
}
expect(flattenSimpleAllOf(input)).toStrictEqual({
type: "integer",
exclusiveMinimum: 0,
title: "PositiveInt"
})
})
it("flattens string type with multiple constraints", () => {
const input = {
type: "string",
allOf: [
{ minLength: 1, maxLength: 255 },
{ title: "NonEmptyString255" }
]
}
expect(flattenSimpleAllOf(input)).toStrictEqual({
type: "string",
minLength: 1,
maxLength: 255,
title: "NonEmptyString255"
})
})
it("does not flatten allOf with $ref", () => {
const input = {
type: "object",
allOf: [{ $ref: "#/components/schemas/Base" }]
}
expect(flattenSimpleAllOf(input)).toStrictEqual(input)
})
it("does not flatten allOf entries with their own type", () => {
const input = {
type: "object",
allOf: [{ type: "string", minLength: 1 }]
}
expect(flattenSimpleAllOf(input)).toStrictEqual(input)
})
it("allOf entry wins on property conflict", () => {
const input = {
type: "integer",
title: "OldTitle",
allOf: [{ title: "NewTitle", minimum: 0 }]
}
expect(flattenSimpleAllOf(input)).toStrictEqual({
type: "integer",
title: "NewTitle",
minimum: 0
})
})
})
describe("Post-processing integration — real Effect Schema types", () => {
it("PositiveInt — allOf flattened, no wrapping", () => {
const doc = specialJsonSchemaDocument(AppSchema.PositiveInt)
expect(doc.definitions["PositiveInt"]).toStrictEqual({
type: "integer",
exclusiveMinimum: 0
})
})
it("NonEmptyString255 — multiple allOf constraints merged", () => {
const doc = specialJsonSchemaDocument(AppSchema.NonEmptyString255)
expect(doc.definitions["NonEmptyString255"]).toStrictEqual({
type: "string",
minLength: 1,
maxLength: 255
})
})
it("NullOr(NonEmptyString64k) — null preserved in anyOf, allOf flattened in definition", () => {
const schema = S.Struct({ note: S.NullOr(AppSchema.NonEmptyString64k) })
const doc = specialJsonSchemaDocument(schema)
// null variant preserved (correct JSON Schema for NullOr)
expect(doc.schema).toStrictEqual({
type: "object",
properties: {
note: {
anyOf: [
{ $ref: "#/$defs/NonEmptyString64k" },
{ type: "null" }
]
}
},
required: ["note"]
})
// allOf flattened in the referenced definition
expect(doc.definitions["NonEmptyString64k"]).toStrictEqual({
type: "string",
minLength: 1,
maxLength: 65536
})
})
it("NonNegativeInt — allOf flattened", () => {
const doc = specialJsonSchemaDocument(AppSchema.NonNegativeInt)
expect(doc.definitions["NonNegativeInt"]).toStrictEqual({
type: "integer",
minimum: 0
})
})
it("NullOr union flattens nested anyOf members", () => {
const A = S.String.annotate({ identifier: "A" })
const B = S.Boolean.annotate({ identifier: "B" })
const schema = S.Struct({
value: S.NullOr(S.Union([A, B]))
})
const doc = specialJsonSchemaDocument(schema)
const valueProp = (doc.schema as Record)["properties"]["value"]
expect(valueProp).toStrictEqual({
anyOf: [
{ $ref: "#/$defs/A" },
{ $ref: "#/$defs/B" },
{ type: "null" }
]
})
})
})
describe("flattenNestedAnyOf", () => {
it("flattens nested anyOf with no sibling keys", () => {
const input = {
anyOf: [
{ anyOf: [{ type: "string" }, { type: "number" }] },
{ type: "null" }
]
}
expect(flattenNestedAnyOf(input)).toStrictEqual({
anyOf: [
{ type: "string" },
{ type: "number" },
{ type: "null" }
]
})
})
it("does not flatten anyOf entry with sibling keys", () => {
const input = {
anyOf: [
{ anyOf: [{ type: "string" }], title: "X" },
{ type: "null" }
]
}
// The inner anyOf is not flattened into the outer (sibling "title" prevents it),
// but the single-element inner anyOf is unwrapped within the entry itself
expect(flattenNestedAnyOf(input)).toStrictEqual({
anyOf: [
{ type: "string", title: "X" },
{ type: "null" }
]
})
})
it("unwraps anyOf with single item after flattening", () => {
const input = {
anyOf: [
{ anyOf: [{ type: "string" }] }
]
}
expect(flattenNestedAnyOf(input)).toStrictEqual({ type: "string" })
})
it("unwraps anyOf with single item, merging sibling properties", () => {
const input = {
title: "MyField",
anyOf: [{ type: "string" }]
}
expect(flattenNestedAnyOf(input)).toStrictEqual({
title: "MyField",
type: "string"
})
})
it("recurses into nested objects", () => {
const input = {
properties: {
field: {
anyOf: [
{ anyOf: [{ $ref: "#/defs/A" }, { $ref: "#/defs/B" }] },
{ type: "null" }
]
}
}
}
expect(flattenNestedAnyOf(input)).toStrictEqual({
properties: {
field: {
anyOf: [
{ $ref: "#/defs/A" },
{ $ref: "#/defs/B" },
{ type: "null" }
]
}
}
})
})
it("passes through non-objects unchanged", () => {
expect(flattenNestedAnyOf(null)).toBe(null)
expect(flattenNestedAnyOf(42)).toBe(42)
expect(flattenNestedAnyOf("hello")).toBe("hello")
})
})