From 483a70545e2fbcb52a6b8fe6404fde570a6bdc80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Thu, 22 Aug 2024 21:50:20 +0200 Subject: [PATCH] feat(abg)!: provide untyped fields always and typed ones if type available (#1603) Fixes #1585 I have to confess, that I did not check every change in the unit test workflow files, so they should probably be looked again closely. But I think it should have done the right thing. Or at least how I envisioned it. no typing => - only `_Untyped` class with `_Untyped` properties - nullability according to required status full typing => - same `_Untyped` class as above - but marked as `@Deprecated` with a replace hint for the typed class (works in the IDE), - additionally a typed class with each property as typed and untyped version - all properties nullable, with runtime checks that - not the typed and untyped is set both for one property - one of the two is set for required properties partly typing => - same `_Untyped` and typed class as above - for inputs that have no type declared only the `_Untyped` property is added - the properties that are only untyped have the nullability according to required status, also in the typed class --- ...uming-jit-bindings.main.do-not-compile.kts | 5 +- .../generation/Generation.kt | 319 ++++++++++++++---- .../metadata/InputNullability.kt | 2 +- .../ActionWithAllTypesOfInputs.kt | 238 ++++++++++--- .../ActionWithAllTypesOfInputsTest.kt | 71 ++++ .../ActionWithAllTypesOfInputs_Untyped.kt | 182 ++++++++++ .../ActionWithDeprecatedInputAndNameClash.kt | 29 +- .../ActionWithFancyCharsInDocs.kt | 29 +- .../ActionWithInputsSharingType.kt | 62 +++- .../ActionWithNoInputs.kt | 2 +- .../ActionWithNoTypings_Untyped.kt | 15 +- .../ActionWithOutputs.kt | 29 +- .../ActionWithPartlyTypings.kt | 90 +++++ .../ActionWithPartlyTypings_Untyped.kt | 94 ++++++ .../ActionWithSomeOptionalInputs.kt | 76 ++++- .../SimpleActionWithRequiredStringInputs.kt | 57 +++- .../generation/GenerationTest.kt | 42 +++ .../metadata/InputNullabilityTest.kt | 2 +- .../migrating-to-Maven-based-bindings.md | 7 +- docs/user-guide/using-actions.md | 15 +- 20 files changed, 1192 insertions(+), 174 deletions(-) create mode 100644 action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs_Untyped.kt create mode 100644 action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings.kt create mode 100644 action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings_Untyped.kt diff --git a/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts b/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts index 3c409bab75..ddce34f2ec 100755 --- a/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts +++ b/.github/workflows/test-script-consuming-jit-bindings.main.do-not-compile.kts @@ -22,8 +22,9 @@ import io.github.typesafegithub.workflows.actions.actions.Checkout_Untyped import io.github.typesafegithub.workflows.actions.gradle.ActionsSetupGradle import io.github.typesafegithub.workflows.actions.typesafegithub.AlwaysUntypedActionForTests_Untyped -println(Checkout_Untyped(fetchTags = "false")) +println(Checkout_Untyped(fetchTags_Untyped = "false")) println(Checkout(fetchTags = false)) -println(AlwaysUntypedActionForTests_Untyped(foobar = "baz")) +println(Checkout(fetchTags_Untyped = "false")) +println(AlwaysUntypedActionForTests_Untyped(foobar_Untyped = "baz")) println(ActionsSetupGradle()) println(Cache(path = listOf("some-path"), key = "some-key")) diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt index 5fecb329c0..12709a597c 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt @@ -22,7 +22,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.generation.Prop import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.Input import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.Metadata import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.fetchMetadata -import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.shouldBeNonNullInBinding +import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.shouldBeRequiredInBinding import io.github.typesafegithub.workflows.actionbindinggenerator.typing.StringTyping import io.github.typesafegithub.workflows.actionbindinggenerator.typing.Typing import io.github.typesafegithub.workflows.actionbindinggenerator.typing.asString @@ -63,27 +63,19 @@ public fun ActionCoords.generateBinding( val inputTypingsResolved = inputTypings ?: this.provideTypes(metadataRevision) - val classNameUntyped = this.buildActionClassName() + "_Untyped" - val actionBindingSourceCodeUntyped = - generateActionBindingSourceCode(metadataProcessed, this, emptyMap(), classNameUntyped, untyped = true) - - val classNameAndSourceCodeTyped = - if (inputTypingsResolved.second != null) { - val className = this.buildActionClassName() - val actionBindingSourceCode = - generateActionBindingSourceCode( - metadataProcessed, - this, - inputTypingsResolved.first, - className, - untyped = false, - ) - Pair(className, actionBindingSourceCode) - } else { - null - } - val packageName = owner.toKotlinPackageName() + val className = this.buildActionClassName() + val classNameUntyped = "${className}_Untyped" + + val actionBindingSourceCodeUntyped = + generateActionBindingSourceCode( + metadataProcessed, + this, + emptyMap(), + classNameUntyped, + untypedClass = true, + replaceWith = inputTypingsResolved.second?.let { CodeBlock.of("ReplaceWith(%S)", className) }, + ) return listOfNotNull( ActionBinding( @@ -93,13 +85,20 @@ public fun ActionCoords.generateBinding( packageName = packageName, typingActualSource = null, ), - classNameAndSourceCodeTyped?.let { (className, actionBindingSourceCode) -> + inputTypingsResolved.second?.let { + val actionBindingSourceCode = + generateActionBindingSourceCode( + metadata = metadataProcessed, + coords = this, + inputTypings = inputTypingsResolved.first, + className = className, + ) ActionBinding( kotlinCode = actionBindingSourceCode, filePath = "kotlin/io/github/typesafegithub/workflows/actions/$packageName/$className.kt", className = className, packageName = packageName, - typingActualSource = inputTypingsResolved.second, + typingActualSource = it, ) }, ) @@ -123,7 +122,8 @@ private fun generateActionBindingSourceCode( coords: ActionCoords, inputTypings: Map, className: String, - untyped: Boolean, + untypedClass: Boolean = false, + replaceWith: CodeBlock? = null, ): String { val fileSpec = FileSpec @@ -136,7 +136,7 @@ private fun generateActionBindingSourceCode( changes will be overwritten with the next binding code regeneration. See https://github.com/typesafegithub/github-workflows-kt for more info. """.trimIndent(), - ).addType(generateActionClass(metadata, coords, inputTypings, className, untyped)) + ).addType(generateActionClass(metadata, coords, inputTypings, className, untypedClass, replaceWith)) .addSuppressAnnotation(metadata) .indent(" ") .build() @@ -167,17 +167,20 @@ private fun generateActionClass( coords: ActionCoords, inputTypings: Map, className: String, - untyped: Boolean, + untypedClass: Boolean, + replaceWith: CodeBlock?, ): TypeSpec = TypeSpec .classBuilder(className) .addModifiers(KModifier.DATA) - .addKdoc(actionKdoc(metadata, coords, untyped)) + .addKdocIfNotEmpty(actionKdoc(metadata, coords, untypedClass)) + .replaceWith(replaceWith) .inheritsFromRegularAction(coords, metadata, className) - .primaryConstructor(metadata.primaryConstructor(inputTypings, coords, className)) - .properties(metadata, coords, inputTypings, className) - .addFunction(metadata.secondaryConstructor(inputTypings, coords, className)) - .addFunction(metadata.buildToYamlArgumentsFunction(inputTypings)) + .primaryConstructor(metadata.primaryConstructor(inputTypings, coords, className, untypedClass)) + .properties(metadata, coords, inputTypings, className, untypedClass) + .addInitializerBlockIfNecessary(metadata, inputTypings, untypedClass) + .addFunction(metadata.secondaryConstructor(inputTypings, coords, className, untypedClass)) + .addFunction(metadata.buildToYamlArgumentsFunction(inputTypings, untypedClass)) .addCustomTypes(inputTypings, coords, className) .addOutputClassIfNecessary(metadata) .addBuildOutputObjectFunctionIfNecessary(metadata) @@ -200,12 +203,32 @@ private fun TypeSpec.Builder.properties( coords: ActionCoords, inputTypings: Map, className: String, + untypedClass: Boolean, ): TypeSpec.Builder { metadata.inputs.forEach { (key, input) -> + val typedInput = inputTypings.containsKey(key) + if (!untypedClass && typedInput) { + addProperty( + PropertySpec + .builder( + key.toCamelCase(), + inputTypings.getInputType( + key, + input, + coords, + className, + untypedClass = false, + typedInput = true, + ), + ).initializer(key.toCamelCase()) + .annotateDeprecated(input) + .build(), + ) + } addProperty( PropertySpec - .builder(key.toCamelCase(), inputTypings.getInputType(key, input, coords, className)) - .initializer(key.toCamelCase()) + .builder("${key.toCamelCase()}_Untyped", null.getInputType(key, input, coords, className, untypedClass, typedInput)) + .initializer("${key.toCamelCase()}_Untyped") .annotateDeprecated(input) .build(), ) @@ -231,7 +254,7 @@ private fun TypeSpec.Builder.addOutputClassIfNecessary(metadata: Metadata): Type PropertySpec .builder(key.toCamelCase(), String::class) .initializer("\"steps.\$stepId.outputs.$key\"") - .addKdoc(value.description.escapedForComments.removeTrailingWhitespacesForEachLine()) + .addKdocIfNotEmpty(value.description.escapedForComments.removeTrailingWhitespacesForEachLine()) .build() } addType( @@ -251,6 +274,25 @@ private fun TypeSpec.Builder.addOutputClassIfNecessary(metadata: Metadata): Type return this } +private fun B.addKdocIfNotEmpty(kdoc: String): B { + if (kdoc.isNotEmpty()) { + when (this) { + is TypeSpec.Builder -> addKdoc("%L", kdoc) + is PropertySpec.Builder -> addKdoc("%L", kdoc) + is ParameterSpec.Builder -> addKdoc("%L", kdoc) + else -> error("Unexpected type ${this::class}}") + } + } + return this +} + +private fun ParameterSpec.Builder.addKdocIfNotEmpty(kdoc: CodeBlock): ParameterSpec.Builder { + if (kdoc.toString().isNotEmpty()) { + addKdoc(kdoc) + } + return this +} + private fun TypeSpec.Builder.addBuildOutputObjectFunctionIfNecessary(metadata: Metadata): TypeSpec.Builder { addFunction( FunSpec @@ -271,26 +313,31 @@ private fun PropertySpec.Builder.annotateDeprecated(input: Input) = addAnnotation( AnnotationSpec .builder(Deprecated::class.asClassName()) - .addMember(CodeBlock.of("%S", input.deprecationMessage)) + .addMember("%S", input.deprecationMessage) .build(), ) } } -private fun Metadata.buildToYamlArgumentsFunction(inputTypings: Map) = - FunSpec - .builder("toYamlArguments") - .addModifiers(KModifier.OVERRIDE) - .returns(LinkedHashMap::class.parameterizedBy(String::class, String::class)) - .addAnnotation( - AnnotationSpec - .builder(Suppress::class) - .addMember("\"SpreadOperator\"") - .build(), - ).addCode(linkedMapOfInputs(inputTypings)) - .build() +private fun Metadata.buildToYamlArgumentsFunction( + inputTypings: Map, + untypedClass: Boolean, +) = FunSpec + .builder("toYamlArguments") + .addModifiers(KModifier.OVERRIDE) + .returns(LinkedHashMap::class.parameterizedBy(String::class, String::class)) + .addAnnotation( + AnnotationSpec + .builder(Suppress::class) + .addMember("\"SpreadOperator\"") + .build(), + ).addCode(linkedMapOfInputs(inputTypings, untypedClass)) + .build() -private fun Metadata.linkedMapOfInputs(inputTypings: Map): CodeBlock { +private fun Metadata.linkedMapOfInputs( + inputTypings: Map, + untypedClass: Boolean, +): CodeBlock { if (inputs.isEmpty()) { return CodeBlock .Builder() @@ -305,11 +352,16 @@ private fun Metadata.linkedMapOfInputs(inputTypings: Map): CodeB add("*listOfNotNull(\n") indent() inputs.forEach { (key, value) -> - val asStringCode = inputTypings.getInputTyping(key).asString() - if (!value.shouldBeNonNullInBinding()) { - add("%N?.let { %S·to·it$asStringCode },\n", key.toCamelCase(), key) + val propertyName = key.toCamelCase() + if (!untypedClass && inputTypings.containsKey(key)) { + val asStringCode = inputTypings.getInputTyping(key).asString() + add("%N?.let { %S·to·it$asStringCode },\n", propertyName, key) + } + val asStringCode = null.getInputTyping(key).asString() + if (value.shouldBeRequiredInBinding() && !value.shouldBeNullable(untypedClass, inputTypings.containsKey(key))) { + add("%S·to·%N$asStringCode,\n", key, "${propertyName}_Untyped") } else { - add("%S·to·%N$asStringCode,\n", key, key.toCamelCase()) + add("%N?.let { %S·to·it$asStringCode },\n", "${propertyName}_Untyped", key) } } add("*$CUSTOM_INPUTS.%M().%M(),\n", Types.mapToList, Types.listToArray) @@ -321,6 +373,19 @@ private fun Metadata.linkedMapOfInputs(inputTypings: Map): CodeB } } +private fun TypeSpec.Builder.replaceWith(replaceWith: CodeBlock?): TypeSpec.Builder { + if (replaceWith != null) { + addAnnotation( + AnnotationSpec + .builder(Deprecated::class.asClassName()) + .addMember("%S", "Use the typed class instead") + .addMember(replaceWith) + .build(), + ) + } + return this +} + private fun TypeSpec.Builder.inheritsFromRegularAction( coords: ActionCoords, metadata: Metadata, @@ -350,17 +415,19 @@ private fun Metadata.primaryConstructor( inputTypings: Map, coords: ActionCoords, className: String, + untypedClass: Boolean, ): FunSpec = FunSpec .constructorBuilder() .addModifiers(KModifier.PRIVATE) - .addParameters(buildCommonConstructorParameters(inputTypings, coords, className)) + .addParameters(buildCommonConstructorParameters(inputTypings, coords, className, untypedClass)) .build() private fun Metadata.secondaryConstructor( inputTypings: Map, coords: ActionCoords, className: String, + untypedClass: Boolean, ): FunSpec = FunSpec .constructorBuilder() @@ -369,53 +436,150 @@ private fun Metadata.secondaryConstructor( .builder("pleaseUseNamedArguments", Unit::class) .addModifiers(KModifier.VARARG) .build(), - ).addParameters(buildCommonConstructorParameters(inputTypings, coords, className)) + ).addParameters(buildCommonConstructorParameters(inputTypings, coords, className, untypedClass)) .callThisConstructor( - (inputs.keys.map { it.toCamelCase() } + CUSTOM_INPUTS + CUSTOM_VERSION) - .map { CodeBlock.of("%N=%N", it, it) }, + inputs + .keys + .flatMap { inputName -> + val typedInput = inputTypings.containsKey(inputName) + listOfNotNull( + untypedClass.takeIf { !it && typedInput }?.let { inputName.toCamelCase() }, + "${inputName.toCamelCase()}_Untyped", + ) + }.plus(CUSTOM_INPUTS) + .plus(CUSTOM_VERSION) + .map { CodeBlock.of("%N = %N", it, it) }, ).build() private fun Metadata.buildCommonConstructorParameters( inputTypings: Map, coords: ActionCoords, className: String, + untypedClass: Boolean, ): List = inputs - .map { (key, input) -> - ParameterSpec - .builder(key.toCamelCase(), inputTypings.getInputType(key, input, coords, className)) - .defaultValueIfNullable(input) - .addKdoc(input.description.escapedForComments.removeTrailingWhitespacesForEachLine()) - .build() + .flatMap { (key, input) -> + val typedInput = inputTypings.containsKey(key) + val description = input.description.escapedForComments.removeTrailingWhitespacesForEachLine() + val kdocBuilder = CodeBlock.builder() + if (typedInput && !untypedClass && input.shouldBeRequiredInBinding()) { + kdocBuilder.add("%L", "".escapedForComments) + if (description.isNotEmpty()) { + kdocBuilder.add(" ") + } + } + kdocBuilder.add("%L", description) + val kdoc = kdocBuilder.build() + + listOfNotNull( + untypedClass.takeIf { !it && typedInput }?.let { + ParameterSpec + .builder( + key.toCamelCase(), + inputTypings.getInputType( + key, + input, + coords, + className, + untypedClass = false, + typedInput = true, + ), + ).defaultValue("null") + .addKdocIfNotEmpty(kdoc) + .build() + }, + ParameterSpec + .builder( + "${key.toCamelCase()}_Untyped", + null.getInputType(key, input, coords, className, untypedClass, typedInput), + ).defaultValueIfNullable(input, untypedClass, typedInput) + .addKdocIfNotEmpty(kdoc) + .build(), + ) }.plus( ParameterSpec .builder(CUSTOM_INPUTS, Types.mapStringString) .defaultValue("mapOf()") - .addKdoc("Type-unsafe map where you can put any inputs that are not yet supported by the binding") + .addKdocIfNotEmpty("Type-unsafe map where you can put any inputs that are not yet supported by the binding") .build(), ).plus( ParameterSpec .builder(CUSTOM_VERSION, Types.nullableString) .defaultValue("null") - .addKdoc( + .addKdocIfNotEmpty( "Allows overriding action's version, for example to use a specific minor version, " + "or a newer version that the binding doesn't yet know about", ).build(), ) -private fun ParameterSpec.Builder.defaultValueIfNullable(input: Input): ParameterSpec.Builder { - if (!input.shouldBeNonNullInBinding()) { +private fun ParameterSpec.Builder.defaultValueIfNullable( + input: Input, + untypedClass: Boolean, + typedInput: Boolean, +): ParameterSpec.Builder { + if (input.shouldBeNullable(untypedClass, typedInput)) { defaultValue("null") } return this } +private fun TypeSpec.Builder.addInitializerBlockIfNecessary( + metadata: Metadata, + inputTypings: Map, + untypedClass: Boolean, +): TypeSpec.Builder { + if (untypedClass || metadata.inputs.isEmpty() || metadata.inputs.none { inputTypings.containsKey(it.key) }) { + return this + } + addInitializerBlock(metadata.initializerBlock(inputTypings)) + return this +} + +private fun Metadata.initializerBlock(inputTypings: Map): CodeBlock { + val codeBlockBuilder = CodeBlock.builder() + var first = true + inputs + .filter { inputTypings.containsKey(it.key) } + .forEach { (key, input) -> + if (!first) { + codeBlockBuilder.add("\n") + } + first = false + val propertyName = key.toCamelCase() + codeBlockBuilder + .add( + """ + require(!((%1N != null) && (%1L_Untyped != null))) { + %2S + } + + """.trimIndent(), + propertyName, + "Only $propertyName or ${propertyName}_Untyped must be set, but not both", + ) + if (input.shouldBeRequiredInBinding()) { + codeBlockBuilder + .add( + """ + require((%1N != null) || (%1L_Untyped != null)) { + %2S + } + + """.trimIndent(), + propertyName, + "Either $propertyName or ${propertyName}_Untyped must be set, one of them is required", + ) + } + } + return codeBlockBuilder.build() +} + private fun actionKdoc( metadata: Metadata, coords: ActionCoords, - untyped: Boolean, + untypedClass: Boolean, ) = ( - if (untyped) { + if (untypedClass) { """ |```text |!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -453,16 +617,24 @@ private fun actionKdoc( )}${if ("/" in coords.name) "/tree/${coords.version}/${coords.name.substringAfter('/')}" else ""}) """.trimMargin() -private fun Map.getInputTyping(key: String) = this[key] ?: StringTyping +private fun Map?.getInputTyping(key: String) = this?.get(key) ?: StringTyping -private fun Map.getInputType( +private fun Map?.getInputType( key: String, input: Input, coords: ActionCoords, className: String, + untypedClass: Boolean, + typedInput: Boolean, ) = getInputTyping(key) .getClassName(coords.owner.toKotlinPackageName(), className, key) - .copy(nullable = !input.shouldBeNonNullInBinding()) + .copy(nullable = input.shouldBeNullable(untypedClass, typedInput)) + +private fun Input.shouldBeNullable( + untypedClass: Boolean, + typedInput: Boolean, +) = (untypedClass && !shouldBeRequiredInBinding()) || + (!untypedClass && (typedInput || !shouldBeRequiredInBinding())) private val String.escapedForComments get() = @@ -472,6 +644,5 @@ private val String.escapedForComments .replace("*/", "*/") .replace("`[^`]++`".toRegex()) { it.value.replace("*", "`*`") - } - // Escape placeholders like in java.text.Format, used by KotlinPoet. - .replace("%", "%%") + }.replace("<", "<") + .replace(">", ">") diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullability.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullability.kt index 29ca0f1c74..11f1142d26 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullability.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullability.kt @@ -4,4 +4,4 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.metadata * [Input.required] is in theory a required field in action's metadata, but in practice a lot of actions don't specify * it. It's thus a challenge to infer nullability of inputs in the Kotlin bindings. This function tackles this task. */ -internal fun Input.shouldBeNonNullInBinding() = default == null && required == true +internal fun Input.shouldBeRequiredInBinding() = default == null && required == true diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs.kt index 534b01aba3..a0cb60b3f8 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs.kt @@ -29,18 +29,30 @@ import kotlin.collections.toTypedArray * * [Action on GitHub](https://github.com/john-smith/action-with-all-types-of-inputs) * - * @param fooBar Short description - * @param bazGoo First boolean input! + * @param fooBar <required> Short description + * @param fooBar_Untyped <required> Short description + * @param bazGoo <required> First boolean input! + * @param bazGoo_Untyped <required> First boolean input! * @param binKin Boolean and nullable - * @param intPint Integer - * @param floPint Float - * @param finBin Enumeration - * @param gooZen Integer with special value - * @param bahEnum Enum with custom naming + * @param binKin_Untyped Boolean and nullable + * @param intPint <required> Integer + * @param intPint_Untyped <required> Integer + * @param floPint <required> Float + * @param floPint_Untyped <required> Float + * @param finBin <required> Enumeration + * @param finBin_Untyped <required> Enumeration + * @param gooZen <required> Integer with special value + * @param gooZen_Untyped <required> Integer with special value + * @param bahEnum <required> Enum with custom naming + * @param bahEnum_Untyped <required> Enum with custom naming * @param listStrings List of strings + * @param listStrings_Untyped List of strings * @param listInts List of integers + * @param listInts_Untyped List of integers * @param listEnums List of enums + * @param listEnums_Untyped List of enums * @param listIntSpecial List of integer with special values + * @param listIntSpecial_Untyped List of integer with special values * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor @@ -48,53 +60,101 @@ import kotlin.collections.toTypedArray */ public data class ActionWithAllTypesOfInputs private constructor( /** - * Short description + * <required> Short description */ - public val fooBar: String, + public val fooBar: String? = null, /** - * First boolean input! + * <required> Short description */ - public val bazGoo: Boolean, + public val fooBar_Untyped: String? = null, + /** + * <required> First boolean input! + */ + public val bazGoo: Boolean? = null, + /** + * <required> First boolean input! + */ + public val bazGoo_Untyped: String? = null, /** * Boolean and nullable */ public val binKin: Boolean? = null, /** - * Integer + * Boolean and nullable + */ + public val binKin_Untyped: String? = null, + /** + * <required> Integer + */ + public val intPint: Int? = null, + /** + * <required> Integer + */ + public val intPint_Untyped: String? = null, + /** + * <required> Float */ - public val intPint: Int, + public val floPint: Float? = null, /** - * Float + * <required> Float */ - public val floPint: Float, + public val floPint_Untyped: String? = null, /** - * Enumeration + * <required> Enumeration */ - public val finBin: ActionWithAllTypesOfInputs.Bin, + public val finBin: ActionWithAllTypesOfInputs.Bin? = null, /** - * Integer with special value + * <required> Enumeration */ - public val gooZen: ActionWithAllTypesOfInputs.Zen, + public val finBin_Untyped: String? = null, /** - * Enum with custom naming + * <required> Integer with special value */ - public val bahEnum: ActionWithAllTypesOfInputs.BahEnum, + public val gooZen: ActionWithAllTypesOfInputs.Zen? = null, + /** + * <required> Integer with special value + */ + public val gooZen_Untyped: String? = null, + /** + * <required> Enum with custom naming + */ + public val bahEnum: ActionWithAllTypesOfInputs.BahEnum? = null, + /** + * <required> Enum with custom naming + */ + public val bahEnum_Untyped: String? = null, /** * List of strings */ public val listStrings: List? = null, + /** + * List of strings + */ + public val listStrings_Untyped: String? = null, /** * List of integers */ public val listInts: List? = null, + /** + * List of integers + */ + public val listInts_Untyped: String? = null, /** * List of enums */ public val listEnums: List? = null, + /** + * List of enums + */ + public val listEnums_Untyped: String? = null, /** * List of integer with special values */ public val listIntSpecial: List? = null, + /** + * List of integer with special values + */ + public val listIntSpecial_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -106,43 +166,143 @@ public data class ActionWithAllTypesOfInputs private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-all-types-of-inputs", _customVersion ?: "v3") { + init { + require(!((fooBar != null) && (fooBar_Untyped != null))) { + "Only fooBar or fooBar_Untyped must be set, but not both" + } + require((fooBar != null) || (fooBar_Untyped != null)) { + "Either fooBar or fooBar_Untyped must be set, one of them is required" + } + + require(!((bazGoo != null) && (bazGoo_Untyped != null))) { + "Only bazGoo or bazGoo_Untyped must be set, but not both" + } + require((bazGoo != null) || (bazGoo_Untyped != null)) { + "Either bazGoo or bazGoo_Untyped must be set, one of them is required" + } + + require(!((binKin != null) && (binKin_Untyped != null))) { + "Only binKin or binKin_Untyped must be set, but not both" + } + + require(!((intPint != null) && (intPint_Untyped != null))) { + "Only intPint or intPint_Untyped must be set, but not both" + } + require((intPint != null) || (intPint_Untyped != null)) { + "Either intPint or intPint_Untyped must be set, one of them is required" + } + + require(!((floPint != null) && (floPint_Untyped != null))) { + "Only floPint or floPint_Untyped must be set, but not both" + } + require((floPint != null) || (floPint_Untyped != null)) { + "Either floPint or floPint_Untyped must be set, one of them is required" + } + + require(!((finBin != null) && (finBin_Untyped != null))) { + "Only finBin or finBin_Untyped must be set, but not both" + } + require((finBin != null) || (finBin_Untyped != null)) { + "Either finBin or finBin_Untyped must be set, one of them is required" + } + + require(!((gooZen != null) && (gooZen_Untyped != null))) { + "Only gooZen or gooZen_Untyped must be set, but not both" + } + require((gooZen != null) || (gooZen_Untyped != null)) { + "Either gooZen or gooZen_Untyped must be set, one of them is required" + } + + require(!((bahEnum != null) && (bahEnum_Untyped != null))) { + "Only bahEnum or bahEnum_Untyped must be set, but not both" + } + require((bahEnum != null) || (bahEnum_Untyped != null)) { + "Either bahEnum or bahEnum_Untyped must be set, one of them is required" + } + + require(!((listStrings != null) && (listStrings_Untyped != null))) { + "Only listStrings or listStrings_Untyped must be set, but not both" + } + + require(!((listInts != null) && (listInts_Untyped != null))) { + "Only listInts or listInts_Untyped must be set, but not both" + } + + require(!((listEnums != null) && (listEnums_Untyped != null))) { + "Only listEnums or listEnums_Untyped must be set, but not both" + } + + require(!((listIntSpecial != null) && (listIntSpecial_Untyped != null))) { + "Only listIntSpecial or listIntSpecial_Untyped must be set, but not both" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, - fooBar: String, - bazGoo: Boolean, + fooBar: String? = null, + fooBar_Untyped: String? = null, + bazGoo: Boolean? = null, + bazGoo_Untyped: String? = null, binKin: Boolean? = null, - intPint: Int, - floPint: Float, - finBin: ActionWithAllTypesOfInputs.Bin, - gooZen: ActionWithAllTypesOfInputs.Zen, - bahEnum: ActionWithAllTypesOfInputs.BahEnum, + binKin_Untyped: String? = null, + intPint: Int? = null, + intPint_Untyped: String? = null, + floPint: Float? = null, + floPint_Untyped: String? = null, + finBin: ActionWithAllTypesOfInputs.Bin? = null, + finBin_Untyped: String? = null, + gooZen: ActionWithAllTypesOfInputs.Zen? = null, + gooZen_Untyped: String? = null, + bahEnum: ActionWithAllTypesOfInputs.BahEnum? = null, + bahEnum_Untyped: String? = null, listStrings: List? = null, + listStrings_Untyped: String? = null, listInts: List? = null, + listInts_Untyped: String? = null, listEnums: List? = null, + listEnums_Untyped: String? = null, listIntSpecial: List? = null, + listIntSpecial_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooBar=fooBar, bazGoo=bazGoo, binKin=binKin, intPint=intPint, floPint=floPint, - finBin=finBin, gooZen=gooZen, bahEnum=bahEnum, listStrings=listStrings, - listInts=listInts, listEnums=listEnums, listIntSpecial=listIntSpecial, - _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(fooBar = fooBar, fooBar_Untyped = fooBar_Untyped, bazGoo = bazGoo, bazGoo_Untyped = + bazGoo_Untyped, binKin = binKin, binKin_Untyped = binKin_Untyped, intPint = intPint, + intPint_Untyped = intPint_Untyped, floPint = floPint, floPint_Untyped = floPint_Untyped, + finBin = finBin, finBin_Untyped = finBin_Untyped, gooZen = gooZen, gooZen_Untyped = + gooZen_Untyped, bahEnum = bahEnum, bahEnum_Untyped = bahEnum_Untyped, listStrings = + listStrings, listStrings_Untyped = listStrings_Untyped, listInts = listInts, + listInts_Untyped = listInts_Untyped, listEnums = listEnums, listEnums_Untyped = + listEnums_Untyped, listIntSpecial = listIntSpecial, listIntSpecial_Untyped = + listIntSpecial_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "foo-bar" to fooBar, - "baz-goo" to bazGoo.toString(), + fooBar?.let { "foo-bar" to it }, + fooBar_Untyped?.let { "foo-bar" to it }, + bazGoo?.let { "baz-goo" to it.toString() }, + bazGoo_Untyped?.let { "baz-goo" to it }, binKin?.let { "bin-kin" to it.toString() }, - "int-pint" to intPint.toString(), - "flo-pint" to floPint.toString(), - "fin-bin" to finBin.stringValue, - "goo-zen" to gooZen.integerValue.toString(), - "bah-enum" to bahEnum.stringValue, + binKin_Untyped?.let { "bin-kin" to it }, + intPint?.let { "int-pint" to it.toString() }, + intPint_Untyped?.let { "int-pint" to it }, + floPint?.let { "flo-pint" to it.toString() }, + floPint_Untyped?.let { "flo-pint" to it }, + finBin?.let { "fin-bin" to it.stringValue }, + finBin_Untyped?.let { "fin-bin" to it }, + gooZen?.let { "goo-zen" to it.integerValue.toString() }, + gooZen_Untyped?.let { "goo-zen" to it }, + bahEnum?.let { "bah-enum" to it.stringValue }, + bahEnum_Untyped?.let { "bah-enum" to it }, listStrings?.let { "list-strings" to it.joinToString(",") }, + listStrings_Untyped?.let { "list-strings" to it }, listInts?.let { "list-ints" to it.joinToString(",") { it.toString() } }, + listInts_Untyped?.let { "list-ints" to it }, listEnums?.let { "list-enums" to it.joinToString(",") { it.stringValue } }, + listEnums_Untyped?.let { "list-enums" to it }, listIntSpecial?.let { "list-int-special" to it.joinToString(",") { it.integerValue.toString() } }, + listIntSpecial_Untyped?.let { "list-int-special" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputsTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputsTest.kt index 4e9607f6f7..e784add0b5 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputsTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputsTest.kt @@ -1,6 +1,7 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.bindingsfromunittests import io.github.typesafegithub.workflows.actions.johnsmith.ActionWithAllTypesOfInputs +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -70,4 +71,74 @@ class ActionWithAllTypesOfInputsTest : DescribeSpec({ "bah-enum" to "very-custom", ) } + + it("untyped input is sufficient for required input") { + // given + val action = ActionWithAllTypesOfInputs( + fooBar = "test", + bazGoo_Untyped = "\${{ 1 == 1 }}", + binKin = false, + intPint = 43, + floPint = 123.456f, + finBin = ActionWithAllTypesOfInputs.Bin.Custom("this-is-custom!"), + gooZen = ActionWithAllTypesOfInputs.Zen.Value(123), + bahEnum = ActionWithAllTypesOfInputs.BahEnum.Custom("very-custom"), + ) + + // when + val yaml = action.toYamlArguments() + + // then + yaml shouldBe linkedMapOf( + "foo-bar" to "test", + "baz-goo" to "\${{ 1 == 1 }}", + "bin-kin" to "false", + "int-pint" to "43", + "flo-pint" to "123.456", + "fin-bin" to "this-is-custom!", + "goo-zen" to "123", + "bah-enum" to "very-custom", + ) + } + + it("validates required inputs are not missing") { + // expect + val exception = + shouldThrow { + ActionWithAllTypesOfInputs() + } + exception.message shouldBe "Either fooBar or fooBar_Untyped must be set, one of them is required" + } + + it("validates required inputs are not supplied typed and untyped") { + // expect + val exception = + shouldThrow { + ActionWithAllTypesOfInputs( + fooBar = "test", + fooBar_Untyped = "untyped test", + ) + } + exception.message shouldBe "Only fooBar or fooBar_Untyped must be set, but not both" + } + + it("validates not-required inputs are not supplied typed and untyped") { + // expect + val exception = + shouldThrow { + ActionWithAllTypesOfInputs( + fooBar = "test", + bazGoo_Untyped = "\${{ 1 == 1 }}", + binKin = false, + intPint = 43, + floPint = 123.456f, + finBin = ActionWithAllTypesOfInputs.Bin.Custom("this-is-custom!"), + gooZen = ActionWithAllTypesOfInputs.Zen.Value(123), + bahEnum = ActionWithAllTypesOfInputs.BahEnum.Custom("very-custom"), + listStrings = listOf("test"), + listStrings_Untyped = "untyped test", + ) + } + exception.message shouldBe "Only listStrings or listStrings_Untyped must be set, but not both" + } }) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs_Untyped.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs_Untyped.kt new file mode 100644 index 0000000000..abf3807fae --- /dev/null +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithAllTypesOfInputs_Untyped.kt @@ -0,0 +1,182 @@ +// This file was generated using action-binding-generator. Don't change it by hand, otherwise your +// changes will be overwritten with the next binding code regeneration. +// See https://github.com/typesafegithub/github-workflows-kt for more info. +@file:Suppress( + "DataClassPrivateConstructor", + "UNUSED_PARAMETER", +) + +package io.github.typesafegithub.workflows.actions.johnsmith + +import io.github.typesafegithub.workflows.domain.actions.Action +import io.github.typesafegithub.workflows.domain.actions.RegularAction +import java.util.LinkedHashMap +import kotlin.Deprecated +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.collections.Map +import kotlin.collections.toList +import kotlin.collections.toTypedArray + +/** + * ```text + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!! WARNING !!! + * !!! !!! + * !!! This action binding has no typings provided. All inputs will !!! + * !!! have a default type of String. !!! + * !!! To be able to use this action in a type-safe way, ask the !!! + * !!! action's owner to provide the typings using !!! + * !!! !!! + * !!! https://github.com/typesafegithub/github-actions-typing !!! + * !!! !!! + * !!! or if it's impossible, contribute typings to a community-driven !!! + * !!! !!! + * !!! https://github.com/typesafegithub/github-actions-typing-catalog !!! + * !!! !!! + * !!! This '_Untyped' binding will be available even once the typings !!! + * !!! are added. !!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ``` + * + * Action: Do something cool + * + * This is a test description that should be put in the KDoc comment for a class + * + * [Action on GitHub](https://github.com/john-smith/action-with-all-types-of-inputs) + * + * @param fooBar_Untyped Short description + * @param bazGoo_Untyped First boolean input! + * @param binKin_Untyped Boolean and nullable + * @param intPint_Untyped Integer + * @param floPint_Untyped Float + * @param finBin_Untyped Enumeration + * @param gooZen_Untyped Integer with special value + * @param bahEnum_Untyped Enum with custom naming + * @param listStrings_Untyped List of strings + * @param listInts_Untyped List of integers + * @param listEnums_Untyped List of enums + * @param listIntSpecial_Untyped List of integer with special values + * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by + * the binding + * @param _customVersion Allows overriding action's version, for example to use a specific minor + * version, or a newer version that the binding doesn't yet know about + */ +@Deprecated( + "Use the typed class instead", + ReplaceWith("ActionWithAllTypesOfInputs"), +) +public data class ActionWithAllTypesOfInputs_Untyped private constructor( + /** + * Short description + */ + public val fooBar_Untyped: String, + /** + * First boolean input! + */ + public val bazGoo_Untyped: String, + /** + * Boolean and nullable + */ + public val binKin_Untyped: String? = null, + /** + * Integer + */ + public val intPint_Untyped: String, + /** + * Float + */ + public val floPint_Untyped: String, + /** + * Enumeration + */ + public val finBin_Untyped: String, + /** + * Integer with special value + */ + public val gooZen_Untyped: String, + /** + * Enum with custom naming + */ + public val bahEnum_Untyped: String, + /** + * List of strings + */ + public val listStrings_Untyped: String? = null, + /** + * List of integers + */ + public val listInts_Untyped: String? = null, + /** + * List of enums + */ + public val listEnums_Untyped: String? = null, + /** + * List of integer with special values + */ + public val listIntSpecial_Untyped: String? = null, + /** + * Type-unsafe map where you can put any inputs that are not yet supported by the binding + */ + public val _customInputs: Map = mapOf(), + /** + * Allows overriding action's version, for example to use a specific minor version, or a newer + * version that the binding doesn't yet know about + */ + public val _customVersion: String? = null, +) : RegularAction("john-smith", + "action-with-all-types-of-inputs", _customVersion ?: "v3") { + public constructor( + vararg pleaseUseNamedArguments: Unit, + fooBar_Untyped: String, + bazGoo_Untyped: String, + binKin_Untyped: String? = null, + intPint_Untyped: String, + floPint_Untyped: String, + finBin_Untyped: String, + gooZen_Untyped: String, + bahEnum_Untyped: String, + listStrings_Untyped: String? = null, + listInts_Untyped: String? = null, + listEnums_Untyped: String? = null, + listIntSpecial_Untyped: String? = null, + _customInputs: Map = mapOf(), + _customVersion: String? = null, + ) : this(fooBar_Untyped = fooBar_Untyped, bazGoo_Untyped = bazGoo_Untyped, binKin_Untyped = + binKin_Untyped, intPint_Untyped = intPint_Untyped, floPint_Untyped = floPint_Untyped, + finBin_Untyped = finBin_Untyped, gooZen_Untyped = gooZen_Untyped, bahEnum_Untyped = + bahEnum_Untyped, listStrings_Untyped = listStrings_Untyped, listInts_Untyped = + listInts_Untyped, listEnums_Untyped = listEnums_Untyped, listIntSpecial_Untyped = + listIntSpecial_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) + + @Suppress("SpreadOperator") + override fun toYamlArguments(): LinkedHashMap = linkedMapOf( + *listOfNotNull( + "foo-bar" to fooBar_Untyped, + "baz-goo" to bazGoo_Untyped, + binKin_Untyped?.let { "bin-kin" to it }, + "int-pint" to intPint_Untyped, + "flo-pint" to floPint_Untyped, + "fin-bin" to finBin_Untyped, + "goo-zen" to gooZen_Untyped, + "bah-enum" to bahEnum_Untyped, + listStrings_Untyped?.let { "list-strings" to it }, + listInts_Untyped?.let { "list-ints" to it }, + listEnums_Untyped?.let { "list-enums" to it }, + listIntSpecial_Untyped?.let { "list-int-special" to it }, + *_customInputs.toList().toTypedArray(), + ).toTypedArray() + ) + + override fun buildOutputObject(stepId: String): Outputs = Outputs(stepId) + + public class Outputs( + stepId: String, + ) : Action.Outputs(stepId) { + /** + * Cool output! + */ + public val bazGoo: String = "steps.$stepId.outputs.baz-goo" + } +} diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithDeprecatedInputAndNameClash.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithDeprecatedInputAndNameClash.kt index eaee489740..78bbb6ba85 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithDeprecatedInputAndNameClash.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithDeprecatedInputAndNameClash.kt @@ -25,7 +25,8 @@ import kotlin.collections.toTypedArray * * [Action on GitHub](https://github.com/john-smith/action-with-deprecated-input-and-name-clash) * - * @param fooBar Foo bar - new + * @param fooBar <required> Foo bar - new + * @param fooBar_Untyped <required> Foo bar - new * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor @@ -33,9 +34,13 @@ import kotlin.collections.toTypedArray */ public data class ActionWithDeprecatedInputAndNameClash private constructor( /** - * Foo bar - new + * <required> Foo bar - new */ - public val fooBar: String, + public val fooBar: String? = null, + /** + * <required> Foo bar - new + */ + public val fooBar_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -47,17 +52,29 @@ public data class ActionWithDeprecatedInputAndNameClash private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-deprecated-input-and-name-clash", _customVersion ?: "v2") { + init { + require(!((fooBar != null) && (fooBar_Untyped != null))) { + "Only fooBar or fooBar_Untyped must be set, but not both" + } + require((fooBar != null) || (fooBar_Untyped != null)) { + "Either fooBar or fooBar_Untyped must be set, one of them is required" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, - fooBar: String, + fooBar: String? = null, + fooBar_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooBar=fooBar, _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(fooBar = fooBar, fooBar_Untyped = fooBar_Untyped, _customInputs = _customInputs, + _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "fooBar" to fooBar, + fooBar?.let { "fooBar" to it }, + fooBar_Untyped?.let { "fooBar" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithFancyCharsInDocs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithFancyCharsInDocs.kt index 373d952812..b1a44bf730 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithFancyCharsInDocs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithFancyCharsInDocs.kt @@ -26,7 +26,9 @@ import kotlin.collections.toTypedArray * [Action on GitHub](https://github.com/john-smith/action-with-fancy-chars-in-docs) * * @param nestedKotlinComments This is a /* test */ + * @param nestedKotlinComments_Untyped This is a /* test */ * @param percent For example "100%" + * @param percent_Untyped For example "100%" * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor @@ -37,10 +39,18 @@ public data class ActionWithFancyCharsInDocs private constructor( * This is a /* test */ */ public val nestedKotlinComments: String? = null, + /** + * This is a /* test */ + */ + public val nestedKotlinComments_Untyped: String? = null, /** * For example "100%" */ public val percent: String? = null, + /** + * For example "100%" + */ + public val percent_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -52,20 +62,35 @@ public data class ActionWithFancyCharsInDocs private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-fancy-chars-in-docs", _customVersion ?: "v3") { + init { + require(!((nestedKotlinComments != null) && (nestedKotlinComments_Untyped != null))) { + "Only nestedKotlinComments or nestedKotlinComments_Untyped must be set, but not both" + } + + require(!((percent != null) && (percent_Untyped != null))) { + "Only percent or percent_Untyped must be set, but not both" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, nestedKotlinComments: String? = null, + nestedKotlinComments_Untyped: String? = null, percent: String? = null, + percent_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(nestedKotlinComments=nestedKotlinComments, percent=percent, - _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(nestedKotlinComments = nestedKotlinComments, nestedKotlinComments_Untyped = + nestedKotlinComments_Untyped, percent = percent, percent_Untyped = percent_Untyped, + _customInputs = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( nestedKotlinComments?.let { "nested-kotlin-comments" to it }, + nestedKotlinComments_Untyped?.let { "nested-kotlin-comments" to it }, percent?.let { "percent" to it }, + percent_Untyped?.let { "percent" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithInputsSharingType.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithInputsSharingType.kt index 032de99e1c..397c6e89c5 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithInputsSharingType.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithInputsSharingType.kt @@ -26,15 +26,34 @@ import kotlin.collections.toTypedArray * * [Action on GitHub](https://github.com/john-smith/action-with-inputs-sharing-type) * + * @param fooOne <required> + * @param fooOne_Untyped <required> + * @param fooTwo <required> + * @param fooTwo_Untyped <required> * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor * version, or a newer version that the binding doesn't yet know about */ public data class ActionWithInputsSharingType private constructor( - public val fooOne: ActionWithInputsSharingType.Foo, - public val fooTwo: ActionWithInputsSharingType.Foo, + /** + * <required> + */ + public val fooOne: ActionWithInputsSharingType.Foo? = null, + /** + * <required> + */ + public val fooOne_Untyped: String? = null, + /** + * <required> + */ + public val fooTwo: ActionWithInputsSharingType.Foo? = null, + /** + * <required> + */ + public val fooTwo_Untyped: String? = null, public val fooThree: ActionWithInputsSharingType.Foo? = null, + public val fooThree_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -46,22 +65,49 @@ public data class ActionWithInputsSharingType private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-inputs-sharing-type", _customVersion ?: "v3") { + init { + require(!((fooOne != null) && (fooOne_Untyped != null))) { + "Only fooOne or fooOne_Untyped must be set, but not both" + } + require((fooOne != null) || (fooOne_Untyped != null)) { + "Either fooOne or fooOne_Untyped must be set, one of them is required" + } + + require(!((fooTwo != null) && (fooTwo_Untyped != null))) { + "Only fooTwo or fooTwo_Untyped must be set, but not both" + } + require((fooTwo != null) || (fooTwo_Untyped != null)) { + "Either fooTwo or fooTwo_Untyped must be set, one of them is required" + } + + require(!((fooThree != null) && (fooThree_Untyped != null))) { + "Only fooThree or fooThree_Untyped must be set, but not both" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, - fooOne: ActionWithInputsSharingType.Foo, - fooTwo: ActionWithInputsSharingType.Foo, + fooOne: ActionWithInputsSharingType.Foo? = null, + fooOne_Untyped: String? = null, + fooTwo: ActionWithInputsSharingType.Foo? = null, + fooTwo_Untyped: String? = null, fooThree: ActionWithInputsSharingType.Foo? = null, + fooThree_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooOne=fooOne, fooTwo=fooTwo, fooThree=fooThree, _customInputs=_customInputs, - _customVersion=_customVersion) + ) : this(fooOne = fooOne, fooOne_Untyped = fooOne_Untyped, fooTwo = fooTwo, fooTwo_Untyped = + fooTwo_Untyped, fooThree = fooThree, fooThree_Untyped = fooThree_Untyped, _customInputs + = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "foo-one" to fooOne.integerValue.toString(), - "foo-two" to fooTwo.integerValue.toString(), + fooOne?.let { "foo-one" to it.integerValue.toString() }, + fooOne_Untyped?.let { "foo-one" to it }, + fooTwo?.let { "foo-two" to it.integerValue.toString() }, + fooTwo_Untyped?.let { "foo-two" to it }, fooThree?.let { "foo-three" to it.integerValue.toString() }, + fooThree_Untyped?.let { "foo-three" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoInputs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoInputs.kt index 806cf07a41..f39e09f714 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoInputs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoInputs.kt @@ -43,7 +43,7 @@ public data class ActionWithNoInputs private constructor( vararg pleaseUseNamedArguments: Unit, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(_customInputs=_customInputs, _customVersion=_customVersion) + ) : this(_customInputs = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = LinkedHashMap(_customInputs) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoTypings_Untyped.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoTypings_Untyped.kt index 9fe30ca92c..aeaaffe1f4 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoTypings_Untyped.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithNoTypings_Untyped.kt @@ -51,8 +51,8 @@ import kotlin.collections.toTypedArray * version, or a newer version that the binding doesn't yet know about */ public data class ActionWithNoTypings_Untyped private constructor( - public val foo: String, - public val bar: String? = null, + public val foo_Untyped: String, + public val bar_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -65,17 +65,18 @@ public data class ActionWithNoTypings_Untyped private constructor( ) : RegularAction("john-smith", "action-with-no-typings", _customVersion ?: "v3") { public constructor( vararg pleaseUseNamedArguments: Unit, - foo: String, - bar: String? = null, + foo_Untyped: String, + bar_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(foo=foo, bar=bar, _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(foo_Untyped = foo_Untyped, bar_Untyped = bar_Untyped, _customInputs = _customInputs, + _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "foo" to foo, - bar?.let { "bar" to it }, + "foo" to foo_Untyped, + bar_Untyped?.let { "bar" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithOutputs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithOutputs.kt index a928288b04..8be5ab9bfa 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithOutputs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithOutputs.kt @@ -25,7 +25,8 @@ import kotlin.collections.toTypedArray * * [Action on GitHub](https://github.com/john-smith/action-with-outputs) * - * @param fooBar Short description + * @param fooBar <required> Short description + * @param fooBar_Untyped <required> Short description * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor @@ -33,9 +34,13 @@ import kotlin.collections.toTypedArray */ public data class ActionWithOutputs private constructor( /** - * Short description + * <required> Short description */ - public val fooBar: String, + public val fooBar: String? = null, + /** + * <required> Short description + */ + public val fooBar_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -47,17 +52,29 @@ public data class ActionWithOutputs private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-outputs", _customVersion ?: "v3") { + init { + require(!((fooBar != null) && (fooBar_Untyped != null))) { + "Only fooBar or fooBar_Untyped must be set, but not both" + } + require((fooBar != null) || (fooBar_Untyped != null)) { + "Either fooBar or fooBar_Untyped must be set, one of them is required" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, - fooBar: String, + fooBar: String? = null, + fooBar_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooBar=fooBar, _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(fooBar = fooBar, fooBar_Untyped = fooBar_Untyped, _customInputs = _customInputs, + _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "foo-bar" to fooBar, + fooBar?.let { "foo-bar" to it }, + fooBar_Untyped?.let { "foo-bar" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings.kt new file mode 100644 index 0000000000..202bcd497e --- /dev/null +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings.kt @@ -0,0 +1,90 @@ +// This file was generated using action-binding-generator. Don't change it by hand, otherwise your +// changes will be overwritten with the next binding code regeneration. +// See https://github.com/typesafegithub/github-workflows-kt for more info. +@file:Suppress( + "DataClassPrivateConstructor", + "UNUSED_PARAMETER", +) + +package io.github.typesafegithub.workflows.actions.johnsmith + +import io.github.typesafegithub.workflows.domain.actions.Action +import io.github.typesafegithub.workflows.domain.actions.RegularAction +import java.util.LinkedHashMap +import kotlin.Int +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.collections.Map +import kotlin.collections.toList +import kotlin.collections.toTypedArray + +/** + * Action: Do something cool + * + * This is a test description that should be put in the KDoc comment for a class + * + * [Action on GitHub](https://github.com/john-smith/action-with-partly-typings) + * + * @param foo <required> + * @param foo_Untyped <required> + * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by + * the binding + * @param _customVersion Allows overriding action's version, for example to use a specific minor + * version, or a newer version that the binding doesn't yet know about + */ +public data class ActionWithPartlyTypings private constructor( + /** + * <required> + */ + public val foo: Int? = null, + /** + * <required> + */ + public val foo_Untyped: String? = null, + public val bar_Untyped: String? = null, + public val baz_Untyped: String, + /** + * Type-unsafe map where you can put any inputs that are not yet supported by the binding + */ + public val _customInputs: Map = mapOf(), + /** + * Allows overriding action's version, for example to use a specific minor version, or a newer + * version that the binding doesn't yet know about + */ + public val _customVersion: String? = null, +) : RegularAction("john-smith", "action-with-partly-typings", _customVersion ?: + "v3") { + init { + require(!((foo != null) && (foo_Untyped != null))) { + "Only foo or foo_Untyped must be set, but not both" + } + require((foo != null) || (foo_Untyped != null)) { + "Either foo or foo_Untyped must be set, one of them is required" + } + } + + public constructor( + vararg pleaseUseNamedArguments: Unit, + foo: Int? = null, + foo_Untyped: String? = null, + bar_Untyped: String? = null, + baz_Untyped: String, + _customInputs: Map = mapOf(), + _customVersion: String? = null, + ) : this(foo = foo, foo_Untyped = foo_Untyped, bar_Untyped = bar_Untyped, baz_Untyped = + baz_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) + + @Suppress("SpreadOperator") + override fun toYamlArguments(): LinkedHashMap = linkedMapOf( + *listOfNotNull( + foo?.let { "foo" to it.toString() }, + foo_Untyped?.let { "foo" to it }, + bar_Untyped?.let { "bar" to it }, + "baz" to baz_Untyped, + *_customInputs.toList().toTypedArray(), + ).toTypedArray() + ) + + override fun buildOutputObject(stepId: String): Action.Outputs = Outputs(stepId) +} diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings_Untyped.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings_Untyped.kt new file mode 100644 index 0000000000..d735fe102f --- /dev/null +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithPartlyTypings_Untyped.kt @@ -0,0 +1,94 @@ +// This file was generated using action-binding-generator. Don't change it by hand, otherwise your +// changes will be overwritten with the next binding code regeneration. +// See https://github.com/typesafegithub/github-workflows-kt for more info. +@file:Suppress( + "DataClassPrivateConstructor", + "UNUSED_PARAMETER", +) + +package io.github.typesafegithub.workflows.actions.johnsmith + +import io.github.typesafegithub.workflows.domain.actions.Action +import io.github.typesafegithub.workflows.domain.actions.RegularAction +import java.util.LinkedHashMap +import kotlin.Deprecated +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.collections.Map +import kotlin.collections.toList +import kotlin.collections.toTypedArray + +/** + * ```text + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * !!! WARNING !!! + * !!! !!! + * !!! This action binding has no typings provided. All inputs will !!! + * !!! have a default type of String. !!! + * !!! To be able to use this action in a type-safe way, ask the !!! + * !!! action's owner to provide the typings using !!! + * !!! !!! + * !!! https://github.com/typesafegithub/github-actions-typing !!! + * !!! !!! + * !!! or if it's impossible, contribute typings to a community-driven !!! + * !!! !!! + * !!! https://github.com/typesafegithub/github-actions-typing-catalog !!! + * !!! !!! + * !!! This '_Untyped' binding will be available even once the typings !!! + * !!! are added. !!! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ``` + * + * Action: Do something cool + * + * This is a test description that should be put in the KDoc comment for a class + * + * [Action on GitHub](https://github.com/john-smith/action-with-partly-typings) + * + * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by + * the binding + * @param _customVersion Allows overriding action's version, for example to use a specific minor + * version, or a newer version that the binding doesn't yet know about + */ +@Deprecated( + "Use the typed class instead", + ReplaceWith("ActionWithPartlyTypings"), +) +public data class ActionWithPartlyTypings_Untyped private constructor( + public val foo_Untyped: String, + public val bar_Untyped: String? = null, + public val baz_Untyped: String, + /** + * Type-unsafe map where you can put any inputs that are not yet supported by the binding + */ + public val _customInputs: Map = mapOf(), + /** + * Allows overriding action's version, for example to use a specific minor version, or a newer + * version that the binding doesn't yet know about + */ + public val _customVersion: String? = null, +) : RegularAction("john-smith", "action-with-partly-typings", _customVersion ?: + "v3") { + public constructor( + vararg pleaseUseNamedArguments: Unit, + foo_Untyped: String, + bar_Untyped: String? = null, + baz_Untyped: String, + _customInputs: Map = mapOf(), + _customVersion: String? = null, + ) : this(foo_Untyped = foo_Untyped, bar_Untyped = bar_Untyped, baz_Untyped = baz_Untyped, + _customInputs = _customInputs, _customVersion = _customVersion) + + @Suppress("SpreadOperator") + override fun toYamlArguments(): LinkedHashMap = linkedMapOf( + *listOfNotNull( + "foo" to foo_Untyped, + bar_Untyped?.let { "bar" to it }, + "baz" to baz_Untyped, + *_customInputs.toList().toTypedArray(), + ).toTypedArray() + ) + + override fun buildOutputObject(stepId: String): Action.Outputs = Outputs(stepId) +} diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithSomeOptionalInputs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithSomeOptionalInputs.kt index 48ac216cad..8f43293232 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithSomeOptionalInputs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/ActionWithSomeOptionalInputs.kt @@ -26,10 +26,15 @@ import kotlin.collections.toTypedArray * [Action on GitHub](https://github.com/john-smith/action-with-some-optional-inputs) * * @param fooBar Required is default, default is set + * @param fooBar_Untyped Required is default, default is set * @param bazGoo Required is default, default is null + * @param bazGoo_Untyped Required is default, default is null * @param zooDar Required is false, default is set + * @param zooDar_Untyped Required is false, default is set * @param cooPoo Required is false, default is default - * @param package Required is true, default is default + * @param cooPoo_Untyped Required is false, default is default + * @param package <required> Required is true, default is default + * @param package_Untyped <required> Required is true, default is default * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding * @param _customVersion Allows overriding action's version, for example to use a specific minor @@ -40,22 +45,42 @@ public data class ActionWithSomeOptionalInputs private constructor( * Required is default, default is set */ public val fooBar: String? = null, + /** + * Required is default, default is set + */ + public val fooBar_Untyped: String? = null, /** * Required is default, default is null */ public val bazGoo: String? = null, + /** + * Required is default, default is null + */ + public val bazGoo_Untyped: String? = null, /** * Required is false, default is set */ public val zooDar: String? = null, + /** + * Required is false, default is set + */ + public val zooDar_Untyped: String? = null, /** * Required is false, default is default */ public val cooPoo: String? = null, /** - * Required is true, default is default + * Required is false, default is default */ - public val `package`: String, + public val cooPoo_Untyped: String? = null, + /** + * <required> Required is true, default is default + */ + public val `package`: String? = null, + /** + * <required> Required is true, default is default + */ + public val package_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -67,26 +92,63 @@ public data class ActionWithSomeOptionalInputs private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "action-with-some-optional-inputs", _customVersion ?: "v3") { + init { + require(!((fooBar != null) && (fooBar_Untyped != null))) { + "Only fooBar or fooBar_Untyped must be set, but not both" + } + + require(!((bazGoo != null) && (bazGoo_Untyped != null))) { + "Only bazGoo or bazGoo_Untyped must be set, but not both" + } + + require(!((zooDar != null) && (zooDar_Untyped != null))) { + "Only zooDar or zooDar_Untyped must be set, but not both" + } + + require(!((cooPoo != null) && (cooPoo_Untyped != null))) { + "Only cooPoo or cooPoo_Untyped must be set, but not both" + } + + require(!((`package` != null) && (package_Untyped != null))) { + "Only package or package_Untyped must be set, but not both" + } + require((`package` != null) || (package_Untyped != null)) { + "Either package or package_Untyped must be set, one of them is required" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, fooBar: String? = null, + fooBar_Untyped: String? = null, bazGoo: String? = null, + bazGoo_Untyped: String? = null, zooDar: String? = null, + zooDar_Untyped: String? = null, cooPoo: String? = null, - `package`: String, + cooPoo_Untyped: String? = null, + `package`: String? = null, + package_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooBar=fooBar, bazGoo=bazGoo, zooDar=zooDar, cooPoo=cooPoo, `package`=`package`, - _customInputs=_customInputs, _customVersion=_customVersion) + ) : this(fooBar = fooBar, fooBar_Untyped = fooBar_Untyped, bazGoo = bazGoo, bazGoo_Untyped = + bazGoo_Untyped, zooDar = zooDar, zooDar_Untyped = zooDar_Untyped, cooPoo = cooPoo, + cooPoo_Untyped = cooPoo_Untyped, `package` = `package`, package_Untyped = + package_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( fooBar?.let { "foo-bar" to it }, + fooBar_Untyped?.let { "foo-bar" to it }, bazGoo?.let { "baz-goo" to it }, + bazGoo_Untyped?.let { "baz-goo" to it }, zooDar?.let { "zoo-dar" to it }, + zooDar_Untyped?.let { "zoo-dar" to it }, cooPoo?.let { "coo-poo" to it }, - "package" to `package`, + cooPoo_Untyped?.let { "coo-poo" to it }, + `package`?.let { "package" to it }, + package_Untyped?.let { "package" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/SimpleActionWithRequiredStringInputs.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/SimpleActionWithRequiredStringInputs.kt index bc2daa899d..453cd67feb 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/SimpleActionWithRequiredStringInputs.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/bindingsfromunittests/SimpleActionWithRequiredStringInputs.kt @@ -28,8 +28,11 @@ import kotlin.collections.toTypedArray * * [Action on GitHub](https://github.com/john-smith/simple-action-with-required-string-inputs) * - * @param fooBar Short description - * @param bazGoo Just another input + * @param fooBar <required> Short description + * @param fooBar_Untyped <required> Short description + * @param bazGoo <required> Just another input + * with multiline description + * @param bazGoo_Untyped <required> Just another input * with multiline description * @param _customInputs Type-unsafe map where you can put any inputs that are not yet supported by * the binding @@ -38,15 +41,25 @@ import kotlin.collections.toTypedArray */ public data class SimpleActionWithRequiredStringInputs private constructor( /** - * Short description + * <required> Short description + */ + public val fooBar: String? = null, + /** + * <required> Short description */ - public val fooBar: String, + public val fooBar_Untyped: String? = null, /** - * Just another input + * <required> Just another input * with multiline description */ @Deprecated("this is deprecated") - public val bazGoo: String, + public val bazGoo: String? = null, + /** + * <required> Just another input + * with multiline description + */ + @Deprecated("this is deprecated") + public val bazGoo_Untyped: String? = null, /** * Type-unsafe map where you can put any inputs that are not yet supported by the binding */ @@ -58,20 +71,40 @@ public data class SimpleActionWithRequiredStringInputs private constructor( public val _customVersion: String? = null, ) : RegularAction("john-smith", "simple-action-with-required-string-inputs", _customVersion ?: "v3") { + init { + require(!((fooBar != null) && (fooBar_Untyped != null))) { + "Only fooBar or fooBar_Untyped must be set, but not both" + } + require((fooBar != null) || (fooBar_Untyped != null)) { + "Either fooBar or fooBar_Untyped must be set, one of them is required" + } + + require(!((bazGoo != null) && (bazGoo_Untyped != null))) { + "Only bazGoo or bazGoo_Untyped must be set, but not both" + } + require((bazGoo != null) || (bazGoo_Untyped != null)) { + "Either bazGoo or bazGoo_Untyped must be set, one of them is required" + } + } + public constructor( vararg pleaseUseNamedArguments: Unit, - fooBar: String, - bazGoo: String, + fooBar: String? = null, + fooBar_Untyped: String? = null, + bazGoo: String? = null, + bazGoo_Untyped: String? = null, _customInputs: Map = mapOf(), _customVersion: String? = null, - ) : this(fooBar=fooBar, bazGoo=bazGoo, _customInputs=_customInputs, - _customVersion=_customVersion) + ) : this(fooBar = fooBar, fooBar_Untyped = fooBar_Untyped, bazGoo = bazGoo, bazGoo_Untyped = + bazGoo_Untyped, _customInputs = _customInputs, _customVersion = _customVersion) @Suppress("SpreadOperator") override fun toYamlArguments(): LinkedHashMap = linkedMapOf( *listOfNotNull( - "foo-bar" to fooBar, - "baz-goo" to bazGoo, + fooBar?.let { "foo-bar" to it }, + fooBar_Untyped?.let { "foo-bar" to it }, + bazGoo?.let { "baz-goo" to it }, + bazGoo_Untyped?.let { "baz-goo" to it }, *_customInputs.toList().toTypedArray(), ).toTypedArray() ) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt index bf78b34cee..f7769c10d0 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationTest.kt @@ -3,6 +3,7 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.generation import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCoords import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.ACTION +import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.TYPING_CATALOG import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.Input import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.Metadata import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.Output @@ -86,6 +87,7 @@ class GenerationTest : ) val typingsForAllTypesOfInputs = mapOf( + "foo-bar" to StringTyping, "baz-goo" to BooleanTyping, "bin-kin" to BooleanTyping, "int-pint" to IntegerTyping, @@ -220,6 +222,7 @@ class GenerationTest : // then binding.shouldContainAndMatchFile("ActionWithAllTypesOfInputs.kt") + binding.shouldContainAndMatchFile("ActionWithAllTypesOfInputs_Untyped.kt") } test("action with outputs") { @@ -432,6 +435,45 @@ class GenerationTest : binding shouldHaveSize 1 binding.shouldContainAndMatchFile("ActionWithNoTypings_Untyped.kt") } + + test("action with partly typings has only untyped properties for the non-typed inputs") { + // given + val actionManifest = + Metadata( + name = "Do something cool", + description = "This is a test description that should be put in the KDoc comment for a class", + inputs = + mapOf( + "foo" to + Input( + required = true, + default = null, + ), + "bar" to + Input( + required = false, + default = "test", + ), + "baz" to + Input( + required = true, + ), + ), + ) + val coords = ActionCoords("john-smith", "action-with-partly-typings", "v3") + + // when + val binding = + coords.generateBinding( + metadataRevision = NewestForVersion, + metadata = actionManifest, + inputTypings = Pair(mapOf("foo" to IntegerTyping), TYPING_CATALOG), + ) + + // then + binding.shouldContainAndMatchFile("ActionWithPartlyTypings.kt") + binding.shouldContainAndMatchFile("ActionWithPartlyTypings_Untyped.kt") + } }) private fun Metadata.allInputsAsStrings(): Map = this.inputs.mapValues { StringTyping } diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullabilityTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullabilityTest.kt index 8ba07845a8..770d5a6468 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullabilityTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/metadata/InputNullabilityTest.kt @@ -26,7 +26,7 @@ class InputNullabilityTest : description = "Some input", default = default, required = required, - ).shouldBeNonNullInBinding() shouldBe result + ).shouldBeRequiredInBinding() shouldBe result } } } diff --git a/docs/user-guide/migrating-to-Maven-based-bindings.md b/docs/user-guide/migrating-to-Maven-based-bindings.md index 67d8266030..eaf7c136be 100644 --- a/docs/user-guide/migrating-to-Maven-based-bindings.md +++ b/docs/user-guide/migrating-to-Maven-based-bindings.md @@ -37,7 +37,7 @@ workflow( It has a number of downsides: * the library needs to be aware of a certain action, which requires the maintainer's attention -* getting new versions of the bindings happens is tied to the release cadence of the library, which is currently +* getting new versions of the bindings is tied to the release cadence of the library, which is currently released monthly * automatic updates via dependency update bots like Renovate or Dependabot aren't possible @@ -94,8 +94,9 @@ Then regenerate your YAML to ensure there are no changes. 1. Top-level actions like `actions/checkout@v4` map to Maven artifacts like `actions:checkout:v4`. Nested actions like `gradle/actions/setup-gradle@v3` require replacing the slash in the middle of the compound name with `__` (double underscore) because slash is not allowed in Maven coordinates. Hence, we get `gradle:actions__setup-gradle:v3`. -1. So far, only major versions (e.g. `v3`) are supported. Support for full versions like `v1.2.3` will be added in the - future. +1. For inputs with typing information there will now be two properties, a typed one and a `String`-typed with + `_Untyped` suffix. You can only set one of those and for required inputs must set exactly one. For inputs that miss + typing information there will only be the `_Untyped` property with nullability according to required status. 1. If a given binding has incorrect typing, please either ask the action owner to onboard [github-action-typing](https://github.com/typesafegithub/github-actions-typing/), or if it's not possible, contribute to [github-actions-typing-catalog](https://github.com/typesafegithub/github-actions-typing-catalog). diff --git a/docs/user-guide/using-actions.md b/docs/user-guide/using-actions.md index 8e2f33b2f7..c1828fb59f 100644 --- a/docs/user-guide/using-actions.md +++ b/docs/user-guide/using-actions.md @@ -20,12 +20,12 @@ To add a dependency on an action: 3. Use the action by importing a class like `io.github.typesafegithub.workflows.actions.actions.Checkout`. For every action, a binding will be generated. However, some less popular actions don't have typings configured for -their inputs, so by default all inputs are of type `String`, and additionally the class name will have an `_Untyped` -suffix. +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: 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 actions' owner + [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 **not only** with this Kotlin library. There are also no synchronization issues between the action itself and its typings. When trying to use a new action that has no typings, always discuss this approach with the action owner @@ -35,8 +35,13 @@ There are two ways of configuring typings: a community-maintained place to host the typings. You can contribute or fix typings for your favorite action by sending a PR. -Once there are typings in place, a class without the `_Untyped` suffix, and with typed constructor arguments will be -available. +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 +there will still be only the property with `_Untyped` suffix and nullability according to required status. For each +input that does have type information available, there will still be the `_Untyped` property and additionally a +properly typed property. Both of these properties will be nullable. It is a runtime error to set both of these +properties as well as setting none if the input is required. The `_Untyped` properties are not marked `@Deprecated`, +as it could still make sense to use them, for example if you want to set the value from a GitHub Actions expression. This approach supports dependency updating bots that support Kotlin Script's `.main.kts` files. E.g. Renovate is known to support it.