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
5 changes: 5 additions & 0 deletions .changeset/remove-schema-stringtree-keep-declarations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Remove the `keepDeclarations` option from `Schema.toCodecStringTree`.
35 changes: 4 additions & 31 deletions packages/effect/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -4646,7 +4646,7 @@ console.log(String(Schema.decodeUnknownExit(schema)(formData)))
// Success({"a":"1","b":{"c":"2","d":"3"}})
```

If you want to decode values that are not strings, use `Schema.toCodecStringTree` with the `keepDeclarations: true` option. This serializer preserves values such as numbers and `Blob` objects when compatible with the schema.
If you want to decode string fields into non-string primitive values, use `Schema.toCodecStringTree`.

**Example** (Parsing non-string values)

Expand All @@ -4657,8 +4657,7 @@ const schema = Schema.fromFormData(
Schema.toCodecStringTree(
Schema.Struct({
a: Schema.Int
}),
{ keepDeclarations: true }
})
)
)

Expand Down Expand Up @@ -4720,7 +4719,7 @@ console.log(String(Schema.decodeUnknownExit(schema)(urlSearchParams)))
// Success({"a":"1","b":{"c":"2","d":"3"}})
```

If you want to decode values that are not strings, use `Schema.toCodecStringTree` with the `keepDeclarations: true` option. This serializer preserves values such as numbers or declarations when compatible with the schema.
If you want to decode values that are not strings, use `Schema.toCodecStringTree`. This serializer preserves values such as numbers when compatible with the schema.

**Example** (Parsing non-string values)

Expand All @@ -4731,8 +4730,7 @@ const schema = Schema.fromURLSearchParams(
Schema.toCodecStringTree(
Schema.Struct({
a: Schema.Int
}),
{ keepDeclarations: true }
})
)
)

Expand Down Expand Up @@ -4995,31 +4993,6 @@ console.log(stringTree)
// [ '1', '2' ]
```

#### keepDeclarations: true

The `keepDeclarations: true` option behaves like the StringTree codec, but it does **not** convert declarations without a `toCodecJson` annotation to `undefined`. Instead, it keeps them as they are.

This is usefult for example when you encode a schema to a `FormData` format and you want to preserve `Blob` values.

```ts
import { Schema } from "effect"

const schema = Schema.Struct({
a: Schema.instanceOf(URL),
b: Schema.Number
})

const stringTree = Schema.toCodecStringTree(schema, { keepDeclarations: true })

