spring-cloud / spring-cloud-function

Apache License 2.0
1.04k stars 618 forks source link

output-header-mapping-expression does not support special properties characters in key #1178

Closed Felixel42 closed 2 weeks ago

Felixel42 commented 2 months ago

Describe the bug

Sample

import org.junit.jupiter.api.Test;

import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.support.MessageBuilder;

import static org.assertj.core.api.Assertions.assertThat;

public class HeaderMappingTests {

@SuppressWarnings("unchecked")
@Test
public void testOutputHeaderMappingFailsWithKeyContainingPropertiesSpecialCharacter() throws Exception {
    try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
            SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run(
            "--logging.level.org.springframework.cloud.function=DEBUG",
            "--spring.main.lazy-initialization=true",
            "--spring.cloud.function.configuration.foo.output-header-mapping-expression.\"[header.key.with.dot]\"='hello1'")) {

        FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
        FunctionInvocationWrapper function = functionCatalog.lookup("foo");
        Message<byte[]> result = (Message<byte[]>) function.apply(MessageBuilder.withPayload("helo")
                .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build());
        assertThat(result.getHeaders().containsKey("header.key.with.dot")).isTrue();
        assertThat(result.getHeaders().get("header.key.with.dot")).isEqualTo("hello1");
    }
}

@SuppressWarnings("unchecked")
@Test
public void testOutputHeaderSuccessBecauseSecondKeyMappingAddsFirstAnyway() throws Exception {
    try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
            SampleFunctionConfiguration.class).web(WebApplicationType.NONE).run(
            "--logging.level.org.springframework.cloud.function=DEBUG",
            "--spring.main.lazy-initialization=true",
            "--spring.cloud.function.configuration.foo.output-header-mapping-expression.\"[header.key.with.dot]\"='hello1'",
            "--spring.cloud.function.configuration.foo.output-header-mapping-expression.keyOut2=headers.contentType")) {

        FunctionCatalog functionCatalog = context.getBean(FunctionCatalog.class);
        FunctionInvocationWrapper function = functionCatalog.lookup("foo");
        Message<byte[]> result = (Message<byte[]>) function.apply(MessageBuilder.withPayload("helo")
                .setHeader(MessageHeaders.CONTENT_TYPE, "application/json").build());
        assertThat(result.getHeaders().containsKey("header.key.with.dot")).isTrue();
        assertThat(result.getHeaders().get("header.key.with.dot")).isEqualTo("hello1");
        assertThat(result.getHeaders().containsKey("keyOut2")).isTrue();
        assertThat(result.getHeaders().get("keyOut2")).isEqualTo("application/json");
    }
}

@EnableAutoConfiguration
@Configuration
protected static class SampleFunctionConfiguration {

    @Bean
    public Function<Message<?>, Message<?>> echo() {
        return m -> {
            assertThat(m.getHeaders().get("key1")).isEqualTo("hello1");
            assertThat(m.getHeaders().get("key2")).isEqualTo("hello2");
            assertThat(m.getHeaders().get("foo")).isEqualTo("application/json");
            return m;
        };
    }

    @Bean
    public Function<Message<?>, Message<?>> echoFail() {
        return m -> {
            assertThat(m.getHeaders().containsKey("key1")).isFalse();
            assertThat(m.getHeaders().get("key2")).isEqualTo("hello2");
            assertThat(m.getHeaders().containsKey("foo")).isFalse();
            return m;
        };
    }

    @Bean
    public Function<Message<?>, Message<?>> split() {
        return m -> {
            assertThat(m.getHeaders().get("key1")).isEqualTo("foo");
            assertThat(m.getHeaders().get("key2")).isEqualTo("bar");
            assertThat(m.getHeaders().get("key3")).isEqualTo("foo/bar/baz");
            return m;
        };
    }

    @Bean
    public Function<Message<?>, Message<?>> foo() {
        return x -> {
            assertThat(x.getHeaders().containsKey("keyOut1")).isFalse();
            return x;
        };
    }
}

}


- Reason: https://github.com/spring-cloud/spring-cloud-function/blob/main/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java#L134 the `resolve` method does not support lookups for special-characters

### Workaround

- just have a second key without "."
- provide your own enricher function
github-actions[bot] commented 3 weeks ago

This issue has been stale for over 60 days