Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,9 @@ nb-configuration.xml
.env
!/docker-compose/.env

# AI Tools
.claude
.bob

# NoSQLBench
logs/
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,53 @@ public class ApiFeatures {
private final Map<ApiFeature, String> fromConfig;
private final RequestContext.HttpHeaderAccess httpHeaders;

private ApiFeatures(
Map<ApiFeature, String> fromConfig, RequestContext.HttpHeaderAccess httpHeaders) {
this.fromConfig = fromConfig;
private ApiFeatures(Map<String, String> fromConfig, RequestContext.HttpHeaderAccess httpHeaders) {
this.fromConfig = marshallFromConfig(fromConfig);
this.httpHeaders = httpHeaders;
}

public static ApiFeatures empty() {
return new ApiFeatures(Collections.emptyMap(), null);
}

protected Map<ApiFeature, String> marshallFromConfig(Map<String, String> fromConfig) {
if (fromConfig == null || fromConfig.isEmpty()) {
return Collections.emptyMap();
}

Map<ApiFeature, String> result = new java.util.HashMap<>();
for (Map.Entry<String, String> entry : fromConfig.entrySet()) {
String key = entry.getKey();
ApiFeature feature = findFeatureByName(key);
if (feature == null) {
throw new IllegalArgumentException(
"Invalid feature flag key: '"
+ key
+ "'. Expected one of: "
+ java.util.Arrays.stream(ApiFeature.values())
.map(
f ->
"STARGATE_FEATURE_FLAGS_"
+ f.featureName().toUpperCase().replace('-', '_'))
.collect(java.util.stream.Collectors.joining(", ")));
}
result.put(feature, entry.getValue());
}
return result;
}

private ApiFeature findFeatureByName(String name) {
for (ApiFeature feature : ApiFeature.values()) {
if (feature.featureName().equals(name)) {
return feature;
}
}
return null;
}

public static ApiFeatures fromConfigAndRequest(
FeaturesConfig config, RequestContext.HttpHeaderAccess httpHeaders) {
Map<ApiFeature, String> fromConfig = config.flags();
Map<String, String> fromConfig = config.flags();
if (fromConfig == null) {
fromConfig = Collections.emptyMap();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,49 @@
* Configuration mapping for Data API Feature flags as read from main application configuration
* (with possible property / sysenv overrides).
*
* <p>NOTE: actual keys in YAML or similar configuration file would be {@code
* stargate.feature.<feature-name>}, where {@code <feature-name>} comes from {@link
* ApiFeature#featureName()} (which is always lower-case). So, for example, to enable {@link
* ApiFeature#TABLES} feature, one would use: {@code stargate.feature.flags.tables} as the key.
* <p>Feature flags can be configured using either property file format (with dots) or environment
* variables (with underscores). The configuration uses the prefix {@code stargate.feature.flags}
* followed by the feature name from {@link ApiFeature#featureName()} (always in lower-case
* kebab-case format).
*
* <p><b>Property File Examples (application.yaml, application.properties):</b>
*
* <pre>
* stargate.feature.flags.lexical=true
* stargate.feature.flags.tables=false
* stargate.feature.flags.reranking=true
* stargate.feature.flags.mcp=false
* stargate.feature.flags.request-tracing=true
* stargate.feature.flags.billing-events-logging=true
* </pre>
*
* <p><b>Environment Variable Examples:</b>
*
* <pre>
* export STARGATE_FEATURE_FLAGS_LEXICAL=true
* export STARGATE_FEATURE_FLAGS_TABLES=false
* export STARGATE_FEATURE_FLAGS_RERANKING=true
* export STARGATE_FEATURE_FLAGS_MCP=false
* export STARGATE_FEATURE_FLAGS_REQUEST_TRACING=true
* export STARGATE_FEATURE_FLAGS_BILLING_EVENTS_LOGGING=true
* </pre>
*
* <p><b>Note:</b> Quarkus/SmallRye Config automatically translates underscores ({@code _}) in
* environment variable names to dots ({@code .}) when matching configuration keys. Feature flag
* values are bound as Strings and converted to Boolean when needed. Null values are not accepted.
*
* @see ApiFeature for available feature flags
* @see ApiFeatures for runtime feature flag evaluation
*/
@ConfigMapping(prefix = "stargate.feature")
public interface FeaturesConfig {
// Quarkus/SmallRye Config won't accept use of `null` values, so we must bind
// as Strings and only convert to Boolean when needed.
Map<ApiFeature, String> flags();

/**
* Returns a map of feature flag names to their configured values. Keys are feature names in
* kebab-case format (e.g., "lexical", "billing-events-logging"). Values are string
* representations of boolean flags ("true", "false", or blank for undefined).
*
* @return map of feature flag configurations, never null but may be empty
*/
Map<String, String> flags();
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void createReturnsNoOpWhenFeatureDisabled() {
@Test
void createConfigEnabledIsNotOverriddenByHeader() {
var config = mock(FeaturesConfig.class);
when(config.flags()).thenReturn(Map.of(ApiFeature.BILLING_EVENTS_LOGGING, "true"));
when(config.flags()).thenReturn(Map.of("billing-events-logging", "true"));

var headers = MultiMap.caseInsensitiveMultiMap();
headers.add(ApiFeature.BILLING_EVENTS_LOGGING.httpHeaderName(), "false");
Expand Down Expand Up @@ -126,8 +126,7 @@ private static BillingConfig validConfig() {

private static ApiFeatures featuresWithBilling(boolean enabled) {
var config = mock(FeaturesConfig.class);
when(config.flags())
.thenReturn(Map.of(ApiFeature.BILLING_EVENTS_LOGGING, String.valueOf(enabled)));
when(config.flags()).thenReturn(Map.of("billing-events-logging", String.valueOf(enabled)));
return ApiFeatures.fromConfigAndRequest(config, null);
}

Expand Down