re-uploading work
This commit is contained in:
120
work-summary/sessions/2026-01-15-config-url-parsing-fix.md
Normal file
120
work-summary/sessions/2026-01-15-config-url-parsing-fix.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Configuration URL Parsing Fix
|
||||
|
||||
**Date:** 2026-01-15
|
||||
**Status:** ✅ Completed
|
||||
|
||||
## Problem
|
||||
|
||||
The application was failing to start with the following error:
|
||||
|
||||
```
|
||||
Error: Configuration error: invalid type: sequence, expected a string
|
||||
for key `database.url` in the environment
|
||||
|
||||
Error: Configuration error: invalid type: sequence, expected a string
|
||||
for key `message_queue.url` in the environment
|
||||
```
|
||||
|
||||
This error occurred when loading configuration from YAML files that contained array values (like `cors_origins`).
|
||||
|
||||
## Root Cause
|
||||
|
||||
The configuration loader in `crates/common/src/config.rs` was using `.list_separator(",")` when loading environment variables. This caused the `config` crate to incorrectly attempt to parse URL strings (which contain special characters like `://`, `:`, `/`) as comma-separated lists/sequences.
|
||||
|
||||
The problematic code:
|
||||
```rust
|
||||
builder = builder.add_source(
|
||||
config_crate::Environment::with_prefix("ATTUNE")
|
||||
.separator("__")
|
||||
.try_parsing(true)
|
||||
.list_separator(","), // <-- This was the problem
|
||||
);
|
||||
```
|
||||
|
||||
## Solution
|
||||
|
||||
Implemented a two-part fix:
|
||||
|
||||
### 1. Removed `.list_separator(",")`
|
||||
Removed the problematic `.list_separator(",")` call from the environment variable configuration to prevent incorrect parsing of URL strings.
|
||||
|
||||
### 2. Custom Deserializer for Array Fields
|
||||
Added a custom deserializer module `string_or_vec` that allows fields like `cors_origins` to accept both:
|
||||
- **Array format** (YAML): `cors_origins: [http://localhost:3000, http://localhost:5173]`
|
||||
- **Comma-separated string** (env var): `ATTUNE__SERVER__CORS_ORIGINS="http://localhost:3000,http://localhost:5173"`
|
||||
|
||||
The custom deserializer:
|
||||
```rust
|
||||
mod string_or_vec {
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum StringOrVec {
|
||||
String(String),
|
||||
Vec(Vec<String>),
|
||||
}
|
||||
|
||||
match StringOrVec::deserialize(deserializer)? {
|
||||
StringOrVec::String(s) => {
|
||||
Ok(s.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect())
|
||||
}
|
||||
StringOrVec::Vec(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Applied to the `cors_origins` field:
|
||||
```rust
|
||||
#[serde(default, deserialize_with = "string_or_vec::deserialize")]
|
||||
pub cors_origins: Vec<String>,
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Verified the fix works correctly in all scenarios:
|
||||
|
||||
1. ✅ **YAML array format**: Configuration loads successfully from `config.yaml`
|
||||
2. ✅ **Environment variable URLs**: Database and message queue URLs parse correctly
|
||||
- `ATTUNE__DATABASE__URL="postgresql://user:pass@host:5432/db"`
|
||||
- `ATTUNE__MESSAGE_QUEUE__URL="amqp://user:pass@host:5672/%2f"`
|
||||
3. ✅ **Environment variable arrays**: CORS origins parse correctly as comma-separated string
|
||||
- `ATTUNE__SERVER__CORS_ORIGINS="http://test1.com,http://test2.com,http://test3.com"`
|
||||
|
||||
## Files Modified
|
||||
|
||||
- `crates/common/src/config.rs`
|
||||
- Removed `.list_separator(",")` from environment variable loading
|
||||
- Added `string_or_vec` deserializer module
|
||||
- Updated `ServerConfig.cors_origins` to use custom deserializer
|
||||
- Added `Deserializer` import from serde
|
||||
|
||||
## Impact
|
||||
|
||||
- **Backward Compatible**: Existing YAML configurations with array syntax continue to work
|
||||
- **Environment Variables**: URL fields (database, message queue, redis) now parse correctly
|
||||
- **Enhanced Flexibility**: Array fields like `cors_origins` can be specified either way:
|
||||
- As YAML arrays (cleaner for config files)
|
||||
- As comma-separated strings (easier for environment variables)
|
||||
|
||||
## Documentation
|
||||
|
||||
Existing documentation already shows comma-separated format for `ATTUNE__SERVER__CORS_ORIGINS`, which now works correctly:
|
||||
- `CONFIG_README.md`
|
||||
- `docs/configuration.md`
|
||||
- `docs/config-troubleshooting.md`
|
||||
- `docs/env-to-yaml-migration.md`
|
||||
|
||||
No documentation updates required - behavior now matches documented expectations.
|
||||
|
||||
## Notes
|
||||
|
||||
This pattern (custom deserializer for string-or-vec fields) can be reused for any future configuration fields that need to support both array and comma-separated string formats.
|
||||
Reference in New Issue
Block a user