Skip to content

kitlangton/neotype

Repository files navigation

neotype

Release Artifacts Snapshot Artifacts

A friendly newtype library for Scala 3.

"io.github.kitlangton" %% "neotype" % "0.3.5"

Features

  • Compile-time Checked Values
  • Write validations as plain, old Scala expressions
  • Helpful compilation errors (see below)
  • No runtime allocations (Thanks to inline and opaque type)
  • Integrates with other libraries (e.g. zio-json, circe, tapir, etc.)
NEOTYPE VIDEO TOUR

Example

Here is how to define a compile-time validated Newtype.

import neotype.*

// 1. Define a newtype.
object NonEmptyString extends Newtype[String]:

  // 2. Optionally, define a validate method.
  override inline def validate(input: String): Boolean =
    input.nonEmpty

// 3. Construct values.
NonEmptyString("Hello") // OK
NonEmptyString("")      // Compile Error

Attempting to call NonEmptyString("") would result in the following compilation error:

Error: /src/main/scala/examples/Main.scala:9:16
  NonEmptyString("")
  ^^^^^^^^^^^^^^^^^^
  —— Newtype Error ——————————————————————————————————————————————————————————
  NonEmptyString was called with an INVALID String.
  input: ""
  check: input.nonEmpty
  ———————————————————————————————————————————————————————————————————————————

Integrations

Neotype integrates with the following libraries:

ZIO Json Example

import neotype.*

type NonEmptyString = NonEmptyString.Type
object NonEmptyString extends Newtype[String]:
  override inline def validate(value: String): Result =
    if value.nonEmpty then true else "String must not be empty"
import neotype.interop.ziojson.given
import zio.json.*

case class Person(name: NonEmptyString, age: Int) derives JsonCodec

val parsed = """{"name": "Kit", "age": 30}""".fromJson[Person]
// Right(Person(NonEmptyString("Kit"), 30))

val failed = """{"name": "", "age": 30}""".fromJson[Person]
// Left(".name(String must not be empty)")

By importing neotype.ziojson.given, we automatically generate a JsonCodec for NonEmptyString. Custom failure messages are also supported (by overriding def failureMessage in the Newtype definition). Note that import neotype.interop.ziojson.given needs to be in the same file as Person, not NonEmptyString. The generated JsonCodec is not made available to the entire project, but only to the file where it is imported.