Code Monkey home page Code Monkey logo

as's Introduction

as

as is a no-macro, no-reflection, opinionated type refinement library for Scala 3. It is powered by e to handle invalid value errors.

Latest Version Java Version Scala Version
1.0.0 21 3.4.1

Table of Contents

  1. Installation
  2. How to Use
  3. Development and Testing
  4. Releases
  5. Contributing
  6. License

Installation

If you use SBT, add following to your build.sbt:

libraryDependencies += "dev.akif" %% "as" % "1.0.0"

If you use Maven, add following to your pom.xml:

<dependencies>
    <dependency>
        <groupId>dev.akif</groupId>
        <artifactId>as_3</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

If you use Gradle, add following to your project's build.gradle:

dependencies
{
    implementation('dev.akif:as_3:1.0.0')
}

How to Use

Assume we have following Author and Book types.

case class Author(id: Long, name: String)

case class Book(id: Long, authorId: Long, name: String)

Long and String are primitives and may not contain valid values for ids or names. Even if you implement ids and names with their own types to guard against invalid values, author's id is different from a book's id, and they should not be of same type.

To overcome this, we can have separate refined types.

Here is how you can create one, refining Long as AuthorId:

  1. Make an opaque type alias AuthorId = Long in a separate file
    1. Optionally add a subtype constraint <: Long if you want to be able to make AuthorId type a subtype of Long (in other words, to be able to assign an AuthorId value to a Long reference)
  2. Create a companion object extending UnderlyingType as RefinedType (Long as AuthorId in this case)
  3. Implement undefinedValue and validation members
// AuthorId.scala
import dev.akif.as.*

opaque type AuthorId <: Long = Long

// Parenthesis are required because `as` is used as an infix type, which would otherwise be `as[Long, AuthorId]`
object AuthorId extends (Long as AuthorId):
    override val undefinedValue: Long = 0L
   
    override val validation: Validation[Long] =
        Validation.min(lowerLimit = 0L, inclusive = false)

This incurs no additional allocation because AuthorId is defined as an opaque type alias. Within AuthorId.scala file, it can be treated as a Long. However, in any other file, AuthorId and Long are completely different types.

Let's create other types too.

// AuthorName.scala
import dev.akif.as.*

opaque type AuthorName <: String = String

object AuthorName extends (String as AuthorName):
    override val undefinedValue: String = ""

    override val validation: Validation[String] =
       Validation.all(
          Validation.make(predicate = !_.isBlank, failureMessage = _ => "Value is blank"),
          Validation.make(predicate = _.length <= 64, failureMessage = _ => "Value has more than 64 characters")
       )

// BookId.scala
import dev.akif.as.*

opaque type BookId <: Long = Long

object BookId extends (Long as BookId):
    override val undefinedValue: Long = 0L
   
    override val validation: Validation[Long] =
        Validation.min(lowerLimit = 0L, inclusive = false)

// BookName.scala
import dev.akif.as.*

opaque type AuthorName <: String = String

object AuthorName extends (String as AuthorName):
    override val undefinedValue: String = ""

    override val validation: Validation[String] =
        Validation.all(
            Validation.make(predicate = !_.isBlank, failureMessage = _ => "Value is blank"),
            Validation.make(predicate = _.length <= 64, failureMessage = _ => "Value has more than 64 characters")
        )

Now let's change the original example.

case class Book(id: AuthorId, name: AuthorName)

case class Book(id: BookId, authorId: AuthorId, name: BookName)

Now our types are fine-tuned so that they will have correct values, and we are forced to check everything at compile time. We need to use apply method, as extension method or their variants to create a refined value. Creating a refined type by applying an A to an A as B returns an E or B. In other words, object construction can fail and this is reflected to types. Since we have ors in the construction, it's easy to combine them, even in for comprehensions.

import dev.akif.as.*
import e.scala.*

// Failure({"name":"invalid-data","causes":[{"message":"Value is less than or equal to 0"}],"data":{"type":"AuthorId","value":"-1"}})
val authorId1: E or AuthorId = AuthorId(-1L)

// Success(1)
val authorId2: E or AuthorId = AuthorId(1L)

// Can also be written with the extension method
// val authorId2: E or AuthorId = 1L.as[AuthorId]

// Success(Author(1, Mehmet Akif Tütüncü))
val maybeAuthor: E or Author =
    for
       id   <- AuthorId(1L)
       name <- AuthorName("Mehmet Akif Tütüncü")
    yield
        Author(id, name)

In cases where you need to have the refined AuthorId directly instead of E or AuthorId, you can use:

  • AuthorId.applyOrUndefined(-1L) (or -1L.asOrUndefined[AuthorId]) method that will return you the undefined value when validation fails
  • AuthorId.unsafe(-1L) (or -1L.asUnsafe[AuthorId]) method that will throw the validation error as an exception, hence the name unsafe
// Book(0, 0, Type Refinement in Scala)
val book: Book = Book(
    BookId.undefined,
    AuthorId.applyOrEmpty(-1L),
    BookName.unsafe("Type Refinement in Scala")
)

Accessing the unrefined value is possible via the conveniently named extension method value. If defined as a subtype, one can also directly assign a refined value to a reference of the unrefined type.

// 0L
val bookId: Long = book.id.value

// "Type Refinement in Scala"
val bookName: String = book.name // since BookName alias is defined as a subtype of String

Development and Testing

as is built with SBT. You can use clean, compile, test tasks for development and testing.

Releases

as packages are published to Maven Central, and they are versioned according to semantic versioning. Release process is managed by sbt-release.

Contributing

All contributions are welcome, including requests to feature your project utilizing as. Please feel free to send a pull request. Thank you.

License

as is licensed with MIT License.

as's People

Contributors

makiftutuncu avatar renovate[bot] avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

as's Issues

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

sbt
build.sbt
  • scala 3.4.1
  • dev.akif:e-scala 3.0.0
  • org.scalameta:munit 1.0.0-RC1
project/build.properties
  • sbt/sbt 1.10.0
project/plugins.sbt
  • com.github.sbt:sbt-release 1.4.0
  • com.github.sbt:sbt-pgp 2.2.1
  • io.github.davidgregory084:sbt-tpolecat 0.3.3

  • Check this box to trigger a request for Renovate to run again on this repository

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.