cloudiful-config 0.6.0

Small serde-based config helpers for default app TOML/SQL stores, env overrides, and atomic saves.
Documentation

cloudiful-config

Small serde-based configuration helpers for the common app case:

  • Read typed config from the platform-default user config directory.
  • Create a missing config.toml from T::default().
  • Read and write whole config blobs from SQLite/Postgres through the same read/save API.
  • Use a default config table named app_configs unless you override it.
  • Apply optional environment variable overrides.
  • Resolve explicit secret://... references before deserializing.
  • Save config back atomically as TOML.

For an app named stock, the default file path is:

  • Linux and other XDG platforms: $XDG_CONFIG_HOME/stock/config.toml or ~/.config/stock/config.toml
  • macOS: ~/Library/Application Support/stock/config.toml
  • Windows: %APPDATA%\\stock\\config.toml

Install

[dependencies]
cloudiful-config = "0.5.0"

Enable keyring when you want secret://keyring?... references to resolve through the system credential store:

[dependencies]
cloudiful-config = { version = "0.5.0", features = ["keyring"] }

Usage

Example:

use cloudiful_config::{ReadOptions, read, save};
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize)]
struct AppConfig {
    port: u16,
    debug: bool,
}

let config: AppConfig = read("stock", None).unwrap();
let config: AppConfig = read("stock", Some(ReadOptions::with_env_prefix("STOCK_"))).unwrap();
save("stock", config).unwrap();

SQLite example:

use cloudiful_config::{read, save, sqlite_store};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize)]
struct AppConfig {
    host: String,
    port: u16,
}

let conn = Connection::open("config.db").unwrap();
let mut store = sqlite_store(&conn, "stock");

let config: AppConfig = read(&mut store, None).unwrap();
save(&mut store, config).unwrap();

Postgres example:

use cloudiful_config::{postgres_store, read, save};
use postgres::{Client, NoTls};
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize)]
struct AppConfig {
    host: String,
    port: u16,
}

let mut client = Client::connect("host=localhost user=postgres dbname=app", NoTls).unwrap();
let mut store = postgres_store(&mut client, "stock");

let config: AppConfig = read(&mut store, None).unwrap();
save(&mut store, config).unwrap();

Default table schema:

CREATE TABLE app_configs (
  app_name TEXT PRIMARY KEY,
  config_json TEXT NOT NULL,
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)

SQLite uses the same logical shape, with updated_at stored as TEXT and CURRENT_TIMESTAMP.

Read behavior

  • read("app-name", ...) loads the default config file, creates it from T::default() if missing, applies optional env overrides, resolves secret references, and then deserializes into T.
  • read(store, ...) does the same flow against the provided store.
  • save("app-name", config) writes TOML back to the default path.
  • save(store, config) writes the full config blob to the provided store.
  • sqlite_store(conn, "stock") and postgres_store(client, "stock") use the default app_configs table.
  • sqlite_store_with_table(...) and postgres_store_with_table(...) let you override the table name.
  • When the target table already exists but does not match the expected config schema, the store returns an error instead of writing into it.

Environment overrides

  • Pass Some(ReadOptions::with_env_prefix("APP_")) to enable env overrides.
  • Keys must start with the configured prefix.
  • The suffix after the prefix is lowercased before matching fields.
  • __ creates nested objects.
  • Values are parsed as JSON literals first.
  • If JSON parsing fails, the raw value is treated as a string.
  • Arrays must be provided as full JSON values such as ["a","b"].
  • Array index syntax is not supported.

Secret references

  • Only explicit string values starting with secret:// are resolved.
  • Secret resolution is strict: invalid or missing secrets return an error.
  • save(...) never resolves or writes secrets to the system store. If a config value is secret://..., it is saved as that literal string.
  • Query parameters support percent-encoding.

Supported provider:

  • keyring via the optional keyring feature

Reference format:

secret://keyring?service=<service>&user=<user>
  • service is required.
  • user is required.

Example TOML:

[database]
user = "app"
password = "secret://keyring?service=stock&user=db-prod"

Example environment override:

APP_DATABASE__PASSWORD=secret://keyring?service=stock&user=db-prod

On macOS this resolves through Keychain via the Rust keyring crate. The same reference format also works for other platforms supported by keyring.