Skip to content

Implement daemon config loader#4

Merged
leynos merged 8 commits intomainfrom
codex/implement-configuration-loading-from-toml-file
Jul 26, 2025
Merged

Implement daemon config loader#4
leynos merged 8 commits intomainfrom
codex/implement-configuration-loading-from-toml-file

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Jul 26, 2025

Summary

  • implement daemon config loader using ortho-config
  • expose daemon config in a new library crate
  • document configuration in design doc and mark roadmap item as done
  • add behavioural tests for config loading

closes #97

Testing

  • make fmt
  • make lint
  • make test
  • make markdownlint
  • make nixie
  • cargo test -p comenqd

https://chatgpt.com/codex/tasks/task_e_68840bbda06883229414392eb40f4da2

Summary by Sourcery

Introduce a typed configuration loader for the Comenqd daemon using ortho-config, expose it in a library crate, integrate config loading in the daemon startup, and add tests and documentation for the new functionality.

New Features:

  • Implement Config struct with file, environment, and CLI override support via ortho-config
  • Expose Config and daemon run logic in the comenqd library crate
  • Update main binary to load configuration and spawn Unix socket listener and worker tasks asynchronously

Enhancements:

  • Merge configuration file values, COMENQD_* environment variables, and CLI arguments with correct precedence
  • Apply defaults for optional fields and report errors for missing required fields

Build:

  • Add ortho_config and figment dependencies for config loading
  • Add rstest, tempfile, and serial_test dev-dependencies for testing

Documentation:

  • Document configuration loading behavior in the design doc and mark roadmap item as done
  • Add crate-level docs and usage example for the Config API

Tests:

  • Add unit tests for Config loader covering file loading, overrides, defaults, and error cases
  • Add Cucumber behavioural tests for daemon configuration loading scenarios

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jul 26, 2025

Reviewer's Guide

Implements a typed configuration loader for the Comenqd daemon leveraging ortho-config/figment to merge defaults, TOML files, environment variables, and CLI overrides; exposes it via a new library crate; updates the daemon entrypoint to use the loaded Config and spawn tasks; enhances documentation and roadmap; updates dependencies; and adds comprehensive unit and BDD tests.

Sequence diagram for configuration loading and merging in the daemon

sequenceDiagram
    participant Main as main()
    participant Config as Config
    participant OrthoConfig as ortho_config/figment
    participant Env as Environment
    participant CLI as CLI Args
    Main->>Config: Config::load()
    Config->>CLI: Parse CLI arguments
    Config->>OrthoConfig: load_config_file(path)
    OrthoConfig-->>Config: Config from TOML file
    Config->>Env: Merge COMENQD_* env vars
    Env-->>Config: Env overrides
    Config->>CLI: Apply CLI overrides
    Config-->>Main: Config instance
Loading

Class diagram for the new Config loader and CLI overrides

classDiagram
    class Config {
        +String github_token
        +PathBuf socket_path
        +PathBuf queue_path
        +u64 cooldown_period_seconds
        +const DEFAULT_PATH: &'static str
        +fn load() -> Result<Config, OrthoError>
        +fn from_file(path: &Path) -> Result<Config, OrthoError>
        -fn from_file_with_cli(path: &Path, cli: &CliArgs) -> Result<Config, OrthoError>
    }
    class CliArgs {
        +PathBuf config
        +Option<String> github_token
        +Option<PathBuf> socket_path
        +Option<PathBuf> queue_path
    }
    Config <.. CliArgs : uses
Loading

Class diagram for the new daemon module and run function

classDiagram
    class daemon {
        +async fn run(config: Config) -> Result<()> 
        -async fn run_listener(config: Arc<Config>, tx: Sender) -> Result<()> 
        -async fn run_worker(config: Arc<Config>, rx: Receiver, octocrab: Arc<Octocrab>) -> Result<()> 
        -async fn handle_client(stream: UnixStream, tx: &mut Sender) -> Result<()> 
        -fn build_octocrab(token: &str) -> Result<Octocrab>
        -fn prepare_listener(path: &Path) -> Result<UnixListener>
    }
    Config <.. daemon : used by
    Octocrab <.. daemon : used by
    Sender <.. daemon : used by
    Receiver <.. daemon : used by
Loading

File-Level Changes

Change Details Files
Add Config loader logic using ortho-config and Figment
  • Define Config struct with serde defaults and constants
  • Implement CliArgs for CLI overrides
  • Use ortho-config to load from file and merge Env provider
  • Provide load(), from_file(), and merge logic with error handling
crates/comenqd/src/config.rs
Integrate and expose configuration in new library and adjust daemon startup
  • Add lib.rs exposing config and daemon modules
  • Implement daemon run logic in daemon.rs spawning listener and worker
  • Update main.rs to call Config::load(), log paths, and invoke run()
crates/comenqd/src/lib.rs
crates/comenqd/src/daemon.rs
crates/comenqd/src/main.rs
Update project dependencies for configuration and testing
  • Add ortho-config and figment to dependencies
  • Add rstest, tempfile, serial_test as dev-dependencies
  • Sync ortho-config entries in root and crate Cargo.toml files
crates/comenqd/Cargo.toml
Cargo.toml
Enhance documentation and roadmap for config loader
  • Document loading precedence and error behavior in design doc
  • Mark configuration loading item done in roadmap
docs/comenq-design.md
docs/roadmap.md
Add unit and BDD tests for configuration loading
  • Write unit tests covering file load, env overrides and defaults
  • Add cucumber steps and feature file for config scenarios
  • Register ConfigWorld in steps mod and cucumber runner
crates/comenqd/src/config.rs
tests/steps/mod.rs
tests/steps/config_steps.rs
tests/features/config.feature
tests/cucumber.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 26, 2025

Warning

Rate limit exceeded

@leynos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 24 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 31c336a and 6a98db8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml (2 hunks)
  • crates/comenqd/Cargo.toml (2 hunks)
  • crates/comenqd/src/config.rs (1 hunks)
  • crates/comenqd/src/daemon.rs (1 hunks)
  • crates/comenqd/src/lib.rs (1 hunks)
  • crates/comenqd/src/main.rs (1 hunks)
  • docs/comenq-design.md (2 hunks)
  • docs/roadmap.md (1 hunks)
  • tests/cucumber.rs (1 hunks)
  • tests/features/config.feature (1 hunks)
  • tests/steps/config_steps.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)

Summary by CodeRabbit

New Features

  • Introduced configuration loading for the daemon, supporting TOML files, environment variables, and command-line overrides.
  • Added a new asynchronous daemon that listens on a Unix socket and processes comment requests for GitHub pull requests.
  • Provided default values and error handling for required configuration fields.

Documentation

  • Updated design documentation to clarify configuration loading precedence and error handling.
  • Marked configuration loading as complete in the project roadmap.

Tests

  • Added comprehensive behavioural tests for configuration scenarios, including file loading, environment overrides, and error cases.
  • Expanded test coverage to include new configuration features in the automated test suite.

Walkthrough

Introduce configuration loading to the comenqd daemon using the ortho_config crate. Add a new configuration module, update the main function to load configuration at startup, and extend tests and documentation to cover and describe this behaviour. Update dependency declarations, workspace settings, and test infrastructure accordingly.

Changes

File(s) Change Summary
Cargo.toml, crates/comenqd/Cargo.toml Add new dependencies (ortho_config, figment, tempfile, rstest), update workspace members and paths.
crates/comenqd/src/config.rs Add new Config struct and configuration loading logic using ortho_config; provide error handling and tests.
crates/comenqd/src/lib.rs Introduce public config module for configuration functionality.
crates/comenqd/src/main.rs Update main to load configuration at startup, propagate errors, and log loaded paths.
crates/comenqd/src/daemon.rs Add asynchronous daemon tasks: Unix socket listener and queue worker processing GitHub comments.
docs/comenq-design.md, docs/roadmap.md Update documentation to describe configuration loading and mark config loading as complete in roadmap.
tests/cucumber.rs Add ConfigWorld to test runner; run configuration feature tests alongside others.
tests/features/config.feature Add new Cucumber feature file for configuration loading scenarios.
tests/steps/config_steps.rs Add step definitions and world context for configuration testing.
tests/steps/mod.rs Add and re-export config_steps module for test step integration.

Sequence Diagram(s)

