-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add cats-xml module
- Loading branch information
Showing
13 changed files
with
517 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
# Erules Cats Xml | ||
The purpose of this module is to provide `Encoder` instances of `erules` types | ||
and the `XmlReportEncoder` instances to produce an XML report. | ||
|
||
**Sbt** | ||
```sbt | ||
libraryDependencies += "com.github.geirolz" %% "erules-core" % "0.0.7" | ||
libraryDependencies += "com.github.geirolz" %% "erules-cats-xml" % "0.0.7" | ||
``` | ||
|
||
### Usage | ||
|
||
Given these data classes | ||
```scala | ||
case class Country(value: String) | ||
case class Age(value: Int) | ||
|
||
case class Citizenship(country: Country) | ||
case class Person( | ||
name: String, | ||
lastName: String, | ||
age: Age, | ||
citizenship: Citizenship | ||
) | ||
``` | ||
|
||
Let's write the rules! | ||
```scala | ||
import erules.core.Rule | ||
import erules.core.RuleVerdict.* | ||
import cats.data.NonEmptyList | ||
import cats.Id | ||
|
||
val checkCitizenship: Rule[Id, Citizenship] = | ||
Rule("Check UK citizenship").apply[Id, Citizenship]{ | ||
case Citizenship(Country("UK")) => Allow.withoutReasons | ||
case _ => Deny.because("Only UK citizenship is allowed!") | ||
} | ||
// checkCitizenship: Rule[Id, Citizenship] = RuleImpl(<function1>,Check UK citizenship,None,None) | ||
|
||
val checkAdultAge: Rule[Id, Age] = | ||
Rule("Check Age >= 18").apply[Id, Age] { | ||
case a: Age if a.value >= 18 => Allow.withoutReasons | ||
case _ => Deny.because("Only >= 18 age are allowed!") | ||
} | ||
// checkAdultAge: Rule[Id, Age] = RuleImpl(<function1>,Check Age >= 18,None,None) | ||
|
||
val allPersonRules: NonEmptyList[Rule[Id, Person]] = NonEmptyList.of( | ||
checkCitizenship | ||
.targetInfo("citizenship") | ||
.contramap(_.citizenship), | ||
checkAdultAge | ||
.targetInfo("age") | ||
.contramap(_.age) | ||
) | ||
// allPersonRules: NonEmptyList[Rule[Id, Person]] = NonEmptyList(RuleImpl(scala.Function1$$Lambda$12894/0x0000000802cbcc38@791d9aef,Check UK citizenship,None,Some(citizenship)), RuleImpl(scala.Function1$$Lambda$12894/0x0000000802cbcc38@4c8568e9,Check Age >= 18,None,Some(age))) | ||
``` | ||
|
||
Import | ||
```scala | ||
import erules.cats.xml.implicits.* | ||
``` | ||
|
||
Define the `Person` encoder | ||
```scala | ||
import cats.xml.codec.Encoder | ||
import cats.xml.XmlNode | ||
import cats.xml.implicits.* | ||
|
||
implicit val personEncoder: Encoder[Person] = Encoder.of(person => | ||
XmlNode("Person") | ||
.withAttributes( | ||
"name" := person.name, | ||
"lastName" := person.lastName, | ||
"age" := person.age.value | ||
) | ||
.withChildren( | ||
XmlNode("Citizenship") | ||
.withAttributes( | ||
"country" := person.citizenship.country.value | ||
) | ||
) | ||
) | ||
``` | ||
|
||
And create the JSON report | ||
```scala | ||
import erules.core.* | ||
import erules.implicits.* | ||
import erules.cats.xml.implicits.* | ||
|
||
import cats.effect.IO | ||
import cats.effect.unsafe.implicits.* | ||
|
||
val person: Person = Person("Mimmo", "Rossi", Age(16), Citizenship(Country("IT"))) | ||
// person: Person = Person(Mimmo,Rossi,Age(16),Citizenship(Country(IT))) | ||
|
||
val result: IO[EngineResult[Person]] = for { | ||
engine <- RulesEngine[IO].withRules[Id, Person](allPersonRules).denyAllNotAllowed | ||
result <- engine.parEval(person) | ||
} yield result | ||
// result: IO[EngineResult[Person]] = IO(...) | ||
|
||
//yolo | ||
result.unsafeRunSync().asXmlReport | ||
// res0: cats.xml.Xml = <EngineResult> | ||
// <Data> | ||
// <Person name="Mimmo" lastName="Rossi" age="16"> | ||
// <Citizenship country="IT"/> | ||
// </Person> | ||
// </Data> | ||
// <Verdict type="Denied"> | ||
// <EvaluatedRules> | ||
// <RuleResult> | ||
// <Rule name="Check UK citizenship" description="" targetInfo="citizenship"> | ||
// <FullDescription> | ||
// Check UK citizenship for citizenship | ||
// </FullDescription> | ||
// </Rule> | ||
// <Verdict type="Deny"> | ||
// <Reasons> | ||
// <Reason> | ||
// Only UK citizenship is allowed! | ||
// </Reason> | ||
// </Reasons> | ||
// </Verdict> | ||
// <ExecutionTime> | ||
// <Duration length="134583" unit="NANOSECONDS"/> | ||
// </ExecutionTime> | ||
// </RuleResult> | ||
// <RuleResult> | ||
// <Rule name="Check Age >= 18" description="" targetInfo="age"> | ||
// <FullDescription>Check Age >= 18 for age</FullDescription> | ||
// </Rule> | ||
// <Verdict type="Deny"> | ||
// <Reasons> | ||
// <Reason>Only >= 18 age are allowed!</Reason> | ||
// </Reasons> | ||
// </Verdict> | ||
// <ExecutionTime> | ||
// <Duration length="8833" unit="NANOSECONDS"/> | ||
// </ExecutionTime> | ||
// </RuleResult> | ||
// </EvaluatedRules> | ||
// </Verdict> | ||
// </EngineResult> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Erules Cats Xml | ||
The purpose of this module is to provide `Encoder` instances of `erules` types | ||
and the `XmlReportEncoder` instances to produce an XML report. | ||
|
||
**Sbt** | ||
```sbt | ||
libraryDependencies += "com.github.geirolz" %% "erules-core" % "@VERSION@" | ||
libraryDependencies += "com.github.geirolz" %% "erules-cats-xml" % "@VERSION@" | ||
``` | ||
|
||
### Usage | ||
|
||
Given these data classes | ||
```scala mdoc:to-string | ||
case class Country(value: String) | ||
case class Age(value: Int) | ||
|
||
case class Citizenship(country: Country) | ||
case class Person( | ||
name: String, | ||
lastName: String, | ||
age: Age, | ||
citizenship: Citizenship | ||
) | ||
``` | ||
|
||
Let's write the rules! | ||
```scala mdoc:to-string | ||
import erules.core.Rule | ||
import erules.core.RuleVerdict.* | ||
import cats.data.NonEmptyList | ||
import cats.Id | ||
|
||
val checkCitizenship: Rule[Id, Citizenship] = | ||
Rule("Check UK citizenship").apply[Id, Citizenship]{ | ||
case Citizenship(Country("UK")) => Allow.withoutReasons | ||
case _ => Deny.because("Only UK citizenship is allowed!") | ||
} | ||
|
||
val checkAdultAge: Rule[Id, Age] = | ||
Rule("Check Age >= 18").apply[Id, Age] { | ||
case a: Age if a.value >= 18 => Allow.withoutReasons | ||
case _ => Deny.because("Only >= 18 age are allowed!") | ||
} | ||
|
||
val allPersonRules: NonEmptyList[Rule[Id, Person]] = NonEmptyList.of( | ||
checkCitizenship | ||
.targetInfo("citizenship") | ||
.contramap(_.citizenship), | ||
checkAdultAge | ||
.targetInfo("age") | ||
.contramap(_.age) | ||
) | ||
``` | ||
|
||
Import | ||
```scala mdoc:silent | ||
import erules.cats.xml.implicits.* | ||
``` | ||
|
||
Define the `Person` encoder | ||
```scala mdoc:silent | ||
import cats.xml.codec.Encoder | ||
import cats.xml.XmlNode | ||
import cats.xml.implicits.* | ||
|
||
implicit val personEncoder: Encoder[Person] = Encoder.of(person => | ||
XmlNode("Person") | ||
.withAttributes( | ||
"name" := person.name, | ||
"lastName" := person.lastName, | ||
"age" := person.age.value | ||
) | ||
.withChildren( | ||
XmlNode("Citizenship") | ||
.withAttributes( | ||
"country" := person.citizenship.country.value | ||
) | ||
) | ||
) | ||
``` | ||
|
||
And create the JSON report | ||
```scala mdoc:to-string | ||
import erules.core.* | ||
import erules.implicits.* | ||
import erules.cats.xml.implicits.* | ||
|
||
import cats.effect.IO | ||
import cats.effect.unsafe.implicits.* | ||
|
||
val person: Person = Person("Mimmo", "Rossi", Age(16), Citizenship(Country("IT"))) | ||
|
||
val result: IO[EngineResult[Person]] = for { | ||
engine <- RulesEngine[IO].withRules[Id, Person](allPersonRules).denyAllNotAllowed | ||
result <- engine.parEval(person) | ||
} yield result | ||
|
||
//yolo | ||
result.unsafeRunSync().asXmlReport | ||
``` |
34 changes: 34 additions & 0 deletions
34
modules/cats-xml/src/main/scala/erules/cats/xml/GenericCatsXmlInstances.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package erules.cats.xml | ||
|
||
import cats.xml.codec.Encoder | ||
import cats.xml.XmlNode | ||
|
||
import scala.concurrent.duration.FiniteDuration | ||
|
||
private[xml] object GenericCatsXmlInstances extends GenericCatsXmlEncoderInstances | ||
private[xml] sealed trait GenericCatsXmlEncoderInstances { | ||
|
||
import cats.xml.syntax.* | ||
|
||
implicit final val finiteDurationEncoder: Encoder[FiniteDuration] = | ||
Encoder.of(a => | ||
XmlNode("Duration").withAttributes( | ||
"length" := a.length, | ||
"unit" := a.unit.name | ||
) | ||
) | ||
|
||
implicit def eitherEncoder[A, B](implicit a: Encoder[A], b: Encoder[B]): Encoder[Either[A, B]] = | ||
Encoder.of { | ||
case Left(v) => v.toXml | ||
case Right(v) => v.toXml | ||
} | ||
|
||
implicit val throwableEncoder: Encoder[Throwable] = | ||
Encoder.of(ex => | ||
XmlNode("Throwable").withAttributes( | ||
"message" := ex.getMessage, | ||
"causeMessage" := Option(ex.getCause).map(_.getMessage).getOrElse("") | ||
) | ||
) | ||
} |
Oops, something went wrong.