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

Add resource references #317

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions codegen/src/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,8 @@ class CodeGen(implicit

val baseClassFileContent =
s"""|package ${baseClassCoordinates.fullPackageName}
|
|import besom.internal.Encoder.*
|import besom.internal.Decoder.*
|${baseClassComment}
|${baseClass}
|
Expand Down Expand Up @@ -614,7 +615,8 @@ class CodeGen(implicit

val fileContent =
s"""|package ${classCoordinates.fullPackageName}
|
|import besom.internal.Encoder.*
|import besom.internal.Decoder.*
|${classComment}
|${classDef}
|
Expand Down Expand Up @@ -648,7 +650,8 @@ class CodeGen(implicit

val fileContent =
s"""|package ${classCoordinates.fullPackageName}
|
|import besom.internal.Encoder.*
|import besom.internal.Decoder.*
|${argsClass}
|
|${argsCompanion}
Expand Down
6 changes: 4 additions & 2 deletions codegen/src/CodeGen.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ class CodeGenTest extends munit.FunSuite {
expected = Map(
"src/index/Provider.scala" ->
s"""|package besom.api.example
|
|import besom.internal.Encoder.*
|import besom.internal.Decoder.*
|
|final case class Provider private(
| urn: besom.types.Output[besom.types.URN],
Expand All @@ -86,7 +87,8 @@ class CodeGenTest extends munit.FunSuite {
|""".stripMargin,
"src/index/ProviderArgs.scala" ->
s"""|package besom.api.example
|
|import besom.internal.Encoder.*
|import besom.internal.Decoder.*
|final case class ProviderArgs private(
| helmReleaseSettings: besom.types.Output[scala.Option[besom.api.example.inputs.HelmReleaseSettingsArgs]]
|) derives besom.types.ProviderArgsEncoder
Expand Down
55 changes: 47 additions & 8 deletions codegen/src/PulumiPackage.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ class PulumiPackageTest extends munit.FunSuite {
case class Data(
name: String,
json: String,
expectedType: String
expectedType: String,
expectedArgsType: Option[String] = None,
metadata: Option[PackageMetadata] = None,
package_ : Option[PulumiPackage] = None,
tags: Set[munit.Tag] = Set()
)

Vector(
Expand Down Expand Up @@ -46,23 +50,58 @@ class PulumiPackageTest extends munit.FunSuite {
|}
|""".stripMargin,
expectedType = "scala.Predef.Map[String, String]"
),
Data(
name = "TypeReference with union with a resource",
json = """|{
| "type": "string",
| "oneOf": [
| {
| "type": "string"
| },
| {
| "type": "string",
| "$ref": "#/types/aws:apigateway%2FrestApi:RestApi"
| }
| ],
| "willReplaceOnChanges": true
|}
|""".stripMargin,
expectedType = "String | besom.api.aws.apigateway.RestApi",
metadata = Some(PackageMetadata("aws", "6.7.0")),
),
Data(
name = "TypeReference with external (downloaded) named type with duplicate object",
json = """|{
| "type": "object",
| "$ref": "/kubernetes/v3.7.0/schema.json#/types/kubernetes:core%2Fv1:Pod"
|}
|""".stripMargin,
expectedType = "besom.api.kubernetes.core.v1.outputs.Pod",
expectedArgsType = Some("besom.api.kubernetes.core.v1.inputs.PodArgs"),
)
).foreach(data =>
test(data.name) {
test(data.name.withTags(data.tags)) {
implicit val config: Config.CodegenConfig = CodegenConfig()
implicit val logger: Logger = new Logger(config.logLevel)

implicit val schemaProvider: SchemaProvider = new DownloadingSchemaProvider(schemaCacheDirPath = Config.DefaultSchemasDir)
val (_, packageInfo) = schemaProvider.packageInfo(
PackageMetadata(defaultTestSchemaName, "0.0.0"),
PulumiPackage(defaultTestSchemaName)
)
implicit val schemaProvider: SchemaProvider =
new DownloadingSchemaProvider(schemaCacheDirPath = Config.DefaultSchemasDir)
val (_, packageInfo) = (data.metadata, data.package_) match {
case (Some(metadata), Some(package_)) => schemaProvider.packageInfo(metadata, package_)
case (Some(metadata), None) => schemaProvider.packageInfo(metadata)
case (None, package_) => schemaProvider.packageInfo(
PackageMetadata(defaultTestSchemaName, "0.0.0"),
package_.getOrElse(PulumiPackage(defaultTestSchemaName))
)
}
implicit val tm: TypeMapper = new TypeMapper(packageInfo, schemaProvider)

val propertyDefinition = UpickleApi.read[PropertyDefinition](data.json)

assertEquals(propertyDefinition.typeReference.asScalaType().syntax, data.expectedType)
assertEquals(propertyDefinition.typeReference.asScalaType(asArgsType = true).syntax, data.expectedType)
if (data.expectedArgsType.isDefined)
assertEquals(propertyDefinition.typeReference.asScalaType(asArgsType = true).syntax, data.expectedArgsType.get)
}
)
}
42 changes: 20 additions & 22 deletions codegen/src/TypeMapper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,27 @@ class TypeMapper(
}
}

// We need to be careful here because a type URI can refer to both a resource and an object type
// and we need to be flexible, because schemas are not always consistent
val classCoordinates: Option[ScalaDefinitionCoordinates] =
if (isFromResourceUri) {
resourceClassCoordinates
} else if (isFromTypeUri) {
objectClassCoordinates
} else {
(resourceClassCoordinates, objectClassCoordinates) match {
case (Some(coordinates), None) =>
logger.warn(
s"Assuming a '/resources/` prefix for type URI, fileUri: '${fileUri}', typePath: '${typePath}'"
)
Some(coordinates)
case (None, Some(coordinates)) =>
logger.warn(s"Assuming a '/types/` prefix for type URI, fileUri: '${fileUri}', typePath: '${typePath}'")
Some(coordinates)
case (None, None) =>
logger.debug(s"Found no type definition for type URI, fileUri: '${fileUri}', typePath: '${typePath}'")
None
case _ =>
throw TypeMapperError(
s"Type URI can refer to both a resource or an object type, fileUri: '${fileUri}', typePath: '${typePath}'"
)
}
(resourceClassCoordinates, objectClassCoordinates) match {
case (Some(_), _) if isFromResourceUri =>
resourceClassCoordinates
case (_, Some(_)) if isFromTypeUri =>
objectClassCoordinates
case (Some(_), None) =>
logger.warn(s"Assuming '/resources/` prefix for type URI, fileUri: '${fileUri}', typePath: '${typePath}'")
resourceClassCoordinates
case (None, Some(_)) =>
logger.warn(s"Assuming '/types/` prefix for type URI, fileUri: '${fileUri}', typePath: '${typePath}'")
objectClassCoordinates
case (None, None) =>
logger.warn(s"Found no type definition for type URI, fileUri: '${fileUri}', typePath: '${typePath}'")
None
case _ =>
throw TypeMapperError(
s"Type URI can refer to both a resource or an object type, fileUri: '${fileUri}', typePath: '${typePath}'"
)
}

classCoordinates.map(_.fullyQualifiedTypeRef)
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/TypeMapper.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TypeMapperTest extends munit.FunSuite {
schemaVersion = Some("6.7.0"),
tags = Set(munit.Slow)
)(
Expectations("String")
Expectations("String | besom.api.aws.iam.Role")
),
Data(
NamedType("#/types/aws-native:index%2Fregion:Region"),
Expand Down
20 changes: 15 additions & 5 deletions core/src/test/scala/besom/internal/DecoderTest.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package besom.internal

import com.google.protobuf.struct.*
import Constants.*
import Decoder.*
import ProtobufUtil.*
import besom.types.Label
import besom.internal.Constants.*
import besom.internal.Decoder.*
import besom.internal.ProtobufUtil.*
import besom.types.{Label, URN}
import besom.util.*
import com.google.protobuf.struct.*

object DecoderTest:
case class TestCaseClass(
Expand Down Expand Up @@ -116,4 +116,14 @@ 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!")
}

test("decode URN") {
val v = "urn:pulumi:stack::project::custom:resources:Resource$besom:testing/test:Resource::my-test-resource".asValue
val d = summon[Decoder[URN]]
d.decode(v, dummyLabel) match
case Validated.Invalid(ex) => throw ex.head
case Validated.Valid(OutputData.Known(res, isSecret, value)) => assert(value == Some(URN("urn:pulumi:stack::project::custom:resources:Resource$besom:testing/test:Resource::my-test-resource")))
case Validated.Valid(_) => throw Exception("Unexpected unknown!")
}

end DecoderTest
27 changes: 27 additions & 0 deletions core/src/test/scala/besom/internal/EncoderTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,33 @@ class EncoderTest extends munit.FunSuite with ValueAssertions:
assert(res.isEmpty)
assert(value.getStructValue.fields.isEmpty, value)
}

test("encode a union of string and int") {
given Context = DummyContext().unsafeRunSync()
val e = summon[Encoder[Output[String | Int]]]

val (_, valueStr) = e.encode(Output[String | Int]("test1")).unsafeRunSync()
assertEquals(valueStr.getStringValue, "test1")

val (_, valueInt) = e.encode(Output[String | Int](123)).unsafeRunSync()
assertEquals(valueInt.getNumberValue, 123d)
}

test("encode a union of string and case class") {
val e = summon[Encoder[String | TestCaseClass]]

val (_, encodedString) = e.encode("abc").unsafeRunSync()
assertEqualsValue(encodedString, "abc".asValue)

val (_, encodedCaseClass) = e.encode(TestCaseClass(10, List("qwerty"), None, None, Some("abc"))).unsafeRunSync()
val expected = Map(
"foo" -> 10.asValue,
"bar" -> List("qwerty".asValue).asValue,
"optNone1" -> Null,
"optSome" -> Some("abc").asValue
).asValue
assertEqualsValue(encodedCaseClass, expected)
}

class ArgsEncoderTest extends munit.FunSuite with ValueAssertions:
import EncoderTest.*
Expand Down