Skip to content

Commit

Permalink
Kpoliwka/create case class from yaml (#534)
Browse files Browse the repository at this point in the history
* created crd module in codegen using CR overlays for kubernetes @polkx
* moved crd module to separate crd2besom module @lbialy 

---------

Co-authored-by: Łukasz Biały <[email protected]>
  • Loading branch information
polkx and lbialy authored Aug 28, 2024
1 parent fffd9c9 commit 2f59f60
Show file tree
Hide file tree
Showing 18 changed files with 1,588 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ src/main/scala/besom/rpc
.out/

*.asc

20 changes: 20 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,26 @@ publish-local-codegen: test-codegen
publish-maven-codegen: test-codegen
scala-cli --power publish {{no-bloop-ci}} codegen --project-version {{besom-version}} {{publish-maven-auth-options}} --suppress-experimental-feature-warning

####################
# crd2besom
####################

# Compiles crd2besom module
compile-crd2besom:
scala-cli --power compile {{no-bloop-ci}} crd2besom --suppress-experimental-feature-warning

# Runs tests for crd2besom
test-crd2besom:
scala-cli --power test {{no-bloop-ci}} crd2besom --suppress-experimental-feature-warning

# Cleans crd2besom build
clean-crd2besom:
scala-cli clean crd2besom

# Build crd2besom binary
build-crd2besom:
scala-cli --power package {{no-bloop-ci}} crd2besom --suppress-experimental-feature-warning --graal -o .out/crd2besom/bin/$(arch)/crd2besom

####################
# Integration testing
####################
Expand Down
1 change: 1 addition & 0 deletions codegen/project.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//> using scala 3.3.1
//> using options -release:11 -deprecation -Werror -Wunused:all -Wvalue-discard -Wnonunit-statement

//> using dep org.virtuslab::scala-yaml:0.1.0
//> using dep org.scalameta:scalameta_2.13:4.8.15
//> using dep com.lihaoyi::upickle:3.1.4
//> using dep com.lihaoyi::os-lib:0.9.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object CustomResource:
def apply[A: besom.types.Encoder: besom.types.Decoder](using ctx: besom.types.Context)(
name: besom.util.NonEmptyString,
args: CustomResourceArgs[A],
opts: besom.ResourceOptsVariant.Custom ?=> besom.CustomResourceOptions = besom.CustomResourceOptions()
opts: besom.ResourceOptsVariant.Component ?=> besom.ComponentResourceOptions = besom.ComponentResourceOptions()
): besom.types.Output[CustomResource[A]] = {
val resourceName = besom.types.ResourceType.unsafeOf(s"kubernetes:${args.apiVersion}:${args.kind}")
given besom.types.ResourceDecoder[CustomResource[A]] = besom.internal.ResourceDecoder.derived[CustomResource[A]]
Expand All @@ -24,7 +24,7 @@ object CustomResource:
resourceName,
name,
args,
opts(using besom.ResourceOptsVariant.Custom)
opts(using besom.ResourceOptsVariant.Component)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object CustomResourcePatch:
def apply[A: besom.types.Encoder: besom.types.Decoder](using ctx: besom.types.Context)(
name: besom.util.NonEmptyString,
args: CustomResourcePatchArgs[A],
opts: besom.ResourceOptsVariant.Custom ?=> besom.CustomResourceOptions = besom.CustomResourceOptions()
opts: besom.ResourceOptsVariant.Component ?=> besom.ComponentResourceOptions = besom.ComponentResourceOptions()
): besom.types.Output[CustomResourcePatch[A]] = {
val resourceName = besom.types.ResourceType.unsafeOf(s"kubernetes:${args.apiVersion}:${args.kind}")
given besom.types.ResourceDecoder[CustomResourcePatch[A]] = besom.internal.ResourceDecoder.derived[CustomResourcePatch[A]]
Expand All @@ -24,7 +24,7 @@ object CustomResourcePatch:
resourceName,
name,
args,
opts(using besom.ResourceOptsVariant.Custom)
opts(using besom.ResourceOptsVariant.Component)
)
}

Expand Down
3 changes: 3 additions & 0 deletions codegen/src/scalameta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ object scalameta:

val Boolean: Type.Ref = Type.Name("Boolean")
val String: Type.Ref = Type.Name("String")
val Byte: Type.Ref = Type.Name("Byte")
val Int: Type.Ref = Type.Name("Int")
val Long: Type.Ref = Type.Name("Long")
val Float: Type.Ref = Type.Name("Float")
val Double: Type.Ref = Type.Name("Double")
val Unit: Type.Ref = Type.Select(Term.Name("scala"), Type.Name("Unit"))
val Option: Type.Ref = Type.Select(Term.Name("scala"), Type.Name("Option"))
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/besom/internal/ProtobufUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ object ProtobufUtil:
given ToValue[Int] with
extension (i: Int) def asValue: Value = Value(Kind.NumberValue(i))

given ToValue[Long] with
extension (l: Long) def asValue: Value = Value(Kind.NumberValue(l))

given ToValue[String] with
extension (s: String) def asValue: Value = Value(Kind.StringValue(s))

given ToValue[Float] with
extension (f: Float) def asValue: Value = Value(Kind.NumberValue(f))

given ToValue[Double] with
extension (d: Double) def asValue: Value = Value(Kind.NumberValue(d))

Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/besom/internal/codecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,22 @@ object Decoder extends DecoderInstancesLowPrio1:
if v.kind.isNumberValue then v.getNumberValue.valid
else error(s"$label: Expected a number, got: '${v.kind}'", label).invalid

given floatDecoder: Decoder[Float] with
def mapping(v: Value, label: Label): Validated[DecodingError, Float] =
doubleDecoder.mapping(v, label).map(_.toFloat)

given intDecoder(using doubleDecoder: Decoder[Double]): Decoder[Int] =
doubleDecoder.emap { (double, label) =>
if (double % 1 == 0) ValidatedResult.valid(double.toInt)
else error(s"$label: Numeric value was expected to be integer, but had a decimal value", label).invalidResult
}

given longDecoder(using doubleDecoder: Decoder[Double]): Decoder[Long] =
doubleDecoder.emap { (double, label) =>
if (double % 1 == 0) ValidatedResult.valid(double.toLong)
else error(s"$label: Numeric value was expected to be long, but had a decimal value", label).invalidResult
}

given stringDecoder: Decoder[String] with
def mapping(v: Value, label: Label): Validated[DecodingError, String] =
if v.kind.isStringValue then v.getStringValue.valid
Expand Down Expand Up @@ -964,6 +974,12 @@ object Encoder:
given intEncoder: Encoder[Int] with
def encode(int: Int)(using Context): Result[(Metadata, Value)] = Result.pure(Metadata.empty -> int.asValue)

given longEncoder: Encoder[Long] with
def encode(long: Long)(using Context): Result[(Metadata, Value)] = Result.pure(Metadata.empty -> long.asValue)

given floatEncoder: Encoder[Float] with
def encode(float: Float)(using Context): Result[(Metadata, Value)] = Result.pure(Metadata.empty -> float.asValue)

given doubleEncoder: Encoder[Double] with
def encode(dbl: Double)(using Context): Result[(Metadata, Value)] = Result.pure(Metadata.empty -> dbl.asValue)

Expand Down
11 changes: 11 additions & 0 deletions crd2besom/.scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version = 3.5.2
runner.dialect = scala3
project.git = true
align = most
align.openParenCallSite = false
align.openParenDefnSite = false
align.tokens = [{code = "=>", owner = "Case"}, "<-", "%", "%%", "="]
indent.defnSite = 2
maxColumn = 140

rewrite.scala3.insertEndMarkerMinLines = 40
18 changes: 18 additions & 0 deletions crd2besom/project.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//> using scala 3.3.1
//> using options -java-output-version:11
//> using options -deprecation -feature -Werror -Wunused:all

//> using dep org.virtuslab::besom-codegen:0.4.0-SNAPSHOT
//> using dep org.virtuslab::scala-yaml:0.1.0

//> using dep org.scalameta::munit:1.0.1

//> using publish.name "besom-crd2besom"
//> using publish.organization "org.virtuslab"
//> using publish.url "https://github.com/VirtusLab/besom"
//> using publish.vcs "github:VirtusLab/besom"
//> using publish.license "Apache-2.0"
//> using publish.repository "central"
//> using publish.developer "lbialy|Łukasz Biały|https://github.com/lbialy"
//> using publish.developer "pawelprazak|Paweł Prażak|https://github.com/pawelprazak"
//> using repository sonatype:snapshots
84 changes: 84 additions & 0 deletions crd2besom/src/AdditionalCodecs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package besom.codegen.crd

import scala.meta.*
import scala.meta.dialects.Scala33
import besom.codegen.scalameta.interpolator.*
import besom.codegen.scalameta.ref

enum AdditionalCodecs(val name: Type, val codecs: Seq[Stat]):
case LocalDateTime
extends AdditionalCodecs(
Type.Select(ref("java", "time"), Type.Name("LocalDateTime")),
Seq(
m"""
| given besom.Encoder[java.time.LocalDateTime] with
| def encode(t: java.time.LocalDateTime)(using besom.Context): besom.internal.Result[(besom.internal.Metadata, com.google.protobuf.struct.Value)] =
| besom.internal.Encoder.stringEncoder.encode(t.format(java.time.format.DateTimeFormatter.ISO_DATE_TIME))
|""",
m"""
| given besom.Decoder[java.time.LocalDateTime] with
| def mapping(v: com.google.protobuf.struct.Value, label: besom.types.Label): besom.util.Validated[besom.internal.DecodingError, java.time.LocalDateTime] =
| besom.internal.Decoder.stringDecoder.mapping(v, label).flatMap(str =>
| scala.util.Try(java.time.LocalDateTime.parse(v.getStringValue, java.time.format.DateTimeFormatter.ISO_DATE_TIME)) match
| case scala.util.Success(value) =>
| besom.util.Validated.valid(value)
| case scala.util.Failure(_) =>
| besom.util.Validated.invalid(besom.internal.Decoder.error(s"$$label: Expected a LocalDateTime, got: '$${v.kind}'", label))
| )
|"""
).map(_.stripMargin.parse[Stat].get)
)

case LocalDate
extends AdditionalCodecs(
Type.Select(ref("java", "time"), Type.Name("LocalDate")),
Seq(
m"""
| given besom.Encoder[java.time.LocalDate] with
| def encode(t: java.time.LocalDate)(using besom.Context): besom.internal.Result[(besom.internal.Metadata, com.google.protobuf.struct.Value)] =
| besom.internal.Encoder.stringEncoder.encode(t.format(java.time.format.DateTimeFormatter.ISO_DATE))
|""",
m"""
| given besom.Decoder[java.time.LocalDate] with
| def mapping(v: com.google.protobuf.struct.Value, label: besom.types.Label): besom.util.Validated[besom.internal.DecodingError, java.time.LocalDate] =
| besom.internal.Decoder.stringDecoder.mapping(v, label).flatMap(str =>
| scala.util.Try(java.time.LocalDate.parse(v.getStringValue, java.time.format.DateTimeFormatter.ISO_DATE)) match
| case scala.util.Success(value) =>
| besom.util.Validated.valid(value)
| case scala.util.Failure(_) =>
| besom.util.Validated.invalid(besom.internal.Decoder.error(s"$$label: Expected a LocalDate, got: '$${v.kind}'", label))
| )
|"""
).map(_.stripMargin.parse[Stat].get)
)
end AdditionalCodecs

object AdditionalCodecs:
private val nameToValuesMap: Map[String, AdditionalCodecs] =
AdditionalCodecs.values.map(c => c.name.syntax -> c).toMap

def getCodec(name: Type): Option[AdditionalCodecs] =
nameToValuesMap.get(name.syntax)

private def enumEncoder(enumName: String): Stat =
m"""
| given besom.Encoder[$enumName] with
| def encode(e: $enumName)(using besom.Context): besom.internal.Result[(besom.internal.Metadata, com.google.protobuf.struct.Value)] =
| besom.internal.Encoder.stringEncoder.encode(e.toString)
|""".stripMargin.parse[Stat].get

private def enumDecoder(enumName: String): Stat =
m"""
| given besom.Decoder[$enumName] with
| def mapping(v: com.google.protobuf.struct.Value, label: besom.types.Label): besom.util.Validated[besom.internal.DecodingError, $enumName] =
| besom.internal.Decoder.stringDecoder.mapping(v, label).flatMap(str =>
| scala.util.Try($enumName.valueOf(str)) match
| case scala.util.Success(value) =>
| besom.util.Validated.valid(value)
| case scala.util.Failure(_) =>
| besom.util.Validated.invalid(besom.internal.Decoder.error(s"$$label: Expected a $enumName enum, got: '$${v.kind}'", label))
| )
|""".stripMargin.parse[Stat].get

def enumCodecs(enumName: String): Seq[Stat] = Seq(enumEncoder(enumName), enumDecoder(enumName))
end AdditionalCodecs
Loading

0 comments on commit 2f59f60

Please sign in to comment.