{"id":33922952,"url":"https://github.com/lambdalisue/rs-serviceconf","last_synced_at":"2026-03-11T19:31:47.364Z","repository":{"id":319961482,"uuid":"1080252147","full_name":"lambdalisue/rs-serviceconf","owner":"lambdalisue","description":"Environment variable configuration with file-based secrets support","archived":false,"fork":false,"pushed_at":"2025-10-21T08:10:06.000Z","size":102,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-19T05:26:06.828Z","etag":null,"topics":["docker","env","kubenetes","rust"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/serviceconf","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambdalisue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"lambdalisue"}},"created_at":"2025-10-21T05:10:27.000Z","updated_at":"2026-01-30T17:20:34.000Z","dependencies_parsed_at":"2025-10-21T07:15:25.667Z","dependency_job_id":"5bfc022a-899b-4fe8-84af-1904a9da3cf9","html_url":"https://github.com/lambdalisue/rs-serviceconf","commit_stats":null,"previous_names":["lambdalisue/rs-envconf"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/lambdalisue/rs-serviceconf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdalisue%2Frs-serviceconf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdalisue%2Frs-serviceconf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdalisue%2Frs-serviceconf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdalisue%2Frs-serviceconf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambdalisue","download_url":"https://codeload.github.com/lambdalisue/rs-serviceconf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdalisue%2Frs-serviceconf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30395597,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-11T18:46:22.935Z","status":"ssl_error","status_checked_at":"2026-03-11T18:46:17.045Z","response_time":84,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","env","kubenetes","rust"],"created_at":"2025-12-12T09:18:03.333Z","updated_at":"2026-03-11T19:31:47.358Z","avatar_url":"https://github.com/lambdalisue.png","language":"Rust","funding_links":["https://github.com/sponsors/lambdalisue"],"categories":[],"sub_categories":[],"readme":"# serviceconf\n\n[![Build](https://github.com/lambdalisue/rs-serviceconf/actions/workflows/build.yml/badge.svg)](https://github.com/lambdalisue/rs-serviceconf/actions/workflows/build.yml)\n[![Crates.io Version](https://img.shields.io/crates/v/serviceconf)](https://crates.io/crates/serviceconf)\n[![docs.rs](https://img.shields.io/docsrs/serviceconf)](https://docs.rs/serviceconf/0.1.0/serviceconf/)\n\n**Environment variable configuration with file-based secrets support**\n\nLoad configuration from environment variables with native support for **file-based secrets** (Kubernetes Secrets, Docker Secrets). This is the primary feature that distinguishes `serviceconf` from other environment variable configuration libraries.\n\n## Key Feature: File-based Secrets\n\nThe `#[conf(from_file)]` attribute allows reading secrets from files mounted by Kubernetes or Docker, avoiding the security risks of exposing secrets directly in environment variables:\n\n```rust\nuse serviceconf::ServiceConf;\n\n#[derive(ServiceConf)]\nstruct Config {\n    #[conf(from_file)]\n    pub api_key: String,  // Reads from API_KEY or API_KEY_FILE\n\n    #[conf(from_file)]\n    pub database_password: String,\n}\n```\n\n**Why file-based secrets?**\n- ✅ **More secure**: Secrets stored in files, not environment variables (which can leak in logs, process lists, etc.)\n- ✅ **Kubernetes native**: Works seamlessly with Kubernetes Secrets mounting\n- ✅ **Docker Secrets**: Direct support for Docker Swarm secrets\n- ✅ **Flexible**: Falls back to direct environment variables for local development\n\n**Loading priority:**\n1. Direct env var (`API_KEY`) - for local development\n2. File path from env var (`API_KEY_FILE`) - for production\n\n**Kubernetes Secret example:**\n\n```yaml\napiVersion: v1\nkind: Secret\nmetadata:\n  name: app-secrets\ntype: Opaque\nstringData:\n  api-key: \"prod-api-key-123\"\n  db-password: \"secure-password\"\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myservice\nspec:\n  template:\n    spec:\n      containers:\n      - name: app\n        image: myservice:latest\n        env:\n          - name: API_KEY_FILE\n            value: /etc/secrets/api-key\n          - name: DATABASE_PASSWORD_FILE\n            value: /etc/secrets/db-password\n        volumeMounts:\n          - name: secrets\n            mountPath: /etc/secrets\n            readOnly: true\n      volumes:\n        - name: secrets\n          secret:\n            secretName: app-secrets\n            items:\n              - key: api-key\n                path: api-key\n              - key: db-password\n                path: db-password\n```\n\n**Local development** (no files needed):\n```bash\nexport API_KEY=dev-key-123\nexport DATABASE_PASSWORD=dev-password\n```\n\n## Installation\n\n```toml\n[dependencies]\nserviceconf = \"0.2\"\n# Or from GitHub\n#serviceconf = { git = \"https://github.com/lambdalisue/rs-serviceconf\" }\n```\n\n## Quick Start\n\n```rust\nuse serviceconf::ServiceConf;\n\n#[derive(Debug, ServiceConf)]\nstruct Config {\n    #[conf(from_file)]\n    pub api_key: String,\n\n    #[conf(default = 8080)]\n    pub port: u16,\n}\n\nfn main() {\n    let config = Config::from_env().unwrap();\n    println!(\"Port: {}\", config.port);\n}\n```\n\n**Local development** (direct environment variable):\n```bash\nexport API_KEY=dev-key-123\nexport PORT=3000\n```\n\n**Production** (Kubernetes/Docker with file-based secret):\n```bash\nexport API_KEY_FILE=/run/secrets/api-key\nexport PORT=8080\n```\n\n## Other Features\n\n### Default Values\n\nUse `#[conf(default)]` for `Default::default()` or `#[conf(default = value)]` for explicit values.\n\n```rust\n#[derive(ServiceConf)]\nstruct Config {\n    #[conf(default = 8080)]\n    pub port: u16,  // 8080 if PORT not set\n\n    #[conf(default = \"localhost\".to_string())]\n    pub host: String,  // \"localhost\" if HOST not set\n}\n```\n\n### Optional Fields\n\nUse `Option\u003cT\u003e` for optional fields. Returns `None` if not set.\n\n```rust\n#[derive(ServiceConf)]\nstruct Config {\n    pub api_key: Option\u003cString\u003e,  // None if API_KEY not set\n}\n```\n\n### Prefix\n\nUse `#[conf(prefix = \"...\")]` at struct level to prefix all environment variables.\n\n```rust\n#[derive(ServiceConf)]\n#[conf(prefix = \"MYAPP_\")]\nstruct Config {\n    pub database_url: String,  // Reads from MYAPP_DATABASE_URL\n    pub api_key: String,       // Reads from MYAPP_API_KEY\n}\n```\n\n```bash\nexport MYAPP_DATABASE_URL=postgres://localhost/db\nexport MYAPP_API_KEY=secret123\n```\n\n### Custom Environment Variable Names\n\nUse `#[conf(name = \"...\")]` to override the auto-generated name.\n\n```rust\n#[derive(ServiceConf)]\nstruct Config {\n    #[conf(name = \"POSTGRES_URL\")]\n    pub database_url: String,  // Reads from POSTGRES_URL, not DATABASE_URL\n}\n```\n\n### Custom Deserializers\n\nUse `#[conf(deserializer = \"function\")]` for complex types or custom parsing.\n\n```rust\n// Custom parser\nfn comma_separated(s: \u0026str) -\u003e Result\u003cVec\u003cString\u003e, String\u003e {\n    Ok(s.split(',').map(|s| s.trim().to_string()).collect())\n}\n\n#[derive(ServiceConf)]\nstruct Config {\n    // JSON array\n    #[conf(deserializer = \"serde_json::from_str\")]\n    pub tags: Vec\u003cString\u003e,\n\n    // Comma-separated\n    #[conf(deserializer = \"comma_separated\")]\n    pub features: Vec\u003cString\u003e,\n\n    // TOML (requires toml crate)\n    #[conf(deserializer = \"toml::from_str\")]\n    pub settings: MySettings,\n}\n```\n\n```bash\nexport TAGS='[\"prod\",\"api\",\"v2\"]'\nexport FEATURES=feature1,feature2,feature3\n```\n\n## Attribute Reference\n\n### Struct-level Attributes\n\n| Attribute                    | Description                                  |\n| ---------------------------- | -------------------------------------------- |\n| `#[conf(prefix = \"PREFIX_\")]` | Add prefix to all environment variable names |\n\n### Field-level Attributes\n\n| Attribute                     | Description                         | When to Use                                  |\n| ----------------------------- | ----------------------------------- | -------------------------------------------- |\n| `#[conf(name = \"VAR\")]`        | Override environment variable name  | When field name differs from desired env var |\n| `#[conf(default)]`             | Use `Default::default()` if not set | For optional fields with sensible defaults   |\n| `#[conf(default = value)]`     | Use explicit default value          | When you need a specific default             |\n| `#[conf(from_file)]`           | Support `{VAR}_FILE` pattern        | For secrets stored in files                  |\n| `#[conf(deserializer = \"fn\")]` | Use custom parser                   | For complex types (Vec, HashMap, etc.)       |\n\n### Type Behavior\n\n| Type                                | When Env Var Missing | When Env Var Set            |\n| ----------------------------------- | -------------------- | --------------------------- |\n| `T` (no attribute)                  | Error                | Parsed with `FromStr`       |\n| `T` + `#[conf(default)]`             | `Default::default()` | Parsed with `FromStr`       |\n| `T` + `#[conf(default = value)]`     | Uses `value`         | Parsed with `FromStr`       |\n| `Option\u003cT\u003e`                         | `None`               | `Some(parsed_value)`        |\n| `T` + `#[conf(deserializer = \"fn\")]` | Error                | Parsed with custom function |\n\n## Combining Attributes\n\nMultiple attributes can be combined:\n\n```rust\n#[derive(ServiceConf)]\n#[conf(prefix = \"APP_\")]\nstruct Config {\n    // Combines: prefix + custom name + from_file + Option\n    #[conf(name = \"DB_URL\")]\n    #[conf(from_file)]\n    pub database_url: Option\u003cString\u003e,  // Reads from APP_DB_URL or APP_DB_URL_FILE\n\n    // Combines: prefix + default\n    #[conf(default = 8080)]\n    pub port: u16,  // Reads from APP_PORT, defaults to 8080\n}\n```\n\n**Invalid combinations** (compile errors):\n\n- `Option\u003cT\u003e` + `#[conf(default)]` or `#[conf(default = value)]` → Option already defaults to None\n\n## Examples\n\nSee the [`examples/`](examples/) directory for complete working examples:\n\n| Example                                                         | Features Demonstrated                             |\n| --------------------------------------------------------------- | ------------------------------------------------- |\n| [`basic.rs`](examples/basic.rs)                                 | Required fields, explicit default values          |\n| [`optional_fields.rs`](examples/optional_fields.rs)             | `Option\u003cT\u003e` for optional fields                   |\n| [`default_trait.rs`](examples/default_trait.rs)                 | `#[conf(default)]` using `Default` trait           |\n| [`prefix.rs`](examples/prefix.rs)                               | `#[conf(prefix = \"...\")]` at struct level          |\n| [`file_based_secrets.rs`](examples/file_based_secrets.rs)       | `#[conf(from_file)]` for Kubernetes/Docker secrets |\n| [`custom_names.rs`](examples/custom_names.rs)                   | `#[conf(name = \"...\")]` for custom env var names   |\n| [`complex_types.rs`](examples/complex_types.rs)                 | `Vec`, `HashMap` with JSON deserializer           |\n| [`custom_deserialize_fn.rs`](examples/custom_deserialize_fn.rs) | Custom deserializer functions                     |\n| [`comprehensive.rs`](examples/comprehensive.rs)                 | Multiple features combined                        |\n\nRun with: `cargo run --example \u003cname\u003e`\n\n## Error Handling\n\n```rust\nmatch Config::from_env() {\n    Ok(config) =\u003e println!(\"Config: {:?}\", config),\n    Err(e) =\u003e eprintln!(\"Error: {}\", e),\n}\n```\n\nExample errors:\n\n- `Environment variable 'DATABASE_URL' is required but not set`\n- `Failed to parse environment variable 'PORT' as u16: invalid digit found in string`\n- `Failed to read file '/etc/secrets/key' for environment variable 'API_KEY_FILE': No such file or directory`\n\n## Testing\n\n```bash\ncargo test\n```\n\n## License\n\nLicensed under MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT).\n\n## Contribution\n\nContributions are welcome! Please feel free to submit a Pull Request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdalisue%2Frs-serviceconf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambdalisue%2Frs-serviceconf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdalisue%2Frs-serviceconf/lists"}