sequenceDiagram
    participant Main
    participant Config
    participant OrthoConfig

    Main->>Config: Config::load()
    Config->>OrthoConfig: Load /etc/comenqd/config.toml
    OrthoConfig-->>Config: Return parsed config or error
    Config->>Config: Merge environment variables (COMENQD_)
    Config-->>Main: Return Config struct or error
    Main->>Main: Log daemon start with config paths
Loading
sequenceDiagram
    participant Run as Daemon
    participant Listener Task
    participant Queue Worker
    participant GitHub API

    Run as Daemon->>Listener Task: Bind Unix socket, accept connections
    Listener Task->>Listener Task: Read request, deserialize, serialize
    Listener Task->>Queue Worker: Send serialized request via channel
    Queue Worker->>Queue Worker: Receive request, deserialize
    Queue Worker->>GitHub API: Post comment on PR
    GitHub API-->>Queue Worker: Success or error response
    Queue Worker->>Queue Worker: Commit message or log error
    Queue Worker->>Queue Worker: Wait cooldown period
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~18 minutes

Possibly related PRs

  • Implement workspace scaffolding #1: Initial workspace scaffolding with basic crate structure and stub main functions; foundational for the current daemon configuration and logic additions.

Poem

In the daemon’s heart, a config blooms,
From TOML files and envs it resumes.
With tests that check each path and key,
The roadmap ticks with certainty.
Now Comenqd can start with grace,
Its settings sourced from every place!
🦀📝✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/implement-configuration-loading-from-toml-file

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Use of #[allow(clippy::result_large_err)] is forbidden; use narrowly scoped #[expect(...)] with a reason instead. (link)
  • Use of #[allow(clippy::result_large_err)] is forbidden; use narrowly scoped #[expect(...)] with a reason instead. (link)

General comments:

  • There’s now both a workspace ortho_config dependency and a git:v0.4.0 ortho_config in the root Cargo.toml; unify these to avoid duplicate versions.
  • The docs mention merging CLI arguments in Config::load, but the implementation only handles file and env vars—either update the docs or add CLI support.
  • Consider adding tests for environment variable overrides to verify that COMENQD_-prefixed vars correctly override file values.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- There’s now both a workspace ortho_config dependency and a git:v0.4.0 ortho_config in the root Cargo.toml; unify these to avoid duplicate versions.
- The docs mention merging CLI arguments in Config::load, but the implementation only handles file and env vars—either update the docs or add CLI support.
- Consider adding tests for environment variable overrides to verify that COMENQD_-prefixed vars correctly override file values.

## Individual Comments

