From 0ae6d21c6cf539efebe6e424129d37932685ac80 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 28 Aug 2025 13:55:53 +0200 Subject: [PATCH 1/2] add enum support for custom types --- .../jackson/CustomTypeDeserializer.java | 24 +++++ .../toolbox/jackson/CustomTypeSerializer.java | 1 + .../javatype/base/WrappedEnumJavaType.java | 102 ++++++++++++++++++ .../toolbox/web/CustomTypePropertyEditor.java | 19 ++++ .../javatype/WrapperTypesJpaTest.java | 72 +++++++++++++ .../impl/javatype/WrapEnumClassJavaType.java | 11 ++ .../impl/javatype/WrapEnumRecordJavaType.java | 11 ++ .../javatype/impl/jpa/WrapperTypesModel.java | 12 +++ .../impl/jpa/WrapperTypesModelRepository.java | 6 ++ .../javatype/impl/type/SampleEnum.java | 5 + .../javatype/impl/type/WrapEnumClass.java | 15 +++ .../javatype/impl/type/WrapEnumRecord.java | 6 ++ ...9-06-create-wrapper-type-testing-table.yml | 6 ++ 13 files changed, 290 insertions(+) create mode 100644 src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedEnumJavaType.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumClassJavaType.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumRecordJavaType.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/SampleEnum.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumClass.java create mode 100644 src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumRecord.java diff --git a/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java b/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java index 41f7fae..c50e6e5 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeDeserializer.java @@ -96,6 +96,9 @@ private static Function getTypeConverter(Class wrappedTyp if (UUID.class.isAssignableFrom(wrappedType)) { return getUUIDConverter(); } + if (Enum.class.isAssignableFrom(wrappedType) || wrappedType.isEnum()) { + return getEnumConverter(wrappedType); + } throw new CustomTypeDeserializerException("Value type not supported: " + wrappedType.getName()); } @@ -237,6 +240,27 @@ private static Function getUUIDConverter() { }; } + @SuppressWarnings("unchecked") + private static Function getEnumConverter(Class wrappedType) { + var enumClass = (Class>) wrappedType.asSubclass(Enum.class); + return jsonParser -> { + try { + String value = jsonParser.getValueAsString(); + if (value == null) { + return null; + } + return Enum.valueOf((Class) enumClass, value); + } catch (IllegalArgumentException e) { + throw new CustomTypeDeserializerException( + "Failed to read value as Enum: " + enumClass.getName(), + e + ); + } catch (IOException e) { + throw new CustomTypeDeserializerException("Failed to read value as Enum.", e); + } + }; + } + public static final class CustomTypeDeserializerException extends RuntimeException { public CustomTypeDeserializerException(String message) { super(message); diff --git a/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeSerializer.java b/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeSerializer.java index b39584d..bd21229 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeSerializer.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/jackson/CustomTypeSerializer.java @@ -26,6 +26,7 @@ public void serialize( switch (value) { case String stringValue -> jsonGenerator.writeString(stringValue); case UUID uuidValue -> jsonGenerator.writeString(uuidValue.toString()); + case Enum enumValue -> jsonGenerator.writeString(enumValue.name()); case null -> jsonGenerator.writeNull(); default -> jsonGenerator.writeRawValue(String.valueOf(value)); } diff --git a/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedEnumJavaType.java b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedEnumJavaType.java new file mode 100644 index 0000000..3377506 --- /dev/null +++ b/src/main/java/it/aboutbits/springboot/toolbox/persistence/javatype/base/WrappedEnumJavaType.java @@ -0,0 +1,102 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.base; + +import it.aboutbits.springboot.toolbox.type.CustomType; +import lombok.SneakyThrows; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.AbstractClassJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.sql.Types; + +public abstract class WrappedEnumJavaType>> extends AbstractClassJavaType { + private final transient Constructor constructor; + private final Class> enumClass; + + @SuppressWarnings("unchecked") + protected WrappedEnumJavaType(Class type) { + super(type); + + try { + this.enumClass = determineEnumClass(type); + this.constructor = type.getConstructor(enumClass); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("No constructor found for " + type.getName() + " with enum parameter", e); + } + } + + @SuppressWarnings("unchecked") + private Class> determineEnumClass(Class type) { + // Try to extract from the generic superclass (if the CustomType exposes the enum via inheritance) + var genericSuperclass = type.getGenericSuperclass(); + if (genericSuperclass instanceof java.lang.reflect.ParameterizedType parameterizedType) { + var actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class enumType) { + if (enumType.isEnum()) { + return (Class>) enumType; + } + } + } + // Fallback: inspect constructors and use the single-arg enum constructor (works for records and classes) + for (var ctor : type.getDeclaredConstructors()) { + var params = ctor.getParameterTypes(); + if (params.length == 1 && params[0].isEnum()) { + return (Class>) params[0]; + } + } + throw new IllegalStateException("Could not determine enum type for " + type.getName()); + } + + @Override + public JdbcType getRecommendedJdbcType(JdbcTypeIndicators indicators) { + return indicators.getTypeConfiguration() + .getJdbcTypeRegistry() + .getDescriptor(Types.VARCHAR); + } + + @SuppressWarnings("unchecked") + @Override + public X unwrap(T id, Class aClass, WrapperOptions wrapperOptions) { + var javaTypeClass = getJavaTypeClass(); + + if (id == null) { + return null; + } + if (javaTypeClass.isAssignableFrom(aClass)) { + return (X) id; + } + if (String.class.isAssignableFrom(aClass)) { + return (X) id.value().name(); + } + if (enumClass.isAssignableFrom(aClass)) { + return (X) id.value(); + } + + throw unknownUnwrap(aClass); + } + + @SuppressWarnings("unchecked") + @SneakyThrows({InstantiationException.class, IllegalAccessException.class, InvocationTargetException.class}) + @Override + public T wrap(X value, WrapperOptions wrapperOptions) { + var clazz = getJavaTypeClass(); + + if (value == null) { + return null; + } + if (clazz.isInstance(value)) { + return (T) value; + } + if (value instanceof String stringValue) { + var enumValue = Enum.valueOf((Class) enumClass, stringValue); + return constructor.newInstance(enumValue); + } + if (enumClass.isInstance(value)) { + return constructor.newInstance(value); + } + + throw unknownWrap(value.getClass()); + } +} diff --git a/src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java b/src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java index c8c3237..a354853 100644 --- a/src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java +++ b/src/main/java/it/aboutbits/springboot/toolbox/web/CustomTypePropertyEditor.java @@ -104,6 +104,25 @@ private static Function getTextToTypeConverter(Class wrappedT if (UUID.class.isAssignableFrom(wrappedType)) { return UUID::fromString; } + if (Enum.class.isAssignableFrom(wrappedType)) { + return toEnumConverter(wrappedType); + } throw new IllegalArgumentException("Unable to convert text to type: " + wrappedType.getName()); } + + @SuppressWarnings("unchecked") + private static Function toEnumConverter(Class wrappedType) { + var enumClass = (Class>) wrappedType.asSubclass(Enum.class); + + return text -> { + if (text == null) { + return null; + } + try { + return Enum.valueOf((Class) enumClass, text); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unable to convert text to enum: " + enumClass.getName(), e); + } + }; + } } diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java index 5e64926..15360ad 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/WrapperTypesJpaTest.java @@ -3,6 +3,7 @@ import it.aboutbits.springboot.toolbox._support.ApplicationTest; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModel; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.jpa.WrapperTypesModelRepository; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.SampleEnum; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimalClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigDecimalRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapBigIntegerClass; @@ -15,6 +16,8 @@ import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass; @@ -33,6 +36,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; @@ -521,6 +525,40 @@ void givenStringValues_inAndOut_shouldSucceed(String uuidStringValue) { .isEqualTo(savedItem); } } + + @Nested + class WrapEnumRecordType { + @Test + void givenNull_inAndOut_shouldSucceed() { + var item = new WrapperTypesModel(); + item.setEnumValue(null); + + var savedItem = repository.save(item); + + var retrievedItem = repository.findByEnumValue(null); + + assertThat(retrievedItem).isPresent() + .get() + .usingRecursiveComparison() + .isEqualTo(savedItem); + } + + @ParameterizedTest + @EnumSource(SampleEnum.class) + void givenValues_inAndOut_shouldSucceed(SampleEnum enumValue) { + var item = new WrapperTypesModel(); + item.setEnumValue(new WrapEnumRecord(enumValue)); + + var savedItem = repository.save(item); + + var retrievedItem = repository.findByEnumValue(savedItem.getEnumValue()); + + assertThat(retrievedItem).isPresent() + .get() + .usingRecursiveComparison() + .isEqualTo(savedItem); + } + } } @Nested @@ -997,5 +1035,39 @@ void givenStringValues_inAndOut_shouldSucceed(String uuidStringValue) { .isEqualTo(savedItem); } } + + @Nested + class WrapEnumClassType { + @Test + void givenNull_inAndOut_shouldSucceed() { + var item = new WrapperTypesModel(); + item.setEnumValueClass(null); + + var savedItem = repository.save(item); + + var retrievedItem = repository.findByEnumValueClass(null); + + assertThat(retrievedItem).isPresent() + .get() + .usingRecursiveComparison() + .isEqualTo(savedItem); + } + + @ParameterizedTest + @EnumSource(SampleEnum.class) + void givenValues_inAndOut_shouldSucceed(SampleEnum enumValue) { + var item = new WrapperTypesModel(); + item.setEnumValueClass(new WrapEnumClass(enumValue)); + + var savedItem = repository.save(item); + + var retrievedItem = repository.findByEnumValueClass(savedItem.getEnumValueClass()); + + assertThat(retrievedItem).isPresent() + .get() + .usingRecursiveComparison() + .isEqualTo(savedItem); + } + } } } diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumClassJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumClassJavaType.java new file mode 100644 index 0000000..ffb45e5 --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumClassJavaType.java @@ -0,0 +1,11 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype; + +import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.AutoRegisteredJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedEnumJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass; + +public final class WrapEnumClassJavaType extends WrappedEnumJavaType implements AutoRegisteredJavaType { + public WrapEnumClassJavaType() { + super(WrapEnumClass.class); + } +} diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumRecordJavaType.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumRecordJavaType.java new file mode 100644 index 0000000..759abfa --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/javatype/WrapEnumRecordJavaType.java @@ -0,0 +1,11 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype; + +import it.aboutbits.springboot.toolbox.autoconfiguration.persistence.AutoRegisteredJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.base.WrappedEnumJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord; + +public final class WrapEnumRecordJavaType extends WrappedEnumJavaType implements AutoRegisteredJavaType { + public WrapEnumRecordJavaType() { + super(WrapEnumRecord.class); + } +} diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java index 8d5e81a..971c28c 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModel.java @@ -14,6 +14,8 @@ import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapCharacterRecordJavaType; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapDoubleClassJavaType; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapDoubleRecordJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapEnumClassJavaType; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapEnumRecordJavaType; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapFloatClassJavaType; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapFloatRecordJavaType; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.javatype.WrapIntegerClassJavaType; @@ -40,6 +42,8 @@ import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass; @@ -136,6 +140,10 @@ public class WrapperTypesModel implements Identified { @JdbcType(CharJdbcType.class) private WrapUUIDRecord uuidValueAsString; + @SuppressWarnings("JpaAttributeTypeInspection") + @JavaType(WrapEnumRecordJavaType.class) + private WrapEnumRecord enumValue; + @SuppressWarnings("JpaAttributeTypeInspection") @JavaType(WrapBigDecimalClassJavaType.class) private WrapBigDecimalClass bigDecimalValueClass; @@ -194,6 +202,10 @@ public class WrapperTypesModel implements Identified { @JdbcType(CharJdbcType.class) private WrapUUIDClass uuidValueClassAsString; + @SuppressWarnings("JpaAttributeTypeInspection") + @JavaType(WrapEnumClassJavaType.class) + private WrapEnumClass enumValueClass; + public record ID( Long value ) implements EntityId { diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java index 69e7532..ca4c724 100644 --- a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/jpa/WrapperTypesModelRepository.java @@ -12,6 +12,8 @@ import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapCharacterRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapDoubleRecord; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumClass; +import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapEnumRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatClass; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapFloatRecord; import it.aboutbits.springboot.toolbox.persistence.javatype.impl.type.WrapIntegerClass; @@ -86,4 +88,8 @@ public interface WrapperTypesModelRepository extends JpaRepository findByUuidValueClass(WrapUUIDClass value); Optional findByUuidValueClassAsString(WrapUUIDClass value); + + Optional findByEnumValue(WrapEnumRecord value); + + Optional findByEnumValueClass(WrapEnumClass value); } diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/SampleEnum.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/SampleEnum.java new file mode 100644 index 0000000..5b6a8e6 --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/SampleEnum.java @@ -0,0 +1,5 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type; + +public enum SampleEnum { + A, B, C +} diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumClass.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumClass.java new file mode 100644 index 0000000..242d4ba --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumClass.java @@ -0,0 +1,15 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type; + +import it.aboutbits.springboot.toolbox.type.CustomType; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; + +@Getter +@Accessors(fluent = true) +@EqualsAndHashCode +@RequiredArgsConstructor +public class WrapEnumClass implements CustomType { + private final SampleEnum value; +} diff --git a/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumRecord.java b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumRecord.java new file mode 100644 index 0000000..4c024fa --- /dev/null +++ b/src/test/java/it/aboutbits/springboot/toolbox/persistence/javatype/impl/type/WrapEnumRecord.java @@ -0,0 +1,6 @@ +package it.aboutbits.springboot.toolbox.persistence.javatype.impl.type; + +import it.aboutbits.springboot.toolbox.type.CustomType; + +public record WrapEnumRecord(SampleEnum value) implements CustomType { +} diff --git a/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml b/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml index d4a6270..3b84059 100644 --- a/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml +++ b/src/test/resources/db/changelog/2024-09-06-create-wrapper-type-testing-table.yml @@ -97,3 +97,9 @@ databaseChangeLog: - column: name: uuid_value_class_as_string type: char(36) + - column: + name: enum_value + type: text + - column: + name: enum_value_class + type: text From f6dbd7ab498373eb9f8f1403b677acc66846fa33 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 28 Aug 2025 13:58:28 +0200 Subject: [PATCH 2/2] align tag and release name --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ae88c0b..de5c28a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,7 @@ jobs: github.rest.repos.createRelease({ owner: context.repo.owner, repo: context.repo.repo, - tag_name: '${{ github.event.inputs.version }}', - name: 'Release ${{ github.event.inputs.version }}', + tag_name: 'v${{ github.event.inputs.version }}', + name: 'v${{ github.event.inputs.version }}', prerelease: '${{ github.event.inputs.version }}'.includes('RC') })