Skip to content

Commit

Permalink
[#7] Take type info from action-typing.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
krzema12 committed Jun 22, 2022
1 parent 365bd57 commit 39ca5c8
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 39 deletions.
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,49 @@

Bring type-safety to your GitHub actions' API!

This is a GitHub action that validates your action's manifest (action.y(a)ml) and ensures the inputs and outputs have
types set according to a certain specification.
This is a GitHub action that validates your action's type specs (`action-types.y(a)ml`) and ensures the inputs and
outputs have types set according to a certain specification.

# Example

Let's say your action has such manifest:
Let's say your action has such manifest (`action.yml`):

```yaml
name: My cool action
description: Just to showcase GitHub Actions typing
typingSpec: krzema12/github-actions-typing@v0.1
inputs:
verbose:
description: 'Set to true to display debug information helpful when troubleshooting issues with this action.'
required: false
default: 'false'
type: boolean
log-level:
description: 'Specify the level of details for logging.'
required: true
permissions:
description: 'Who should have access.'
type: inttteger
runs:
using: 'node16'
image: 'dist/main.js'
```

and such `action-types.yml` next to it:

```yaml
typingSpec: krzema12/github-actions-typing@v0.1
inputs:
verbose:
type: boolean
permissions:
type: inttteger
```

This action, once used within a workflow, will fail the workflow run and produce such output:

![Example output](docs/ExampleOutput.png)

# Usage

In your action's `action.yml`:
Create a new file in your action repo's root directory: `action-types.yml`, then:

- add top-level attribute: `typingSpec: krzema12/github-actions-typing@v0.1`. Thanks to this, you as the actions' author
state which kind of typings your actions adheres to. At the time of writing this, no standard has emerged yet. This
Expand Down
4 changes: 4 additions & 0 deletions action-types.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
typingSpec: krzema12/github-actions-typing@v0.1
inputs:
verbose:
type: boolean
2 changes: 0 additions & 2 deletions action.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
name: GitHub Actions Typing
description: Bring type-safety to your GitHub actions' API!
author: Piotr Krzemiński
typingSpec: krzema12/github-actions-typing@v0.1
inputs:
verbose:
description: 'Set to true to display debug information helpful when troubleshooting issues with this action.'
required: false
default: 'false'
type: boolean
runs:
using: 'docker'
image: 'Dockerfile'
12 changes: 8 additions & 4 deletions src/main/kotlin/it/krzeminski/githubactionstyping/Main.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package it.krzeminski.githubactionstyping

import it.krzeminski.githubactionstyping.github.getBooleanInput
import it.krzeminski.githubactionstyping.parsing.parseManifest
import it.krzeminski.githubactionstyping.parsing.readActionManifest
import it.krzeminski.githubactionstyping.parsing.parseTypesManifest
import it.krzeminski.githubactionstyping.parsing.readActionTypesManifest
import it.krzeminski.githubactionstyping.reporting.toPlaintextReport
import it.krzeminski.githubactionstyping.validation.ItemValidationResult
import it.krzeminski.githubactionstyping.validation.validate
import kotlin.system.exitProcess

fun main() {
val manifest = readActionManifest() ?: return
val parsedManifest = parseManifest(manifest)
val manifest = readActionTypesManifest() ?: run {
println("No types manifest (action-types.yml or action-types.yaml) found!")
exitProcess(1)
throw IllegalStateException()
}
val parsedManifest = parseTypesManifest(manifest)

if (getBooleanInput("verbose")) {
println("Action's manifest:")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString

@Serializable
data class Manifest(
data class TypesManifest(
val typingSpec: String? = null,
val inputs: Map<String, ApiItem> = emptyMap(),
val outputs: Map<String, ApiItem> = emptyMap(),
Expand All @@ -26,5 +26,5 @@ private val myYaml = Yaml(
)
)

fun parseManifest(manifestString: String): Manifest =
fun parseTypesManifest(manifestString: String): TypesManifest =
myYaml.decodeFromString(manifestString)
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package it.krzeminski.githubactionstyping.parsing

import java.io.File

fun readActionManifest(): String? =
fun readActionTypesManifest(): String? =
listOf("yaml", "yml")
.map { "action.$it" }
.map { "action-types.$it" }
.firstOrNull { File(it).exists() }
?.let { File(it).readText() }
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package it.krzeminski.githubactionstyping.validation

import it.krzeminski.githubactionstyping.parsing.ApiItem
import it.krzeminski.githubactionstyping.parsing.Manifest
import it.krzeminski.githubactionstyping.parsing.TypesManifest
import it.krzeminski.githubactionstyping.validation.types.validateBoolean
import it.krzeminski.githubactionstyping.validation.types.validateEnum
import it.krzeminski.githubactionstyping.validation.types.validateFloat
Expand All @@ -11,7 +11,7 @@ import it.krzeminski.githubactionstyping.validation.types.validateString

const val expectedTypingSpec = "krzema12/github-actions-typing@v0.1"

fun Manifest.validate(): ActionValidationResult {
fun TypesManifest.validate(): ActionValidationResult {
if (this.typingSpec == null || this.typingSpec != expectedTypingSpec) {
return ActionValidationResult(
overallResult = ItemValidationResult.Invalid(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package it.krzeminski.githubactionstyping.validation
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import it.krzeminski.githubactionstyping.parsing.ApiItem
import it.krzeminski.githubactionstyping.parsing.Manifest
import it.krzeminski.githubactionstyping.parsing.TypesManifest

class ManifestValidationTest : FunSpec({
context("success cases") {
test("primitive types") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"string-input" to ApiItem(type = "string"),
Expand Down Expand Up @@ -38,7 +38,7 @@ class ManifestValidationTest : FunSpec({

test("enum type") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"enum-input" to ApiItem(type = "enum", allowedValues = listOf("foo", "bar", "baz")),
Expand All @@ -59,7 +59,7 @@ class ManifestValidationTest : FunSpec({

test("list type") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-of-strings-input" to ApiItem(
Expand Down Expand Up @@ -113,7 +113,7 @@ class ManifestValidationTest : FunSpec({
context("failure cases") {
test("no typing spec set") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = null,
inputs = emptyMap(),
outputs = emptyMap(),
Expand All @@ -134,7 +134,7 @@ class ManifestValidationTest : FunSpec({

test("incorrect typing spec set") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = "incorrect-typing-spec",
inputs = emptyMap(),
outputs = emptyMap(),
Expand All @@ -156,7 +156,7 @@ class ManifestValidationTest : FunSpec({

test("input and output without type") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"some-input" to ApiItem(type = null),
Expand Down Expand Up @@ -187,7 +187,7 @@ class ManifestValidationTest : FunSpec({

test("unknown type") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"some-input" to ApiItem(type = "for-sure-unknown-type"),
Expand All @@ -210,7 +210,7 @@ class ManifestValidationTest : FunSpec({

test("primitive types with 'allowedValues' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"string-input" to ApiItem(type = "string", allowedValues = listOf("foo", "bar")),
Expand All @@ -237,7 +237,7 @@ class ManifestValidationTest : FunSpec({

test("primitive types with 'separator' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"string-input" to ApiItem(type = "string", separator = ","),
Expand All @@ -264,7 +264,7 @@ class ManifestValidationTest : FunSpec({

test("non-list types with 'listItem' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"string-input" to ApiItem(type = "string", listItem = ApiItem(type = "string")),
Expand Down Expand Up @@ -297,7 +297,7 @@ class ManifestValidationTest : FunSpec({

test("enum type with 'separator' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"enum-input" to ApiItem(type = "enum", allowedValues = listOf("foo", "bar", "baz"), separator = ","),
Expand All @@ -318,7 +318,7 @@ class ManifestValidationTest : FunSpec({

test("enum type without 'allowedValues' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"enum-input" to ApiItem(type = "enum", allowedValues = null),
Expand All @@ -339,7 +339,7 @@ class ManifestValidationTest : FunSpec({

test("enum type with just one allowed value") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"enum-input" to ApiItem(type = "enum", allowedValues = listOf("foo")),
Expand All @@ -360,7 +360,7 @@ class ManifestValidationTest : FunSpec({

test("list type without 'listItem' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-input" to ApiItem(type = "list", separator = ","),
Expand All @@ -381,7 +381,7 @@ class ManifestValidationTest : FunSpec({

test("list type without 'separator' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-input" to ApiItem(type = "list", listItem = ApiItem(type = "string")),
Expand All @@ -402,7 +402,7 @@ class ManifestValidationTest : FunSpec({

test("list type with 'allowedValues' attribute") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-input" to ApiItem(
Expand All @@ -428,7 +428,7 @@ class ManifestValidationTest : FunSpec({

test("list type with forbidden list item type") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-of-lists-input" to ApiItem(
Expand Down Expand Up @@ -459,7 +459,7 @@ class ManifestValidationTest : FunSpec({

test("list type with list item type with incorrect attributes") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"list-of-enums-without-allowed-values-input" to ApiItem(
Expand Down Expand Up @@ -502,7 +502,7 @@ class ManifestValidationTest : FunSpec({

test("non-integer types with named values") {
// given
val manifest = Manifest(
val manifest = TypesManifest(
typingSpec = expectedTypingSpec,
inputs = mapOf(
"string-input" to ApiItem(type = "string", namedValues = mapOf("foo" to 1)),
Expand Down

0 comments on commit 39ca5c8

Please sign in to comment.