### Comment 1
<location> `tests/steps/config_steps.rs:23` </location>
<code_context>
+    result: Option<Result<Config, ortho_config::OrthoError>>,
+}
+
+#[given(regex = r#"^a configuration file with token \"(.+)\"$"#)]
+fn config_file_with_token(world: &mut ConfigWorld, token: String) {
+    let dir = TempDir::new().expect("create temp dir");
+    let path = dir.path().join("config.toml");
</code_context>

<issue_to_address>
Consider adding a step to test environment variable overrides in the Cucumber tests.

Please add tests that set COMENQD_* environment variables and confirm they override file values as intended.

Suggested implementation:

```rust
use comenqd::config::Config;
use std::env;
use temp_env::with_var;

#[derive(Debug, Default, World)]
pub struct ConfigWorld {
    dir: Option<TempDir>,
    path: Option<PathBuf>,
    result: Option<Result<Config, ortho_config::OrthoError>>,
    env_token: Option<String>,
}

```

```rust
#[given(regex = r#"^a configuration file with token \"(.+)\"$"#)]
fn config_file_with_token(world: &mut ConfigWorld, token: String) {
    let dir = TempDir::new().expect("create temp dir");
    let path = dir.path().join("config.toml");
    fs::write(&path, format!("github_token='{token}'")).expect("write file");
    world.dir = Some(dir);
    world.path = Some(path);
}

#[given(regex = r#"^the environment variable COMENQD_GITHUB_TOKEN is set to \"(.+)\"$"#)]
fn set_env_github_token(world: &mut ConfigWorld, token: String) {
    world.env_token = Some(token);
}

#[when("the configuration is loaded with environment variable overrides")]
fn load_config_with_env_override(world: &mut ConfigWorld) {
    let path = world.path.as_ref().expect("config file path");
    let env_token = world.env_token.clone().expect("env token set");
    with_var("COMENQD_GITHUB_TOKEN", Some(env_token), || {
        let config = Config::from_path(path);
        world.result = Some(config);
    });
}

#[then(regex = r#"^the github_token value should be \"(.+)\"$"#)]
fn assert_github_token(world: &mut ConfigWorld, expected: String) {
    let config = world.result.as_ref().expect("config loaded").as_ref().expect("config ok");
    assert_eq!(config.github_token, expected);
}

```
</issue_to_address>

### Comment 2
<location> `tests/features/config.feature:1` </location>
<code_context>
+Feature: Daemon configuration
+
+  Scenario: loading a valid configuration file
</code_context>

<issue_to_address>
Consider replacing the Gherkin-based feature tests with plain Rust unit tests in the config module.

These scenarios can be covered with plain Rust tests and remove the extra Gherkin‐DSL dependency. For example, in your `config` module add:

```rust
// src/config.rs
use std::{fs, path::Path};
use serde::Deserialize;

#[derive(Deserialize)]
pub struct Config {
    pub token: String,
}

pub fn load_config<P: AsRef<Path>>(path: P) -> anyhow::Result<Config> {
    let contents = fs::read_to_string(path)?;
    Ok(toml::from_str(&contents)?)
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::NamedTempFile;
    use std::io::Write;

    #[test]
    fn valid_config_file() {
        let mut file = NamedTempFile::new().unwrap();
        write!(file, r#"token = "abc""#).unwrap();

        let cfg = load_config(file.path()).unwrap();
        assert_eq!(cfg.token, "abc");
    }

    #[test]
    fn missing_config_file() {
        let path = file.path().with_file_name("does_not_exist.toml");
        let err = load_config(&path).unwrap_err();
        assert!(err.to_string().contains("No such file"));
    }
}
```

Steps to apply:

1. Remove the `.feature` file and any cucumber/Gherkin crate.
2. Add the above Rust tests under `#[cfg(test)]` in your `config` module.
3. Ensure `tempfile = "3"` (or latest) is in your `[dev-dependencies]`.
4. Run `cargo test` to verify coverage.

This keeps all scenarios covered, removes the DSL parser, and keeps your crate purely Rust-centric.
</issue_to_address>

### Comment 3
<location> `tests/steps/config_steps.rs:1` </location>
<code_context>
+//! Behavioural steps for daemon configuration loading.
+#![allow(
+    clippy::expect_used,
</code_context>

<issue_to_address>
Consider replacing the Cucumber-based step definitions with simple Rust unit tests using #[test] functions.

Consider dropping the Cucumber layer entirely for these two simple cases and using plain Rust `#[test]`s instead. You can still use `tempfile::TempDir` and call `Config::from_file` directly. For example:

```rust
// In your src/lib.rs or src/config.rs
#[cfg(test)]
mod tests {
    use super::Config;
    use tempfile::TempDir;
    use std::{fs, path::PathBuf};

    #[test]
    fn load_config_success() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().join("config.toml");
        fs::write(&path, "github_token='tok123'").unwrap();

        let cfg = Config::from_file(&path).expect("should parse");
        assert_eq!(cfg.github_token, "tok123");
    }

    #[test]
    fn load_config_missing_file_fails() {
        let bad = PathBuf::from("/does/not/exist.toml");
        let err = Config::from_file(&bad).expect_err("should fail");
        // optionally assert on error kind:
        // assert!(matches!(err, ortho_config::OrthoError::Io(_)));
    }
}
```

Steps to migrate:

1. Remove the `ConfigWorld`, all `#[given]`, `#[when]`, `#[then]` functions and the `cucumber` dependency.
2. Create a `tests` module under `#[cfg(test)]` and add two `#[test]` functions as above.
3. Use `tempfile::TempDir` to isolate your temp files, call `Config::from_file`, and assert on the `Ok`/`Err` result.

This eliminates the extra abstraction layer while keeping behavior coverage intact.
</issue_to_address>

### Comment 4
<location> `crates/comenqd/src/config.rs:37` </location>
<code_context>
+    pub const DEFAULT_PATH: &'static str = "/etc/comenqd/config.toml";
+
+    /// Load the configuration from `DEFAULT_PATH`.
+    #[allow(clippy::result_large_err)]
+    pub fn load() -> Result<Self, ortho_config::OrthoError> {
+        Self::from_file(Path::new(Self::DEFAULT_PATH))
</code_context>

<issue_to_address>
Use of `#[allow(clippy::result_large_err)]` is forbidden; use narrowly scoped `#[expect(...)]` with a reason instead.

The review instructions explicitly forbid the use of `#[allow]` for lint suppression. Please replace this with a narrowly scoped `#[expect(clippy::result_large_err, reason = "...")]` and provide a justification, or remove the suppression if not strictly necessary.
</issue_to_address>

### Comment 5
<location> `crates/comenqd/src/config.rs:37` </location>
<code_context>
+    pub const DEFAULT_PATH: &'static str = "/etc/comenqd/config.toml";
+
+    /// Load the configuration from `DEFAULT_PATH`.
+    #[allow(clippy::result_large_err)]
+    pub fn load() -> Result<Self, ortho_config::OrthoError> {
+        Self::from_file(Path::new(Self::DEFAULT_PATH))
</code_context>

<issue_to_address>
Use of `#[allow(clippy::result_large_err)]` is forbidden; use narrowly scoped `#[expect(...)]` with a reason instead.

The review instructions explicitly forbid the use of `#[allow]` for lint suppression. Please replace this with a narrowly scoped `#[expect(clippy::result_large_err, reason = "...")]` and provide a justification, or remove the suppression if not strictly necessary.
</issue_to_address>

### Comment 6
<location> `docs/comenq-design.md:507` </location>
<code_context>
 | cooldown_period_seconds | u64     | The cooling-off period in seconds after each comment post.                                | 900                     |

+Configuration is loaded using the `ortho_config` crate. The daemon calls
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments.
+
</code_context>

<issue_to_address>
This line, as part of the paragraph, exceeds 80 columns and should be wrapped.

Ensure that all lines in paragraphs and bullet points are wrapped to 80 columns or fewer.
</issue_to_address>

### Comment 7
<location> `docs/comenq-design.md:508` </location>
<code_context>

+Configuration is loaded using the `ortho_config` crate. The daemon calls
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments.
+
 Robust logging is non-negotiable for a background process. The `tracing` crate
</code_context>

<issue_to_address>
This line, as part of the paragraph, exceeds 80 columns and should be wrapped.

Please wrap this line to comply with the 80 column limit for documentation text.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread tests/steps/config_steps.rs
Comment thread tests/features/config.feature
Comment thread tests/steps/config_steps.rs
Comment thread crates/comenqd/src/config.rs Outdated
Comment thread crates/comenqd/src/config.rs Outdated
Comment thread docs/comenq-design.md
Comment thread docs/comenq-design.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🔭 Outside diff range comments (1)
tests/features/config.feature (1)

1-12: Well-structured BDD scenarios.

The feature file covers essential configuration loading scenarios with clear Given-When-Then structure. Both success and failure paths are tested appropriately.

Consider adding scenarios for:

  • Environment variable overrides
  • Invalid TOML syntax handling
  • Missing required fields (github_token)
  • Default value validation
Scenario: environment variable override
  Given a configuration file with token "file_token"
  And environment variable COMENQD_GITHUB_TOKEN is "env_token"  
  When the config is loaded
  Then github token is "env_token"

Scenario: missing required field
  Given a configuration file without github_token
  When the config is loaded
  Then config loading fails
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5704251 and db8d38f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Cargo.toml (2 hunks)
  • crates/comenqd/Cargo.toml (2 hunks)
  • crates/comenqd/src/config.rs (1 hunks)
  • crates/comenqd/src/lib.rs (1 hunks)
  • crates/comenqd/src/main.rs (1 hunks)
  • docs/comenq-design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • tests/cucumber.rs (1 hunks)
  • tests/features/config.feature (1 hunks)
  • tests/steps/config_steps.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comments must use en-GB-oxendict spelling and grammar.
Function documentation must include clear examples.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Clippy warnings MUST be disallowed.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use predicate functions for conditional criteria with more than two branches.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Fix any warnings emitted during tests in the code itself rather than silencing them.

Files:

  • crates/comenqd/src/lib.rs
  • tests/steps/mod.rs
  • tests/cucumber.rs
  • crates/comenqd/src/main.rs
  • tests/steps/config_steps.rs
  • crates/comenqd/src/config.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • crates/comenqd/src/lib.rs
  • tests/steps/mod.rs
  • tests/cucumber.rs
  • crates/comenqd/src/main.rs
  • tests/steps/config_steps.rs
  • crates/comenqd/src/config.rs
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.

Files:

  • Cargo.toml
**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.md: Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Markdown Mermaid diagrams using by running make nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks must be wrapped at 120 columns.
Tables and headings must not be wrapped.
Use dashes (-) for list bullets.
Documentation must use en-GB-oxendict spelling and grammar (with the exception of "license" which is to be left unchanged for community consistency).

Files:

  • docs/roadmap.md
  • docs/comenq-design.md

⚙️ CodeRabbit Configuration File

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -our) spelling and grammar
  • Paragraphs and bullets must be wrapped to 80 columns, except where a long URL would prevent this (in which case, silence MD013 for that line)
  • Code blocks should be wrapped to 120 columns.
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/roadmap.md
  • docs/comenq-design.md
docs/**/*.md

📄 CodeRabbit Inference Engine (AGENTS.md)

docs/**/*.md: Use the markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state. Ensure the documentation remains accurate and current.

Files:

  • docs/roadmap.md
  • docs/comenq-design.md
🧬 Code Graph Analysis (1)
crates/comenqd/src/main.rs (1)
crates/comenqd/src/config.rs (1)
  • load (38-40)
🔇 Additional comments (14)
crates/comenqd/Cargo.toml (1)

21-23: Justify figment dependency

figment is introduced but is not referenced in the provided code. Confirm it is used in config.rs; if not, drop it to keep the dependency set minimal.

crates/comenqd/src/main.rs (2)

7-8: LGTM!

Clean module organisation and import structure.


13-13: Excellent structured logging.

The logging statement properly uses structured fields for socket and queue paths, which will aid in debugging and monitoring.

tests/cucumber.rs (2)

3-3: LGTM!

Clean integration of ConfigWorld following the established import pattern.


9-10: LGTM!

Proper addition of configuration tests to the concurrent test execution. The pattern is consistent with existing test organisation.

crates/comenqd/src/config.rs (6)

1-5: Excellent module documentation.

The module-level comment clearly explains the purpose, configuration file location, and environment variable support. Follows coding guidelines perfectly.


12-22: Well-structured configuration struct.

Clean field organisation with appropriate defaults and clear documentation. The serde defaults integration is properly implemented.


37-40: LGTM!

Clean delegation to the file-based loader with proper error propagation.


52-53: Excellent environment variable integration.

The environment variable override with COMENQD_ prefix and __ splitting for nested keys follows configuration best practices.


64-77: Comprehensive test coverage.

The unit test covers all configuration fields and validates the complete loading workflow. Well-structured with proper setup and assertions.


79-84: LGTM!

Proper error case testing ensures robust error handling behaviour.

tests/steps/config_steps.rs (3)

9-14: LGTM!

Import organisation is clear and follows good practices.


16-21: LGTM!

The ConfigWorld struct is well-designed for holding test state across Cucumber steps.


23-57: LGTM!

The test step functions are well-implemented with proper error handling using .expect() and appropriate pattern matching for assertions. The functions correctly exercise both success and failure scenarios for configuration loading.

Comment thread Cargo.toml
Comment thread Cargo.toml
Comment thread crates/comenqd/Cargo.toml Outdated
Comment thread crates/comenqd/Cargo.toml
Comment thread crates/comenqd/Cargo.toml Outdated
Comment thread crates/comenqd/src/main.rs Outdated
Comment thread docs/comenq-design.md Outdated
Comment thread docs/roadmap.md Outdated
Comment thread tests/steps/config_steps.rs Outdated
Comment thread tests/steps/mod.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (11)
Cargo.toml (2)

20-20: Pin tempfile to an explicit version range.

The guidelines mandate explicit version ranges. "3" expands to ^3.0 and will float to any 3.x.y release, which harms reproducibility.

-tempfile = "3"
+tempfile = "3.10"  # latest 3.x at time of writing; update as new patch versions release

44-44: Promote ortho_config to workspace-scoped dependency.

ortho_config is added here under [workspace.dependencies], yet each crate still lists it individually. Remove the per-crate entries and rely on the workspace stanza to avoid version drift.

crates/comenqd/Cargo.toml (3)

6-8: Delete redundant [lib] section unless deviating from default.

src/lib.rs is Cargo's default path. The explicit stanza adds noise without value.

-[lib]
-path = "src/lib.rs"

21-21: Rely on workspace for ortho_config; remove local declaration.

The root Cargo.toml already defines ortho_config in [workspace.dependencies]. Duplicating it here risks version skew.


25-27: Align dev-dependency versions with workspace policy.

rstest = "0.18" and tempfile = "3" float across patch versions. Pin explicit minor & patch numbers for deterministic builds, mirroring the root guidelines.

tests/features/config.feature (1)

1-18: Replace the Gherkin-based feature tests with plain Rust unit tests in the config module.

These scenarios can be covered with plain Rust tests and remove the extra Gherkin‐DSL dependency. The config module already has unit tests that cover similar scenarios more directly.

crates/comenqd/src/config.rs (4)

56-56: Replace #[allow] with narrowly scoped #[expect] with reason.

The coding guidelines explicitly forbid #[allow] and require narrowly scoped #[expect] with reasons instead.

-#[allow(clippy::result_large_err)]
+#[expect(clippy::result_large_err, reason = "OrthoError is large but rarely returned")]

64-64: Replace #[allow] with narrowly scoped #[expect] with reason.

The coding guidelines explicitly forbid #[allow] and require narrowly scoped #[expect] with reasons instead.

-#[allow(clippy::result_large_err)]
+#[expect(clippy::result_large_err, reason = "OrthoError is large but rarely returned")]

69-69: Replace #[allow] with narrowly scoped #[expect] with reason.

The coding guidelines explicitly forbid #[allow] and require narrowly scoped #[expect] with reasons instead.

-#[allow(clippy::result_large_err)]
+#[expect(clippy::result_large_err, reason = "OrthoError is large but rarely returned")]

42-48: Extract default paths to constants.

Move the default paths to module-level constants for better maintainability and to avoid function call overhead.

+const DEFAULT_SOCKET_PATH: &str = "/run/comenq/comenq.sock";
+const DEFAULT_QUEUE_PATH: &str = "/var/lib/comenq/queue";
+
 fn default_socket_path() -> PathBuf {
-    PathBuf::from("/run/comenq/comenq.sock")
+    PathBuf::from(DEFAULT_SOCKET_PATH)
 }

 fn default_queue_path() -> PathBuf {
-    PathBuf::from("/var/lib/comenq/queue")  
+    PathBuf::from(DEFAULT_QUEUE_PATH)
 }
tests/steps/config_steps.rs (1)

2-7: Replace file-wide #[allow] with narrowly scoped #[expect] attributes.

The coding guidelines explicitly forbid #[allow] and require narrowly scoped #[expect] with reasons instead. File-wide lint suppressions are not permitted.

Apply #[expect] attributes directly to the specific functions where these lints occur instead.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db8d38f and 033bc03.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • Cargo.toml (2 hunks)
  • crates/comenqd/Cargo.toml (2 hunks)
  • crates/comenqd/src/config.rs (1 hunks)
  • tests/features/config.feature (1 hunks)
  • tests/steps/config_steps.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
Cargo.toml

📄 CodeRabbit Inference Engine (AGENTS.md)

Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.

Files:

  • Cargo.toml
**/*.rs

📄 CodeRabbit Inference Engine (AGENTS.md)

**/*.rs: Comments must use en-GB-oxendict spelling and grammar.
Function documentation must include clear examples.
Every module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Place function attributes after doc comments.
Do not use return in single-line functions.
Prefer immutable data and avoid unnecessary mut bindings.
Handle errors with the Result type instead of panicking where feasible.
Avoid unsafe code unless absolutely necessary and document any usage clearly.
Clippy warnings MUST be disallowed.
Lints must not be silenced except as a last resort.
Lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow.
Prefer .expect() over .unwrap().
Use predicate functions for conditional criteria with more than two branches.
Where a function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a function has too many parameters, group related parameters in meaningfully named structs.
Where a function is returning a large error consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new functionality. Run both before and after making any change.
Fix any warnings emitted during tests in the code itself rather than silencing them.

Files:

  • crates/comenqd/src/config.rs
  • tests/steps/config_steps.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • crates/comenqd/src/config.rs
  • tests/steps/config_steps.rs
🧬 Code Graph Analysis (2)
crates/comenqd/src/config.rs (1)
tests/steps/cli_steps.rs (1)
  • socket_path (47-53)
tests/steps/config_steps.rs (1)
crates/comenqd/src/config.rs (1)
  • from_file (65-67)

Comment thread crates/comenqd/src/config.rs Outdated
Comment thread crates/comenqd/src/config.rs Outdated
Comment on lines +134 to +140
unsafe {
std::env::set_var("COMENQD_SOCKET_PATH", "/tmp/override.sock");
}
let cfg = Config::from_file(&path).unwrap();
unsafe {
std::env::remove_var("COMENQD_SOCKET_PATH");
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace unsafe environment variable manipulation with safe alternatives.

Use safe environment variable functions. The std::env::set_var and std::env::remove_var functions are not unsafe.

-        unsafe {
-            std::env::set_var("COMENQD_SOCKET_PATH", "/tmp/override.sock");
-        }
+        std::env::set_var("COMENQD_SOCKET_PATH", "/tmp/override.sock");
         let cfg = Config::from_file(&path).unwrap();
-        unsafe {
-            std::env::remove_var("COMENQD_SOCKET_PATH");
-        }
+        std::env::remove_var("COMENQD_SOCKET_PATH");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
unsafe {
std::env::set_var("COMENQD_SOCKET_PATH", "/tmp/override.sock");
}
let cfg = Config::from_file(&path).unwrap();
unsafe {
std::env::remove_var("COMENQD_SOCKET_PATH");
}
std::env::set_var("COMENQD_SOCKET_PATH", "/tmp/override.sock");
let cfg = Config::from_file(&path).unwrap();
std::env::remove_var("COMENQD_SOCKET_PATH");
🤖 Prompt for AI Agents
In crates/comenqd/src/config.rs around lines 134 to 140, the code uses unsafe
blocks around std::env::set_var and std::env::remove_var calls, which is
unnecessary because these functions are safe. Remove the unsafe blocks wrapping
these environment variable manipulations to fix the issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this now been resolved?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos No, the issue has not been fully resolved. Looking at the current code, there are still unsafe blocks around environment variable manipulation in the test functions:

The code still contains unsafe blocks in the test functions, particularly in loads_from_file and env_vars_override_file. However, I notice you've added an EnvVarGuard helper struct that handles environment variables safely with proper restoration. Consider using this pattern consistently across all tests instead of the direct unsafe calls.


🏁 Script executed:

# Check for remaining unsafe blocks around environment variable calls
rg -A 2 -B 2 "unsafe.*env::" crates/comenqd/src/config.rs

Length of output: 286


🏁 Script executed:

# Check for all unsafe blocks in the config.rs file
rg -n "unsafe" crates/comenqd/src/config.rs

# Also check for any direct env::set_var or env::remove_var calls
rg -n "env::(set_var|remove_var)" crates/comenqd/src/config.rs

Length of output: 540


@leynos No, the issue has not been resolved. The unsafe blocks are still present in multiple locations:

  1. Line 126-127: EnvVarGuard::set function has an unnecessary unsafe block around std::env::set_var
  2. Lines 139-140: EnvVarGuard's Drop implementation has unnecessary unsafe blocks around both std::env::set_var and std::env::remove_var
  3. Line 146-147: remove_env function has an unnecessary unsafe block around std::env::remove_var

All of these unsafe blocks should be removed since std::env::set_var and std::env::remove_var are safe functions. The unsafe blocks are completely unnecessary and violate the coding guidelines that state "Avoid unsafe unless absolutely necessary."

Remove all the unsafe blocks wrapping these environment variable function calls to resolve the issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this now been resolved?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos No, the issue has not been resolved. Looking at the current code, there are still unnecessary unsafe blocks around environment variable manipulation in several locations:

The unsafe blocks are still present and need to be removed since std::env::set_var and std::env::remove_var are safe functions. This violates your coding guidelines which state "Avoid unsafe unless absolutely necessary."


🏁 Script executed:

# Check for all unsafe blocks in the config.rs file
rg -n "unsafe" crates/comenqd/src/config.rs

# Show the context around each unsafe block
rg -A 3 -B 1 "unsafe" crates/comenqd/src/config.rs

Length of output: 751


@leynos No, the issue has NOT been resolved. The script output confirms there are still 4 unsafe blocks in the code:

Lines with unnecessary unsafe blocks:

  • Line 126: unsafe { std::env::set_var(key, val); }
  • Line 139: unsafe { std::env::set_var(&self.key, v) }
  • Line 140: unsafe { std::env::remove_var(&self.key) }
  • Line 146: unsafe { std::env::remove_var(key); }

These unsafe blocks must be removed because:

  1. std::env::set_var and std::env::remove_var are safe functions in Rust
  2. The unsafe blocks are completely unnecessary
  3. This violates your coding guidelines: "Avoid unsafe unless absolutely necessary"

Required fixes:

// Remove unsafe blocks from all these locations:
std::env::set_var(key, val);                    // Line 126
std::env::set_var(&self.key, v);                // Line 139  
std::env::remove_var(&self.key);                // Line 140
std::env::remove_var(key);                      // Line 146

The issue remains unresolved and needs to be fixed by removing all four unsafe blocks.


🧬 Code Graph Analysis Results

Relevant snippets from tests/steps/config_steps.rs (lines 18-28, 32-37, 40-44, 158-162):

fn set(key: &str, value: &str) -> Self {
    let original = std::env::var(key).ok();
    // Safety: serial_test ensures these manipulations are single-threaded.
    unsafe {
        std::env::set_var(key, value);
    }
    Self {
        key: key.to_string(),
        original,
    }
}

fn drop(&mut self) {
    match &self.original {
        Some(val) => unsafe { std::env::set_var(&self.key, val) },
        None => unsafe { std::env::remove_var(&self.key) },
    }
}

fn remove_env(key: &str) {
    unsafe {
        std::env::remove_var(key);
    }
}

fn drop(&mut self) {
    if let Some(_guard) = self.env_guard.take() {
        // dropping the guard restores the previous state
    }
}

These snippets show environment variable management utilities used in tests, including setting, removing, and restoring environment variables safely, which is relevant to how environment variables override config file values in config.rs.


Relevant snippet from tests/steps/cli_steps.rs (lines 47-53):

fn socket_path(world: &mut CliWorld, path: String) {
    if let Some(mut args) = world.args.take() {
        args.push(OsString::from("--socket"));
        args.push(OsString::from(path));
        world.args = Some(args);
    }
}

This snippet demonstrates how command-line arguments for socket path overrides are added in CLI test steps, which relates to the CLI overrides handled in config.rs.


These snippets provide context on environment variable and CLI argument handling relevant to the configuration loading and overriding logic in crates/comenqd/src/config.rs.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai new GitHub issue, please

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos I'll create a GitHub issue to track the removal of unnecessary unsafe blocks in the configuration module.


🏁 Script executed:

#!/bin/bash
# Create GitHub issue for removing unnecessary unsafe blocks
gh issue create \
  --title "Remove unnecessary unsafe blocks around environment variable calls in config.rs" \
  --body "## Description

There are 4 unnecessary unsafe blocks in \`crates/comenqd/src/config.rs\` that wrap calls to \`std::env::set_var\` and \`std::env::remove_var\`. These functions are safe in Rust and do not require unsafe blocks.

## Problem

The unsafe blocks violate the coding guidelines which state \"Avoid \`unsafe\` unless absolutely necessary.\"

## Locations

- **Line 126**: \`unsafe { std::env::set_var(key, val); }\`
- **Line 139**: \`unsafe { std::env::set_var(&self.key, v) }\`
- **Line 140**: \`unsafe { std::env::remove_var(&self.key) }\`
- **Line 146**: \`unsafe { std::env::remove_var(key); }\`

## Solution

Remove all unsafe blocks wrapping these environment variable function calls since they are safe functions.

## References

- Pull Request: https://github.com/leynos/comenq/pull/4
- Comment: https://github.com/leynos/comenq/pull/4#discussion_r2232951469
- Requested by: @leynos" \
  --assignee leynos

Length of output: 1059


@leynosGitHub issue created successfully!

Issue #5: Remove unnecessary unsafe blocks around environment variable calls in config.rs

The issue has been created with:

  • Clear description of the 4 locations where unsafe blocks need to be removed
  • Reference to this PR and comment thread
  • Assigned to you (@leynos) as requested
  • Proper documentation of the problem and solution

The issue is now tracked for follow-up action to remove the unnecessary unsafe blocks around std::env::set_var and std::env::remove_var calls in the configuration module.


🧬 Code Graph Analysis Results

Relevant snippets from tests/steps/config_steps.rs lines 18-28, 32-37, and 40-44:

fn set(key: &str, value: &str) -> Self {
    let original = std::env::var(key).ok();
    // Safety: serial_test ensures these manipulations are single-threaded.
    unsafe {
        std::env::set_var(key, value);
    }
    Self {
        key: key.to_string(),
        original,
    }
}

fn drop(&mut self) {
    match &self.original {
        Some(val) => unsafe { std::env::set_var(&self.key, val) },
        None => unsafe { std::env::remove_var(&self.key) },
    }
}

fn remove_env(key: &str) {
    unsafe {
        std::env::remove_var(key);
    }
}

These functions provide environment variable management utilities used in tests to set, restore, and remove environment variables safely.


Relevant snippet from tests/steps/cli_steps.rs lines 47-53:

fn socket_path(world: &mut CliWorld, path: String) {
    if let Some(mut args) = world.args.take() {
        args.push(OsString::from("--socket"));
        args.push(OsString::from(path));
        world.args = Some(args);
    }
}

This function modifies CLI arguments in test scenarios to override the socket path configuration.


These snippets relate to environment variable handling and CLI argument manipulation, which are directly relevant to the configuration loading and overriding behavior implemented in crates/comenqd/src/config.rs.

Comment thread tests/steps/config_steps.rs Outdated
Comment on lines +40 to +42
unsafe {
std::env::set_var(&key, &value);
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jul 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unnecessary unsafe blocks from environment variable operations.

Environment variable functions std::env::set_var and std::env::remove_var are not unsafe operations.

-    unsafe {
-        std::env::set_var(&key, &value);
-    }
+    std::env::set_var(&key, &value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
unsafe {
std::env::set_var(&key, &value);
}
std::env::set_var(&key, &value);
🤖 Prompt for AI Agents
In tests/steps/config_steps.rs around lines 40 to 42, remove the unsafe block
surrounding the call to std::env::set_var because this function is safe to use
and does not require unsafe. Simply call std::env::set_var(&key, &value)
directly without wrapping it in unsafe.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this now been resolved?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos No, this has not been resolved. The file still contains multiple unnecessary unsafe blocks around environment variable operations:

The std::env::set_var and std::env::remove_var functions are safe operations and do not require unsafe blocks. Remove the unsafe wrappers from:

  1. EnvVarGuard::set method (lines ~31-33)
  2. Drop implementation for EnvVarGuard (lines ~38-40)
  3. remove_env function (lines ~43-46)
  4. set_env_var function in the diff

Simply call these functions directly without the unsafe blocks.


🏁 Script executed:

#!/bin/bash
# Check for unsafe blocks around environment variable operations
rg -n "unsafe.*env::(set_var|remove_var)" tests/steps/config_steps.rs

Length of output: 212


@leynos No, the issue has not been resolved. The script confirms that unsafe blocks are still present around environment variable operations on lines 34-35, and examining the full file shows additional unsafe blocks that need to be removed:

Lines that still contain unnecessary unsafe blocks:

  1. Lines 31-33 in EnvVarGuard::set:

    unsafe {
        std::env::set_var(key, value);
    }
  2. Lines 34-35 in Drop implementation:

    Some(val) => unsafe { std::env::set_var(&self.key, val) },
    None => unsafe { std::env::remove_var(&self.key) },
  3. Lines 43-46 in remove_env function:

    unsafe {
        std::env::remove_var(key);
    }

All of these unsafe blocks should be removed since std::env::set_var and std::env::remove_var are safe operations in Rust. Simply call these functions directly without the unsafe wrappers.


🧬 Code Graph Analysis Results

Relevant snippets from crates/comenqd/src/config.rs (lines 79-81, 124-149)

pub fn from_file(path: &Path) -> Result<Self, ortho_config::OrthoError> {
    Self::from_file_with_cli(path, &CliArgs::default())
}

fn set(key: &str, val: &str) -> Self {
    let original = std::env::var(key).ok();
    unsafe {
        std::env::set_var(key, val);
    }
    Self {
        key: key.to_string(),
        original,
    }
}

fn drop(&mut self) {
    match &self.original {
        Some(v) => unsafe { std::env::set_var(&self.key, v) },
        None => unsafe { std::env::remove_var(&self.key) },
    }
}

fn remove_env(key: &str) {
    unsafe {
        std::env::remove_var(key);
    }
}

Summary:

  • from_file loads a configuration from a file path, returning a Result<Config, OrthoError>.
  • set and drop are methods related to an RAII guard for environment variables, managing setting and restoring environment variables safely.
  • remove_env removes an environment variable unsafely.
  • These functions are used in the test steps to manipulate environment variables and load configuration files.

Relevant snippet from tests/steps/cli_steps.rs (lines 47-53)

fn socket_path(world: &mut CliWorld, path: String) {
    if let Some(mut args) = world.args.take() {
        args.push(OsString::from("--socket"));
        args.push(OsString::from(path));
        world.args = Some(args);
    }
}

Summary:

  • This function modifies CLI arguments to include a socket path.
  • While not directly related to config_steps.rs, it shows how socket paths might be set in CLI tests, potentially relevant for understanding socket path handling in configuration tests.

These snippets provide context on how configuration files are loaded, how environment variables are managed during tests, and how socket paths might be set, all of which relate to the behavior tested in tests/steps/config_steps.rs.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this now been resolved?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@leynos No, the issue has not been resolved. The file still contains multiple unnecessary unsafe blocks around environment variable operations.

Looking at the current file content, I can see the following locations still have unsafe blocks that need to be removed:

  1. Lines 31-33 in EnvVarGuard::set:

    unsafe {
        std::env::set_var(key, value);
    }
  2. Lines 38-40 in Drop implementation for EnvVarGuard:

    Some(val) => unsafe { std::env::set_var(&self.key, val) },
    None => unsafe { std::env::remove_var(&self.key) },
  3. Lines 43-46 in remove_env function:

    unsafe {
        std::env::remove_var(key);
    }

Remove all these unsafe wrappers since std::env::set_var and std::env::remove_var are safe operations in Rust. Simply call these functions directly without the unsafe blocks.


🏁 Script executed:

#!/bin/bash
# Check current state of unsafe blocks around environment variable operations
rg -n "unsafe.*\{" tests/steps/config_steps.rs -A 2 | grep -E "(set_var|remove_var)"

Length of output: 311


@leynos Partial progress has been made, but the issue is not fully resolved.

The script results show that some unsafe blocks have been removed:

  • EnvVarGuard::set method (line 22) - fixed
  • remove_env function (line 42) - fixed

However, two unsafe blocks still remain in the Drop implementation for EnvVarGuard:

  • Line 34: Some(val) => unsafe { std::env::set_var(&self.key, val) },
  • Line 35: None => unsafe { std::env::remove_var(&self.key) },

These two lines still need the unsafe blocks removed:

// Should be:
Some(val) => std::env::set_var(&self.key, val),
None => std::env::remove_var(&self.key),

Once these final two unsafe wrappers are removed from the Drop implementation, the issue will be fully resolved.

Comment thread tests/steps/config_steps.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 26, 2025

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 26, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (5)
Cargo.toml (1)

44-44: Remove individual ortho_config entries from crate Cargo.toml files.

The ortho_config dependency is declared under [workspace.dependencies] but individual crates may still list it. Remove ortho_config entries from all individual crate Cargo.toml files to prevent version drift and ensure consistency.

docs/comenq-design.md (1)

506-512: Remove incorrect CLI argument claim from configuration documentation.

The documentation incorrectly states that configuration includes "any supplied CLI arguments," but the daemon's Config::load() only merges from config file and environment variables. Remove the CLI arguments reference to match the actual implementation.

crates/comenqd/src/config.rs (1)

123-149: Remove unsafe blocks from environment variable operations.

Environment variable functions are safe operations and don't require unsafe blocks. This duplicates previous review feedback but the issue persists.

Apply this diff to remove unnecessary unsafe blocks:

     impl EnvVarGuard {
         fn set(key: &str, val: &str) -> Self {
             let original = std::env::var(key).ok();
-            unsafe {
-                std::env::set_var(key, val);
-            }
+            std::env::set_var(key, val);
             Self {
     impl Drop for EnvVarGuard {
         fn drop(&mut self) {
             match &self.original {
-                Some(v) => unsafe { std::env::set_var(&self.key, v) },
-                None => unsafe { std::env::remove_var(&self.key) },
+                Some(v) => std::env::set_var(&self.key, v),
+                None => std::env::remove_var(&self.key),
             }
         }
     }
     fn remove_env(key: &str) {
-        unsafe {
-            std::env::remove_var(key);
-        }
+        std::env::remove_var(key);
     }
tests/steps/config_steps.rs (2)

17-38: Remove unsafe blocks from environment variable operations.

Environment variable functions don't require unsafe blocks. This issue was flagged in past reviews but persists.

Apply this diff to remove unnecessary unsafe blocks:

     impl EnvVarGuard {
         fn set(key: &str, value: &str) -> Self {
             let original = std::env::var(key).ok();
-            // Safety: serial_test ensures these manipulations are single-threaded.
-            unsafe {
-                std::env::set_var(key, value);
-            }
+            std::env::set_var(key, value);
             Self {
     impl Drop for EnvVarGuard {
         fn drop(&mut self) {
             match &self.original {
-                Some(val) => unsafe { std::env::set_var(&self.key, val) },
-                None => unsafe { std::env::remove_var(&self.key) },
+                Some(val) => std::env::set_var(&self.key, val),
+                None => std::env::remove_var(&self.key),
             }
         }
     }

40-44: Remove unsafe block from environment variable operation.

Apply this diff:

     fn remove_env(key: &str) {
-        unsafe {
-            std::env::remove_var(key);
-        }
+        std::env::remove_var(key);
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4036b6 and 8d7d01d.

📒 Files selected for processing (8)
  • Cargo.toml (2 hunks)
  • crates/comenqd/src/config.rs (1 hunks)
  • crates/comenqd/src/daemon.rs (1 hunks)
  • crates/comenqd/src/lib.rs (1 hunks)
  • crates/comenqd/src/main.rs (1 hunks)
  • docs/comenq-design.md (2 hunks)
  • tests/steps/config_steps.rs (1 hunks)
  • tests/steps/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

⚙️ CodeRabbit Configuration File

**/*.rs: * Seek to keep the cyclomatic complexity of functions no more than 12.

  • Adhere to single responsibility and CQRS

  • Place function attributes after doc comments.

  • Do not use return in single-line functions.

  • Move conditionals with >2 branches into a predicate function.

  • Avoid unsafe unless absolutely necessary.

  • Every module must begin with a //! doc comment that explains the module's purpose and utility.

  • Comments and docs must follow en-GB-oxendict (-ize / -our) spelling and grammar

  • Lints must not be silenced except as a last resort.

    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.

  • Replace duplicated tests with #[rstest(...)] parameterised cases.

  • Prefer mockall for mocks/stubs.

  • Prefer .expect() over .unwrap()

  • Ensure that any API or behavioural changes are reflected in the documentation in docs/

  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/

  • Files must not exceed 400 lines in length

    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.

Files:

  • tests/steps/mod.rs
  • crates/comenqd/src/main.rs
  • crates/comenqd/src/daemon.rs
  • crates/comenqd/src/config.rs
  • tests/steps/config_steps.rs
  • crates/comenqd/src/lib.rs
**/*.md

⚙️ CodeRabbit Configuration File

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -our) spelling and grammar
  • Paragraphs and bullets must be wrapped to 80 columns, except where a long URL would prevent this (in which case, silence MD013 for that line)
  • Code blocks should be wrapped to 120 columns.
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/comenq-design.md
🧬 Code Graph Analysis (3)
crates/comenqd/src/main.rs (2)
crates/comenqd/src/daemon.rs (1)
  • run (18-36)
crates/comenqd/src/config.rs (1)
  • load (71-74)
crates/comenqd/src/config.rs (2)
tests/steps/config_steps.rs (4)
  • set (18-28)
  • drop (32-37)
  • drop (158-162)
  • remove_env (40-44)
tests/steps/cli_steps.rs (1)
  • socket_path (47-53)
tests/steps/config_steps.rs (2)
crates/comenqd/src/config.rs (4)
  • set (124-133)
  • drop (137-142)
  • remove_env (145-149)
  • from_file (79-81)
tests/steps/cli_steps.rs (1)
  • socket_path (47-53)
🔇 Additional comments (19)
Cargo.toml (1)

18-20: LGTM on dev-dependencies additions.

The new dev-dependencies appropriately support the configuration testing infrastructure. The tempfile version specification addresses the previous reproducibility concern.

tests/steps/mod.rs (1)

1-6: LGTM on module structure and alphabetical ordering.

The module declarations follow proper alphabetical ordering and the new ConfigWorld export appropriately supports the configuration testing framework.

crates/comenqd/src/main.rs (2)

7-10: LGTM on module structure.

The module imports appropriately expose the configuration and daemon functionality for the main entry point.


12-17: LGTM on async main implementation.

The async main function properly loads configuration, provides structured logging with key paths, and delegates to the daemon runner. The error propagation with anyhow::Result follows Rust best practices.

crates/comenqd/src/lib.rs (1)

1-18: LGTM on comprehensive module documentation.

The module documentation excellently describes the public API with clear overview and practical usage examples. The public module declarations appropriately expose the configuration and daemon functionality.

docs/comenq-design.md (1)

1003-1004: LGTM on updated daemon functionality description.

The documentation correctly describes the CommentRequest serialization capabilities and daemon's Unix listener and queue worker implementation.

crates/comenqd/src/daemon.rs (2)

1-5: LGTM! Module documentation follows guidelines.

The module documentation clearly explains the purpose and utility as required by the coding guidelines.


53-60: LGTM! Client handling is appropriate.

The function correctly handles the client stream, deserialises the request, and forwards it to the queue.

crates/comenqd/src/config.rs (6)

1-5: LGTM! Module documentation follows guidelines.

The module clearly explains its purpose and configuration loading behaviour.


12-17: LGTM! Constants improve maintainability.

The extracted constants address previous feedback about avoiding hard-coded paths in functions.


19-33: LGTM! Well-structured configuration.

The Config struct is properly designed with appropriate defaults and serialisation attributes.


70-74: LGTM! Proper lint suppression.

The #[expect] attributes correctly replace forbidden #[allow] and include proper reasons as required by guidelines.


84-108: LGTM! Configuration merging logic is sound.

The function correctly handles file loading, environment variable merging, and CLI overrides with proper precedence.


151-218: LGTM! Comprehensive test coverage.

The tests cover all major scenarios: successful loading, missing files, environment overrides, invalid TOML, missing tokens, and default values. The use of serial_test::serial properly handles environment variable isolation.

tests/steps/config_steps.rs (5)

1-2: LGTM! Clear module documentation.

The module purpose is clearly stated.


54-67: LGTM! Proper lint suppression and test setup.

The #[expect] attributes correctly follow guidelines with proper reasons, and the test setup is appropriate.


69-87: LGTM! Test functions follow proper patterns.

The step functions are well-structured with appropriate lint suppressions.


122-147: LGTM! Configuration loading and assertion logic.

The step functions correctly handle configuration loading and result validation.


157-163: LGTM! Proper cleanup implementation.

The Drop implementation ensures environment variables are properly restored.

Comment thread crates/comenqd/src/daemon.rs
Comment thread crates/comenqd/src/daemon.rs
Comment thread crates/comenqd/src/daemon.rs Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jul 26, 2025

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @leynos - I've reviewed your changes and found some issues that need to be addressed.

  • Consolidate your ortho_config dependency so you’re not pulling it in from both the workspace and a git tag—pick one source to avoid version conflicts.
  • Consider making the config file optional or providing a fallback so that loading from env vars or CLI flags alone won’t error out if /etc/comenqd/config.toml is missing.
  • You can remove the unsafe blocks around std::env::set_var/remove_var in your tests since those functions are safe to call directly.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consolidate your ortho_config dependency so you’re not pulling it in from both the workspace and a git tag—pick one source to avoid version conflicts.
- Consider making the config file optional or providing a fallback so that loading from env vars or CLI flags alone won’t error out if `/etc/comenqd/config.toml` is missing.
- You can remove the unsafe blocks around std::env::set_var/remove_var in your tests since those functions are safe to call directly.

## Individual Comments

### Comment 1
<location> `crates/comenqd/src/daemon.rs:26` </location>
<code_context>
+}
+
+fn prepare_listener(path: &Path) -> Result<UnixListener> {
+    if fs::metadata(path).is_ok() {
+        fs::remove_file(path)?;
+    }
+    let listener = UnixListener::bind(path)?;
+    fs::set_permissions(path, fs::Permissions::from_mode(0o660))?;
+    Ok(listener)
+}
</code_context>

<issue_to_address>
Potential race condition when removing and binding the Unix socket file.

Another process could create a file at the same path between removal and binding. Consider file locking or atomic operations if this is a concern.
</issue_to_address>

### Comment 2
<location> `crates/comenqd/src/daemon.rs:40` </location>
<code_context>
+    let (tx, rx) = channel(&config.queue_path)?;
+    let cfg = Arc::new(config);
+
+    let listener = tokio::spawn(run_listener(cfg.clone(), tx));
+    let worker = tokio::spawn(run_worker(cfg.clone(), rx, octocrab));
+
+    tokio::select! {
</code_context>

<issue_to_address>
Spawning both listener and worker as tasks may cause the process to exit if either fails.

Consider implementing error handling or task restarts to improve daemon resilience if a task fails.
</issue_to_address>

### Comment 3
<location> `crates/comenqd/src/daemon.rs:69` </location>
<code_context>
+}
+
+async fn handle_client(mut stream: UnixStream, tx: &mut Sender) -> Result<()> {
+    let mut buffer = Vec::new();
+    stream.read_to_end(&mut buffer).await?;
+    let request: CommentRequest = serde_json::from_slice(&buffer)?;
+    let bytes = serde_json::to_vec(&request)?;
+    tx.send(bytes).await?;
</code_context>

<issue_to_address>
Reading the entire stream to end may block if the client does not close the connection.

Consider implementing a length-prefixed protocol or adding a timeout to prevent the server from hanging if the client does not close the connection.
</issue_to_address>

### Comment 4
<location> `tests/steps/config_steps.rs:149` </location>
<code_context>
+    }
+}
+
+#[then(regex = r#"^socket path is \"(.+)\"$"#)]
+fn socket_path_is(world: &mut ConfigWorld, expected: String) {
+    match world.result.take() {
+        Some(Ok(cfg)) => assert_eq!(cfg.socket_path, PathBuf::from(expected)),
</code_context>

<issue_to_address>
Consider adding a test for default value of cooldown_period_seconds.

Currently, there's no test asserting that the default cooldown_period_seconds is set when not provided. Please add a scenario to cover this case.

Suggested implementation:

```rust
#[then(regex = r#"^cooldown_period_seconds is (\d+)$"#)]
fn cooldown_period_seconds_is(world: &mut ConfigWorld, expected: u64) {
    match world.result.take() {
        Some(Ok(cfg)) => assert_eq!(cfg.cooldown_period_seconds, expected),
        other => panic!("expected success, got {other:?}"),
    }
}

impl Drop for ConfigWorld {
    fn drop(&mut self) {
        if let Some(_guard) = self.env_guard.take() {
            // dropping the guard restores the previous state
        }
    }
}

```

You will also need to add a scenario in your Cucumber feature file that loads a config without specifying `cooldown_period_seconds` and then uses the step:
```
Then cooldown_period_seconds is <DEFAULT_VALUE>
```
Replace `<DEFAULT_VALUE>` with the actual default value set in your config implementation.
</issue_to_address>

### Comment 5
<location> `tests/steps/config_steps.rs:117` </location>
<code_context>
+    clippy::needless_pass_by_value,
+    reason = "cucumber requires owned values"
+)]
+#[given(regex = r#"^environment variable \"(.+)\" is \"(.+)\"$"#)]
+fn set_env_var(world: &mut ConfigWorld, key: String, value: String) {
+    world.env_guard = Some(EnvVarGuard::set(&key, &value));
+}
</code_context>

<issue_to_address>
Test for precedence between CLI, environment, and file values is missing.

Please add a test that sets a value in the file, overrides it with an environment variable, and then with a CLI argument to verify the correct precedence.

Suggested implementation:

```rust
#[expect(
    clippy::needless_pass_by_value,
    reason = "cucumber requires owned values"
)]

// Test for precedence: file < env < CLI
#[given("a configuration file with key \"config_key\" set to \"file_value\"")]
fn config_file_with_key(world: &mut ConfigWorld) {
    // Write a config file with the key set to "file_value"
    let path = tempfile::NamedTempFile::new().unwrap();
    std::fs::write(path.path(), "config_key = \"file_value\"").unwrap();
    world.path = Some(path.path().to_path_buf());
    world.temp_file = Some(path); // Store to keep file alive
}

#[given("environment variable \"config_key\" is \"env_value\"")]
fn set_env_var_env_value(world: &mut ConfigWorld) {
    world.env_guard = Some(EnvVarGuard::set("config_key", "env_value"));
}

#[given("CLI argument \"--config_key\" is \"cli_value\"")]
fn set_cli_arg(world: &mut ConfigWorld) {
    world.cli_args.push("--config_key".to_string());
    world.cli_args.push("cli_value".to_string());
}

#[then("the value for \"config_key\" is \"cli_value\"")]
fn assert_cli_precedence(world: &mut ConfigWorld) {
    let value = world.get_config_value("config_key");
    assert_eq!(value, Some("cli_value".to_string()));
}

#[test]
fn test_precedence_cli_env_file() {
    let mut world = ConfigWorld::default();

    // Set up file
    config_file_with_key(&mut world);

    // Set up env
    set_env_var_env_value(&mut world);

    // Set up CLI
    set_cli_arg(&mut world);

    // Assert precedence
    assert_cli_precedence(&mut world);
}

```

- If `ConfigWorld` does not have `cli_args`, `temp_file`, or `get_config_value` fields/methods, you will need to add them.
- If you use a test runner other than `#[test]`, adapt the test function accordingly.
- If step definitions for setting CLI args or file values already exist, reuse them instead of duplicating.
- Ensure that the test runner executes this test, or add it to your test suite as appropriate.
</issue_to_address>

### Comment 6
<location> `crates/comenqd/src/config.rs:84` </location>
<code_context>
+    }
+
+    #[expect(clippy::result_large_err, reason = "propagate figment errors")]
+    fn from_file_with_cli(path: &Path, cli: &CliArgs) -> Result<Self, ortho_config::OrthoError> {
+        let mut fig = ortho_config::load_config_file(path)?.ok_or_else(|| {
+            ortho_config::OrthoError::File {
</code_context>

<issue_to_address>
Consider replacing the manual CLI override logic with Figment's built-in Cli provider and simplifying default handling.

```rust
// 1) Drop the custom CLI‐override loop and instead use Figment’s built-in Cli provider.
//    You can even derive clap::Parser on Config itself (or keep a tiny one for only
//    `--config`) and then do:
//
//   .merge(Cli::from_env())
//
// 2) Remove the `default_*` helpers in favor of serde(default) with constants or
//    make Config implement Default for those fields.

// before (inside from_file_with_cli):
let mut fig = ortho_config::load_config_file(path)?
    .ok_or(...)?
    .merge(Env::prefixed("COMENQD_").split("__"));

let mut cfg: Self = fig.extract()?;
if let Some(token) = &cli.github_token {
    cfg.github_token = token.clone();
}
// …etc for socket_path, queue_path…

// after:
use figment::providers::Cli;

let fig = ortho_config::load_config_file(path)?
    .ok_or(...)?
    .merge(Env::prefixed("COMENQD_").split("__"))
    .merge(Cli::from_env());

let cfg: Self = fig.extract().map_err(ortho_config::OrthoError::from)?;
Ok(cfg)
```

Steps:

1. Add `use figment::providers::Cli;`
2. Delete the `if let Some(…)` overrides in `from_file_with_cli`.
3. Append `.merge(Cli::from_env())` to your Figment chain.
4. Remove `CliArgs::{github_token,socket_path,queue_path}` and the default_* fns (you can use `#[serde(default = "…")]` on fields or implement `Default`).
5. Keep one small clap derive for `--config` only (if you still need to override the file path), or better yet derive `Parser` on `Config` itself.

This preserves all current behaviors (file loading → env overrides → CLI overrides) while cutting out ~40 lines of manual merge logic.
</issue_to_address>

### Comment 7
<location> `docs/comenq-design.md:506` </location>
<code_context>
 | log_level               | String  | The minimum log level to record (e.g., "info", "debug", "trace").                         | info                    |
 | cooldown_period_seconds | u64     | The cooling-off period in seconds after each comment post.                                | 900                     |

+Configuration is loaded using the `ortho_config` crate. The daemon calls
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments. CLI
</code_context>

<issue_to_address>
This paragraph exceeds the 80 column limit and should be wrapped accordingly.

Please wrap this paragraph so that no line exceeds 80 columns, as per the documentation guidelines.
</issue_to_address>

### Comment 8
<location> `docs/comenq-design.md:508` </location>
<code_context>

+Configuration is loaded using the `ortho_config` crate. The daemon calls
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments. CLI
+arguments have the highest precedence, followed by environment variables, and
+finally the configuration file. Missing optional fields are replaced with
</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

Please wrap this and the surrounding lines to 80 columns.
</issue_to_address>

### Comment 9
<location> `docs/comenq-design.md:509` </location>
<code_context>
+Configuration is loaded using the `ortho_config` crate. The daemon calls
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments. CLI
+arguments have the highest precedence, followed by environment variables, and
+finally the configuration file. Missing optional fields are replaced with
+defaults, while an absent `github_token` or invalid TOML results in a
</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

All lines in this paragraph should be wrapped to 80 columns.
</issue_to_address>

### Comment 10
<location> `docs/comenq-design.md:510` </location>
<code_context>
+`Config::load()` which merges values from `/etc/comenqd/config.toml`,
+`COMENQD_*` environment variables, and any supplied CLI arguments. CLI
+arguments have the highest precedence, followed by environment variables, and
+finally the configuration file. Missing optional fields are replaced with
+defaults, while an absent `github_token` or invalid TOML results in a
+configuration error.
</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

Please ensure this paragraph is wrapped to 80 columns.
</issue_to_address>

### Comment 11
<location> `docs/comenq-design.md:511` </location>
<code_context>
+`COMENQD_*` environment variables, and any supplied CLI arguments. CLI
+arguments have the highest precedence, followed by environment variables, and
+finally the configuration file. Missing optional fields are replaced with
+defaults, while an absent `github_token` or invalid TOML results in a
+configuration error.
+
</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

Wrap this and the rest of the paragraph to 80 columns.
</issue_to_address>

### Comment 12
<location> `docs/comenq-design.md:1003` </location>
<code_context>
 binary crates under `crates/`. `CommentRequest` resides in the library and
-derives both `Serialize` and `Deserialize`. The binaries currently contain stub
-`main` functions awaiting further implementation.
+derives both `Serialize` and `Deserialize`. The daemon now spawns a Unix
+listener and queue worker as described above.

</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

Please wrap this paragraph to 80 columns as per the style guide.
</issue_to_address>

### Comment 13
<location> `docs/comenq-design.md:1004` </location>
<code_context>
-derives both `Serialize` and `Deserialize`. The binaries currently contain stub
-`main` functions awaiting further implementation.
+derives both `Serialize` and `Deserialize`. The daemon now spawns a Unix
+listener and queue worker as described above.

 ## Works cited
</code_context>

<issue_to_address>
This line is part of a paragraph that is not wrapped to 80 columns.

Ensure the entire paragraph is wrapped to 80 columns.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread crates/comenqd/src/daemon.rs
Comment thread crates/comenqd/src/daemon.rs
Comment thread crates/comenqd/src/daemon.rs
Comment thread tests/steps/config_steps.rs
Comment thread tests/steps/config_steps.rs
Comment thread docs/comenq-design.md
Comment thread docs/comenq-design.md
Comment thread docs/comenq-design.md
Comment thread docs/comenq-design.md
Comment thread docs/comenq-design.md
@github-actions
Copy link
Copy Markdown

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 26, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos leynos force-pushed the codex/implement-configuration-loading-from-toml-file branch from 31c336a to 6a98db8 Compare July 26, 2025 19:45
@leynos leynos merged commit fca4183 into main Jul 26, 2025
1 check passed
@leynos leynos deleted the codex/implement-configuration-loading-from-toml-file branch July 26, 2025 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant