Skip to content

Commit

Permalink
Fix traverse problem from #430, fix component API function too (#441)
Browse files Browse the repository at this point in the history
* fix traverse problem from #430
* fix component function with proxies, also change component so that Output[A] is no longer a valid return value to ameliorate the dry run plan short-circuiting problem
  • Loading branch information
lbialy authored Apr 8, 2024
1 parent 4b10f3f commit d88194a
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 14 deletions.
28 changes: 27 additions & 1 deletion besom-cats/src/main/scala/runtime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,30 @@ trait CatsEffectModule extends BesomModule:
// override def run(program: Context ?=> Output[Exports]): Future[Unit] = ???

object Pulumi extends CatsEffectModule
export Pulumi.{*, given}
export Pulumi.{component => _, *, given}

import scala.reflect.Typeable

// this proxy is only necessary due to https://github.com/scala/scala3/issues/17930
/** Creates a new component resource.
*
* @param name
* The unique name of the resource.
* @param typ
* The type of the resource.
* @param opts
* A bag of options that control this resource's behavior.
* @param f
* The function that will create the component resource.
* @tparam A
* The type of the component resource.
* @return
* The component resource.
*/
def component[A <: ComponentResource & Product: RegistersOutputs: Typeable](using ctx: Context)(
name: NonEmptyString,
typ: ResourceType,
opts: ComponentResourceOptions = ComponentResourceOptions()
)(
f: Context ?=> ComponentBase ?=> A
): Output[A] = Pulumi.component(name, typ, opts)(f)
28 changes: 27 additions & 1 deletion besom-zio/src/main/scala/runtime.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,30 @@ trait ZIOModule extends BesomModule:
// override def run(program: Context ?=> Output[Exports]): Future[Unit] = ???

object Pulumi extends ZIOModule
export Pulumi.{*, given}
export Pulumi.{component => _, *, given}

import scala.reflect.Typeable

// this proxy is only necessary due to https://github.com/scala/scala3/issues/17930
/** Creates a new component resource.
*
* @param name
* The unique name of the resource.
* @param typ
* The type of the resource.
* @param opts
* A bag of options that control this resource's behavior.
* @param f
* The function that will create the component resource.
* @tparam A
* The type of the component resource.
* @return
* The component resource.
*/
def component[A <: ComponentResource & Product: RegistersOutputs: Typeable](using ctx: Context)(
name: NonEmptyString,
typ: ResourceType,
opts: ComponentResourceOptions = ComponentResourceOptions()
)(
f: Context ?=> ComponentBase ?=> A
): Output[A] = Pulumi.component(name, typ, opts)(f)
28 changes: 27 additions & 1 deletion core/src/main/scala/besom/future.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,30 @@ trait FutureMonadModule extends BesomModule:
* }}}
*/
object Pulumi extends FutureMonadModule
export Pulumi.{*, given}
export Pulumi.{component => _, *, given}

import scala.reflect.Typeable

