Skip to content

Commit

Permalink
feat(server): make it possible to POST custom typings for testing dur…
Browse files Browse the repository at this point in the history
…ing typing creation
  • Loading branch information
Vampire committed Oct 27, 2024
1 parent 37ad070 commit cbc29ce
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 28 deletions.
15 changes: 9 additions & 6 deletions action-binding-generator/api/action-binding-generator.api
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;
public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;
public final fun component5 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;
public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;
public fun equals (Ljava/lang/Object;)Z
public final fun getName ()Ljava/lang/String;
public final fun getOwner ()Ljava/lang/String;
public final fun getPath ()Ljava/lang/String;
public final fun getTypesUuid ()Ljava/lang/String;
public final fun getVersion ()Ljava/lang/String;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
Expand Down Expand Up @@ -46,6 +48,7 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/dom

public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource : java/lang/Enum {
public static final field ACTION Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource;
public static final field CUSTOM Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource;
public static final field TYPING_CATALOG Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource;
Expand All @@ -72,8 +75,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/gen
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationKt {
public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;)Ljava/util/List;
public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;ILjava/lang/Object;)Ljava/util/List;
public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;)Ljava/util/List;
public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List;
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public data class ActionCoords(
val name: String,
val version: String,
val path: String? = null,
val typesUuid: String? = null,
)

/**
Expand All @@ -13,7 +14,7 @@ public data class ActionCoords(
*/
public val ActionCoords.isTopLevel: Boolean get() = path == null

public val ActionCoords.prettyPrint: String get() = "$owner/$fullName@$version"
public val ActionCoords.prettyPrint: String get() = "$owner/$fullName@$version${typesUuid?.let { " (types: $it)" } ?: ""}"

