From 27a53f94907d72acfc1fa6c4ec26bd9a0c762497 Mon Sep 17 00:00:00 2001 From: Piotr Krzeminski Date: Sat, 2 Nov 2024 22:39:29 +0100 Subject: [PATCH] Extend reporting part to handle more than one action per repo --- .../krzeminski/githubactionstyping/Logic.kt | 6 +- .../githubactionstyping/ManifestsToReport.kt | 6 +- .../parsing/TypesManifestReading.kt | 4 +- .../reporting/PlaintextReport.kt | 51 ++++++------ .../validation/ManifestValidation.kt | 79 +++++++++---------- .../githubactionstyping/validation/Model.kt | 19 +++++ .../githubactionstyping/LogicTest.kt | 6 +- .../validation/ManifestValidationTest.kt | 37 ++++----- .../validation/ManifestsToReportTest.kt | 26 +++--- 9 files changed, 133 insertions(+), 101 deletions(-) create mode 100644 src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt index 614887f..6b335ea 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/Logic.kt @@ -15,11 +15,11 @@ import kotlin.io.path.exists */ fun validateTypings(repoRoot: Path = Path.of(".")): Pair { require(repoRoot.exists()) { "The given repo root leads to non-existent dir: $repoRoot" } - val manifest = repoRoot.readYamlFile("action") ?: + val (manifest, manifestPath) = repoRoot.readYamlFile("action") ?: return Pair(false, "No action manifest (action.yml or action.yaml) found!") - val typesManifest = repoRoot.readYamlFile("action-types") ?: + val (typesManifest, _) = repoRoot.readYamlFile("action-types") ?: return Pair(false, "No types manifest (action-types.yml or action-types.yaml) found!") - return manifestsToReport(manifest, typesManifest) + return manifestsToReport(repoRoot.relativize(manifestPath), manifest, typesManifest) } diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt index 38d2a09..a459b36 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/ManifestsToReport.kt @@ -7,8 +7,9 @@ import it.krzeminski.githubactionstyping.reporting.toPlaintextReport import it.krzeminski.githubactionstyping.validation.ItemValidationResult import it.krzeminski.githubactionstyping.validation.buildInputOutputMismatchValidationResult import it.krzeminski.githubactionstyping.validation.validate +import java.nio.file.Path -fun manifestsToReport(manifest: String, typesManifest: String): Pair { +fun manifestsToReport(manifestPath: Path, manifest: String, typesManifest: String): Pair { val parsedTypesManifest = if (typesManifest.isNotBlank()) { parseTypesManifest(typesManifest) } else { @@ -24,6 +25,7 @@ fun manifestsToReport(manifest: String, typesManifest: String): Pair? = listOf("yaml", "yml") .map { "$nameWithoutExtension.$it" } .firstOrNull { this.resolve(it).exists() } - ?.let { this.resolve(it).readText() } + ?.let { this.resolve(it).let { Pair(it.readText(), it) } } diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt index e3a3a48..25aefd7 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/reporting/PlaintextReport.kt @@ -1,34 +1,37 @@ package it.krzeminski.githubactionstyping.reporting -import it.krzeminski.githubactionstyping.validation.ActionValidationResult import it.krzeminski.githubactionstyping.validation.ItemValidationResult +import it.krzeminski.githubactionstyping.validation.RepoValidationResult -fun ActionValidationResult.toPlaintextReport(): String = buildString { - appendLine("Overall result: ") - this@toPlaintextReport.overallResult.appendStatus(this) - appendLine() +fun RepoValidationResult.toPlaintextReport(): String = buildString { + this@toPlaintextReport.pathToActionValidationResult.forEach { (path, resultForAction) -> + appendLine("For action with manifest at '$path':") + appendLine("Result:") + resultForAction.overallResult.appendStatus(this) + appendLine() - appendLine("Inputs:") - this@toPlaintextReport.inputs.forEach { (key, value) -> - appendLine("• $key:") - append(" ") - value.appendStatus(this) - } - if (this@toPlaintextReport.inputs.isEmpty()) { - appendLine("None.") - } - appendLine() + appendLine("Inputs:") + resultForAction.inputs.forEach { (key, value) -> + appendLine("• $key:") + append(" ") + value.appendStatus(this) + } + if (resultForAction.inputs.isEmpty()) { + appendLine("None.") + } + appendLine() - appendLine("Outputs:") - this@toPlaintextReport.outputs.forEach { (key, value) -> - appendLine("• $key:") - append(" ") - value.appendStatus(this) - } - if (this@toPlaintextReport.outputs.isEmpty()) { - appendLine("None.") + appendLine("Outputs:") + resultForAction.outputs.forEach { (key, value) -> + appendLine("• $key:") + append(" ") + value.appendStatus(this) + } + if (resultForAction.outputs.isEmpty()) { + appendLine("None.") + } + appendLine() } - appendLine() } private fun ItemValidationResult.appendStatus( diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt index 724a98a..3dba2de 100644 --- a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidation.kt @@ -8,54 +8,64 @@ import it.krzeminski.githubactionstyping.validation.types.validateFloat import it.krzeminski.githubactionstyping.validation.types.validateInteger import it.krzeminski.githubactionstyping.validation.types.validateList import it.krzeminski.githubactionstyping.validation.types.validateString +import java.nio.file.Path -fun TypesManifest.validate(): ActionValidationResult { +fun TypesManifest.validate(manifestPath: Path): RepoValidationResult { val inputValidationResults = this.inputs.mapValues { (_, value) -> value.validate() } val outputValidationResults = this.outputs.mapValues { (_, value) -> value.validate() } val isSomethingInvalid = (inputValidationResults.values + outputValidationResults.values) .any { it != ItemValidationResult.Valid } - return ActionValidationResult( + return RepoValidationResult( overallResult = if (isSomethingInvalid) ItemValidationResult.Invalid("Some typing is invalid.") else ItemValidationResult.Valid, - inputs = inputValidationResults, - outputs = outputValidationResults, + pathToActionValidationResult = mapOf(manifestPath to ActionValidationResult( + overallResult = if (isSomethingInvalid) ItemValidationResult.Invalid("Some typing is invalid.") else ItemValidationResult.Valid, + inputs = inputValidationResults, + outputs = outputValidationResults, + ) + ) ) } fun buildInputOutputMismatchValidationResult( + manifestPath: Path, inputsInManifest: Set, inputsInTypesManifest: Set, outputsInManifest: Set, outputsInTypesManifest: Set, -): ActionValidationResult { - return ActionValidationResult( - overallResult = ItemValidationResult.Invalid( - "Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.", - ), - inputs = (inputsInManifest + inputsInTypesManifest) - .associateWith { - if (it in inputsInManifest && it in inputsInTypesManifest) { - ItemValidationResult.Valid - } else { - if (it !in inputsInManifest) { - ItemValidationResult.Invalid("This input doesn't exist in the action manifest.") +): RepoValidationResult { + return RepoValidationResult( + overallResult = ItemValidationResult.Invalid("There was input/output mismatch for one of the actions."), + pathToActionValidationResult = mapOf(manifestPath to ActionValidationResult( + overallResult = ItemValidationResult.Invalid( + "Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.", + ), + inputs = (inputsInManifest + inputsInTypesManifest) + .associateWith { + if (it in inputsInManifest && it in inputsInTypesManifest) { + ItemValidationResult.Valid } else { - ItemValidationResult.Invalid("This input doesn't exist in the types manifest.") + if (it !in inputsInManifest) { + ItemValidationResult.Invalid("This input doesn't exist in the action manifest.") + } else { + ItemValidationResult.Invalid("This input doesn't exist in the types manifest.") + } } - } - }, - outputs = (outputsInManifest + outputsInTypesManifest) - .associateWith { - if (it in outputsInManifest && it in outputsInTypesManifest) { - ItemValidationResult.Valid - } else { - if (it !in outputsInManifest) { - ItemValidationResult.Invalid("This output doesn't exist in the action manifest.") + }, + outputs = (outputsInManifest + outputsInTypesManifest) + .associateWith { + if (it in outputsInManifest && it in outputsInTypesManifest) { + ItemValidationResult.Valid } else { - ItemValidationResult.Invalid("This output doesn't exist in the types manifest.") + if (it !in outputsInManifest) { + ItemValidationResult.Invalid("This output doesn't exist in the action manifest.") + } else { + ItemValidationResult.Invalid("This output doesn't exist in the types manifest.") + } } - } - }, + }, + ) + ) ) } @@ -74,14 +84,3 @@ private fun ApiItem.validate(): ItemValidationResult { else -> ItemValidationResult.Invalid("Unknown type: '${this.type}'.") } } - -data class ActionValidationResult( - val overallResult: ItemValidationResult, - val inputs: Map = emptyMap(), - val outputs: Map = emptyMap(), -) - -sealed interface ItemValidationResult { - object Valid : ItemValidationResult - data class Invalid(val message: String) : ItemValidationResult -} diff --git a/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt new file mode 100644 index 0000000..fc61b39 --- /dev/null +++ b/src/main/kotlin/it/krzeminski/githubactionstyping/validation/Model.kt @@ -0,0 +1,19 @@ +package it.krzeminski.githubactionstyping.validation + +import java.nio.file.Path + +data class RepoValidationResult( + val overallResult: ItemValidationResult, + val pathToActionValidationResult: Map, +) + +data class ActionValidationResult( + val overallResult: ItemValidationResult, + val inputs: Map = emptyMap(), + val outputs: Map = emptyMap(), +) + +sealed interface ItemValidationResult { + data object Valid : ItemValidationResult + data class Invalid(val message: String) : ItemValidationResult +} diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt index bba70ac..2c66c01 100644 --- a/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/LogicTest.kt @@ -21,7 +21,8 @@ class LogicTest : FunSpec({ assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -48,7 +49,8 @@ class LogicTest : FunSpec({ assertSoftly { isValid shouldBe false report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[31m❌ INVALID: Some typing is invalid.${'\u001b'}[0m Inputs: diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt index 0a68cee..f178500 100644 --- a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestValidationTest.kt @@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import it.krzeminski.githubactionstyping.parsing.ApiItem import it.krzeminski.githubactionstyping.parsing.TypesManifest +import kotlin.io.path.Path class ManifestValidationTest : FunSpec({ context("success cases") { @@ -28,7 +29,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -58,7 +59,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -106,7 +107,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -135,7 +136,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -162,7 +163,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -187,7 +188,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -213,7 +214,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -244,7 +245,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -268,7 +269,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -288,7 +289,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -308,7 +309,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -328,7 +329,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -348,7 +349,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -373,7 +374,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -401,7 +402,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -437,7 +438,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -469,7 +470,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( @@ -497,7 +498,7 @@ class ManifestValidationTest : FunSpec({ ) // when - val result = manifest.validate() + val result = manifest.validate(Path("action.yml")).pathToActionValidationResult[Path("action.yml")] // then result shouldBe ActionValidationResult( diff --git a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt index 0adf1ae..32a766a 100644 --- a/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt +++ b/src/test/kotlin/it/krzeminski/githubactionstyping/validation/ManifestsToReportTest.kt @@ -4,6 +4,7 @@ import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import it.krzeminski.githubactionstyping.manifestsToReport +import kotlin.io.path.Path class ManifestsToReportTest : FunSpec({ test("success case") { @@ -36,13 +37,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -95,13 +97,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -133,13 +136,14 @@ class ManifestsToReportTest : FunSpec({ val typesManifest = " " // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe true report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[32m✔ VALID${'\u001b'}[0m Inputs: @@ -180,13 +184,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe false report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[31m❌ INVALID: Some typing is invalid.${'\u001b'}[0m Inputs: @@ -239,13 +244,14 @@ class ManifestsToReportTest : FunSpec({ """.trimIndent() // when - val (isValid, report) = manifestsToReport(manifest, typesManifest) + val (isValid, report) = manifestsToReport(Path("action.yml"), manifest, typesManifest) // then assertSoftly { isValid shouldBe false report shouldBe """ - Overall result: + For action with manifest at 'action.yml': + Result: ${'\u001b'}[31m❌ INVALID: Input/output mismatch detected. Please fix it first, then rerun to see other possible violations.${'\u001b'}[0m Inputs: