Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core refactor #312

Merged
merged 53 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
db90e9d
Rename CaseClass1Rep to ValueClassLike
Sep 1, 2023
68c400e
Create ValueEnumLike and EnumLike interfaces
Sep 4, 2023
f90e0dc
Implement ValueEnumLike and EnumLike interfaces
Sep 4, 2023
58b4cc2
Minor naming fix
Sep 4, 2023
0976595
Rename CaseClass1Rep to ValueClassLike also in tests
Sep 4, 2023
38f7d64
Resolve conflict in changes.md
Sep 4, 2023
882e017
Introduce Enumeration to EnumLike implicit macro
Sep 6, 2023
0be1ac6
Introduce implicit macro Enumeratum to EnumLike converter
Sep 6, 2023
bd32584
Remove scala3 enum support for testing reasons
Sep 6, 2023
b9842cb
Fixed scala2 Enumeration support bug
Sep 7, 2023
01d7f47
Scala 2.12.18 support drop
Sep 7, 2023
2d119f4
Added s3 enum support
Sep 11, 2023
514fee8
Added s3 enumeratum support
Sep 11, 2023
9734840
Introduced support for enumeratum value-enum. In progress
Sep 13, 2023
bee33eb
Added value enum for s3 support. Still bugs in value enumeratum s2
Sep 15, 2023
1d33f57
Added debug comments in KebsValueEnumeratum
Sep 15, 2023
d899b8b
Changed debug comments in KebsValueEnumeratum
Sep 15, 2023
49b60e3
Draft for value enumeratum debug
Sep 19, 2023
aa455ef
Structure types value enums.
luksow Sep 19, 2023
4f44a4c
Merge pull request #1 from luksow/core-refactor
AnthonyGrod Sep 21, 2023
cc85189
Remove redundant wrappers, introduce package files and common tests f…
Oct 9, 2023
4d1ddac
Merge branch 'master' of github.com:theiterators/kebs into core-refactor
pk044 Oct 11, 2023
9790377
Scala 2.12.18 support drop
Sep 7, 2023
41efa96
Revert "Scala 2.12.18 support drop"
pk044 Oct 11, 2023
f6ac945
Merge pull request #2 from pk044/core-refactor
AnthonyGrod Oct 17, 2023
c607c44
Fixed cyclic reference bug
Oct 17, 2023
a2c0688
Introduce package objects in enum and enumeratum tests
Oct 17, 2023
2b964f6
Introduce EnumLike and ValueEnumLike in slick and circe support. Test…
Oct 22, 2023
7f11cc9
Bring back one test for slick and one test for circe that cause errors
Oct 23, 2023
2fc4877
Replace EnumOf and ValueEnumOf in favour of EnumLike and ValueEnumLik…
Oct 23, 2023
01e2143
Moved domain files in http4s-stir and in http4s to dedicated domain p…
Oct 23, 2023
b66483f
Pekko http unmarshall test debug
Oct 31, 2023
98b6ab2
Bring back kebsValueEnumUnmarshaller
Oct 31, 2023
187229c
Solved ambiguity problem. Type safety bug remains
Oct 31, 2023
6be55e2
Throw an exception when generic types in pekko-http unmarshaller macr…
Nov 8, 2023
0f206fa
Remove redundant import
Nov 8, 2023
98e47cc
Rewrite core and enum module to the state before struct types
Nov 20, 2023
4399a08
Replace struct types with mixed trait
Dec 8, 2023
a74a75a
Remove redundant enum macros from core
Dec 8, 2023
2f0b237
Fix http4sStirSupport unmarshaller test
Dec 15, 2023
240a74e
Remove implicits prioritisation
Dec 15, 2023
8db39c8
Update README with a migration guide
Feb 12, 2024
ba3c3a7
Make conversion from Product to VCL optional
Feb 12, 2024
909fd88
Use FlatCaseClass1
Feb 13, 2024
feda380
Rename FlatCaseClass1 to CaseClass1ToValueClass
Feb 13, 2024
c4cda0a
Update README
Feb 13, 2024
abc397f
Proper package naming convention, works for s2
Mar 5, 2024
66ff774
Proper packages naming convention for s2 and s3
Mar 6, 2024
1f88ea8
Remove minor bug in README
Mar 6, 2024
ace5433
Remove minor bug regarding instance keyword in README
Mar 6, 2024
80759c1
Enum interface refactor and minor stylistic fixes
Mar 6, 2024
7532380
Remove instances naming bug
Mar 8, 2024
97df748
Fix naming
Mar 8, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.12.18, 2.13.11, 3.3.0]
scala: [2.13.11, 3.3.0]
java: [temurin@17]
runs-on: ${{ matrix.os }}
steps:
Expand Down
1 change: 0 additions & 1 deletion .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pull_request_rules:
conditions:
- base=master
- author=scala-steward
- status-success=Build and Test (ubuntu-latest, 2.12.18, temurin@17)
- status-success=Build and Test (ubuntu-latest, 2.13.11, temurin@17)
- status-success=Build and Test (ubuntu-latest, 3.3.0, temurin@17)
actions:
Expand Down
66 changes: 49 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A library maintained by [Iterators](https://www.iteratorshq.com).
* [JsonSchema support](#jsonschema-support)
* [Scalacheck support](#scalacheck-support)
* [Kebs for IntelliJ](#kebs-for-intellij)
* [Kebs 2.0 migration guide](#kebs-20-migration-guide)

### Why?

Expand Down Expand Up @@ -171,7 +172,7 @@ class People(tag: Tag) extends Table[Person](tag, "people") {
If you prefer to **mix in trait** instead of import (for example you're using a custom driver like `slick-pg`), you can do it as well:

```scala
import pl.iterators.kebs.Kebs
import pl.iterators.kebs.slick.Kebs
object MyPostgresProfile extends ExPostgresDriver with PgArraySupport {
override val api: API = new API {}
trait API extends super.API with ArrayImplicits with Kebs
Expand Down Expand Up @@ -338,8 +339,8 @@ class People(tag: Tag) extends Table[Person](tag, "people") {

```scala

import pl.iterators.kebs._
import enums._
import pl.iterators.kebs.slick._
import pl.iterators.kebs.slick.enums._

class People(tag: Tag) extends Table[Person](tag, "people") {

Expand All @@ -361,8 +362,8 @@ If you import `enums.lowercase._` or `enums.uppercase._` then it'll save enum na
Of course, enums also work with traits:

```scala
import pl.iterators.kebs.Kebs
import pl.iterators.kebs.enums.KebsEnums
import pl.iterators.kebs.slick.Kebs
import pl.iterators.kebs.slick.enums.KebsEnums

object MyPostgresProfile extends ExPostgresDriver {
override val api: API = new API {}
Expand All @@ -389,12 +390,12 @@ import MyPostgresProfile.api._

kebs-doobie works similarly to [kebs-slick](#--kebs-generates-slick-mappers-for-your-case-class-wrappers-kebs-slick). It provides doobie's `Meta` instances for:

* Instances of `CaseClass1Rep` (value classes, tagged types, opaque types)
* Instances of `ValueClassLike` (value classes, tagged types, opaque types)
* Instances of `InstanceConverter`
* Enumeratum for Scala 2
* Native enums for Scala 3

To make the magic happen, do `import pl.iterators.kebs._` and `import pl.iterators.kebs.enums._` (or `import pl.iterators.kebs.enums.uppercase._` or `import pl.iterators.kebs.enums.lowercase._`).
To make the magic happen, do `import pl.iterators.doobie.kebs._` and `import pl.iterators.kebs.doobie.enums._` (or `import pl.iterators.kebs.doobie.enums.uppercase._` or `import pl.iterators.kebs.doobie.enums.lowercase._`).

#### - kebs eliminates spray-json induced boilerplate (kebs-spray-json)

Expand Down Expand Up @@ -722,8 +723,8 @@ case class Limit(value: Int) extends AnyVal

case class PaginationQuery(sortBy: Column, sortOrder: SortOrder, offset: Offset, limit: Limit)

import pl.iterators.kebs.unmarshallers._
import enums._
import pl.iterators.kebs.akkahttp.unmarshallers._
import pl.iterators.kebs.akkahttp.unmarshallers.enums._

val route = get {
parameters('sortBy.as[Column], 'order.as[SortOrder] ? (SortOrder.Desc: SortOrder), 'offset.as[Offset] ? Offset(0), 'limit.as[Limit])
Expand Down Expand Up @@ -901,13 +902,13 @@ object Tags {
}

object PositiveIntTag {
implicit val PositiveIntCaseClass1Rep = new CaseClass1Rep[PositiveInt, Int](PositiveInt.apply(_), identity)
implicit val PositiveIntValueClassLike = new ValueClassLike[PositiveInt, Int](PositiveInt.apply(_), identity)
}
object IdTag {
implicit def IdCaseClass1Rep[A] = new CaseClass1Rep[Id[A], Int](Id.apply(_), identity)
implicit def IdValueClassLike[A] = new ValueClassLike[Id[A], Int](Id.apply(_), identity)
}
object NameTag {
implicit val NameCaseClass1Rep = new CaseClass1Rep[Name, String](Name.apply(_), identity)
implicit val NameValueClassLike = new ValueClassLike[Name, String](Name.apply(_), identity)
}
}
```
Expand Down Expand Up @@ -946,15 +947,15 @@ There are some conventions that are assumed during generation.
* take a single argument
* return Either (this is not enforced though - you'll have a compilation error later)

Also, `CaseClass1Rep` is generated for each tag meaning you will get a lot of `kebs` machinery for free eg. spray formats etc.
Also, `ValueClassLike` is generated for each tag meaning you will get a lot of `kebs` machinery for free eg. spray formats etc.

### Opaque types

As an alternative to tagged types, Scala 3 provides [opaque types](https://docs.scala-lang.org/scala3/reference/other-new-features/opaques.html).
The principles of opaque types are similar to tagged type. The basic usage of opaque types requires the
same amount of boilerplate as tagged types - e.g. you have to write smart constructors, validations and unwrapping
mechanisms all by hand. `kebs-opaque` is meant to help with that by generating a handful of methods and providing a
`CaseClass1Rep` for an easy typclass derivation.
`ValueClassLike` for an easy typclass derivation.

```scala
import pl.iterators.kebs.opaque._
Expand All @@ -966,10 +967,10 @@ object MyDomain {
```

That's the basic usage. Inside the companion object you will get methods like `from`, `apply`, `unsafe` and extension
method `unwrap` plus an instance of `CaseClass1Rep[ISBN, String]`. A more complete example below.
method `unwrap` plus an instance of `ValueClassLike[ISBN, String]`. A more complete example below.

```scala
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.core.macros.ValueClassLike
import pl.iterators.kebs.opaque._

object MyDomain {
Expand Down Expand Up @@ -1000,7 +1001,7 @@ trait Showable[A] {
def show(a: A): String
}
given Showable[String] = (a: String) => a
given[S, A](using showable: Showable[S], cc1Rep: CaseClass1Rep[A, S]): Showable[A] = (a: A) => showable.show(cc1Rep.unapply(a))
given[S, A](using showable: Showable[S], vcLike: ValueClassLike[A, S]): Showable[A] = (a: A) => showable.show(vcLike.unapply(a))
implicitly[Showable[ISBN]].show(ISBN("1234567890")) // "1234567890"
```

Expand Down Expand Up @@ -1105,3 +1106,34 @@ The code generated by macros in `kebs-tagged-meta` is not visible to IntelliJ ID
plugin that enhances experience with the library by adding support for generated code. You can install it from the IntelliJ Marketplace.
In the Settings/Preferences dialog, select "Plugins" and type "Kebs" into search input (see https://www.jetbrains.com/help/idea/managing-plugins.html for detailed instructions).
You can also use this web page: https://plugins.jetbrains.com/plugin/16069-kebs.

### Kebs 2.0 migration guide

Please be aware that recent changes in the source code might require some changes in your codebase. Follow the guide below to migrate your code to Kebs 2.0:
* If you are using value classes instead of tagged/opaque types, please mix in the `CaseClass1ToValueClass` trait.
* Extend your value-enums with `pl.iterators.kebs.enums.ValueEnumLikeEntry` parameterized with the type of the value.
* Native Scala 3 value-enums:
```scala
enum ColorButRGB(val value: Int) extends ValueEnumLikeEntry[Int] {slick
case Red extends ColorButRGB(0xFF0000)
case Green extends ColorButRGB(0x00FF00)
case Blue extends ColorButRGB(0x0000FF)
}
```
* enumeratum value-enums for Scala 2 and Scala 3:
```scala
sealed abstract class LibraryItem(val value: Int) extends IntEnumEntry with ValueEnumLikeEntry[Int]
object LibraryItem extends IntEnum[LibraryItem] {
case object Book extends LibraryItem(value = 1)
case object Movie extends LibraryItem(value = 2)
case object Magazine extends LibraryItem(3)
case object CD extends LibraryItem(4)
val values = findValues
}
```
* Extend your traits/classes/objects, if inside of one an implicit enum (or value-enum) conversion for `kebs` library's needs should occur, with one of the following traits:
* For Scala 2 and Scala 3 enums from `enumeratum` library: `pl.iterators.kebs.enumeratum.KebsEnumeratum`
* For Scala 2 and Scala 3 value-enums from `enumeratum` library: `pl.iterators.kebs.enumeratum.KebsValueEnumeratum`
* For Scala 3 native value-enums: `pl.iterators.kebs.enums.KebsValueEnum`
* For Scala 2 `scala.Enumeration` enums or Scala 3 native enums: `pl.iterators.kebs.enums.KebsEnum`

Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package pl.iterators.kebs.matchers
package pl.iterators.kebs.akkahttp.matchers

import akka.http.scaladsl.server.{PathMatcher1, PathMatchers}
import enumeratum.{Enum, EnumEntry}
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.core.instances.InstanceConverter
import pl.iterators.kebs.core.macros.ValueClassLike

import scala.language.implicitConversions

trait KebsMatchers extends PathMatchers {

implicit class SegmentIsomorphism[U](segment: PathMatcher1[U]) {
def as[T](implicit rep: CaseClass1Rep[T, U]): PathMatcher1[T] = segment.map(rep.apply)
def as[T](implicit rep: ValueClassLike[T, U]): PathMatcher1[T] = segment.map(rep.apply)
}

implicit class SegmentConversion[Source](segment: PathMatcher1[Source]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package pl.iterators.kebs
package pl.iterators.kebs.akkahttp

package object matchers extends KebsMatchers
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package pl.iterators.kebs.unmarshallers
package pl.iterators.kebs.akkahttp.unmarshallers

import akka.http.scaladsl.unmarshalling.{FromStringUnmarshaller, Unmarshaller}
import pl.iterators.kebs.instances.InstanceConverter
import pl.iterators.kebs.macros.CaseClass1Rep
import pl.iterators.kebs.core.instances.InstanceConverter
import pl.iterators.kebs.core.macros.{CaseClass1ToValueClass, ValueClassLike}

trait KebsUnmarshallers {
implicit def kebsUnmarshaller[A, B](implicit rep: CaseClass1Rep[B, A]): Unmarshaller[A, B] =
trait KebsUnmarshallers extends CaseClass1ToValueClass {
implicit def kebsUnmarshaller[A, B](implicit rep: ValueClassLike[B, A]): Unmarshaller[A, B] =
Unmarshaller.strict[A, B](rep.apply)
@inline
implicit def kebsFromStringUnmarshaller[A, B](implicit rep: CaseClass1Rep[B, A],
implicit def kebsFromStringUnmarshaller[A, B](implicit rep: ValueClassLike[B, A],
fsu: FromStringUnmarshaller[A]): FromStringUnmarshaller[B] =
fsu andThen kebsUnmarshaller(rep)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package pl.iterators.kebs.akkahttp.unmarshallers.enums

import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers._
import akka.http.scaladsl.unmarshalling.{FromStringUnmarshaller, Unmarshaller}
import akka.http.scaladsl.util.FastFuture
import pl.iterators.kebs.core.enums.{EnumLike, ValueEnumLike, ValueEnumLikeEntry}

trait EnumUnmarshallers {
final def enumUnmarshaller[E](`enum`: EnumLike[E]): FromStringUnmarshaller[E] = Unmarshaller { _ =>name =>
`enum`.withNameInsensitiveOption(name) match {
case Some(enumEntry) => FastFuture.successful(enumEntry)
case None =>
FastFuture.failed(new IllegalArgumentException(s"""Invalid value '$name'. Expected one of: ${`enum`.getNamesToValuesMap.keysIterator
.mkString(", ")}"""))
}
}

implicit def kebsEnumUnmarshaller[E](implicit ev: EnumLike[E]): FromStringUnmarshaller[E] =
enumUnmarshaller(ev)
}

trait ValueEnumUnmarshallers {
final def valueEnumUnmarshaller[V, E <: ValueEnumLikeEntry[V]](`enum`: ValueEnumLike[V, E]): Unmarshaller[V, E] = Unmarshaller { _ =>v =>
`enum`.withValueOption(v) match {
case Some(enumEntry) => FastFuture.successful(enumEntry)
case None =>
FastFuture.failed(new IllegalArgumentException(s"""Invalid value '$v'. Expected one of: ${`enum`.getValuesToEntriesMap.keysIterator
.mkString(", ")}"""))
}
}

implicit def kebsValueEnumUnmarshaller[V, E <: ValueEnumLikeEntry[V]](implicit ev: ValueEnumLike[V, E]): Unmarshaller[V, E] =
valueEnumUnmarshaller(ev)

implicit def kebsIntValueEnumFromStringUnmarshaller[E <: ValueEnumLikeEntry[Int]](implicit ev: ValueEnumLike[Int, E]): FromStringUnmarshaller[E] =
intFromStringUnmarshaller andThen valueEnumUnmarshaller(ev)
implicit def kebsLongValueEnumFromStringUnmarshaller[E <: ValueEnumLikeEntry[Long]](implicit ev: ValueEnumLike[Long, E]): FromStringUnmarshaller[E] =
longFromStringUnmarshaller andThen valueEnumUnmarshaller(ev)
implicit def kebsShortValueEnumFromStringUnmarshaller[E <: ValueEnumLikeEntry[Short]](
implicit ev: ValueEnumLike[Short, E]): FromStringUnmarshaller[E] =
shortFromStringUnmarshaller andThen valueEnumUnmarshaller(ev)
implicit def kebsByteValueEnumFromStringUnmarshaller[E <: ValueEnumLikeEntry[Byte]](implicit ev: ValueEnumLike[Byte, E]): FromStringUnmarshaller[E] =
byteFromStringUnmarshaller andThen valueEnumUnmarshaller(ev)
}

trait KebsEnumUnmarshallers extends EnumUnmarshallers with ValueEnumUnmarshallers {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package pl.iterators.kebs.unmarshallers
package pl.iterators.kebs.akkahttp.unmarshallers

package object enums extends KebsEnumUnmarshallers
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package pl.iterators.kebs
package pl.iterators.kebs.akkahttp

package object unmarshallers extends KebsUnmarshallers

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package pl.iterators.kebs
package pl.iterators.kebs.akkahttp.domain

import enumeratum.values.{IntEnum, IntEnumEntry, StringEnum, StringEnumEntry}
import enumeratum.{Enum, EnumEntry}
import pl.iterators.kebs.tag.meta.tagged
import pl.iterators.kebs.tagged._
import pl.iterators.kebs.core.enums.ValueEnumLikeEntry

import java.net.URI
import java.util.UUID
Expand Down Expand Up @@ -47,7 +48,7 @@ object Domain extends Tags {
val values = findValues
}

sealed abstract class LibraryItem(val value: Int) extends IntEnumEntry
sealed abstract class LibraryItem(val value: Int) extends IntEnumEntry with ValueEnumLikeEntry[Int]

object LibraryItem extends IntEnum[LibraryItem] {
case object Book extends LibraryItem(1)
Expand All @@ -63,7 +64,7 @@ object Domain extends Tags {
case class Blue(value: Int)
case class Color(red: Red, green: Green, blue: Blue)

sealed abstract class ShirtSize(val value: String) extends StringEnumEntry
sealed abstract class ShirtSize(val value: String) extends StringEnumEntry with ValueEnumLikeEntry[String]
object ShirtSize extends StringEnum[ShirtSize] {
case object Small extends ShirtSize("S")
case object Medium extends ShirtSize("M")
Expand Down
Loading
Loading