/**
* For most actions, it's empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.domain
public enum class TypingActualSource {
ACTION,
TYPING_CATALOG,
CUSTOM,
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ public fun ActionCoords.generateBinding(
metadataRevision: MetadataRevision,
metadata: Metadata? = null,
inputTypings: Pair<Map<String, Typing>, TypingActualSource?>? = null,
types: String? = null,
): List<ActionBinding> {
val metadataResolved = metadata ?: this.fetchMetadata(metadataRevision) ?: return emptyList()
val metadataProcessed = metadataResolved.removeDeprecatedInputsIfNameClash()

val inputTypingsResolved = inputTypings ?: this.provideTypes(metadataRevision)
val (inputTypingsResolved, typingActualSource) =
inputTypings ?: this.provideTypes(metadataRevision, types = types)

val packageName = owner.toKotlinPackageName()
val className = this.buildActionClassName()
Expand All @@ -78,7 +80,7 @@ public fun ActionCoords.generateBinding(
emptyMap(),
classNameUntyped,
untypedClass = true,
replaceWith = inputTypingsResolved.second?.let { CodeBlock.of("ReplaceWith(%S)", className) },
replaceWith = typingActualSource?.let { CodeBlock.of("ReplaceWith(%S)", className) },
)

return listOfNotNull(
Expand All @@ -89,12 +91,12 @@ public fun ActionCoords.generateBinding(
packageName = packageName,
typingActualSource = null,
),
inputTypingsResolved.second?.let {
typingActualSource?.let {
val actionBindingSourceCode =
generateActionBindingSourceCode(
metadata = metadataProcessed,
coords = this,
inputTypings = inputTypingsResolved.first,
inputTypings = inputTypingsResolved,
className = className,
)
ActionBinding(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.Metadata
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.ACTION
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.CUSTOM
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.TYPING_CATALOG
import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName
import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.fetchUri
Expand All @@ -20,10 +21,12 @@ import java.net.URI
internal fun ActionCoords.provideTypes(
metadataRevision: MetadataRevision,
fetchUri: (URI) -> String = ::fetchUri,
types: String? = null,
): Pair<Map<String, Typing>, TypingActualSource?> =
(
this.fetchTypingMetadata(metadataRevision, fetchUri)
?: this.toMajorVersion().fetchFromTypingsFromCatalog(fetchUri)
customTypingMetadata(types)
?: this.fetchTypingMetadata(metadataRevision, fetchUri)
?: this.toMajorVersion().fetchTypingsFromCatalog(fetchUri)
)?.let { Pair(it.first.toTypesMap(), it.second) }
?: Pair(emptyMap(), null)

Expand All @@ -41,6 +44,9 @@ private fun ActionCoords.catalogMetadata() =
private fun ActionCoords.actionTypesYamlUrl(gitRef: String) =
"https://raw.githubusercontent.com/$owner/$name/$gitRef$subName/action-types.yaml"

private fun customTypingMetadata(types: String? = null) =
types?.let { Pair(yaml.decodeFromStringOrDefaultIfEmpty(it, ActionTypes()), CUSTOM) }

private fun ActionCoords.fetchTypingMetadata(
metadataRevision: MetadataRevision,
fetchUri: (URI) -> String = ::fetchUri,
Expand All @@ -64,7 +70,7 @@ private fun ActionCoords.fetchTypingMetadata(
return Pair(yaml.decodeFromStringOrDefaultIfEmpty(typesMetadataYaml, ActionTypes()), ACTION)
}

private fun ActionCoords.fetchFromTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair<ActionTypes, TypingActualSource>? =
private fun ActionCoords.fetchTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair<ActionTypes, TypingActualSource>? =
(
fetchTypingsFromUrl(url = actionTypesFromCatalog(), fetchUri = fetchUri)
?: fetchTypingsForOlderVersionFromCatalog(fetchUri = fetchUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ class TypesProvidingTest :
stored-in-typing-catalog:
type: string
""".trimIndent()
val custom =
"""
inputs:
custom:
type: string
""".trimIndent()
val metadata =
"""
"versionsWithTypings":
Expand Down Expand Up @@ -311,6 +317,134 @@ class TypesProvidingTest :
types shouldBe Pair(mapOf("hosted-by-action-yml" to StringTyping), TypingActualSource.ACTION)
}

test("only custom") {
// Given
val fetchUri: (URI) -> String = { throw IOException() }
val actionCoord = ActionCoords("some-owner", "some-name", "v3")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom)

// Then
types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM)
}

test("only custom for subaction") {
// Given
val fetchUri: (URI) -> String = { throw IOException() }
val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom)

// Then
types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM)
}

test("hosted by action, stored in typing catalog, and custom") {
// Given
val fetchUri: (URI) -> String = {
when (it) {
URI(
"https://raw.githubusercontent.com/some-owner/some-name/" +
"some-hash/action-types.yml",
),
-> hostedByActionYml
URI(
"https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" +
"main/typings/some-owner/some-name/v3/action-types.yml",
),
-> storedInTypingCatalog
else -> throw IOException()
}
}
val actionCoord = ActionCoords("some-owner", "some-name", "v3")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom)

// Then
types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM)
}

test("hosted by subaction, stored in typing catalog, and custom") {
// Given
val fetchUri: (URI) -> String = {
when (it) {
URI(
"https://raw.githubusercontent.com/some-owner/some-name/" +
"some-hash/some-sub/action-types.yml",
),
-> hostedByActionYml
URI(
"https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" +
"main/typings/some-owner/some-name/v3/some-sub/action-types.yml",
),
-> storedInTypingCatalog
else -> throw IOException()
}
}
val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom)

// Then
types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM)
}

test("hosted by action, stored in typing catalog, and empty custom") {
// Given
val fetchUri: (URI) -> String = {
when (it) {
URI(
"https://raw.githubusercontent.com/some-owner/some-name/" +
"some-hash/action-types.yml",
),
-> hostedByActionYml
URI(
"https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" +
"main/typings/some-owner/some-name/v3/action-types.yml",
),
-> storedInTypingCatalog
else -> throw IOException()
}
}
val actionCoord = ActionCoords("some-owner", "some-name", "v3")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = "")

// Then
types shouldBe Pair(emptyMap(), TypingActualSource.CUSTOM)
}

test("hosted by subaction, stored in typing catalog, and empty custom") {
// Given
val fetchUri: (URI) -> String = {
when (it) {
URI(
"https://raw.githubusercontent.com/some-owner/some-name/" +
"some-hash/some-sub/action-types.yml",
),
-> hostedByActionYml
URI(
"https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" +
"main/typings/some-owner/some-name/v3/some-sub/action-types.yml",
),
-> storedInTypingCatalog
else -> throw IOException()
}
}
val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = "")

// Then
types shouldBe Pair(emptyMap(), TypingActualSource.CUSTOM)
}

test("only stored in typing catalog for older version") {
// Given
val fetchUri: (URI) -> String = {
Expand Down Expand Up @@ -511,6 +645,26 @@ class TypesProvidingTest :
)
}

test("only custom") {
// Given
val fetchUri: (URI) -> String = { throw IOException() }
val actionCoord = ActionCoords("some-owner", "some-name", "v3")

// When
val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = typingYml)

// Then
types shouldBe
Pair(
mapOf(
"granted-scopes" to ListOfTypings(",", EnumTyping("GrantedScopes", listOf("read", "write"))),
"granted-scopes2" to ListOfTypings(",", EnumTyping("GrantedScopes", listOf("read", "write"))),
"granted-scopes3" to ListOfTypings("""\n""", EnumTyping("GrantedScopes", listOf("read", "write"))),
),
TypingActualSource.CUSTOM,
)
}

test("billion laughs attack is prevented") {
// Given
val billionLaughsAttack =
Expand Down
13 changes: 12 additions & 1 deletion docs/user-guide/using-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ For every action, a binding will be generated. However, some less popular action
their inputs, so by default all inputs are of type `String`, have the suffix `_Untyped`, and additionally the class
name will have an `_Untyped` suffix. The nullability of the inputs will be according to their required status.

There are two ways of configuring typings:
There are two and a half ways of configuring typings:
1. Recommended: a typing manifest (`action-typing.yml`) in the action's repo, see
[github-actions-typing](https://github.com/typesafegithub/github-actions-typing/). Thanks to this, the action's owner
is responsible for providing and maintaining the typings defined in a technology-agnostic way, to be used
Expand All @@ -34,6 +34,17 @@ There are two ways of configuring typings:
[github-actions-typing-catalog](https://github.com/typesafegithub/github-actions-typing-catalog),
a community-maintained place to host the typings. You can contribute or fix typings for your favorite action by
sending a PR.
3. Temporary: while developing a typing manifest it might be a good idea to test the result without needing to
release the action in question or merge a PR in the catalog. For this you can `POST` the typing manifest you have
on disk to the binding server using any valid URL for the action in question, for example using
```bash
curl --data-binary @action-types.yml https://bindings.krzeminski.it/pbrisbin/setup-tool-action/v2/setup-tool-action-v2.pom
```
The binding server generates a binding with only the given type manifest and answer with some unique coordinates
that you can use in a test workflow script. The binding will be available the normal cache time on the binding
server and locally as long as you do not delete it from your local Maven repository where it is cached. After
the cache period on the server ended requesting the same coordinates will return a binding as if no typing
information is available at all.

Once there are any typings in place for the action, the `_Untyped` suffixed class is marked `@Deprecated`, and a class
without that suffix is created additionally. In that class for each input that does not have type information available
Expand Down
1 change: 1 addition & 0 deletions jit-binding-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation("io.ktor:ktor-client-cio")
implementation("io.ktor:ktor-client-content-negotiation")
implementation("io.ktor:ktor-serialization-kotlinx-json")
implementation("it.krzeminski:snakeyaml-engine-kmp:3.0.2")
implementation("io.opentelemetry.instrumentation:opentelemetry-ktor-2.0:2.3.0-alpha")
implementation("io.opentelemetry:opentelemetry-sdk:1.43.0")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.43.0")
Expand Down
Loading

0 comments on commit cbc29ce

Please sign in to comment.