4.2 KiB
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:
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:
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:
#[serde(default, deserialize_with = "string_or_vec::deserialize")]
pub cors_origins: Vec<String>,
Testing
Verified the fix works correctly in all scenarios:
- ✅ YAML array format: Configuration loads successfully from
config.yaml - ✅ 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"
- ✅ 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_vecdeserializer module - Updated
ServerConfig.cors_originsto use custom deserializer - Added
Deserializerimport from serde
- Removed
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_originscan 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.mddocs/configuration.mddocs/config-troubleshooting.mddocs/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.