This is an automated email from the ASF dual-hosted git repository. christophd pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9de1478f20efd888d857ea240a69120235c037bc Author: Christoph Deppisch <[email protected]> AuthorDate: Wed Oct 7 16:17:08 2026 +0200 CAMEL-25333: Add evaluationType path parameter and isEnabled support Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/catalog/docs/openfeature-component.adoc | 44 +++++++++++++++------- .../camel/catalog/docs/openfeature-language.adoc | 2 +- .../openfeature/OpenFeatureEndpointConfigurer.java | 6 --- .../openfeature/OpenFeatureEndpointUriFactory.java | 3 +- .../camel/component/openfeature/openfeature.json | 16 ++++---- .../src/main/docs/openfeature-component.adoc | 44 +++++++++++++++------- .../src/main/docs/openfeature-language.adoc | 2 +- .../openfeature/OpenFeatureComponent.java | 12 +++++- .../openfeature/OpenFeatureConfiguration.java | 9 +++-- .../openfeature/OpenFeatureConstants.java | 2 +- .../component/openfeature/OpenFeatureEndpoint.java | 19 ++++++++-- .../openfeature/OpenFeatureLanguageTest.java | 2 +- .../openfeature/OpenFeatureProducerTest.java | 30 ++++++++++++++- 13 files changed, 133 insertions(+), 58 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc index 46bdfbe4734b..f224658909f0 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-component.adoc @@ -39,10 +39,12 @@ for this component: == URI format ---- -openfeature:domain[?options] +openfeature:domain[/evaluationType][?options] ---- -Where *domain* is the OpenFeature domain to bind the provider to. +Where *domain* is the OpenFeature domain to bind the provider to and +*evaluationType* is an optional path parameter that selects the evaluation +type (`boolean`, `variant`, or `isEnabled`). // component-configure options: START // component-configure options: END @@ -69,13 +71,26 @@ The component resolves the feature flag provider in this order: == Evaluation type -The evaluation type (boolean or string) is determined by the `evaluationType` -option when set: +The evaluation type (boolean or string) can be set as a path parameter in +the endpoint URI or as a query parameter / component property: -* `evaluationType=boolean` — boolean evaluation (`getBooleanValue`) -* `evaluationType=variant` — string evaluation (`getStringValue`) +* `boolean` — boolean evaluation (`getBooleanValue`) +* `isEnabled` — same as `boolean`, semantically indicates a feature toggle check +* `variant` — string evaluation (`getStringValue`) -When `evaluationType` is not set, the type is inferred from `defaultValue`: +When `evaluationType` appears both as a path parameter and as a query +parameter or component property, the path parameter takes precedence. + +Examples: + +---- +openfeature:flags/boolean?flagKey=enrichment-enabled +openfeature:flags/isEnabled?flagKey=enrichment-enabled +openfeature:flags/variant?flagKey=checkout-banner +openfeature:flags?flagKey=enrichment-enabled&evaluationType=boolean +---- + +When `evaluationType` is not set at all, the type is inferred from `defaultValue`: * When `defaultValue` is `"true"` or `"false"` (case-insensitive), boolean evaluation is used. @@ -172,8 +187,9 @@ Boolean evaluation is the default (`evaluationType` is unset and === Variant flag (string evaluation) -A variant flag returns a string value rather than a boolean. Use -`evaluationType=variant` to select string evaluation: +A variant flag returns a string value rather than a boolean. Set +`evaluationType` to `variant` — either as a path parameter or query +parameter: [source,json] ---- @@ -194,7 +210,7 @@ A variant flag returns a string value rather than a boolean. Use [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=checkout-banner&evaluationType=variant&resultProperty=banner") + .to("openfeature:flags/variant?flagKey=checkout-banner&resultProperty=banner") .log("Banner: ${exchangeProperty.banner}"); ---- @@ -236,7 +252,7 @@ to the same variant: ---- from("direct:start") .setHeader("CamelOpenFeatureTargetingKey", simple("${header.orderId}")) - .to("openfeature:flags?flagKey=routing-algorithm&evaluationType=variant&resultProperty=algorithm") + .to("openfeature:flags/variant?flagKey=routing-algorithm&resultProperty=algorithm") .toD("direct:route-${exchangeProperty.algorithm}"); ---- @@ -274,7 +290,7 @@ The route stores the result in an exchange property and routes dynamically: [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant") + .to("openfeature:flags/variant?flagKey=order-compliance-v2&resultProperty=orderVariant") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- @@ -287,7 +303,7 @@ from("direct:start") .setHeader("CamelOpenFeatureTargetingKey", constant("order-123")) .setHeader("CamelOpenFeatureEvaluationContext", constant(Map.of("customer_tier", "ENTERPRISE"))) - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant") + .to("openfeature:flags/variant?flagKey=order-compliance-v2&resultProperty=orderVariant") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- @@ -297,7 +313,7 @@ evaluation context: [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant" + .to("openfeature:flags/variant?flagKey=order-compliance-v2" + "&resultProperty=orderVariant&contextFromBody=true") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc index 61fba4be34a0..71a09874d522 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/openfeature-language.adoc @@ -120,7 +120,7 @@ properties: | 0 | `String` | Endpoint URI (default: `openfeature:flags`) | 1 | `String` | Targeting key | 2 | `Map<String, Object>` | Evaluation context key-value pairs -| 3 | `String` | Evaluation type: `boolean` or `variant` (default: `boolean` for createPredicate, `variant` for createExpression) +| 3 | `String` | Evaluation type: `boolean`, `isEnabled`, or `variant` (default: `boolean` for createPredicate, `variant` for createExpression) |=== `createPredicate` defaults to boolean evaluation and returns a `Predicate`: diff --git a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java index 772592acfc52..a0d850f363f7 100644 --- a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java +++ b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointConfigurer.java @@ -30,8 +30,6 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp case "deadline": target.getConfiguration().setDeadline(property(camelContext, int.class, value)); return true; case "defaultvalue": case "defaultValue": target.getConfiguration().setDefaultValue(property(camelContext, java.lang.String.class, value)); return true; - case "evaluationtype": - case "evaluationType": target.getConfiguration().setEvaluationType(property(camelContext, java.lang.String.class, value)); return true; case "flagkey": case "flagKey": target.getConfiguration().setFlagKey(property(camelContext, java.lang.String.class, value)); return true; case "flags": target.getConfiguration().setFlags(property(camelContext, java.lang.String.class, value)); return true; @@ -59,8 +57,6 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp case "deadline": return int.class; case "defaultvalue": case "defaultValue": return java.lang.String.class; - case "evaluationtype": - case "evaluationType": return java.lang.String.class; case "flagkey": case "flagKey": return java.lang.String.class; case "flags": return java.lang.String.class; @@ -89,8 +85,6 @@ public class OpenFeatureEndpointConfigurer extends PropertyConfigurerSupport imp case "deadline": return target.getConfiguration().getDeadline(); case "defaultvalue": case "defaultValue": return target.getConfiguration().getDefaultValue(); - case "evaluationtype": - case "evaluationType": return target.getConfiguration().getEvaluationType(); case "flagkey": case "flagKey": return target.getConfiguration().getFlagKey(); case "flags": return target.getConfiguration().getFlags(); diff --git a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java index 0236dcd2e007..c8896e422b27 100644 --- a/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java +++ b/components/camel-openfeature/src/generated/java/org/apache/camel/component/openfeature/OpenFeatureEndpointUriFactory.java @@ -17,7 +17,7 @@ import org.apache.camel.spi.EndpointUriFactory; @Generated("org.apache.camel.maven.packaging.GenerateEndpointUriFactoryMojo") public class OpenFeatureEndpointUriFactory extends org.apache.camel.support.component.EndpointUriFactorySupport implements EndpointUriFactory { - private static final String BASE = ":domain"; + private static final String BASE = ":domain/evaluationType"; private static final Set<String> PROPERTY_NAMES; private static final Set<String> SECRET_PROPERTY_NAMES; @@ -59,6 +59,7 @@ public class OpenFeatureEndpointUriFactory extends org.apache.camel.support.comp Map<String, Object> copy = copyParameters(properties); uri = buildPathParameter(syntax, uri, "domain", null, true, copy); + uri = buildPathParameter(syntax, uri, "evaluationType", null, false, copy); uri = buildQueryParameters(uri, copy, encode); return uri; } diff --git a/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json b/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json index 163924b3bbcf..e5795988c0d6 100644 --- a/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json +++ b/components/camel-openfeature/src/generated/resources/META-INF/org/apache/camel/component/openfeature/openfeature.json @@ -14,7 +14,7 @@ "version": "4.23.0-SNAPSHOT", "scheme": "openfeature", "extendsScheme": "", - "syntax": "openfeature:domain", + "syntax": "openfeature:domain\/evaluationType", "async": false, "api": false, "consumerOnly": false, @@ -29,7 +29,7 @@ "contextFromBody": { "index": 2, "kind": "property", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When false [...] "deadline": { "index": 3, "kind": "property", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." }, "defaultValue": { "index": 4, "kind": "property", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not set [...] - "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluatio [...] + "evaluationType": { "index": 5, "kind": "property", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant", "isEnabled" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. 'boolean' and 'isEnabled' u [...] "flagKey": { "index": 6, "kind": "property", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." }, "flags": { "index": 7, "kind": "property", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." }, "flagsResource": { "index": 8, "kind": "property", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclusi [...] @@ -45,18 +45,18 @@ "CamelOpenFeatureFlagKey": { "index": 0, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Overrides the configured flag key for this message.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#FLAG_KEY" }, "CamelOpenFeatureTargetingKey": { "index": 1, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the targeting key for the OpenFeature evaluation context.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#TARGETING_KEY" }, "CamelOpenFeatureEvaluationContext": { "index": 2, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "java.util.Map", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "A Map of additional evaluation context key-value pairs.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_CONTEXT" }, - "CamelOpenFeatureEvaluationType": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the evaluation type (boolean, variant) for the OpenFeature evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_TYPE" }, + "CamelOpenFeatureEvaluationType": { "index": 3, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Sets the evaluation type (boolean, variant, isEnabled) for the OpenFeature evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_TYPE" }, "CamelOpenFeatureVariant": { "index": 4, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The variant name returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_VARIANT" }, "CamelOpenFeatureReason": { "index": 5, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The reason string returned by the provider for this evaluation.", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_REASON" }, "CamelOpenFeatureErrorCode": { "index": 6, "kind": "header", "displayName": "", "group": "producer", "label": "", "required": false, "javaType": "String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The error code when the evaluation failed (e.g. FLAG_NOT_FOUND, TYPE_MISMATCH).", "constantName": "org.apache.camel.component.openfeature.OpenFeatureConstants#EVALUATION_ERROR_CODE" } }, "properties": { "domain": { "index": 0, "kind": "path", "displayName": "Domain", "group": "producer", "label": "", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The OpenFeature domain to bind the provider to." }, - "certPath": { "index": 1, "kind": "parameter", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." }, - "contextFromBody": { "index": 2, "kind": "parameter", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When fals [...] - "deadline": { "index": 3, "kind": "parameter", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." }, - "defaultValue": { "index": 4, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...] - "evaluationType": { "index": 5, "kind": "parameter", "displayName": "Evaluation Type", "group": "common", "label": "common", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant" ], "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The evaluation type. When set to 'boolean', boolean evaluati [...] + "evaluationType": { "index": 1, "kind": "path", "displayName": "Evaluation Type", "group": "producer", "label": "", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "boolean", "variant", "isEnabled" ], "deprecated": false, "autowired": false, "secret": false, "description": "The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation (getBooleanValue). 'variant' uses string evaluation (getStringValue). When not set, the type is inferred from defa [...] + "certPath": { "index": 2, "kind": "parameter", "displayName": "Cert Path", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Path to the TLS certificate for the remote flagd connection." }, + "contextFromBody": { "index": 3, "kind": "parameter", "displayName": "Context From Body", "group": "common", "label": "common", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "When true, a Map message body is used as the evaluation context. When fals [...] + "deadline": { "index": 4, "kind": "parameter", "displayName": "Deadline", "group": "common", "label": "common", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": 500, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Deadline in milliseconds for the remote flagd connection." }, + "defaultValue": { "index": 5, "kind": "parameter", "displayName": "Default Value", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "false", "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Default value when flag evaluation fails. When evaluationType is not se [...] "flagKey": { "index": 6, "kind": "parameter", "displayName": "Flag Key", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "The feature flag key to evaluate. Can be overridden per message via the CamelOpenFeatureFlagKey header." }, "flags": { "index": 7, "kind": "parameter", "displayName": "Flags", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "A JSON object defining feature flags in flagd format (inline). Mutually exclusive with flagsResource." }, "flagsResource": { "index": 8, "kind": "parameter", "displayName": "Flags Resource", "group": "common", "label": "common", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.openfeature.OpenFeatureConfiguration", "configurationField": "configuration", "description": "Camel resource URI pointing to a feature flag definition file in flagd format. Mutually exclus [...] diff --git a/components/camel-openfeature/src/main/docs/openfeature-component.adoc b/components/camel-openfeature/src/main/docs/openfeature-component.adoc index 46bdfbe4734b..f224658909f0 100644 --- a/components/camel-openfeature/src/main/docs/openfeature-component.adoc +++ b/components/camel-openfeature/src/main/docs/openfeature-component.adoc @@ -39,10 +39,12 @@ for this component: == URI format ---- -openfeature:domain[?options] +openfeature:domain[/evaluationType][?options] ---- -Where *domain* is the OpenFeature domain to bind the provider to. +Where *domain* is the OpenFeature domain to bind the provider to and +*evaluationType* is an optional path parameter that selects the evaluation +type (`boolean`, `variant`, or `isEnabled`). // component-configure options: START // component-configure options: END @@ -69,13 +71,26 @@ The component resolves the feature flag provider in this order: == Evaluation type -The evaluation type (boolean or string) is determined by the `evaluationType` -option when set: +The evaluation type (boolean or string) can be set as a path parameter in +the endpoint URI or as a query parameter / component property: -* `evaluationType=boolean` — boolean evaluation (`getBooleanValue`) -* `evaluationType=variant` — string evaluation (`getStringValue`) +* `boolean` — boolean evaluation (`getBooleanValue`) +* `isEnabled` — same as `boolean`, semantically indicates a feature toggle check +* `variant` — string evaluation (`getStringValue`) -When `evaluationType` is not set, the type is inferred from `defaultValue`: +When `evaluationType` appears both as a path parameter and as a query +parameter or component property, the path parameter takes precedence. + +Examples: + +---- +openfeature:flags/boolean?flagKey=enrichment-enabled +openfeature:flags/isEnabled?flagKey=enrichment-enabled +openfeature:flags/variant?flagKey=checkout-banner +openfeature:flags?flagKey=enrichment-enabled&evaluationType=boolean +---- + +When `evaluationType` is not set at all, the type is inferred from `defaultValue`: * When `defaultValue` is `"true"` or `"false"` (case-insensitive), boolean evaluation is used. @@ -172,8 +187,9 @@ Boolean evaluation is the default (`evaluationType` is unset and === Variant flag (string evaluation) -A variant flag returns a string value rather than a boolean. Use -`evaluationType=variant` to select string evaluation: +A variant flag returns a string value rather than a boolean. Set +`evaluationType` to `variant` — either as a path parameter or query +parameter: [source,json] ---- @@ -194,7 +210,7 @@ A variant flag returns a string value rather than a boolean. Use [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=checkout-banner&evaluationType=variant&resultProperty=banner") + .to("openfeature:flags/variant?flagKey=checkout-banner&resultProperty=banner") .log("Banner: ${exchangeProperty.banner}"); ---- @@ -236,7 +252,7 @@ to the same variant: ---- from("direct:start") .setHeader("CamelOpenFeatureTargetingKey", simple("${header.orderId}")) - .to("openfeature:flags?flagKey=routing-algorithm&evaluationType=variant&resultProperty=algorithm") + .to("openfeature:flags/variant?flagKey=routing-algorithm&resultProperty=algorithm") .toD("direct:route-${exchangeProperty.algorithm}"); ---- @@ -274,7 +290,7 @@ The route stores the result in an exchange property and routes dynamically: [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant") + .to("openfeature:flags/variant?flagKey=order-compliance-v2&resultProperty=orderVariant") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- @@ -287,7 +303,7 @@ from("direct:start") .setHeader("CamelOpenFeatureTargetingKey", constant("order-123")) .setHeader("CamelOpenFeatureEvaluationContext", constant(Map.of("customer_tier", "ENTERPRISE"))) - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant&resultProperty=orderVariant") + .to("openfeature:flags/variant?flagKey=order-compliance-v2&resultProperty=orderVariant") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- @@ -297,7 +313,7 @@ evaluation context: [source,java] ---- from("direct:start") - .to("openfeature:flags?flagKey=order-compliance-v2&evaluationType=variant" + .to("openfeature:flags/variant?flagKey=order-compliance-v2" + "&resultProperty=orderVariant&contextFromBody=true") .toD("direct:orders-${exchangeProperty.orderVariant}"); ---- diff --git a/components/camel-openfeature/src/main/docs/openfeature-language.adoc b/components/camel-openfeature/src/main/docs/openfeature-language.adoc index 61fba4be34a0..71a09874d522 100644 --- a/components/camel-openfeature/src/main/docs/openfeature-language.adoc +++ b/components/camel-openfeature/src/main/docs/openfeature-language.adoc @@ -120,7 +120,7 @@ properties: | 0 | `String` | Endpoint URI (default: `openfeature:flags`) | 1 | `String` | Targeting key | 2 | `Map<String, Object>` | Evaluation context key-value pairs -| 3 | `String` | Evaluation type: `boolean` or `variant` (default: `boolean` for createPredicate, `variant` for createExpression) +| 3 | `String` | Evaluation type: `boolean`, `isEnabled`, or `variant` (default: `boolean` for createPredicate, `variant` for createExpression) |=== `createPredicate` defaults to boolean evaluation and returns a `Predicate`: diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java index 8b287829322f..65bb25a1455f 100644 --- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java +++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureComponent.java @@ -54,7 +54,17 @@ public class OpenFeatureComponent extends DefaultComponent { @Override protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { - OpenFeatureEndpoint endpoint = new OpenFeatureEndpoint(uri, this, remaining, configuration.copy()); + String domain = remaining; + String evaluationType = null; + int idx = remaining.indexOf('/'); + if (idx >= 0) { + domain = remaining.substring(0, idx); + String pathValue = remaining.substring(idx + 1); + if (!pathValue.isBlank()) { + evaluationType = pathValue; + } + } + OpenFeatureEndpoint endpoint = new OpenFeatureEndpoint(uri, this, domain, evaluationType, configuration.copy()); setProperties(endpoint, parameters); return endpoint; } diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java index a71e0651fa8f..cb44bc145c1e 100644 --- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java +++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConfiguration.java @@ -32,10 +32,11 @@ public class OpenFeatureConfiguration implements Cloneable { + " selects boolean evaluation, any other value selects string evaluation.") private String defaultValue = "false"; - @UriParam(label = "common", enums = "boolean,variant", - description = "The evaluation type. When set to 'boolean', boolean evaluation is used (getBooleanValue)." - + " When set to 'variant', string evaluation is used (getStringValue)." - + " When not set, the type is inferred from defaultValue.") + @UriParam(label = "common", enums = "boolean,variant,isEnabled", + description = "The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation (getBooleanValue)." + + " 'variant' uses string evaluation (getStringValue)." + + " When not set, the type is inferred from defaultValue." + + " Can also be set as a path parameter in the endpoint URI, which takes precedence.") private String evaluationType; @UriParam(label = "producer", diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java index fb836b014b90..2686a94ad678 100644 --- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java +++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureConstants.java @@ -32,7 +32,7 @@ public final class OpenFeatureConstants { javaType = "java.util.Map", applicableFor = "openfeature") public static final String EVALUATION_CONTEXT = "CamelOpenFeatureEvaluationContext"; - @Metadata(description = "Sets the evaluation type (boolean, variant) for the OpenFeature evaluation.", + @Metadata(description = "Sets the evaluation type (boolean, variant, isEnabled) for the OpenFeature evaluation.", javaType = "String", applicableFor = "openfeature") public static final String EVALUATION_TYPE = "CamelOpenFeatureEvaluationType"; diff --git a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java index 67bd82cca458..3abd2c293368 100644 --- a/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java +++ b/components/camel-openfeature/src/main/java/org/apache/camel/component/openfeature/OpenFeatureEndpoint.java @@ -51,7 +51,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** Evaluate feature flags using the OpenFeature specification with flagd. */ -@UriEndpoint(firstVersion = "4.23.0", scheme = "openfeature", title = "OpenFeature", syntax = "openfeature:domain", +@UriEndpoint(firstVersion = "4.23.0", scheme = "openfeature", title = "OpenFeature", + syntax = "openfeature:domain/evaluationType", producerOnly = true, category = { Category.CLOUD }, headersClass = OpenFeatureConstants.class) public class OpenFeatureEndpoint extends DefaultEndpoint { @@ -59,9 +60,15 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { private static final String DEFAULT_PROVIDER_BEAN = "flags"; @UriPath - @Metadata(required = true) + @Metadata(required = true, description = "The OpenFeature domain to bind the provider to.") private String domain; + @UriPath(enums = "boolean,variant,isEnabled", + description = "The evaluation type. 'boolean' and 'isEnabled' use boolean evaluation (getBooleanValue)." + + " 'variant' uses string evaluation (getStringValue)." + + " When not set, the type is inferred from defaultValue.") + private String evaluationType; + @UriParam private OpenFeatureConfiguration configuration; @@ -69,10 +76,11 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { private volatile File tempFlagFile; private volatile boolean ownedProvider; - public OpenFeatureEndpoint(String uri, OpenFeatureComponent component, String domain, + public OpenFeatureEndpoint(String uri, OpenFeatureComponent component, String domain, String evaluationType, OpenFeatureConfiguration configuration) { super(uri, component); this.domain = domain; + this.evaluationType = evaluationType; this.configuration = configuration; } @@ -283,6 +291,9 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { if (evalType == null) { evalType = exchange.getProperty(OpenFeatureConstants.EVALUATION_TYPE, String.class); } + if (evalType == null) { + evalType = evaluationType; + } if (evalType == null) { evalType = configuration.getEvaluationType(); } @@ -290,7 +301,7 @@ public class OpenFeatureEndpoint extends DefaultEndpoint { } private boolean isBooleanEvaluation(String evalType) { - if ("boolean".equalsIgnoreCase(evalType)) { + if ("boolean".equalsIgnoreCase(evalType) || "isEnabled".equalsIgnoreCase(evalType)) { return true; } if ("variant".equalsIgnoreCase(evalType)) { diff --git a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java index 01882f76092f..ba1d246b11a2 100644 --- a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java +++ b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureLanguageTest.java @@ -79,7 +79,7 @@ class OpenFeatureLanguageTest extends CamelTestSupport { .filter().language("openfeature", "enrichment-enabled") .to("mock:lang-filtered") .end() - .to("openfeature:flags?flagKey=hazmat-compliance-v2&evaluationType=variant&resultProperty=variant"); + .to("openfeature:flags/variant?flagKey=hazmat-compliance-v2&resultProperty=variant"); } }; } diff --git a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java index d0bbd29582aa..aa2d71d0b745 100644 --- a/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java +++ b/components/camel-openfeature/src/test/java/org/apache/camel/component/openfeature/OpenFeatureProducerTest.java @@ -57,11 +57,15 @@ class OpenFeatureProducerTest extends CamelTestSupport { + "&contextFromBody=true"); from("direct:variant-type") - .to("openfeature:test-variant?flagKey=new-routing-algorithm&evaluationType=variant&flagsResource=" + .to("openfeature:test-variant/variant?flagKey=new-routing-algorithm&flagsResource=" + FLAGS_RESOURCE); from("direct:boolean-type-explicit") - .to("openfeature:test-bool-type?flagKey=enrichment-enabled&evaluationType=boolean&flagsResource=" + .to("openfeature:test-bool-type/boolean?flagKey=enrichment-enabled&flagsResource=" + + FLAGS_RESOURCE); + + from("direct:is-enabled") + .to("openfeature:test-is-enabled/isEnabled?flagKey=enrichment-enabled&flagsResource=" + FLAGS_RESOURCE); from("direct:targeted-boolean") @@ -180,6 +184,28 @@ class OpenFeatureProducerTest extends CamelTestSupport { assertThat(result).isEqualTo(true); } + @Test + void testIsEnabledEvaluationType() { + Object result = template.requestBody("direct:is-enabled", "ignored"); + assertThat(result).isInstanceOf(Boolean.class); + assertThat(result).isEqualTo(true); + } + + @Test + void testIsEnabledDisabledFlag() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("direct:is-enabled-disabled") + .to("openfeature:test-is-enabled-off/isEnabled?flagKey=disabled-flag&flagsResource=" + + FLAGS_RESOURCE); + } + }); + Object result = template.requestBody("direct:is-enabled-disabled", "ignored"); + assertThat(result).isInstanceOf(Boolean.class); + assertThat(result).isEqualTo(false); + } + @Test void testInlineFlags() throws Exception { String inlineFlags = """
