Configuration Deserialization in OpenDAL#
OpenDAL maps flat HashMap<String, String> key-value pairs — sourced from environment variables, URI query parameters, or in-process code — into typed Rust service configuration structs. The pipeline has three layers: the Configurator trait, the ConfigDeserializer bridge, and the serde-derived config structs.
The Configurator Trait#
Every service config struct implements Configurator, defined in core/src/types/builder.rs. The trait provides a default from_iter method that:
- Collects the iterator of
(String, String)pairs into aHashMap. - Wraps it in a
ConfigDeserializer. - Calls
Self::deserialize(cfg), mapping errors toErrorKind::ConfigInvalid.
This means any type with #[derive(Deserialize)] automatically supports deserialization from flat string maps — no manual parsing code required per service.
ConfigDeserializer — The Bridge#
ConfigDeserializer lives in core/src/raw/serde_util.rs. It wraps serde's MapDeserializer over a custom Pairs iterator. Key behaviors:
- Key normalization: All map keys are lowercased before dispatching to struct fields , so
DEFAULT_TTLanddefault_ttlboth work. - Typed
Pairdeserializer: Each value is wrapped in aPairthat implements customdeserialize_*methods for every primitive type, producing error messages that include the key name and raw value for debuggability. - Bool aliases: In addition to
"true"/"false", the values"on"and"off"are accepted as booleans . Option<T>handling: An empty string""deserializes asNone; a non-empty string delegates tovisit_some.- Sequence handling: A comma-separated string like
"us-east-1,eu-west-1"deserializes as aVec<T>by splitting on,and trimming whitespace. An empty string becomes an emptyVecrather than[""].
Service Config Structs#
Service configs are plain Rust structs with #[derive(Default, Debug, Serialize, Deserialize)] and #[serde(default)]. The #[serde(default)] attribute means that any key absent from the map falls back to the field's Default value — no error for missing optional keys.
Examples:
MemcachedConfig: fields forendpoint,root, credentials, anddefault_ttl: Option<Duration>.RedisConfig: fields forendpoint,cluster_endpoints, credentials,db: i64, anddefault_ttl: Option<Duration>.
Duration Fields#
Duration-typed fields (e.g., default_ttl) appear in multiple service configs and are marked Option<Duration>. Because std::time::Duration has a native serde Deserialize implementation (from the standard serde support, serialized as a {secs, nanos} struct), these fields work when deserializing from structured formats like JSON or TOML. However, ConfigDeserializer only handles flat string values — it does not have a built-in string-to-Duration parser for ISO-8601 or "friendly" formats like "30s".
PR #6668 (merged 2025-10-16) added from_uri support to all services and introduced a signed_to_duration(value: &str) -> Result<Duration> utility (using jiff::SignedDuration) that parses both ISO-8601 durations (e.g., PT5M) and friendly formats (e.g., 5m), with validation against negative values and overflow. The established pattern for services that need configurable timeouts via string config is: store the raw value as Option<String> in the config struct (passable via ConfigDeserializer), then call signed_to_duration in the builder's build() method when constructing the service. PR #7423 (adding connect_timeout and acquire_timeout to SFTP) is a concrete example of this pattern, including unit tests for invalid duration strings returning ErrorKind::ConfigInvalid.
Entry Points for Debugging#
| Concern | Location |
|---|---|
ConfigDeserializer and Pair impl | core/src/raw/serde_util.rs |
Configurator trait / from_iter | core/src/types/builder.rs:125-142 |
MemcachedConfig example | core/src/services/memcached/config.rs |
RedisConfig example | core/src/services/redis/config.rs |
from_uri / Duration string parsing | PR #6668 |
| SFTP timeout config pattern example | PR #7423 |