From fee16d42ca3a86b7f2f0e7e6b85ccdf5ba00a385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bia=C5=82y?= Date: Tue, 28 Nov 2023 12:21:29 +0100 Subject: [PATCH] implemented invoke, added -Werror -Wunused:all -deprecation -feature and cleaned up the core codebase --- core/.scalafmt.conf | 4 +- core/project.scala | 1 + .../main/scala/besom/experimental/Monad.scala | 2 +- .../scala/besom/internal/CodecMacros.scala | 6 +- .../main/scala/besom/internal/Context.scala | 27 +-- .../main/scala/besom/internal/Exports.scala | 3 +- .../src/main/scala/besom/internal/Input.scala | 21 ++- .../main/scala/besom/internal/Output.scala | 14 +- .../main/scala/besom/internal/Resource.scala | 1 - .../besom/internal/ResourceDecoder.scala | 11 +- .../scala/besom/internal/ResourceOps.scala | 158 +++++++++++++----- .../main/scala/besom/internal/Result.scala | 45 +++-- .../main/scala/besom/internal/Zippable.scala | 3 - .../main/scala/besom/internal/codecs.scala | 35 ++-- .../main/scala/besom/internal/logging.scala | 1 - .../src/main/scala/besom/internal/tasks.scala | 2 - core/src/main/scala/besom/types.scala | 40 ++--- .../main/scala/besom/util/NonEmptySet.scala | 3 - .../scala/besom/util/NonEmptyString.scala | 33 ++-- .../scala/besom/internal/ContextTest.scala | 5 +- .../scala/besom/internal/DecoderTest.scala | 12 +- .../scala/besom/internal/EncoderTest.scala | 9 +- .../scala/besom/internal/LoggingTest.scala | 7 +- .../RegistersOutputsDerivationTest.scala | 5 +- .../scala/besom/internal/ResultSpec.scala | 20 ++- .../besom/internal/ValueAssertions.scala | 16 +- 26 files changed, 259 insertions(+), 225 deletions(-) diff --git a/core/.scalafmt.conf b/core/.scalafmt.conf index 1d0537167..e56579f30 100644 --- a/core/.scalafmt.conf +++ b/core/.scalafmt.conf @@ -9,4 +9,6 @@ align.openParenCallSite = false align.openParenDefnSite = false align.tokens = [{code = "=>", owner = "Case"}, "<-", "%", "%%", "="] indent.defnSite = 2 -maxColumn = 120 +maxColumn = 140 + +rewrite.scala3.insertEndMarkerMinLines = 40 \ No newline at end of file diff --git a/core/project.scala b/core/project.scala index a8db5efda..29798613e 100644 --- a/core/project.scala +++ b/core/project.scala @@ -17,6 +17,7 @@ //> using dep "com.lihaoyi::pprint:0.6.6" // TODO BALEET //> using options "-java-output-version:11", "-Ysafe-init" +//> using options -Werror -Wunused:all -deprecation -feature //#> using options "-Xmax-inlines:64" diff --git a/core/src/main/scala/besom/experimental/Monad.scala b/core/src/main/scala/besom/experimental/Monad.scala index 9b0f5113a..2877569d5 100644 --- a/core/src/main/scala/besom/experimental/Monad.scala +++ b/core/src/main/scala/besom/experimental/Monad.scala @@ -23,7 +23,7 @@ extension [F[+_]: Monad, A](fa: F[A]) def flatMap[B](f: A => F[B]): F[B] = Monad[F].flatMap(fa)(f) def fork: F[A] = Monad[F].fork(fa) -import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.concurrent.{ExecutionContext, Future} class FutureMonad(implicit val ec: ExecutionContext) extends Monad[Future]: diff --git a/core/src/main/scala/besom/internal/CodecMacros.scala b/core/src/main/scala/besom/internal/CodecMacros.scala index 2f69187e1..cd28271fd 100644 --- a/core/src/main/scala/besom/internal/CodecMacros.scala +++ b/core/src/main/scala/besom/internal/CodecMacros.scala @@ -6,7 +6,6 @@ object CodecMacros: inline def summonLabels[A] = ${ summonLabelsImpl[A] } private def summonLabelsImpl[A: Type](using Quotes): Expr[List[String]] = - import quotes.reflect.* Expr(recSummonLabelsImpl(Type.of[A])) private def recSummonLabelsImpl(t: Type[?])(using Quotes): List[String] = @@ -22,8 +21,6 @@ object CodecMacros: inline def summonDecoders[A]: List[Decoder[?]] = ${ summonDecodersImpl[A] } private def summonDecodersImpl[A: Type](using Quotes): Expr[List[Decoder[?]]] = - import quotes.reflect.* - // report.info(s"Deriving for ${Type.show[A]}") Expr.ofList(recSummonDecodersImpl(Type.of[A])) private def recSummonDecodersImpl(t: Type[?])(using Quotes): List[Expr[Decoder[?]]] = @@ -40,7 +37,6 @@ object CodecMacros: inline def summonEncoders[A]: List[Encoder[?]] = ${ summonEncodersImpl[A] } private def summonEncodersImpl[A: Type](using Quotes): Expr[List[Encoder[?]]] = - import quotes.reflect.* Expr.ofList(recSummonEncodersImpl(Type.of[A])) private def recSummonEncodersImpl(t: Type[?])(using Quotes): List[Expr[Encoder[?]]] = @@ -57,7 +53,6 @@ object CodecMacros: inline def summonJsonEncoders[A]: List[JsonEncoder[?]] = ${ summonJsonEncodersImpl[A] } private def summonJsonEncodersImpl[A: Type](using Quotes): Expr[List[JsonEncoder[?]]] = - import quotes.reflect.* Expr.ofList(recSummonJsonEncodersImpl(Type.of[A])) private def recSummonJsonEncodersImpl(t: Type[?])(using Quotes): List[Expr[JsonEncoder[?]]] = @@ -77,3 +72,4 @@ object CodecMacros: // import quotes.reflect.* // '{ List.empty[TC[Any]] } +end CodecMacros diff --git a/core/src/main/scala/besom/internal/Context.scala b/core/src/main/scala/besom/internal/Context.scala index e8ff5db86..9dbdef873 100644 --- a/core/src/main/scala/besom/internal/Context.scala +++ b/core/src/main/scala/besom/internal/Context.scala @@ -5,11 +5,8 @@ import besom.util.* import besom.types.{ResourceType, FunctionToken, URN, Label, ProviderType} import besom.internal.logging.* import scala.annotation.implicitNotFound -import besom.internal.ComponentResource -case class InvokeOptions() - -type Providers = Map[String, ProviderResource] +case class InvokeOptions(parent: Option[Resource], provider: Option[ProviderResource], version: Option[String]) @implicitNotFound(s"""|Pulumi code has to be written with a Context in scope. | @@ -70,6 +67,7 @@ trait Context extends TaskTracker: args: A, opts: InvokeOptions )(using Context): Output[R] +end Context class ComponentContext(private val globalContext: Context, private val componentURN: Result[URN]) extends Context: export globalContext.{getParentURN => _, *} @@ -94,8 +92,8 @@ class ContextImpl( override private[besom] def isDryRun: Boolean = runInfo.dryRun override private[besom] def pulumiOrganization: Option[NonEmptyString] = runInfo.organization - override private[besom] def pulumiProject: NonEmptyString = runInfo.project - override private[besom] def pulumiStack: NonEmptyString = runInfo.stack + override private[besom] def pulumiProject: NonEmptyString = runInfo.project + override private[besom] def pulumiStack: NonEmptyString = runInfo.stack override private[besom] def getParentURN: Result[URN] = stackPromise.get.flatMap(_.urn.getData).map(_.getValue).flatMap { @@ -158,23 +156,15 @@ class ContextImpl( ResourceOps().registerResourceOutputsInternal(urnResult, outputs) } - private[besom] def readOrRegisterResource[R <: Resource: ResourceDecoder, A: ArgsEncoder]( - typ: ResourceType, - name: NonEmptyString, - args: A, - options: ResourceOptions - ): Output[R] = - // val effect: Output[R] = ??? - // registerResourceCreation(typ, name, effect) // put into ConcurrentHashMap eagerly! - // effect - ??? - override private[besom] def invoke[A: ArgsEncoder, R: Decoder]( token: FunctionToken, args: A, opts: InvokeOptions )(using Context): Output[R] = - ??? // TODO: ResourceOps().invoke[A, R](token, args, opts) + BesomMDC(Key.LabelKey, Label.fromFunctionToken(token)) { + ResourceOps().invoke[A, R](token, args, opts) + } +end ContextImpl object Context: @@ -218,3 +208,4 @@ object Context: ctx <- apply(runInfo, featureSupport, config, logger, monitor, engine, taskTracker) _ <- ctx.initializeStack yield ctx +end Context diff --git a/core/src/main/scala/besom/internal/Exports.scala b/core/src/main/scala/besom/internal/Exports.scala index c4f768629..f55b15859 100644 --- a/core/src/main/scala/besom/internal/Exports.scala +++ b/core/src/main/scala/besom/internal/Exports.scala @@ -34,7 +34,8 @@ object Export extends Dynamic: else Expr.summon[Encoder[v]] match case Some(encoder) => - Right('{ ${encoder}.encode(${value}).map { (deps, value1) => (${name}, value1) }}) + // TODO make sure we don't need deps here (replaced with _) + Right('{ ${encoder}.encode(${value}).map { (_, value1) => (${name}, value1) }}) case None => Left(() => report.error(s"Encoder[${Type.show[v]}] is missing", value)) } diff --git a/core/src/main/scala/besom/internal/Input.scala b/core/src/main/scala/besom/internal/Input.scala index eb636eb85..1d4fd6e7c 100644 --- a/core/src/main/scala/besom/internal/Input.scala +++ b/core/src/main/scala/besom/internal/Input.scala @@ -8,15 +8,15 @@ object Input: extension [A](optional: A | Option[A]) private def asOption: Option[A] = optional match - case option: Option[A @ unchecked] => option - case a: A @unchecked => Option(a) + case option: Option[A @unchecked] => option + case a: A @unchecked => Option(a) given simpleInputOps: {} with extension [A](input: Input[A]) private[Input] def wrappedAsOutput(isSecret: Boolean = false)(using ctx: Context): Output[A] = input match case output: Output[_] => output.asInstanceOf[Output[A]] - case a: A @unchecked => if isSecret then Output.secret(a) else Output(a) + case a: A @unchecked => if isSecret then Output.secret(a) else Output(a) extension [A](input: Input[A]) def asOutput(isSecret: Boolean = false)(using ctx: Context): Output[A] = @@ -25,7 +25,7 @@ object Input: extension [A](input: Input.Optional[A]) private[Input] def wrappedAsOptionOutput(isSecret: Boolean = false)(using ctx: Context): Output[Option[A]] = val output = input match - case output: Output[_] => output.asInstanceOf[Output[A | Option[A]]] + case output: Output[_] => output.asInstanceOf[Output[A | Option[A]]] case maybeA: (A | Option[A]) @unchecked => if isSecret then Output.secret(maybeA) else Output(maybeA) output.map(_.asOption) @@ -34,19 +34,18 @@ object Input: given mapInputOps: {} with private def inputMapToMapOutput[A](inputMap: Map[String, Input[A]], isSecret: Boolean)(using ctx: Context): Output[Map[String, A]] = - val outputMap = inputMap.mapValues(_.asOutput(isSecret)).toMap + val outputMap = inputMap.view.mapValues(_.asOutput(isSecret)).toMap Output.traverseMap(outputMap) extension [A](input: Input[Map[String, Input[A]]]) def asOutput(isSecret: Boolean = false)(using ctx: Context): Output[Map[String, A]] = input.wrappedAsOutput(isSecret).flatMap(inputMapToMapOutput(_, isSecret = isSecret)) - extension [A](input: Input.Optional[Map[String, Input[A]]]) def asOptionOutput(isSecret: Boolean = false)(using ctx: Context): Output[Option[Map[String, A]]] = - input.wrappedAsOptionOutput(isSecret).flatMap { + input.wrappedAsOptionOutput(isSecret).flatMap { case Some(map) => inputMapToMapOutput(map, isSecret = isSecret).map(Option(_)) - case None => Output(None) + case None => Output(None) } given listInputOps: {} with @@ -60,8 +59,8 @@ object Input: extension [A](input: Input.Optional[List[Input[A]]]) def asOptionOutput(isSecret: Boolean = false)(using ctx: Context): Output[Option[List[A]]] = - input.wrappedAsOptionOutput(isSecret).flatMap { + input.wrappedAsOptionOutput(isSecret).flatMap { case Some(map) => inputListToListOutput(map, isSecret = isSecret).map(Option(_)) - case None => Output(None) + case None => Output(None) } - \ No newline at end of file +end Input diff --git a/core/src/main/scala/besom/internal/Output.scala b/core/src/main/scala/besom/internal/Output.scala index 0a32ce1c6..6d03cae14 100644 --- a/core/src/main/scala/besom/internal/Output.scala +++ b/core/src/main/scala/besom/internal/Output.scala @@ -1,10 +1,10 @@ package besom.internal -import scala.util.{NotGiven => Not} +import scala.reflect.Typeable import scala.collection.BuildFrom -/** Output is a wrapper for a monadic effect used to model async execution that allows Pulumi to track information about - * dependencies between resources and properties of data (whether it's known or a secret for instance). +/** Output is a wrapper for a monadic effect used to model async execution that allows Pulumi to track information about dependencies + * between resources and properties of data (whether it's known or a secret for instance). * * Invariant: dataResult has to be registered in TaskTracker by the time it reaches the constructor here! * @param dataResult @@ -63,6 +63,7 @@ class Output[+A] private[internal] (using private[besom] val ctx: Context)( o <- dataResult yield o.withIsSecret(secret) ) +end Output /** These factory methods should be the only way to create Output instances in user code! */ @@ -89,14 +90,14 @@ trait OutputExtensionsFactory: def traverse[B, To](f: A => Output[B])(using BuildFrom[CC[Output[B]], B, To], Context): Output[To] = coll.iterator.map(f).asInstanceOf[CC[Output[B]]].sequence implicit final class OutputOptionOps[A](output: Output[Option[A]]): - def getOrElse[B >: A](default: => B | Output[B])(using ctx: Context): Output[B] = + def getOrElse[B >: A: Typeable](default: => B | Output[B])(using ctx: Context): Output[B] = output.flatMap { opt => opt match case Some(a) => Output(a) case None => default match - case b: Output[B] => b - case b: B => Output(b) + case b: Output[B @unchecked] => b + case b: B => Output(b) } def orElse[B >: A](alternative: => Option[B] | Output[Option[B]])(using ctx: Context): Output[Option[B]] = output.flatMap { opt => @@ -151,3 +152,4 @@ object Output: def secret[A](value: A)(using ctx: Context): Output[A] = new Output[A](ctx.registerTask(Result.pure(OutputData(value, Set.empty, isSecret = true)))) +end Output diff --git a/core/src/main/scala/besom/internal/Resource.scala b/core/src/main/scala/besom/internal/Resource.scala index e9e93ecca..4042ad26f 100644 --- a/core/src/main/scala/besom/internal/Resource.scala +++ b/core/src/main/scala/besom/internal/Resource.scala @@ -3,7 +3,6 @@ package besom.internal import besom.util.NonEmptyString import besom.types.* import com.google.protobuf.struct.* -import scala.quoted.* import scala.deriving.Mirror import scala.annotation.implicitNotFound import besom.util.* diff --git a/core/src/main/scala/besom/internal/ResourceDecoder.scala b/core/src/main/scala/besom/internal/ResourceDecoder.scala index 520e93284..96bba4229 100644 --- a/core/src/main/scala/besom/internal/ResourceDecoder.scala +++ b/core/src/main/scala/besom/internal/ResourceDecoder.scala @@ -1,9 +1,8 @@ package besom.internal -import com.google.protobuf.struct.{Struct, Value} +import com.google.protobuf.struct.Value import scala.quoted.* import scala.deriving.Mirror -import scala.util.chaining.* import besom.util.* import besom.internal.logging.* import besom.types.{Label, URN, ResourceId} @@ -31,13 +30,13 @@ object ResourceDecoder: fields .get(NameUnmangler.unmanglePropertyName(propertyName)) .map { value => - log.trace(s"extracting custom property $propertyName from $value using decoder $decoder") + scribe.debug(s"extracting custom property $propertyName from $value using decoder $decoder") // TODO decoder.decode(value, propertyLabel).map(_.withDependency(resource)) match case Validated.Invalid(err) => - log.trace(s"failed to extract custom property $propertyName from $value: $err") + scribe.debug(s"failed to extract custom property $propertyName from $value: $err") // TODO Validated.Invalid(err) case Validated.Valid(value) => - log.trace(s"extracted custom property $propertyName from $value") + scribe.debug(s"extracted custom property $propertyName from $value") // TODO Validated.Valid(value) } .getOrElse { @@ -119,6 +118,7 @@ object ResourceDecoder: (resource, resolver) } + end makeResolver inline def derived[A <: Resource]: ResourceDecoder[A] = ${ derivedImpl[A] } @@ -156,3 +156,4 @@ object ResourceDecoder: customPropertyExtractors = ${ customPropertyExtractorsExpr }.toVector ) } +end ResourceDecoder diff --git a/core/src/main/scala/besom/internal/ResourceOps.scala b/core/src/main/scala/besom/internal/ResourceOps.scala index d150d6d75..7cf00e6bd 100644 --- a/core/src/main/scala/besom/internal/ResourceOps.scala +++ b/core/src/main/scala/besom/internal/ResourceOps.scala @@ -3,11 +3,15 @@ package besom.internal import com.google.protobuf.struct.* import pulumirpc.resource.* import pulumirpc.resource.RegisterResourceRequest.PropertyDependencies - import besom.util.* import besom.types.* import besom.internal.logging.* -import fansi.Str +import pulumirpc.provider.InvokeResponse + +// TODO remove +import scala.annotation.unused + +type Providers = Map[String, ProviderResource] class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): @@ -26,7 +30,90 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): } } - private[besom] def resolveProviderReferences(state: ResourceState): Result[Map[String, String]] = + private[besom] def registerResourceInternal[R <: Resource: ResourceDecoder, A: ArgsEncoder]( + typ: ResourceType, + name: NonEmptyString, + args: A, + options: ResourceOptions + ): Result[R] = + summon[ResourceDecoder[R]].makeResolver.flatMap { (resource, resolver) => + log.debug(s"registering resource, trying to add to cache...") *> + ctx.resources.cacheResource(typ, name, args, options, resource).flatMap { addedToCache => + if addedToCache then + for + _ <- log.debug(s"Registering resource, added to cache...") + state <- createResourceState(typ, name, resource, options) + _ <- log.debug(s"Created resource state") + _ <- ctx.resources.add(resource, state) + _ <- log.debug(s"Added resource to resources") + inputs <- prepareResourceInputs(resource, state, args, options) + _ <- log.debug(s"Prepared inputs for resource") + _ <- addChildToParentResource(resource, options.parent) + _ <- log.debug(s"Added child to parent (isDefined: ${options.parent.isDefined})") + _ <- executeRegisterResourceRequest(resource, state, resolver, inputs) + _ <- log.debug(s"executed RegisterResourceRequest in background") + yield resource + else ctx.resources.getCachedResource(typ, name, args, options).map(_.asInstanceOf[R]) + } + } + + private[besom] def invoke[A: ArgsEncoder, R: Decoder](tok: FunctionToken, args: A, opts: InvokeOptions): Output[R] = + def buildInvokeRequest(args: Struct, provider: Option[String], version: Option[String]): Result[ResourceInvokeRequest] = + Result { + ResourceInvokeRequest( + tok = tok, + args = Some(args), + provider = provider.getOrElse(""), + version = version.getOrElse(""), + acceptResources = ctx.runInfo.acceptResources + ) + } + + def parseInvokeResponse(response: InvokeResponse, props: Map[String, Set[Resource]]): Result[OutputData[R]] = + if response.failures.nonEmpty then + val failures = response.failures + .map { failure => + s"${failure.reason} (${failure.property})" + } + .mkString(", ") + + Result.fail(new Exception(s"Invoke of $tok failed: $failures")) + else if response.`return`.isEmpty then Result.fail(new Exception(s"Invoke of $tok returned empty result")) + else + val result = response.`return`.get + val resultAsValue = Value(Value.Kind.StructValue(result)) + val resourceLabel = mdc.get(Key.LabelKey) + val decoded = summon[Decoder[R]].decode(resultAsValue, resourceLabel) + decoded match + case Validated.Invalid(errs) => + Result.fail(new AggregatedDecodingError(errs)) + case Validated.Valid(value) => + Result.pure(value.withDependencies(props.values.flatten.toSet)) + + val result = PropertiesSerializer.serializeFilteredProperties(args, _ => false).flatMap { invokeArgs => + if invokeArgs.containsUnknowns then Result(OutputData.unknown()) + else + val maybeProviderResult = opts.getNestedProvider(tok) + val maybeProviderIdResult = maybeProviderResult.flatMap { + case Some(value) => value.registrationId.map(Some(_)) + case None => Result(None) + } + val version = opts.version + + for + maybeProviderId <- maybeProviderIdResult + req <- buildInvokeRequest(invokeArgs.serialized, maybeProviderId, version) + _ <- log.debug(s"Invoke RPC prepared, req=${pprint(req)}") + res <- ctx.monitor.invoke(req) + _ <- log.debug(s"Invoke RPC executed, res=${pprint(res)}") + finalRes <- parseInvokeResponse(res, invokeArgs.propertyToDependentResources) + yield finalRes + } + + besom.internal.Output.ofData(result) // TODO why the hell compiler assumes it's besom.aliases.Output? + end invoke + + private def resolveProviderReferences(state: ResourceState): Result[Map[String, String]] = Result .sequence( state.providers.map { case (pkg, provider) => @@ -35,7 +122,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): ) .map(_.toMap) - private[besom] def resolveTransitivelyReferencedComponentResourceUrns( + private def resolveTransitivelyReferencedComponentResourceUrns( resources: Set[Resource] ): Result[Set[URN]] = def findAllReachableResources(left: Set[Resource], acc: Set[Resource]): Result[Set[Resource]] = @@ -79,7 +166,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): options: ResourceOptions ) - private[besom] def aggregateDependencyUrns( + private def aggregateDependencyUrns( directDeps: Set[Resource], propertyDeps: Map[String, Set[Resource]] ): Result[AggregatedDependencyUrns] = @@ -96,13 +183,10 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): } } - private[besom] def resolveAliases(resource: Resource): Result[List[String]] = + private def resolveAliases(@unused resource: Resource): Result[List[String]] = Result.pure(List.empty) // TODO aliases - private[besom] def invoke[A: ArgsEncoder, R: Decoder](typ: ResourceType, args: A, opts: InvokeOptions): Result[R] = - Result(???) - - private[besom] def resolveProviderRegistrationId(state: ResourceState): Result[Option[String]] = + private def resolveProviderRegistrationId(state: ResourceState): Result[Option[String]] = state match case prs: ProviderResourceState => prs.custom.common.provider match @@ -116,7 +200,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): case _ => Result.pure(None) - private[besom] def prepareResourceInputs[A: ArgsEncoder]( + private def prepareResourceInputs[A: ArgsEncoder]( resource: Resource, state: ResourceState, args: A, @@ -140,7 +224,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): _ <- log.trace(s"Preparing inputs: resolved aliases, done") yield PreparedInputs(serResult.serialized, parentUrnOpt, provIdOpt, providerRefs, depUrns, aliases, options) - private[besom] def executeRegisterResourceRequest[R <: Resource]( + private def executeRegisterResourceRequest[R <: Resource]( resource: Resource, state: ResourceState, resolver: ResourceResolver[R], @@ -260,35 +344,6 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): case None => Result.unit case Some(parent) => ctx.resources.updateStateFor(parent)(_.addChild(resource)) - private[besom] def registerResourceInternal[R <: Resource: ResourceDecoder, A: ArgsEncoder]( - typ: ResourceType, - name: NonEmptyString, - args: A, - options: ResourceOptions - ): Result[R] = - summon[ResourceDecoder[R]].makeResolver.flatMap { (resource, resolver) => - log.debug(s"registering resource, trying to add to cache...") *> - ctx.resources.cacheResource(typ, name, args, options, resource).flatMap { addedToCache => - if addedToCache then - for - _ <- log.debug(s"Registering resource, added to cache...") - tuple <- summon[ArgsEncoder[A]].encode(args, _ => false) - _ <- log.debug(s"Encoded args") - state <- createResourceState(typ, name, resource, options) - _ <- log.debug(s"Created resource state") - _ <- ctx.resources.add(resource, state) - _ <- log.debug(s"Added resource to resources") - inputs <- prepareResourceInputs(resource, state, args, options) - _ <- log.debug(s"Prepared inputs for resource") - _ <- addChildToParentResource(resource, options.parent) - _ <- log.debug(s"Added child to parent (isDefined: ${options.parent.isDefined})") - _ <- executeRegisterResourceRequest(resource, state, resolver, inputs) - _ <- log.debug(s"executed RegisterResourceRequest in background") - yield resource - else ctx.resources.getCachedResource(typ, name, args, options).map(_.asInstanceOf[R]) - } - } - // This method returns an Option of Resource because for Stack there is no parent resource, // for any other resource the parent is either explicitly set in ResourceOptions or the stack is the parent. private def resolveParentUrn(typ: ResourceType, resourceOptions: ResourceOptions): Result[Option[URN]] = @@ -304,16 +359,16 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): _ <- log.trace(s"resolveParent - parent not found in ResourceOptions, from Context: $parentUrn") yield Some(parentUrn) - private def resolveParentTransformations(typ: ResourceType, resourceOptions: ResourceOptions): Result[List[Unit]] = + private def resolveParentTransformations(@unused typ: ResourceType, @unused resourceOptions: ResourceOptions): Result[List[Unit]] = Result.pure(List.empty) // TODO parent transformations private def applyTransformations( resourceOptions: ResourceOptions, - parentTransformations: List[Unit] // TODO this needs transformations from ResourceState, not Resource + @unused parentTransformations: List[Unit] // TODO this needs transformations from ResourceState, not Resource ): Result[ResourceOptions] = Result.pure(resourceOptions) // TODO resource transformations - private def collapseAliases(opts: ResourceOptions): Result[List[Output[String]]] = + private def collapseAliases(@unused opts: ResourceOptions): Result[List[Output[String]]] = Result.pure(List.empty) // TODO aliases private def mergeProviders(typ: String, opts: ResourceOptions): Result[Providers] = @@ -377,7 +432,7 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): case None => Result.pure(None) case Some(provider) => Result.pure(Some(provider)) - private[besom] def createResourceState( + private def createResourceState( typ: ResourceType, name: NonEmptyString, resource: Resource, @@ -422,3 +477,16 @@ class ResourceOps(using ctx: Context, mdc: BesomMDC[Label]): case ComponentBase(urn) => ComponentResourceState(common = commonRS) // TODO: ComponentBase should not be registered" } + + extension (invokeOpts: InvokeOptions) + def getNestedProvider(token: FunctionToken)(using Context): Result[Option[ProviderResource]] = + invokeOpts.provider match + case Some(passedProvider) => Result(Some(passedProvider)) + case None => + invokeOpts.parent match + case Some(explicitParent) => + summon[Context].resources.getStateFor(explicitParent).map { parentState => + parentState.providers.get(token.getPackage) + } + case None => Result(None) +end ResourceOps diff --git a/core/src/main/scala/besom/internal/Result.scala b/core/src/main/scala/besom/internal/Result.scala index d9927b032..3d6f4ba5f 100644 --- a/core/src/main/scala/besom/internal/Result.scala +++ b/core/src/main/scala/besom/internal/Result.scala @@ -2,11 +2,8 @@ package besom.internal import scala.util.Try import scala.concurrent.* -import scala.util.Failure -import scala.util.Success import scala.concurrent.duration.Duration -import logging.{LocalBesomLogger => logger} import scala.annotation.implicitNotFound trait Fiber[+A]: @@ -30,7 +27,7 @@ object Queue: new Queue: private val internal = ArrayBlockingQueue[A](capacity) - override def offer(a: A): Result[Unit] = Result.blocking(internal.offer(a)) + override def offer(a: A): Result[Unit] = Result.blocking(internal.put(a)) override def take: Result[A] = Result.blocking(internal.take) } @@ -222,63 +219,64 @@ enum Result[+A]: private def runF[F[+_]](using F: Runtime[F]): F[A] = this match case Suspend(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Suspend from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Suspend from $debug") F.fromFuture(thunk()) case Pure(value, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Pure from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Pure from $debug") F.pure(value) case Fail(err, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Fail from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Fail from $debug") F.fail(err).asInstanceOf[F[A]] // TODO test that Defer catches for all implementations always case Defer(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Defer from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Defer from $debug") F.defer(thunk()) case Blocking(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Blocking from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Blocking from $debug") F.blocking(thunk()) case BiFlatMap(fa, f, debug) => - if (F.debugEnabled) logger.trace(s"interpreting BiFlatMap from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting BiFlatMap from $debug") F.flatMapBoth(fa.runF[F])(either => f(either).runF[F].asInstanceOf[F[A]]) case Fork(fa, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Fork from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Fork from $debug") F.fork(fa.runF[F]).asInstanceOf[F[A]] case Sleep(fa, duration, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Sleep from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Sleep from $debug") F.sleep(fa().runF[F], duration) case GetFinalizers(debug) => - if (F.debugEnabled) logger.trace(s"interpreting GetFinalizers from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting GetFinalizers from $debug") F.fail(new RuntimeException("Panic: GetFinalizers should not be called on base monad F")).asInstanceOf[F[A]] private def runM[F[+_]](using F: Runtime[F]): F.M[A] = this match case Suspend(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Suspend from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Suspend from $debug") F.fromFutureM(thunk()) case Pure(value, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Pure from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Pure from $debug") F.pureM(value) case Fail(err, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Fail from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Fail from $debug") F.failM(err).asInstanceOf[F.M[A]] // TODO test that Defer catches for all implementations always case Defer(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Defer from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Defer from $debug") F.deferM(thunk()) case Blocking(thunk, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Blocking from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Blocking from $debug") F.blockingM(thunk()) case BiFlatMap(fa, f, debug) => - if (F.debugEnabled) logger.trace(s"interpreting BiFlatMap from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting BiFlatMap from $debug") F.flatMapBothM(fa.runM[F])(either => f(either).runM[F].asInstanceOf[F.M[A]]) case Fork(fa, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Fork from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Fork from $debug") F.forkM(fa.runM[F]).asInstanceOf[F.M[A]] case Sleep(fa, duration, debug) => - if (F.debugEnabled) logger.trace(s"interpreting Sleep from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting Sleep from $debug") F.sleepM(fa().runM[F], duration) case GetFinalizers(debug) => - if (F.debugEnabled) logger.trace(s"interpreting GetFinalizers from $debug") + if (F.debugEnabled) scribe.trace(s"interpreting GetFinalizers from $debug") F.getFinalizersM +end Result object Result: import scala.collection.BuildFrom @@ -350,11 +348,12 @@ object Result: for a <- acquire finalizersRef <- getFinalizers - _ <- finalizersRef.update(_.updatedWith(scope)(finalizers => Some(release(a) :: finalizers.toList.flatten))) + _ <- finalizersRef.update(_.updatedWith(scope)(finalizers => Some(release(a) :: finalizers.toList.flatten))) yield a trait ToFuture[F[_]]: def eval[A](fa: => F[A]): () => Future[A] +end Result class FutureRuntime(val debugEnabled: Boolean = false)(using ExecutionContext) extends Runtime[Future]: diff --git a/core/src/main/scala/besom/internal/Zippable.scala b/core/src/main/scala/besom/internal/Zippable.scala index 19ce35e1b..b9f58eabf 100644 --- a/core/src/main/scala/besom/internal/Zippable.scala +++ b/core/src/main/scala/besom/internal/Zippable.scala @@ -1,8 +1,5 @@ package besom.internal -import scala.util.Try -import scala.concurrent.Future - trait Zippable[-A, -B]: type Out def zip(left: A, right: B): Out diff --git a/core/src/main/scala/besom/internal/codecs.scala b/core/src/main/scala/besom/internal/codecs.scala index 8b62fb247..3f04721a6 100644 --- a/core/src/main/scala/besom/internal/codecs.scala +++ b/core/src/main/scala/besom/internal/codecs.scala @@ -8,8 +8,6 @@ import besom.util.* import besom.types.* import Asset.* import Archive.* -import org.checkerframework.checker.units.qual.s -import org.checkerframework.checker.units.qual.m object Constants: final val UnknownValue = "04da6b54-80e4-46f7-96ec-b56ff0331ba9" @@ -31,8 +29,7 @@ object Constants: final val StatePropertyName = "state" case class DecodingError(message: String, cause: Throwable = null, label: Label) extends Exception(message, cause) -case class AggregatedDecodingError(errors: NonEmptyVector[DecodingError]) - extends Exception(errors.map(_.message).toVector.mkString("\n")) +case class AggregatedDecodingError(errors: NonEmptyVector[DecodingError]) extends Exception(errors.map(_.message).toVector.mkString("\n")) /* * Would be awesome to make error reporting better, ie: * - make DecodingError work with any arity of errors and return aggregates @@ -191,9 +188,7 @@ object Decoder extends DecoderInstancesLowPrio1: .map(OutputData.sequence) } .map(_.flatten) - .lmap(exception => - DecodingError(s"$label: Encountered an error when deserializing a list", label = label, cause = exception) - ) + .lmap(exception => DecodingError(s"$label: Encountered an error when deserializing a list", label = label, cause = exception)) } def mapping(value: Value, label: Label): List[A] = List.empty @@ -219,9 +214,7 @@ object Decoder extends DecoderInstancesLowPrio1: .map(_.map(_.toMap)) } .map(_.flatten) - .lmap(exception => - DecodingError(s"$label: Encountered an error when deserializing a map", label = label, cause = exception) - ) + .lmap(exception => DecodingError(s"$label: Encountered an error when deserializing a map", label = label, cause = exception)) } def mapping(value: Value, label: Label): Map[String, A] = Map.empty @@ -234,8 +227,7 @@ object Decoder extends DecoderInstancesLowPrio1: extractSpecialStructSignature(innerValue) match case None => error(s"$label: Expected a special struct signature!", label) case Some(specialSig) => - if specialSig != Constants.SpecialSecretSig then - errorInvalid(s"$label: Expected a special resource signature!", label) + if specialSig != Constants.SpecialSecretSig then errorInvalid(s"$label: Expected a special resource signature!", label) else val structValue = innerValue.getStructValue structValue.fields @@ -268,16 +260,13 @@ object Decoder extends DecoderInstancesLowPrio1: extractSpecialStructSignature(innerValue) match case None => errorInvalid(s"$label: Expected a special struct signature!", label) case Some(extractedSpecialSig) => - if extractedSpecialSig != specialSig then - errorInvalid(s"$label: Expected a special asset signature!", label) + if extractedSpecialSig != specialSig then errorInvalid(s"$label: Expected a special asset signature!", label) else val structValue = innerValue.getStructValue handle(label, structValue) } .map(_.flatten) - .lmap(exception => - DecodingError(s"$label: Encountered an error when deserializing an asset", label = label, cause = exception) - ) + .lmap(exception => DecodingError(s"$label: Encountered an error when deserializing an asset", label = label, cause = exception)) } override def mapping(value: Value, label: Label): A = ??? @@ -386,6 +375,7 @@ object Decoder extends DecoderInstancesLowPrio1: .orElse(archiveDecoder.decode(value, label)) .orElse(errorInvalid(s"$label: Found value is neither an Asset nor an Archive", label)) override def mapping(value: Value, label: Label): AssetOrArchive = ??? +end Decoder trait DecoderInstancesLowPrio1 extends DecoderInstancesLowPrio2: import spray.json.JsValue @@ -493,7 +483,7 @@ trait DecoderHelpers: Iterator(value) .filter(_.kind.isStructValue) .flatMap(_.getStructValue.fields) - .filter((k, v) => k == SpecialSigKey) + .filter((k, _) => k == SpecialSigKey) .flatMap((_, v) => v.kind.stringValue) .nextOption @@ -512,11 +502,12 @@ trait DecoderHelpers: ) { (acc, elementOutputData) => acc :+ elementOutputData } +end DecoderHelpers /** ArgsEncoder - this is a separate typeclass required for serialization of top-level *Args classes * - * ProviderArgsEncoder - this is a separate typeclass required for serialization of top-level ProviderArgs classes that - * have all fields serialized as JSON strings + * ProviderArgsEncoder - this is a separate typeclass required for serialization of top-level ProviderArgs classes that have all fields + * serialized as JSON strings * * JsonEncoder - this is a separate typeclass required for json-serialized fields of ProviderArgs */ @@ -532,7 +523,8 @@ object Encoder: def encoderSum[A](mirror: Mirror.SumOf[A], nameEncoderPairs: List[(String, Encoder[?])]): Encoder[A] = new Encoder[A]: - private val encoderMap = nameEncoderPairs.toMap + // TODO We only serialize dumb enums!! + // private val encoderMap = nameEncoderPairs.toMap override def encode(a: A): Result[(Set[Resource], Value)] = Result.pure(Set.empty -> a.toString.asValue) def encoderProduct[A](nameEncoderPairs: List[(String, Encoder[?])]): Encoder[A] = @@ -818,6 +810,7 @@ object Encoder: optA match case Left(a) => innerA.encode(a) case Right(b) => innerB.encode(b) +end Encoder // ArgsEncoder and ProviderArgsEncoder are nearly the same with the small difference of // ProviderArgsEncoder summoning JsonEncoder instances instead of Encoder (because all diff --git a/core/src/main/scala/besom/internal/logging.scala b/core/src/main/scala/besom/internal/logging.scala index dc994ae78..007ec9342 100644 --- a/core/src/main/scala/besom/internal/logging.scala +++ b/core/src/main/scala/besom/internal/logging.scala @@ -2,7 +2,6 @@ package besom.internal import scribe.*, LoggerSupport.{apply => makeLogRecord} import scribe.message.LoggableMessage -import scribe.output.LogOutput import scribe.data.{MDC => ScribeMDC} import sourcecode.{FileName, Line, Name, Pkg} import scala.util.{NotGiven => Not} diff --git a/core/src/main/scala/besom/internal/tasks.scala b/core/src/main/scala/besom/internal/tasks.scala index d0120322f..5314bfa86 100644 --- a/core/src/main/scala/besom/internal/tasks.scala +++ b/core/src/main/scala/besom/internal/tasks.scala @@ -1,7 +1,5 @@ package besom.internal -import besom.internal.logging.BesomLogger - trait TaskTracker: private[besom] def registerTask[A](fa: => Result[A]): Result[A] private[besom] def waitForAllTasks: Result[Unit] diff --git a/core/src/main/scala/besom/types.scala b/core/src/main/scala/besom/types.scala index e0f0dc602..8b6585b95 100644 --- a/core/src/main/scala/besom/types.scala +++ b/core/src/main/scala/besom/types.scala @@ -4,7 +4,7 @@ import scala.compiletime.* import scala.compiletime.ops.string.* import scala.language.implicitConversions import scala.util.Try -import com.google.protobuf.struct.*, Value.Kind +import com.google.protobuf.struct.* import besom.internal.ProtobufUtil.* import besom.util.* import besom.internal.* @@ -28,32 +28,29 @@ object types: */ opaque type PulumiToken <: String = String - /** Each resource is an instance of a specific Pulumi resource type. This type is specified by a type token in the - * format `::`, where: - * - The ``` component of the type (e.g. aws, azure-native, kubernetes, random) specifies which Pulumi - * Package defines the resource. This is mapped to the package in the Pulumi Registry and to the per-language - * Pulumi SDK package. - * - The `` component of the type (e.g. s3/bucket, compute, apps/v1, index) is the module path where the - * resource lives within the package. It is / delimited by component of the path. The name index indicates that - * the resource is not nested, and is instead available at the top level of the package. Per-language Pulumi SDKs - * use the module path to emit nested namespaces/modules in a language-specific way to organize all the types - * defined in a package. - * - The component of the type (e.g. Bucket, VirtualMachine, Deployment, RandomPassword) is the - * identifier used to refer to the resource itself. It is mapped to the class or constructor name in the - * per-language Pulumi SDK. - */ - opaque type ResourceType <: PulumiToken = String - - extension (rt: ResourceType) + extension (pt: PulumiToken) /** @return * the Pulumi package name of the [[ResourceType]], for example: `aws`, `kubernetes`, `random` */ - def getPackage: String = if isProviderType then rt.split(":").last else rt.split(":").head + def getPackage: String = if isProviderType then pt.split(":").last else pt.split(":").head /** @return * true if the [[ResourceType]] is a provider, false otherwise */ - def isProviderType: Boolean = rt.startsWith("pulumi:providers:") + def isProviderType: Boolean = pt.startsWith("pulumi:providers:") + + /** Each resource is an instance of a specific Pulumi resource type. This type is specified by a type token in the format + * `::`, where: + * - The ``` component of the type (e.g. aws, azure-native, kubernetes, random) specifies which Pulumi Package defines the + * resource. This is mapped to the package in the Pulumi Registry and to the per-language Pulumi SDK package. + * - The `` component of the type (e.g. s3/bucket, compute, apps/v1, index) is the module path where the resource lives within + * the package. It is / delimited by component of the path. The name index indicates that the resource is not nested, and is instead + * available at the top level of the package. Per-language Pulumi SDKs use the module path to emit nested namespaces/modules in a + * language-specific way to organize all the types defined in a package. + * - The component of the type (e.g. Bucket, VirtualMachine, Deployment, RandomPassword) is the identifier used to refer to + * the resource itself. It is mapped to the class or constructor name in the per-language Pulumi SDK. + */ + opaque type ResourceType <: PulumiToken = String /** See [[ResourceType]] type for more information. */ @@ -149,6 +146,7 @@ object types: object Label: def fromNameAndType(name: NonEmptyString, rt: ResourceType): Label = s"$name[$rt]" + def fromFunctionToken(ft: FunctionToken): Label = s"$ft()" /** URN is an automatically constructed globally unique identifier for the resource */ @@ -220,6 +218,7 @@ object types: * the logical name of this [[Resource]] */ def resourceName: String = URN.UrnRegex.findFirstMatchIn(urn).get.group("resourceName") + end URN // TODO This should not be a subtype of string, user's access to underlying string has no meaning // TODO User should never modify Resource Ids, they should be opaque but they should also be @@ -294,3 +293,4 @@ object types: } export besom.aliases.{*, given} +end types diff --git a/core/src/main/scala/besom/util/NonEmptySet.scala b/core/src/main/scala/besom/util/NonEmptySet.scala index 18416c9d1..4b92ee245 100644 --- a/core/src/main/scala/besom/util/NonEmptySet.scala +++ b/core/src/main/scala/besom/util/NonEmptySet.scala @@ -1,8 +1,5 @@ package besom.util -import scala.compiletime.error -import scala.language.implicitConversions - opaque type NonEmptySet[A] <: Set[A] = Set[A] object NonEmptySet: diff --git a/core/src/main/scala/besom/util/NonEmptyString.scala b/core/src/main/scala/besom/util/NonEmptyString.scala index d0fe0e928..883f9fb71 100644 --- a/core/src/main/scala/besom/util/NonEmptyString.scala +++ b/core/src/main/scala/besom/util/NonEmptyString.scala @@ -3,20 +3,18 @@ package besom.util import scala.quoted.* import scala.language.implicitConversions -/** - * A [[String]] that is not empty or blank. - */ +/** A [[String]] that is not empty or blank. + */ opaque type NonEmptyString <: String = String -/** - * A [[String]] that is not empty or blank. - */ +/** A [[String]] that is not empty or blank. + */ object NonEmptyString: /** @param s - * a [[String]] to be converted to a [[NonEmptyString]]. - * @return - * an optional [[NonEmptyString]] if the given [[String]] is not empty or blank, otherwise [[None]]. - */ + * a [[String]] to be converted to a [[NonEmptyString]]. + * @return + * an optional [[NonEmptyString]] if the given [[String]] is not empty or blank, otherwise [[None]]. + */ def apply(s: String): Option[NonEmptyString] = if s.isBlank then None else Some(s) @@ -32,10 +30,10 @@ object NonEmptyString: inline def asString: String = nes /** @param s - * a [[String]] to be converted to a [[NonEmptyString]]. - * @return - * a [[NonEmptyString]] if the given [[String]] is not empty or blank. - */ + * a [[String]] to be converted to a [[NonEmptyString]]. + * @return + * a [[NonEmptyString]] if the given [[String]] is not empty or blank. + */ inline def from(inline s: String): NonEmptyString = ${ fromImpl('s) } private def fromImpl(expr: Expr[String])(using quotes: Quotes): Expr[NonEmptyString] = @@ -77,13 +75,10 @@ object NonEmptyString: report.errorAndAbort( "Only constant strings or string interpolations are allowed here, use NonEmptyString.apply instead!" ) + end fromImpl implicit inline def str2NonEmptyString(inline s: String): NonEmptyString = NonEmptyString.from(s) - - extension (s: String) - /** Returns `true` if the `String` is empty or contains only "white space" characters, otherwise `false`. - */ - private def isBlank = s.trim.isEmpty +end NonEmptyString trait NonEmptyStringFactory: /** @param s diff --git a/core/src/test/scala/besom/internal/ContextTest.scala b/core/src/test/scala/besom/internal/ContextTest.scala index bb9f03d37..fb197e39c 100644 --- a/core/src/test/scala/besom/internal/ContextTest.scala +++ b/core/src/test/scala/besom/internal/ContextTest.scala @@ -2,7 +2,7 @@ package besom.internal import RunResult.{given, *} import com.google.protobuf.struct.* -import besom.types.{ Output => _, * } +import besom.types.{Output => _, *} sealed abstract class TestEnum(val name: String, val value: String) extends StringEnum @@ -74,8 +74,6 @@ class ContextTest extends munit.FunSuite: } test("quick dirty Encoder test - enums") { - given Context = DummyContext().unsafeRunSync() - val (res, value) = encode[TestEnum]( TestEnum.`weird-test` ) @@ -84,3 +82,4 @@ class ContextTest extends munit.FunSuite: assert(value.kind.isStringValue) assertEquals(value.getStringValue, "weird-test value") } +end ContextTest diff --git a/core/src/test/scala/besom/internal/DecoderTest.scala b/core/src/test/scala/besom/internal/DecoderTest.scala index ff6e25efa..87973d04e 100644 --- a/core/src/test/scala/besom/internal/DecoderTest.scala +++ b/core/src/test/scala/besom/internal/DecoderTest.scala @@ -1,12 +1,11 @@ package besom.internal -import com.google.protobuf.struct.*, Value.Kind +import com.google.protobuf.struct.* import Constants.* import Decoder.* import ProtobufUtil.* import besom.types.Label import besom.util.* -import scala.CanEqual.derived object DecoderTest: case class TestCaseClass( @@ -31,7 +30,6 @@ object DecoderTest: override val allInstances: Seq[TestEnum] = Seq(A, B) - class DecoderTest extends munit.FunSuite: import DecoderTest.* val dummyLabel = Label.fromNameAndType("dummy", "dummy:pkg:Dummy") @@ -64,9 +62,10 @@ class DecoderTest extends munit.FunSuite: val v = Null val d = summon[Decoder[TestCaseClass]] d.decode(v, dummyLabel) match - case Validated.Invalid(es) => es.head match - case DecodingError(m, _, _) => - assertEquals(m, "dummy[dummy:pkg:Dummy]: Expected a struct to deserialize Product[TestCaseClass], got: 'NullValue(NULL_VALUE)'") + case Validated.Invalid(es) => + es.head match + case DecodingError(m, _, _) => + assertEquals(m, "dummy[dummy:pkg:Dummy]: Expected a struct to deserialize Product[TestCaseClass], got: 'NullValue(NULL_VALUE)'") case Validated.Valid(_) => throw Exception("Unexpected, valid") } @@ -117,3 +116,4 @@ class DecoderTest extends munit.FunSuite: case Validated.Valid(OutputData.Known(res, isSecret, value)) => assert(value == Some(SpecialCaseClass(10, "abc", "qwerty"))) case Validated.Valid(_) => throw Exception("Unexpected unknown!") } +end DecoderTest diff --git a/core/src/test/scala/besom/internal/EncoderTest.scala b/core/src/test/scala/besom/internal/EncoderTest.scala index 141bc02ac..c1a6b037b 100644 --- a/core/src/test/scala/besom/internal/EncoderTest.scala +++ b/core/src/test/scala/besom/internal/EncoderTest.scala @@ -1,12 +1,10 @@ package besom.internal -import com.google.protobuf.struct.*, Value.Kind -import Constants.* +import com.google.protobuf.struct.* import Encoder.* import ProtobufUtil.* import besom.types.Label import besom.util.* -import scala.CanEqual.derived import RunResult.{given, *} object EncoderTest: @@ -37,7 +35,6 @@ class EncoderTest extends munit.FunSuite with ValueAssertions: val dummyLabel = Label.fromNameAndType("dummy", "dummy:pkg:Dummy") test("encode case class") { - given Context = DummyContext().unsafeRunSync() val e = summon[Encoder[TestCaseClass]] val expected = Map( "foo" -> 10.asValue, @@ -50,8 +47,7 @@ class EncoderTest extends munit.FunSuite with ValueAssertions: } test("encoder enum") { - given Context = DummyContext().unsafeRunSync() - val e = summon[Encoder[TestEnum]] + val e = summon[Encoder[TestEnum]] val (_, encodedA) = e.encode(TestEnum.A).unsafeRunSync() assertEqualsValue(encodedA, "A value".asValue) val (_, encodedB) = e.encode(TestEnum.B).unsafeRunSync() @@ -59,7 +55,6 @@ class EncoderTest extends munit.FunSuite with ValueAssertions: } test("encode special case class with unmangling") { - given Context = DummyContext().unsafeRunSync() val e = summon[Encoder[SpecialCaseClass]] val expected = Map( "equals" -> 10.asValue, diff --git a/core/src/test/scala/besom/internal/LoggingTest.scala b/core/src/test/scala/besom/internal/LoggingTest.scala index ec85c8e00..3b79a67af 100644 --- a/core/src/test/scala/besom/internal/LoggingTest.scala +++ b/core/src/test/scala/besom/internal/LoggingTest.scala @@ -20,7 +20,7 @@ class LoggingTest extends munit.FunSuite: given Context = DummyContext().unsafeRunSync() test("plain logging works") { - val urn = URN("urn:pulumi:stack::project::custom:resources:Resource$besom:testing/test:Resource::my-test-resource") + val urn = URN("urn:pulumi:stack::project::custom:resources:Resource$besom:testing/test:Resource::my-test-resource") val logger = BesomLogger.local().unsafeRunSync() val res = TestResource(Output(urn), Output(ResourceId("bar")), Output("url")) val messages = mutable.ArrayBuffer.empty[LogRecord] @@ -36,13 +36,15 @@ class LoggingTest extends munit.FunSuite: ) .replace() + import scala.language.implicitConversions // for structural logging + val loggingResult = for _ <- logger.info("hello") _ <- logger.warn("world") _ <- logger.debug(StructuralLog(23, "foo", true)) _ <- logger.error("oh no") - _ <- logger.trace("traced") + _ <- logger.trace("traced", Some(res)) _ <- logger.close() yield () @@ -67,3 +69,4 @@ class LoggingTest extends munit.FunSuite: val name = traceFileName(LocalDateTime.parse("2007-12-03T10:15:30")) assertEquals(name, "besom-run-2007-12-03T10-15-30.log") } +end LoggingTest diff --git a/core/src/test/scala/besom/internal/RegistersOutputsDerivationTest.scala b/core/src/test/scala/besom/internal/RegistersOutputsDerivationTest.scala index 844a30a34..456c11d96 100644 --- a/core/src/test/scala/besom/internal/RegistersOutputsDerivationTest.scala +++ b/core/src/test/scala/besom/internal/RegistersOutputsDerivationTest.scala @@ -1,6 +1,5 @@ package besom.internal -import besom.util.* import besom.types.* import RunResult.{given, *} import com.google.protobuf.struct.Struct @@ -25,8 +24,8 @@ class RegistersOutputsDerivationTest extends munit.FunSuite { ) } - case class TestRegistersOutputs3(aField: Output[Int], alsoAField: Output[String], anotherFields: Output[Boolean])( - using ComponentBase + case class TestRegistersOutputs3(aField: Output[Int], alsoAField: Output[String], anotherFields: Output[Boolean])(using + ComponentBase ) extends ComponentResource test("derive an instance for TestRegistersOutputs3") { diff --git a/core/src/test/scala/besom/internal/ResultSpec.scala b/core/src/test/scala/besom/internal/ResultSpec.scala index 72464b8f4..b431e5522 100644 --- a/core/src/test/scala/besom/internal/ResultSpec.scala +++ b/core/src/test/scala/besom/internal/ResultSpec.scala @@ -1,6 +1,5 @@ package besom.internal -import scala.concurrent.{Promise => stdPromise, *}, ExecutionContext.Implicits.global, duration.* import scala.util.Try import RunResult.* @@ -133,7 +132,7 @@ trait ResultSpec[F[+_]: RunResult] extends munit.FunSuite: for ref <- Ref[String]("") append = (s: String) => ref.update(_ + s) - _ <- Result.bracket(append("1a"))(_ => append("1r")) { _ => + _ <- Result.bracket(append("1a"))(_ => append("1r")) { _ => Result.bracket(append("2a"))(_ => append("2r")) { _ => Result.bracket(append("3a"))(_ => append("3r"))(_ => append("use")) } @@ -151,12 +150,14 @@ trait ResultSpec[F[+_]: RunResult] extends munit.FunSuite: for ref <- Ref[String]("") append = (s: String) => ref.update(_ + s) - _ <- Result.bracket(append("a"))(_ => append("r")) { _ => - Result.fail(new RuntimeException("Oopsy daisy")) - }.either + _ <- Result + .bracket(append("a"))(_ => append("r")) { _ => + Result.fail(new RuntimeException("Oopsy daisy")) + } + .either res <- ref.get yield res - + val lhs = program.unsafeRunSync() assertEquals(lhs, "ar") @@ -175,7 +176,7 @@ trait ResultSpec[F[+_]: RunResult] extends munit.FunSuite: } res <- ref.get yield res - + val lhs = program.unsafeRunSync() assertEquals(lhs, "auser") @@ -196,7 +197,7 @@ trait ResultSpec[F[+_]: RunResult] extends munit.FunSuite: } res <- ref.get yield res - + val lhs = program.unsafeRunSync() assertEquals(lhs, "1a2a3ause3r2r1r") @@ -215,11 +216,12 @@ trait ResultSpec[F[+_]: RunResult] extends munit.FunSuite: }.either res <- ref.get yield res - + val lhs = program.unsafeRunSync() assertEquals(lhs, "ar") } +end ResultSpec // TODO test laziness of operators (for Future mostly) somehow // TODO zip should be probably parallelised diff --git a/core/src/test/scala/besom/internal/ValueAssertions.scala b/core/src/test/scala/besom/internal/ValueAssertions.scala index 09fecd70e..817cc7330 100644 --- a/core/src/test/scala/besom/internal/ValueAssertions.scala +++ b/core/src/test/scala/besom/internal/ValueAssertions.scala @@ -1,30 +1,28 @@ package besom.internal import com.google.protobuf.struct.*, Value.Kind -import Constants.* -import ProtobufUtil.* trait ValueAssertions: self: munit.Assertions => def assertEqualsMaybeValue(actual: Option[Value], expected: Option[Value]): Unit = (actual, expected) match - case (None, None) => () + case (None, None) => () case (None, Some(Value(Kind.NullValue(_), _))) => () case (Some(Value(Kind.NullValue(_), _)), None) => () - case (Some(a), Some(b)) => assertEqualsValue(a, b) - case _ => throw Exception(s"Values not equal: $actual != $expected") + case (Some(a), Some(b)) => assertEqualsValue(a, b) + case _ => throw Exception(s"Values not equal: $actual != $expected") def assertEqualsValue(actual: Value, expected: Value): Unit = (actual.kind, expected.kind) match - case (Kind.NullValue(_), Kind.NullValue(_)) => () + case (Kind.NullValue(_), Kind.NullValue(_)) => () case (Kind.NumberValue(a), Kind.NumberValue(b)) => assertEquals(actual, expected) case (Kind.StringValue(a), Kind.StringValue(b)) => assertEquals(actual, expected) - case (Kind.BoolValue(a), Kind.BoolValue(b)) => assertEquals(actual, expected) + case (Kind.BoolValue(a), Kind.BoolValue(b)) => assertEquals(actual, expected) case (Kind.StructValue(a), Kind.StructValue(b)) => (a.fields.keys ++ b.fields.keys).toSeq.distinct.foreach { key => - val actualFieldValue = a.fields.get(key) + val actualFieldValue = a.fields.get(key) val expectedFieldValue = b.fields.get(key) assertEqualsMaybeValue(actualFieldValue, expectedFieldValue) } case (Kind.ListValue(a), Kind.ListValue(b)) => assertEquals(actual, expected) - case _ => throw Exception(s"Values not equal: $actual != $expected") + case _ => throw Exception(s"Values not equal: $actual != $expected")