Repository: kestra-io/kestra
File: core/src/main/java/io/kestra/core/runners/FlowInputOutput.java (Java)

Code before:
        final Type elementType = data instanceof ItemTypeInterface itemTypeInterface ? itemTypeInterface.getItemType() : null;

        return Optional.of(new AbstractMap.SimpleEntry<>(
            data.getId(),
            parseType(execution, data.getType(), data.getId(), elementType, current, data)
        ));
    }

    private Object parseType(Execution execution, Type type, String id, Type elementType, Object current, Data data) throws Exception {
        try {
            return switch (type) {
                case SELECT, ENUM, STRING, EMAIL -> current.toString();
                case SECRET -> {
                    if (secretKey.isEmpty()) {
                        throw new Exception("Unable to use a `SECRET` input/output as encryption is not configured");
                    }
                        SecretInput secretInput = (SecretInput) data;
                        if (secretInput.getValidator() != null && !Pattern.matches(secretInput.getValidator(), current.toString())) {
                            throw ManualConstraintViolation.toConstraintViolationException(
                                "it must match the pattern `" + secretInput.getValidator() + "`",
                                secretInput,
                                SecretInput.class,
                                secretInput.getId(),
                                current.toString()
                            );
                        }
                        String encrypted = EncryptionService.encrypt(secretKey.get(), current.toString());
                        yield  EncryptedString.from(encrypted);
                }
                case INT -> current instanceof Integer ? current : Integer.valueOf(current.toString());
                // Assuming that after the render we must have a double/int, so we can safely use its toString representation
                case FLOAT -> current instanceof Float ? current : Float.valueOf(current.toString());
                case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
                case BOOL -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
                case DATETIME -> current instanceof Instant ? current : Instant.parse(current.toString());
                case DATE -> current instanceof LocalDate ? current : LocalDate.parse(current.toString());
                case TIME -> current instanceof LocalTime ? current : LocalTime.parse(current.toString());
                case DURATION -> current instanceof Duration ? current : Duration.parse(current.toString());
                case FILE -> {
                    URI uri = URI.create(current.toString().replace(File.separator, "/"));

                    if (URIFetcher.supports(uri)) {
                        yield uri;
                    } else {
                        yield storageInterface.from(execution, id, current.toString().substring(current.toString().lastIndexOf("/") + 1), new File(current.toString()));
                    }
                }
                case JSON -> (current instanceof Map || current instanceof Collection<?>) ? current :  JacksonMapper.toObject(current.toString());
                case YAML -> (current instanceof Map || current instanceof Collection<?>) ? current : YAML_MAPPER.readValue(current.toString(), JacksonMapper.OBJECT_TYPE_REFERENCE);
                case URI -> {
                    Matcher matcher = URI_PATTERN.matcher(current.toString());

Review comment (security):
Can you move secret validation back to the SecretInput class but on another method than the existing validation method.
This would allow to better understand why it's done line that:
- The existing method would have a comment saying validation is done after decoding by the other method
- The other method will be colocated, so all secret input related code would still be inside the Secret Input

Code after:
        final Type elementType = data instanceof ItemTypeInterface itemTypeInterface ? itemTypeInterface.getItemType() : null;

        return Optional.of(new AbstractMap.SimpleEntry<>(
            data.getId(),
            parseType(execution, data.getType(), data.getId(), elementType, current, data)
        ));
    }

    private Object parseType(Execution execution, Type type, String id, Type elementType, Object current, Data data) throws Exception {
        try {
            return switch (type) {
                case SELECT, ENUM, STRING, EMAIL -> current.toString();
                case SECRET -> {
                    if (secretKey.isEmpty()) {
                        throw new Exception("Unable to use a `SECRET` input/output as encryption is not configured");
                    }
                        SecretInput secretInput = (SecretInput) data;
                        secretInput.validate(current.toString());
                        String encrypted = EncryptionService.encrypt(secretKey.get(), current.toString());
                        yield  EncryptedString.from(encrypted);
                }
                case INT -> current instanceof Integer ? current : Integer.valueOf(current.toString());
                // Assuming that after the render we must have a double/int, so we can safely use its toString representation
                case FLOAT -> current instanceof Float ? current : Float.valueOf(current.toString());
                case BOOLEAN -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
                case BOOL -> current instanceof Boolean ? current : Boolean.valueOf(current.toString());
                case DATETIME -> current instanceof Instant ? current : Instant.parse(current.toString());
                case DATE -> current instanceof LocalDate ? current : LocalDate.parse(current.toString());
                case TIME -> current instanceof LocalTime ? current : LocalTime.parse(current.toString());
                case DURATION -> current instanceof Duration ? current : Duration.parse(current.toString());
                case FILE -> {
                    URI uri = URI.create(current.toString().replace(File.separator, "/"));

                    if (URIFetcher.supports(uri)) {
                        yield uri;
                    } else {
                        yield storageInterface.from(execution, id, current.toString().substring(current.toString().lastIndexOf("/") + 1), new File(current.toString()));
                    }
                }
                case JSON -> (current instanceof Map || current instanceof Collection<?>) ? current :  JacksonMapper.toObject(current.toString());
                case YAML -> (current instanceof Map || current instanceof Collection<?>) ? current : YAML_MAPPER.readValue(current.toString(), JacksonMapper.OBJECT_TYPE_REFERENCE);
                case URI -> {
                    Matcher matcher = URI_PATTERN.matcher(current.toString());
                    if (matcher.matches()) {
                        yield current.toString();
                    } else {
                        throw new IllegalArgumentException("Invalid URI format.");
                    }
                }
                case ARRAY, MULTISELECT -> {
                    List<?> asList;