// this proxy is only necessary due to https://github.com/scala/scala3/issues/17930
/** Creates a new component resource.
*
* @param name
* The unique name of the resource.
* @param typ
* The type of the resource.
* @param opts
* A bag of options that control this resource's behavior.
* @param f
* The function that will create the component resource.
* @tparam A
* The type of the component resource.
* @return
* The component resource.
*/
def component[A <: ComponentResource & Product: RegistersOutputs: Typeable](using ctx: Context)(
name: NonEmptyString,
typ: ResourceType,
opts: ComponentResourceOptions = ComponentResourceOptions()
)(
f: Context ?=> ComponentBase ?=> A
): Output[A] = Pulumi.component(name, typ, opts)(f)
7 changes: 2 additions & 5 deletions core/src/main/scala/besom/internal/BesomSyntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ trait BesomSyntax:
typ: ResourceType,
opts: ComponentResourceOptions = ComponentResourceOptions()
)(
f: Context ?=> ComponentBase ?=> A | Output[A]
f: Context ?=> ComponentBase ?=> A
): Output[A] =
Output.ofData {
ctx
Expand All @@ -103,10 +103,7 @@ trait BesomSyntax:

val componentContext = ComponentContext(ctx, urnRes)
val componentOutput =
try
f(using componentContext)(using componentBase) match
case output: Output[A] @unchecked => output
case a: A => Output(Result.pure(a))
try Output(Result.pure(f(using componentContext)(using componentBase)))
catch case e: Exception => Output(Result.fail(e))

val componentResult = componentOutput.getValueOrFail {
Expand Down
17 changes: 11 additions & 6 deletions core/src/main/scala/besom/internal/Output.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,18 @@ trait OutputFactory:
)(using BuildFrom[CC[Output[B]], B, To], Context): Output[To] = sequence(coll.map(f).asInstanceOf[CC[Output[B]]])

def fail(t: Throwable)(using Context): Output[Nothing] = Output.fail(t)
end OutputFactory

trait OutputExtensionsFactory:
implicit final class OutputSequenceOps[A, CC[X] <: Iterable[X], To](coll: CC[Output[A]]):
def sequence(using BuildFrom[CC[Output[A]], A, To], Context): Output[To] =
Output.sequence(coll)
implicit object OutputSequenceOps:
extension [A, CC[X] <: Iterable[X]](coll: CC[Output[A]])
def sequence[To](using BuildFrom[CC[Output[A]], A, To], Context): Output[To] =
Output.sequence(coll)

implicit final class OutputTraverseOps[A, CC[X] <: Iterable[X]](coll: CC[A]):
def traverse[B, To](f: A => Output[B])(using BuildFrom[CC[Output[B]], B, To], Context): Output[To] =
coll.map(f).asInstanceOf[CC[Output[B]]].sequence
implicit object OutputTraverseOps:
extension [A, CC[X] <: Iterable[X]](coll: CC[A])
def traverse[B, To](f: A => Output[B])(using BuildFrom[CC[Output[B]], B, To], Context): Output[To] =
Output.sequence(coll.map(f).asInstanceOf[CC[Output[B]]])

implicit final class OutputOptionOps[A](output: Output[Option[A]]):
def getOrElse[B >: A: Typeable](default: => B | Output[B])(using ctx: Context): Output[B] =
Expand All @@ -108,6 +112,7 @@ trait OutputExtensionsFactory:
case b: Output[Option[B]] => b
case b: Option[B] => Output(b)
}
end OutputExtensionsFactory

object Output:
// should be NonEmptyString
Expand Down
56 changes: 56 additions & 0 deletions core/src/test/scala/besom/internal/OutputTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,60 @@ class OutputTest extends munit.FunSuite:
Context().waitForAllTasks.unsafeRunSync()
}

test("Output.sequence works with all kinds of collections") {
given Context = DummyContext().unsafeRunSync()

assertEquals(Output.sequence(List(Output("value"), Output("value2"))).getData.unsafeRunSync(), OutputData(List("value", "value2")))
assertEquals(Output.sequence(Vector(Output("value"), Output("value2"))).getData.unsafeRunSync(), OutputData(Vector("value", "value2")))
assertEquals(Output.sequence(Set(Output("value"), Output("value2"))).getData.unsafeRunSync(), OutputData(Set("value", "value2")))
assertEquals(
Output.sequence(Array(Output("value"), Output("value2")).toList).getData.unsafeRunSync(),
OutputData(List("value", "value2"))
)
val iter: Iterable[String] = List("value", "value2")
assertEquals(Output.sequence(iter.map(Output(_))).getData.unsafeRunSync(), OutputData(List("value", "value2")))

Context().waitForAllTasks.unsafeRunSync()
}

test("extensions for sequence and traverse work will all kinds of collections") {
import besom.* // test global import
given Context = DummyContext().unsafeRunSync()

assertEquals(List(Output("value"), Output("value2")).sequence.getData.unsafeRunSync(), OutputData(List("value", "value2")))
assertEquals(Vector(Output("value"), Output("value2")).sequence.getData.unsafeRunSync(), OutputData(Vector("value", "value2")))
assertEquals(Set(Output("value"), Output("value2")).sequence.getData.unsafeRunSync(), OutputData(Set("value", "value2")))
assertEquals(
Array("value", "value2").toList.traverse(x => Output(x)).getData.unsafeRunSync(),
OutputData(List("value", "value2"))
)

Context().waitForAllTasks.unsafeRunSync()
}

test("issue 430") {
import java.io.File
import besom.*
object s3:
def BucketObject(name: NonEmptyString)(using Context): Output[Unit] = Output(())

given Context = DummyContext().unsafeRunSync()

val uploads = File(".").listFiles().toList.traverse { file =>
val name = NonEmptyString(file.getName) match
case Some(name) => Output(name)
case None => Output(None).map(_ => throw new RuntimeException("Unexpected empty file name"))

name.flatMap {
s3.BucketObject(
_
)
}
}

uploads.getData.unsafeRunSync()

Context().waitForAllTasks.unsafeRunSync()
}

end OutputTest

0 comments on commit d88194a

Please sign in to comment.