console.log(
Schema.encodeUnknownSync(stringTree)({
a: new URL("https://effect.website"),
b: 1
})
)
// { a: URL("https://effect.website"), b: '1' }
```

### ISO Canonical Codec

The ISO canonical codec (`toCodecIso`) converts schemas to their `Iso` representation. This is useful when you want to build isomorphic transformations or optics.
Expand Down
52 changes: 8 additions & 44 deletions packages/effect/src/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11129,10 +11129,8 @@ export interface fromFormData<S extends Constraint> extends decodeTo<S, FormData
*
* You can express nested values using bracket notation.
*
* If you want to decode values that are not strings, use
* `Schema.toCodecStringTree` with the `keepDeclarations: true` option.
* This serializer preserves values such as numbers and `Blob` objects when
* compatible with the schema.
* If you want to decode string fields into non-string primitive values, use
* `Schema.toCodecStringTree`.
*
* **Example** (Decoding a flat structure)
*
Expand Down Expand Up @@ -11186,8 +11184,7 @@ export interface fromFormData<S extends Constraint> extends decodeTo<S, FormData
* Schema.toCodecStringTree(
* Schema.Struct({
* a: Schema.Int
* }),
* { keepDeclarations: true }
* })
* )
* )
*
Expand Down Expand Up @@ -13453,10 +13450,10 @@ export type StringTree = Tree<string | undefined>
* @category Canonical Codecs
* @since 4.0.0
*/
export interface toCodecStringTree<S extends Constraint, Encoded = StringTree> extends
export interface toCodecStringTree<S extends Constraint> extends
BottomLazy<
S["ast"],
toCodecStringTree<S, Encoded>,
toCodecStringTree<S>,
ReadonlyArray<Constraint>,
S["~type.mutability"],
S["~type.optionality"],
Expand All @@ -13466,7 +13463,7 @@ export interface toCodecStringTree<S extends Constraint, Encoded = StringTree> e
>
{
readonly "Type": S["Type"]
readonly "Encoded": Encoded
readonly "Encoded": StringTree
readonly "DecodingServices": S["DecodingServices"]
readonly "EncodingServices": S["EncodingServices"]
readonly "~type.make.in": S["~type.make.in"]
Expand All @@ -13483,33 +13480,11 @@ export interface toCodecStringTree<S extends Constraint, Encoded = StringTree> e
* Declarations are converted to `undefined` (unless they have a
* `toCodecJson` or `toCodec` annotation).
*
* Options:
*
* - `keepDeclarations`: if `true`, it **does not** convert declarations to
* `undefined` but instead keeps them as they are (unless they have a
* `toCodecJson` or `toCodec` annotation).
*
* Defaults to `false`.
*
* @category Canonical Codecs
* @since 4.0.0
*/
export function toCodecStringTree<S extends Constraint>(
schema: S
): toCodecStringTree<S>
export function toCodecStringTree<S extends Constraint>(
schema: S,
options: { readonly keepDeclarations: true } // Used in FormData
): toCodecStringTree<S, unknown>
export function toCodecStringTree<S extends Constraint>(
schema: S,
options?: { readonly keepDeclarations?: boolean | undefined }
): toCodecStringTree<S, unknown> {
return make(
options?.keepDeclarations === true
? serializerStringTreeKeepDeclarations(schema.ast)
: serializerStringTree(schema.ast)
)
export function toCodecStringTree<S extends Constraint>(schema: S): toCodecStringTree<S> {
return make(serializerStringTree(schema.ast))
}

/**
Expand Down Expand Up @@ -13798,17 +13773,6 @@ const unknownToUndefined = new SchemaAST.Link(
)
)

const serializerStringTreeKeepDeclarations = SchemaAST.applyToSelfOrLastLinkEncoding((ast) => {
if (isSerializerArrayFromSingle(ast)) {
return ast
}
const out = serializerTree(ast, serializerStringTreeKeepDeclarations, identity)
if (out !== ast && SchemaAST.isOptional(ast)) {
return SchemaAST.optionalKeyLastLink(out)
}
return out
})

const toArrayFromSingleInputElement = (ast: SchemaAST.AST): SchemaAST.AST =>
SchemaAST.isOptional(ast) ? SchemaAST.optionalKey(SchemaAST.unknown) : SchemaAST.unknown

Expand Down
5 changes: 1 addition & 4 deletions packages/effect/src/SchemaAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3963,10 +3963,7 @@ const StringTree = new Declaration(
isStringTree(input) ?
Effect.succeed(input) :
Effect.fail(new SchemaIssue.InvalidType(ast, Option.some(input))),
{
expected: "StringTree",
toCodecStringTree: () => new Link(unknown, SchemaTransformation.passthrough())
}
{ expected: "StringTree" }
)

/** @internal */
Expand Down
6 changes: 3 additions & 3 deletions packages/effect/src/unstable/httpapi/HttpApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1276,12 +1276,12 @@ export interface StringTree<S extends Schema.Constraint> extends
{}

function ensureStruct(
params: Schema.Struct.Fields | Schema.Constraint | undefined,
transform: typeof Schema.toCodecJson | typeof Schema.toCodecStringTree
params: Schema.Struct.Fields | Schema.Top | undefined,
transform: (schema: Schema.Top) => Schema.Top
): Schema.Top | undefined {
if (params === undefined) return undefined
if (Schema.isSchema(params)) return transform(params)
return transform(Schema.Struct(params as Schema.Struct.Fields))
return transform(Schema.Struct(params))
}

function getPayload(
Expand Down
50 changes: 0 additions & 50 deletions packages/effect/test/schema/toCodec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1583,56 +1583,6 @@ describe("Serializers", () => {
await decoding.succeed("1", "1a")
})

describe("keepDeclarations: true", () => {
describe("Unsupported schemas", () => {
it("Struct with Symbol property name", () => {
const a = Symbol.for("a")
const schema = Schema.Struct({
[a]: Schema.String
})
throws(
() => Schema.toCodecStringTree(schema, { keepDeclarations: true }),
"Objects property names must be strings"
)
})
})

it("should reorder the types in the Union based on the encoded side", async () => {
const schema = Schema.Union([
Schema.String,
Schema.String.pipe(Schema.encodeTo(Schema.BigInt, {
decode: SchemaGetter.transform((n: bigint) => String(n) + "a"),
encode: SchemaGetter.transform(() => 0n)
}))
])
const serializer = Schema.toCodecStringTree(schema, { keepDeclarations: true })
const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer))

const decoding = asserts.decoding()
await decoding.succeed("1", "1a")
})

it("should passthrough the schema if it's a declaration without an annotation", async () => {
const schema = Schema.Struct({
a: Schema.instanceOf(URL),
b: Schema.Number
})
const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema, { keepDeclarations: true }))

const encoding = asserts.encoding()
await encoding.succeed({ a: new URL("https://effect.website"), b: 1 }, {
a: new URL("https://effect.website"),
b: "1"
})

const decoding = asserts.decoding()
await decoding.succeed({
a: new URL("https://effect.website"),
b: "1"
}, { a: new URL("https://effect.website"), b: 1 })
})
})

describe("should return the same reference if nothing changed", () => {
it("String", async () => {
const schema = Schema.String
Expand Down
6 changes: 0 additions & 6 deletions packages/effect/typetest/schema/Schema.tst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,12 +512,6 @@ describe("Schema", () => {
expect(schema).type.toBe<Schema.toCodecStringTree<Schema.FiniteFromString>>()
expect(schema.annotate({})).type.toBe<Schema.toCodecStringTree<Schema.FiniteFromString>>()
})

it("keepDeclarations", () => {
const schema = Schema.toCodecStringTree(Schema.FiniteFromString, { keepDeclarations: true })
expect(Schema.revealCodec(schema)).type.toBe<Schema.Codec<number, unknown, never, never>>()
expect(schema).type.toBe<Schema.toCodecStringTree<Schema.FiniteFromString, unknown>>()
})
})

describe("toCodecArrayFromSingle", () => {
Expand Down
Loading