Skip to content

Commit 04eca28

Browse files
committed
add tests for configid and no configid
1 parent 47fc435 commit 04eca28

File tree

3 files changed

+198
-1
lines changed

3 files changed

+198
-1
lines changed

boolean-conversion-proposal.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Proposal: Standardize Configuration Type Conversion Behavior
2+
3+
## Summary
4+
5+
We propose standardizing configuration type conversion behavior across all data types in the next major release, specifically changing how invalid boolean values are handled to match the behavior of other numeric types.
6+
7+
## Current Problem
8+
9+
### Inconsistent Behavior Between Data Types
10+
11+
Currently, our configuration system exhibits inconsistent behavior when handling invalid values:
12+
13+
**Numeric Types (Integer, Float, Double, Long):**
14+
```java
15+
// Environment: DD_SOME_INT=invalid_number
16+
int value = configProvider.getInteger("some.int", 42);
17+
// Result: 42 (default value) ✅
18+
```
19+
20+
**Boolean Type:**
21+
```java
22+
// Environment: DD_SOME_BOOL=invalid_boolean
23+
boolean value = configProvider.getBoolean("some.bool", true);
24+
// Result: false (hardcoded fallback) ❌
25+
```
26+
27+
### Why This Is Problematic
28+
29+
1. **Unexpected Behavior**: Users expect invalid values to fall back to their explicitly provided defaults
30+
2. **Silent Failures**: Invalid boolean configurations silently become `false`, making debugging difficult
31+
3. **API Inconsistency**: Different data types behave differently for the same logical error condition
32+
4. **Code Complexity**: Requires custom exception handling and special-case logic
33+
34+
## Current Implementation (Temporary Workaround)
35+
36+
To maintain backward compatibility while fixing our test suite, we implemented a temporary solution:
37+
38+
```java
39+
// ConfigConverter.java
40+
static class InvalidBooleanValueException extends IllegalArgumentException {
41+
// Custom exception for backward compatibility
42+
}
43+
44+
public static Boolean booleanValueOf(String value) {
45+
// ... validation logic ...
46+
throw new InvalidBooleanValueException("Invalid boolean value: " + value);
47+
}
48+
49+
// ConfigProvider.java
50+
catch (ConfigConverter.InvalidBooleanValueException ex) {
51+
// Special case: return false instead of default for backward compatibility
52+
return (T) Boolean.FALSE;
53+
}
54+
```
55+
56+
This approach works but adds complexity and maintains the inconsistent behavior.
57+
58+
## Proposed Solution
59+
60+
### For Next Major Release: Standardize All Type Conversions
61+
62+
1. **Remove Custom Boolean Logic**: Eliminate `InvalidBooleanValueException` and special handling
63+
2. **Consistent Exception Handling**: All invalid type conversions throw `IllegalArgumentException`
64+
3. **Consistent Fallback Behavior**: All types fall back to user-provided defaults
65+
66+
### After the Change
67+
68+
```java
69+
// All types will behave consistently:
70+
int intValue = configProvider.getInteger("key", 42); // invalid → 42
71+
boolean boolValue = configProvider.getBoolean("key", true); // invalid → true
72+
float floatValue = configProvider.getFloat("key", 3.14f); // invalid → 3.14f
73+
```
74+
75+
## Implementation Plan
76+
77+
### Phase 1: Next Major Release
78+
- Remove `InvalidBooleanValueException` class
79+
- Update `ConfigConverter.booleanValueOf()` to throw `IllegalArgumentException`
80+
- Remove special boolean handling from `ConfigProvider.get()`
81+
- Update documentation and migration guide
82+
83+
### Phase 2: Communication
84+
- Release notes highlighting the breaking change
85+
- Update configuration documentation with examples
86+
- Provide migration guidance for affected users
87+
88+
## Breaking Change Impact
89+
90+
### Who Is Affected
91+
- Users who rely on invalid boolean values defaulting to `false`
92+
- Applications that depend on the current behavior for error handling
93+
94+
### Migration Path
95+
Users can adapt by:
96+
1. **Fixing Invalid Configurations**: Update configurations to use valid boolean values
97+
2. **Adjusting Defaults**: If they want `false` as fallback, explicitly set `false` as the default
98+
3. **Adding Validation**: Implement application-level validation if needed
99+
100+
### Example Migration
101+
```java
102+
// Before (relied on invalid → false)
103+
boolean feature = configProvider.getBoolean("feature.enabled", true);
104+
105+
// After (explicit about wanting false for invalid values)
106+
boolean feature = configProvider.getBoolean("feature.enabled", false);
107+
```
108+
109+
## Benefits
110+
111+
### For Users
112+
- **Predictable Behavior**: Invalid values consistently fall back to provided defaults
113+
- **Better Debugging**: Clear error signals when configurations are invalid
114+
- **Explicit Intent**: Default values clearly express intended fallback behavior
115+
116+
### For Maintainers
117+
- **Simplified Codebase**: Remove custom exception types and special-case logic
118+
- **Consistent Testing**: All type conversions can be tested with the same patterns
119+
- **Reduced Complexity**: Fewer edge cases and branches to maintain
120+
121+
## Recommendation
122+
123+
We recommend implementing this change in the next major release (e.g., v2.0) because:
124+
125+
1. **Improves User Experience**: More predictable and debuggable configuration behavior
126+
2. **Reduces Technical Debt**: Eliminates custom workarounds and special cases
127+
3. **Aligns with Principle of Least Surprise**: Consistent behavior across all data types
128+
4. **Proper Breaking Change Window**: Major release is the appropriate time for behavior changes
129+
130+
## Questions for Discussion
131+
132+
1. Do we agree this inconsistency is worth fixing with a breaking change?
133+
2. Should we provide any additional migration tooling or validation?
134+
3. Are there other configuration behaviors we should standardize at the same time?
135+
4. What timeline works best for communicating this change to users?
136+
137+
---
138+
139+
**Next Steps**: If approved, we'll create implementation issues and begin updating documentation for the next major release cycle.

internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import datadog.trace.api.config.TraceInstrumentationConfig
99
import datadog.trace.api.config.TracerConfig
1010
import datadog.trace.api.iast.telemetry.Verbosity
1111
import datadog.trace.api.naming.SpanNaming
12+
import datadog.trace.bootstrap.config.provider.ConfigProvider
1213
import datadog.trace.test.util.DDSpecification
1314
import datadog.trace.util.Strings
1415

@@ -228,4 +229,24 @@ class ConfigCollectorTest extends DDSpecification {
228229
"logs.injection.enabled" | "false"
229230
"trace.sample.rate" | "0.3"
230231
}
232+
233+
def "config id is null for non-StableConfigSource"() {
234+
setup:
235+
def key = "test.key"
236+
def value = "test-value"
237+
injectSysConfig(key, value)
238+
239+
when:
240+
// Trigger config collection by getting a value
241+
ConfigProvider.getInstance().getString(key)
242+
def settings = ConfigCollector.get().collect()
243+
244+
then:
245+
// Verify the config was collected but without a config ID
246+
def setting = settings.get(key)
247+
setting != null
248+
setting.configId == null
249+
setting.value == value
250+
setting.origin == ConfigOrigin.JVM_PROP
251+
}
231252
}

internal-api/src/test/groovy/datadog/trace/bootstrap/config/provider/StableConfigSourceTest.groovy

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package datadog.trace.bootstrap.config.provider
22

3+
import datadog.trace.api.ConfigCollector
4+
35
import static java.util.Collections.singletonMap
46

57
import datadog.trace.api.ConfigOrigin
@@ -251,7 +253,42 @@ class StableConfigSourceTest extends DDSpecification {
251253
}
252254
}
253255

254-
// Use this if you want to explicitly write/test configId
256+
def "test config id exists in ConfigCollector when using StableConfigSource"() {
257+
given:
258+
Path filePath = Files.createTempFile("testFile_", ".yaml")
259+
String expectedConfigId = "123"
260+
261+
// Create YAML content with config_id and some configuration
262+
def yamlContent = """
263+
config_id: ${expectedConfigId}
264+
apm_configuration_default:
265+
DD_SERVICE: test-service
266+
DD_ENV: test-env
267+
"""
268+
Files.write(filePath, yamlContent.getBytes())
269+
270+
// Clear any existing collected config
271+
ConfigCollector.get().collect().clear()
272+
273+
when:
274+
// Create StableConfigSource and ConfigProvider
275+
StableConfigSource stableConfigSource = new StableConfigSource(filePath.toString(), ConfigOrigin.LOCAL_STABLE_CONFIG)
276+
ConfigProvider configProvider = new ConfigProvider(stableConfigSource)
277+
278+
// Trigger config collection by getting a value
279+
configProvider.getString("SERVICE", "default-service")
280+
281+
then:
282+
def collectedConfigs = ConfigCollector.get().collect()
283+
def serviceSetting = collectedConfigs.get("SERVICE")
284+
serviceSetting.configId == expectedConfigId
285+
serviceSetting.value == "test-service"
286+
serviceSetting.origin == ConfigOrigin.LOCAL_STABLE_CONFIG
287+
288+
cleanup:
289+
Files.delete(filePath)
290+
}
291+
255292
def writeFileRaw(Path filePath, String configId, String data) {
256293
data = configId + "\n" + data
257294
StandardOpenOption[] openOpts = [StandardOpenOption.WRITE] as StandardOpenOption[]

0 commit comments

Comments
 (0)