From b3b79df25be9642cca3cda67f3f9e2bc41ddaf0a Mon Sep 17 00:00:00 2001 From: Goutam Adwant Date: Thu, 25 Jun 2026 23:37:07 -0700 Subject: [PATCH] GH-1391 Add single function fallback opt-out Signed-off-by: Goutam Adwant --- .../function/context/FunctionProperties.java | 13 +++++++++ .../catalog/SimpleFunctionRegistry.java | 6 +++- .../spring-configuration-metadata.json | 6 ++++ ...BeanFactoryAwareFunctionRegistryTests.java | 29 +++++++++++++++++-- 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java index 5d96d9a58..11d5bd5ed 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/FunctionProperties.java @@ -82,6 +82,11 @@ public class FunctionProperties implements EnvironmentAware, ApplicationContextA */ private final List ineligibleDefinitions; + /** + * Whether a single eligible function can be looked up when the requested name is absent. + */ + private boolean singleFunctionFallbackEnabled = true; + private Map configuration; private String expectedContentType; @@ -196,6 +201,14 @@ public void setIneligibleDefinitions(List definitions) { this.ineligibleDefinitions.addAll(definitions); } + public boolean isSingleFunctionFallbackEnabled() { + return this.singleFunctionFallbackEnabled; + } + + public void setSingleFunctionFallbackEnabled(boolean singleFunctionFallbackEnabled) { + this.singleFunctionFallbackEnabled = singleFunctionFallbackEnabled; + } + public static class FunctionConfigurationProperties { private Map inputHeaderMappingExpression; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java index c01492370..bf3e66805 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java @@ -266,7 +266,7 @@ String normalizeFunctionDefinition(String functionDefinition) { : System.getProperty(FunctionProperties.FUNCTION_DEFINITION, ""); Set names = this.getNames(null); - if (!names.contains(functionDefinition)) { + if (this.isSingleFunctionFallbackEnabled() && !names.contains(functionDefinition)) { List eligibleFunction = names.stream() .filter(name -> !RoutingFunction.FUNCTION_NAME.equals(name)) .filter(name -> !RoutingFunction.DEFAULT_ROUTE_HANDLER.equals(name)) @@ -281,6 +281,10 @@ String normalizeFunctionDefinition(String functionDefinition) { return functionDefinition; } + private boolean isSingleFunctionFallbackEnabled() { + return this.functionProperties == null || this.functionProperties.isSingleFunctionFallbackEnabled(); + } + /* * */ diff --git a/spring-cloud-function-context/src/main/resources/META-INF/spring-configuration-metadata.json b/spring-cloud-function-context/src/main/resources/META-INF/spring-configuration-metadata.json index 18830d28d..393e8b5b2 100644 --- a/spring-cloud-function-context/src/main/resources/META-INF/spring-configuration-metadata.json +++ b/spring-cloud-function-context/src/main/resources/META-INF/spring-configuration-metadata.json @@ -14,6 +14,12 @@ "description": "Name (e.g., 'foo') or composition instruction (e.g., 'foo|bar') used to resolve default function especially for cases where there is more than one function available in catalog.", "defaultValue": "" }, + { + "name": "spring.cloud.function.single-function-fallback-enabled", + "type": "java.lang.Boolean", + "description": "Whether a single eligible function can be looked up when the requested name is absent.", + "defaultValue": true + }, { "name": "spring.cloud.function.routing.enabled", "type": "java.lang.Boolean", diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java index 97389a8f7..52f5ecb51 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistryTests.java @@ -98,9 +98,15 @@ private FunctionCatalog configureCatalog() { } private FunctionCatalog configureCatalog(Class... configClass) { - this.context = new SpringApplicationBuilder(configClass) - .run("--logging.level.org.springframework.cloud.function=DEBUG", - "--spring.main.lazy-initialization=true"); + return this.configureCatalogWithProperties(new String[0], configClass); + } + + private FunctionCatalog configureCatalogWithProperties(String[] properties, Class... configClass) { + List args = new ArrayList<>(); + args.add("--logging.level.org.springframework.cloud.function=DEBUG"); + args.add("--spring.main.lazy-initialization=true"); + args.addAll(Arrays.asList(properties)); + this.context = new SpringApplicationBuilder(configClass).run(args.toArray(new String[0])); FunctionCatalog catalog = context.getBean(FunctionCatalog.class); return catalog; } @@ -304,6 +310,23 @@ public void testDefaultLookup() throws Exception { assertThat(((FunctionInvocationWrapper) function).isComposed()).isTrue(); } + @Test + public void testSingleFunctionFallbackIsEnabledByDefault() { + FunctionCatalog catalog = this.configureCatalog(InputHeaderPropagationConfiguration.class); + + assertThat((Object) catalog.lookup("missingFunction")).isNotNull(); + } + + @Test + public void testSingleFunctionFallbackCanBeDisabled() { + FunctionCatalog catalog = this.configureCatalogWithProperties( + new String[] { "--spring.cloud.function.single-function-fallback-enabled=false" }, + InputHeaderPropagationConfiguration.class); + + assertThat((Object) catalog.lookup("missingFunction")).isNull(); + assertThat((Object) catalog.lookup("uppercase")).isNotNull(); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Test public void testBiFunction() {