ZIO Blocks Schema
The fields-zio-blocks-schema module lets Fields policies participate in ZIO Blocks schema decoding without converting errors to a Fields-specific error model. It provides native zio.blocks.schema.SchemaError instances with native DynamicOptic paths and depends only on Fields Core. Applications select the Value or Lens front end and define the matching DSL locally.
libraryDependencies ++= Seq(
"io.github.0lejk4" %% "fields-zio-blocks-schema" % "1.0.0",
"io.github.0lejk4" %% "fields-value" % "1.0.0" // or fields-lens
)
The compatibility module supports Scala 2.13 and Scala 3, matching ZIO Blocks Schema. The same artifact works with both Fields front ends without depending on either one.
Native error DSLβ
For the Value front end, define a synchronous accumulating DSL and mix in ZioBlocksSchemaErrorDsl. Schema transforms run synchronously, and SchemaError can aggregate independent failures.
import fields._
import fields.ZioBlocksSchemaSyntax._
import fields.typeclass.Effect
import zio.blocks.schema.Schema
import zio.blocks.schema.SchemaError
object Validation
extends fields.value.FieldsDsl.BaseAccumulate[Effect.Sync, SchemaError]
with ZioBlocksSchemaErrorDsl
import Validation._
val itemPolicy: Policy[List[String]] = field =>
Rule.andAll(
field.value.zipWithIndex.map { case (value, index) =>
field.down(index, value).nonEmpty
}
)
// itemPolicy: Function1[Field[List[String]], RuleK[Sync, Accumulate, SchemaError]] = repl.MdocSession$MdocApp$$Lambda/0x0000007002e78000@5dd46bce
Mounting a policyβ
withPolicy returns a schema with the policy mounted in its construction path. ZIO Blocks runs the policy while decoding or constructing from a DynamicValue. Valid values decode normally; failures remain native SchemaError values.
val itemSchema = Schema[List[String]].withPolicy(itemPolicy)
val input = List("", "valid", "")
// input: List[String] = List("", "valid", "")
val result = itemSchema.fromDynamicValue(itemSchema.toDynamicValue(input))
// result: Either[SchemaError, List[String]] = Left(
// SchemaError(
// List(
// ConversionFailed(
// source = DynamicOptic(IndexedSeq(Wrapped)),
// details = """must not be empty at: [0]
// must not be empty at: [2]""",
// cause = None
// )
// )
// )
// )
result.left.map(_.message)
// res0: Either[String, List[String]] = Left(
// """must not be empty at: [0]
// must not be empty at: [2] at: .wrapped"""
// )
ZIO Blocks 0.0.33 represents a thrown transform error as one native SchemaError.ConversionFailed. Its details contain the accumulated policy messages and paths. This is native ZIO Blocks transform behavior.
When code needs the individual structured errors rather than the schema-transform envelope, use validateWithPolicy:
itemSchema.validateWithPolicy(itemPolicy)(input)
.left.map(_.errors.map(_.source.toString))
// res1: Either[List[String], List[String]] = Left(List("[0]", "[2]"))
Mounting validates the schema's decoding/construction direction. Encoding uses the original value unchanged; call the policy directly when an already-constructed in-memory value must be checked before encoding.
Lens policiesβ
For the Lens front end, define the equivalent DSL using fields.lens.FieldsDsl. The schema methods have explicit lens names so importing schema syntax never creates an ambiguous withPolicy overload.
object LensValidation
extends fields.lens.FieldsDsl.BaseAccumulate[Effect.Sync, SchemaError]
with ZioBlocksSchemaErrorDsl
import LensValidation._
val lensPolicy: LensValidation.LensPolicy[String] = LensPolicy.root[String](_.nonEmpty)
val lensSchema = Schema[String].withLensPolicy(lensPolicy.root)
lensSchema.validateWithLensPolicy(lensPolicy.root)("")
.left.map(_.errors.map(_.source.toString))
// res2: Either[List[String], String] = Left(List("."))