An open API service indexing awesome lists of open source software.

https://github.com/typelevel/literally

Compile time validation of literal values built from strings
https://github.com/typelevel/literally

Last synced: 8 months ago
JSON representation

Compile time validation of literal values built from strings

Awesome Lists containing this project

README

          

# Literally

Compile time validation of literal values built from strings.

### Rationale

Consider a type like `Port`:

```scala
case class Port private (value: Int)

object Port {
val MinValue = 0
val MaxValue = 65535

def fromInt(i: Int): Option[Port] =
if (i < MinValue || i > MaxValue) None else Some(new Port(i))
}
```

This library simplifies the definition of literal values which are validated at compilation time:

```scala
val p: Port = port"8080"
// p: Port = Port(8080)

val q: Port = port"100000"
// :17: error: invalid port - must be integer between 0 and 65535
```

Validation is performed at compile time. This library provides the macro implementations for both Scala 2 and Scala 3 which powers custom string literals.

### Quick Start

To get started with **literally** in an SBT project add the following dependency to your **build.sbt**:
```
libraryDependencies += "org.typelevel" %% "literally" % ""
```
where `` is the most recent version of **literally**.

### Usage

Defining a custom string literal for a type `A` involves:
- Implementing an `org.typelevel.literally.Literally[A]` instance
- Defining an extension method on a `StringContext` which uses the defined `Literally[A]` instance

```scala
import org.typelevel.literally.Literally

object literals:
extension (inline ctx: StringContext)
inline def port(inline args: Any*): Port =
${PortLiteral('ctx, 'args)}

object PortLiteral extends Literally[Port]:
def validate(s: String)(using Quotes) =
s.toIntOption.flatMap(Port.fromInt) match
case None => Left(s"invalid port - must be integer between ${Port.MinValue} and ${Port.MaxValue}")
case Some(_) => Right('{Port.fromInt(${Expr(s)}.toInt).get})
```

The same pattern is used for Scala 2, though the syntax for extension methods and macros are a bit different:

```scala
import scala.util.Try
import org.typelevel.literally.Literally

object literals {
implicit class short(val sc: StringContext) extends AnyVal {
def port(args: Any*): Port = macro PortLiteral.make
}

object PortLiteral extends Literally[Port] {
def validate(c: Context)(s: String): Either[String, c.Expr[Port]] = {
import c.universe.{Try => _, _}
Try(s.toInt).toOption.flatMap(Port.fromInt) match {
case None => Left(s"invalid port - must be integer between ${Port.MinValue} and ${Port.MaxValue}")
case Some(_) => Right(c.Expr(q"Port.fromInt($s.toInt).get"))
}
}

def make(c: Context)(args: c.Expr[Any]*): c.Expr[Port] = apply(c)(args: _*)
}
}
```

The `tests` directory in this project has more examples.