Skip to content

Duration config fields cannot be set from the string config map #7887

Description

@chitralverma

Summary

Duration-typed config fields cannot be set from the string config map. Any attempt to set one (via Operator::via_iter, from_uri, or a Configurator built from a key/value map) fails with invalid type: string, expected struct Duration. This is a core issue and is binding-agnostic — every binding routes config through the same ConfigDeserializer.

Root cause

Config fields are declared as plain std::time::Duration with the default serde derive and no string-parsing attribute:

// core/services/redis/src/config.rs:62
pub default_ttl: Option<Duration>,

serde's default Duration impl only deserializes from a { secs, nanos } struct. But ConfigDeserializer (core/core/src/raw/serde_util.rs) forwards struct to deserialize_any, which hands back the raw string. So a string value never matches the expected struct and deserialization fails.

There is no #[serde(with = "humantime_serde")] (or equivalent), and humantime is not a dependency of these crates.

Affected fields

All Duration-typed config fields:

  • core/services/redis/src/config.rs:62default_ttl
  • core/services/memcached/src/config.rs:47default_ttl
  • core/services/cloudflare-kv/src/config.rs:37default_ttl

More broadly, any config field typed Duration hits this. Services that need a duration currently work around it with a plain integer (e.g. S3's assume_role_duration_seconds: Option<u32>), so Duration is effectively a trap for any future config field.

Steps to reproduce (core, Rust)

use std::collections::HashMap;
use opendal::Operator;

let mut opts = HashMap::new();
opts.insert("endpoint".into(), "tcp://127.0.0.1:6379".into());
opts.insert("db".into(), "0".into());
opts.insert("default_ttl".into(), "5s".into()); // any string fails

// Err: ConfigInvalid "failed to deserialize config",
//      source: invalid type: string "5s", expected struct Duration
let _ = Operator::via_iter("redis", opts).unwrap();

The same failure occurs for "5", "100ms", "0", etc. Only omitting the field works. It reproduces identically from any binding (e.g. Python Operator("redis", default_ttl="5s") or Operator.from_config(RedisConfig(..., default_ttl="5s"))).

Suggested fix

Add a string deserializer for Duration config fields in core, e.g. #[serde(with = "humantime_serde")] (plus the humantime/humantime-serde dependency), so values like "5s", "100ms", "1m" parse. This fixes every binding at once. Bindings can then decide how to type the field (a humantime string, and optionally a native duration type).

Note: a bare number like "5" (no unit) is not accepted by humantime and should remain an error; only explicit-unit strings should be supported.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions