Skip to content

feat(config): add NestJS-inspired configuration system(rikta config similarity)#26

Merged
vernonthedev merged 6 commits into
mainfrom
feat/nestforge-config-module
Mar 24, 2026
Merged

feat(config): add NestJS-inspired configuration system(rikta config similarity)#26
vernonthedev merged 6 commits into
mainfrom
feat/nestforge-config-module

Conversation

@vernonthedev
Copy link
Copy Markdown
Owner

@vernonthedev vernonthedev commented Mar 23, 2026

Summary

This PR introduces a centralized, NestJS-inspired configuration system for NestForge. It provides type-safe, injectable, and easy-to-use configuration management that ensures applications load configuration from .env files at startup.

Changes

nestforge-config crate (enhanced)

  • Added ConfigService - like NestJS ConfigService for loading .env files and accessing environment variables
  • Added ConfigModule::for_root() and ConfigModule::for_feature() for modular configuration
  • Added intuitive getters: get_string(), get_string_or(), get_u16_or(), get_bool_or(), etc.
  • Added Config<T> wrapper for DI-compatible injection
  • Added register_config() for typed configuration registration
  • Added load_config() convenience function
  • Added EnvSchema for backward compatibility with existing examples
  • Maintained backward compatibility with FromEnv, EnvStore, EnvValidationIssue

nestforge-core crate (updated)

  • Added register_config() function to integrate ConfigService with the DI container

nestforge-cli crate (updated)

  • Updated nestforge new scaffolding to generate app_config.rs with ConfigService setup

examples (updated)

  • Updated all examples to use the new simplified configuration API

docs (updated)

  • Comprehensive documentation in docs/config-module.md
  • Updated nestforge-config/README.md

Validation

  • cargo test --lib - all workspace tests pass
  • cargo clippy -- -D warnings on nestforge-config - passes

Performance Impact

  • No runtime impact expected - Configuration loading happens once at startup
  • No compile-time impact expected - minimal dependency increase
  • Binary Size Increase: ~50KB due to dotenvy dependency

Docs

  • Updated docs/config-module.md with comprehensive usage guide
  • Updated crates/nestforge-config/README.md

Architecture

  • Changes follow the flat-folder module guidance where applicable
  • New files were added in the owning crate rather than examples

Usage Examples

Basic Setup (via CLI - automatic)

nestforge new my-app
# Generates app_config.rs automatically

Manual Setup

// src/app_config.rs
use nestforge::{ConfigModule, ConfigOptions, ConfigService, injectable};

#[injectable(factory = load_app_config)]
pub struct AppConfig {
    pub app_name: String,
    pub log_level: String,
}

fn load_app_config() -> anyhow::Result<AppConfig> {
    let options = ConfigOptions::new().env_file(".env");
    let config = ConfigModule::for_root_with_options(options);
    Ok(AppConfig {
        app_name: config.get_string_or("APP_NAME", "My App"),
        log_level: config.get_string_or("LOG_LEVEL", "info"),
    })
}

In Services - NestJS-like Ergonomics

use nestforge::prelude::*;

pub struct MyService {
    pub config: Config<AppConfig>,
}

impl MyService {
    pub fn get_port(&self) -> u16 {
        self.config.get_u16_or("PORT", 3000)
    }
    
    pub fn is_debug(&self) -> bool {
        self.config.get_bool_or("DEBUG", false)
    }
}

ConfigService Direct Usage

use nestforge::{ConfigModule, ConfigOptions, ConfigService};

pub type AppConfig = ConfigService;

pub fn load_config() -> AppConfig {
    ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}

// In services
let app_name = config.get_string_or("APP_NAME", "My App");
let port = config.get_u16_or("PORT", 3000);
let debug = config.get_bool_or("DEBUG", false);

Typed Configs (like NestJS registerAs)

use nestforge_config::register_config;

#[derive(Debug, Clone)]
pub struct DatabaseConfig {
    pub host: String,
    pub port: u16,
}

pub static DATABASE = register_config("database", || DatabaseConfig {
    host: "localhost".to_string(),
    port: 5432,
});

// Usage
let db = DATABASE.load();

NestJS Comparison

NestJS NestForge
ConfigModule.forRoot({ isGlobal: true }) ConfigModule::for_root()
configService.get('KEY') config.get_string("KEY")
configService.getOrThrow('KEY') config.get("KEY").unwrap()
registerAs('config', () => ({ ... })) register_config("name", || Config { ... })
@Inject(ConfigService) Config<AppConfig>

Breaking Change Analysis

None. This is a new, optional module. Existing code is unaffected.

Linked Issues

Summary by CodeRabbit

  • New Features

    • Added typed configuration getter methods (get_string, get_u16, get_bool, etc.) for simplified config value retrieval
    • Introduced streamlined configuration registration and injection system via ConfigService and Config<T>
    • Added .env file support for environment-based configuration
  • Documentation

    • Updated configuration module guide with new usage examples and API reference

…lidated environment loading

This commit introduces a comprehensive configuration system for NestForge applications, following the philosophy of NestJS.

## Core Features Implemented

### nestforge-config crate
- Added  for environment variable loading from .env files
- Implemented  with support for custom paths, process env inclusion, and prefix filtering
- Added  and  for modular configuration
- Created  for detailed error reporting at startup
- Implemented  trait for automatic type parsing from environment variables
- Supported primitives: String, bool, u8-u64, i8-i64, f32-f64, usize, isize

### nestforge-macros crate
- Developed  procedural macro for automatic config struct generation
- Macro automatically implements  and  traits
- Generates type-safe field access with automatic env var key mapping

### nestforge-core crate
- Added  function for DI container integration
- Implemented  trait for automatic config loading and validation
- Exported config types for easy access via

### nestforge-cli crate
- Updated app scaffolding to generate  with example config struct
- Added proper  env var support in .env templates

## Technical Details
- Uses  for .env file parsing
- Integrates  crate for schema validation
- Leverages  crate for static registration (prepared for future use)
- Zero runtime overhead after validation - configs are singletons in DI container

## Breaking Changes
None - this is a new optional module that does not affect existing functionality.

## Performance Impact
- Startup: Slight increase due to .env parsing and validation (runs once)
- Runtime: Zero overhead after injection
- Binary size: Minimal increase due to serde and validator dependencies
…n and auto-deriving, fixes #23

Removes the validator-based validation system, prefix support, ConfigField trait, and automatic derive implementation from nestforge-config. The config system now requires manual FromEnv implementation instead of using derive macros. This simplifies the API and removes external validation dependencies.

BREAKING CHANGE: The #[derive(Config)] macro no longer auto-generates FromEnv implementations.
Users must manually implement FromEnv for their config structs.
The `prefix` option in ConfigOptions has been removed.
ConfigField trait and its helper methods (from_env, from_env_or, etc.) have been removed.
…ed approach

Replaced the trait-based configuration pattern (FromEnv, Configurable) with a simpler service-based approach (ConfigService). Removed validation dependencies (validator, serde, inventory) and streamlined error handling. The new ConfigService uses Arc for thread-safe sharing and provides a cleaner API with get, get_or_panic, get_or, and get_as methods.

BREAKING CHANGE: Removed FromEnv trait - configs must now be loaded via ConfigService instead of implementing FromEnv
BREAKING CHANGE: EnvStore replaced with ConfigService - update all references from EnvStore to ConfigService
BREAKING CHANGE: Configurable trait removed - use ConfigService::load_with_options() directly instead of register_config<T: Configurable>
…d getters

Refactored ConfigService to use HashMap directly instead of Arc-wrapped HashMap,
changed get() to return Option<&str> for zero-copy access, and added typed getters
(get_i32, get_u16, get_bool) for more ergonomic configuration access. Removed
generic get_as methods in favor of specific typed methods.
…config loading

Add new typed getter methods (get_u32, get_usize, get_f64, get_isize, get_i64, get_u64) and has() method to ConfigService. Introduce Config<T> and ConfigRegistration<T> for type-safe config registration. Refactor config loading to use ConfigModule::for_root_with_options instead of ConfigService::load_with_options.
- Add EnvSchema for defining required environment variables
- Add EnvStore for storing and retrieving env values
- Add FromEnv trait for typed config conversion
- Add Config<T> wrapper for DI integration
- Add load_config() helper function
- Update all examples to use new simplified API
- Update README and documentation with new API reference
@vernonthedev vernonthedev self-assigned this Mar 23, 2026
@vernonthedev vernonthedev added enhancement New feature or request feature-update A new update to an already existing feature modules core labels Mar 23, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in Nestforge Mar 23, 2026
@vernonthedev vernonthedev moved this from Backlog to In Review in Nestforge Mar 23, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation cli labels Mar 23, 2026
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 23, 2026

Code Review by Qodo

🐞 Bugs (7) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Config wrapper unusable 🐞 Bug ✓ Correctness
Description
Config<T> is documented as the injectable config handle, but it is only a PhantomData wrapper
and exposes none of ConfigService’s getter APIs, so documented usage like
Config<AppConfig>.get_string_or(...) cannot compile.
Code

crates/nestforge-config/src/lib.rs[R275-291]

+pub struct Config<T> {
+    _phantom: std::marker::PhantomData<T>,
+}

-        if issues.is_empty() {
-            Ok(())
-        } else {
-            Err(ConfigError::Validation { issues })
+impl<T> Config<T> {
+    pub fn new() -> Self {
+        Self {
+            _phantom: std::marker::PhantomData,
        }
    }
}

-/**
- * EnvStore
- *
- * A store for environment variable key-value pairs.
- *
- * # Loading
- * - Loads from .env file if present
- * - Includes process environment variables by default
- * - Supports custom loading options
- */
-#[derive(Clone, Debug, Default)]
-pub struct EnvStore {
-    values: HashMap<String, String>,
+impl<T> Default for Config<T> {
+    fn default() -> Self {
+        Self::new()
+    }
}
Evidence
In nestforge-config, Config<T> is defined as a zero-sized wrapper with only new()/Default
and no Deref/forwarded methods, while both docs and crate README show calling
get_string_or/get_u16_or/get_bool_or on a Config<AppConfig> field.

crates/nestforge-config/src/lib.rs[275-291]
docs/config-module.md[54-75]
crates/nestforge-config/README.md[25-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Config<T>` is advertised/used as the DI-friendly config handle, but it has no functionality. Any code following the README/docs (`self.config.get_string_or(...)`) will not compile.

### Issue Context
- `ConfigService` contains the getter methods.
- `Config<T>` currently contains only `PhantomData<T>`.

### Fix Focus Areas
- Implement `Config<T>` as a real wrapper around `ConfigService` (e.g., `struct Config<T>(Arc<ConfigService>, PhantomData<T>)` or `struct Config<T>(ConfigService, PhantomData<T>)`) and forward/Deref getter methods.
- Alternatively, remove `Config<T>` from public API and update docs/README to inject/resolve `ConfigService` (or `AppConfig = ConfigService`) directly.

### Fix Focus Areas (locations)
- crates/nestforge-config/src/lib.rs[75-223]
- crates/nestforge-config/src/lib.rs[275-291]
- docs/config-module.md[54-75]
- crates/nestforge-config/README.md[25-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Broken derive Config 🐞 Bug ✓ Correctness
Description
#[derive(Config)] generates an impl of nestforge_config::FromEnv that declares a non-existent
trait method (config_key) and intentionally emits compile_error! inside function bodies, so any
use of the derive fails compilation.
Code

crates/nestforge-macros/src/lib.rs[R2027-2052]

+#[proc_macro_derive(Config)]
+pub fn derive_config(item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as DeriveInput);
+    let name = &input.ident;
+    let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();
+
+    let Data::Struct(_data) = &input.data else {
+        return syn::Error::new(input.ident.span(), "Config can only be derived on structs")
+            .to_compile_error()
+            .into();
+    };
+
+    let expanded = quote! {
+        impl #impl_generics nestforge_config::FromEnv for #name #ty_generics #where_clause {
+            fn from_env(env: &nestforge_config::EnvStore) -> Result<Self, nestforge_config::ConfigError> {
+                std::compile_error!(
+                    "Config derive requires manual FromEnv implementation. \
+                     Use `impl FromEnv for YourConfig` with `env.get(\"KEY\")` to read values."
+                );
+            }
+
+            fn config_key() -> &'static str {
+                stringify!(#name)
+            }
+        }
+
Evidence
The FromEnv trait in nestforge-config defines only from_env(...). The derive macro generates a
config_key() method in the trait impl, which is not part of the trait; additionally it uses
std::compile_error! inside from_env and Default::default, ensuring compilation failure if the
derive is used.

crates/nestforge-macros/src/lib.rs[2027-2063]
crates/nestforge-config/src/lib.rs[71-73]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`#[derive(Config)]` currently expands to code that cannot compile against the current `FromEnv` trait (extra `config_key()` method) and contains deliberate `compile_error!`.

### Issue Context
If this derive is intended as a placeholder, it should not be shipped in a way that breaks builds when used. If it is intended to work, it must match the `FromEnv` trait and generate a valid `from_env` implementation.

### Fix Focus Areas
- Either delete the derive macro entirely until implemented.
- Or update the macro to:
 - Only implement existing trait methods (`from_env`).
 - Remove `compile_error!` bodies.
 - (Optional) generate `from_env` by reading fields via attributes.

### Fix Focus Areas (locations)
- crates/nestforge-macros/src/lib.rs[2027-2063]
- crates/nestforge-config/src/lib.rs[71-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Scaffolded app won't compile 🐞 Bug ✓ Correctness
Description
The CLI now generates app_config.rs that imports nestforge_config directly and defines
AppConfig = ConfigService, but the generated Cargo.toml doesn’t add a nestforge-config
dependency and other templates still register/consume AppConfig as if it were a struct
(register_injectable::<AppConfig>, config.app_name), causing immediate compilation failures in
new projects.
Code

crates/nestforge-cli/src/main.rs[R2727-2734]

+fn template_app_config_rs(_transport: AppTransport) -> String {
+    r#"use nestforge_config::{ConfigService, ConfigModule};

-fn load_app_config() -> anyhow::Result<AppConfig> {{
-    Ok(ConfigModule::for_root::<AppConfig>(ConfigOptions::new().env_file(".env"))?)
-}}
+pub type AppConfig = ConfigService;

-impl nestforge::FromEnv for AppConfig {{
-    fn from_env(env: &nestforge::EnvStore) -> Result<Self, nestforge::ConfigError> {{
-        let app_name = env.get("APP_NAME").unwrap_or("{default_app_name}").to_string();
-        Ok(Self {{ app_name }})
-    }}
-}}
+pub fn load_config() -> AppConfig {
+    ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
+}
Evidence
template_app_config_rs now imports nestforge_config::{ConfigService, ConfigModule} and aliases
AppConfig = ConfigService. However template_app_cargo_toml only emits the nestforge dependency
line (no nestforge-config). Also module provider lists use plain AppConfig expressions, which
the #[module] macro treats as an injectable type and expands to
register_injectable::<AppConfig>; ConfigService (type alias) does not implement Injectable.
Additionally, the GraphQL main template still reads config.app_name, but ConfigService has no
app_name field (it uses getters). Finally, template_app_service_rs still references removed APIs
(EnvStore::load_with_options).

crates/nestforge-cli/src/main.rs[2727-2736]
crates/nestforge-cli/src/main.rs[2324-2341]
crates/nestforge-cli/src/main.rs[2560-2586]
crates/nestforge-macros/src/lib.rs[1749-1756]
crates/nestforge-macros/src/lib.rs[1783-1792]
crates/nestforge-cli/src/main.rs[2689-2694]
crates/nestforge-cli/src/main.rs[2396-2400]
crates/nestforge-config/src/lib.rs[224-273]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`nestforge new` scaffolding is internally inconsistent after switching `AppConfig` to a `ConfigService` alias. The generated project will not compile.

### Issue Context
- `#[module]` providers accept expressions; a bare path like `AppConfig` is treated as an injectable type and becomes `register_injectable::<AppConfig>`.
- With `pub type AppConfig = ConfigService;`, this is not injectable.

### Fix Focus Areas
- In `template_app_config_rs`, import from `nestforge` (re-export) instead of `nestforge_config`, OR add an explicit `nestforge-config` dependency in generated Cargo.toml.
- In `template_app_module_rs`, register config as a value/factory expression, e.g. `providers = [load_config()]` or `providers = [Provider::value(load_config())]`.
- Update `template_main_rs` (GraphQL) to use getters (e.g. `config.get_string_or("APP_NAME", ...)`) instead of `config.app_name`.
- Update `template_app_service_rs` to use the new API (resolve `AppConfig`/`ConfigService` from container and call getters), removing `FromEnv` + `EnvStore::load_with_options`.

### Fix Focus Areas (locations)
- crates/nestforge-cli/src/main.rs[2289-2342]
- crates/nestforge-cli/src/main.rs[2560-2604]
- crates/nestforge-cli/src/main.rs[2680-2705]
- crates/nestforge-cli/src/main.rs[2390-2405]
- crates/nestforge-cli/src/main.rs[2727-2736]
- crates/nestforge-macros/src/lib.rs[1749-1756]
- crates/nestforge-macros/src/lib.rs[1783-1792]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Config loading panics 🐞 Bug ⛯ Reliability
Description
ConfigModule::for_root_with_options calls expect, so failures to load/parse the .env file
crash the process; nestforge-core::register_config returns Result<()> but cannot report config
load failures because it will panic first.
Code

crates/nestforge-config/src/lib.rs[R262-264]

+    pub fn for_root_with_options(options: ConfigOptions) -> ConfigService {
+        ConfigService::load_with_options(&options).expect("Failed to load configuration")
    }
Evidence
for_root_with_options forces a panic on any ConfigService::load_with_options error.
nestforge-core::register_config is structured to propagate errors via Result, but it calls
for_root_with_options which panics instead of returning an error.

crates/nestforge-config/src/lib.rs[262-264]
crates/nestforge-core/src/config.rs[5-8]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Config loading panics via `expect`, bypassing callers’ `Result`-based error handling.

### Issue Context
`nestforge-core::register_config` returns `anyhow::Result<()>` but cannot surface config load failures while `for_root_with_options` panics.

### Fix Focus Areas
- Change `ConfigModule::for_root_with_options` to return `Result<ConfigService, ConfigError>`.
- Update `load_config()` and `nestforge-core::register_config` to propagate errors instead of panicking.

### Fix Focus Areas (locations)
- crates/nestforge-config/src/lib.rs[85-112]
- crates/nestforge-config/src/lib.rs[257-273]
- crates/nestforge-core/src/config.rs[5-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Dotenv errors dropped 🐞 Bug ⛯ Reliability
Description
When iterating .env entries, per-line parse errors are silently ignored, which can lead to
missing/incorrect configuration without any error or log signal.
Code

crates/nestforge-config/src/lib.rs[R98-107]

+            dotenvy::from_path_iter(path_ref)
+                .map_err(|source| ConfigError::ReadEnvFile {
+                    path: path_ref.display().to_string(),
+                    source,
+                })?
+                .for_each(|result| {
+                    if let Ok((key, value)) = result {
+                        values.insert(key, value);
+                    }
+                });
Evidence
dotenvy::from_path_iter yields an iterator of Result<(key,value), error>, but the implementation
inserts values only for Ok entries and discards Err entries, so malformed lines are ignored
silently.

crates/nestforge-config/src/lib.rs[97-108]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Malformed `.env` lines are ignored silently, producing partial config state with no error.

### Fix Focus Areas
- Collect iterator `Err`s and return a `ConfigError` that includes line errors; OR
- Emit warnings/logs for each parse error so users can detect misconfiguration.

### Fix Focus Areas (locations)
- crates/nestforge-config/src/lib.rs[97-108]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Docs: config not default 🐞 Bug ⚙ Maintainability
Description
Docs claim the config module is “included by default”, but the nestforge crate has default = []
and config is an opt-in feature, so following the docs may lead to missing exports until the
feature is enabled.
Code

docs/config-module.md[R5-8]

+## Installation

-- `ConfigModule`
-- `ConfigOptions`
-- `EnvSchema`
-- `FromEnv`
+The config module is included by default in the `nestforge` crate.

-## Typical Usage
Evidence
The documentation states config is included by default, but nestforge features show no default
features and config must be enabled explicitly.

docs/config-module.md[5-8]
crates/nestforge/Cargo.toml[36-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Docs incorrectly state config is included by default.

### Fix Focus Areas
- Update docs to instruct enabling the `config` feature: `nestforge = { ..., features = ["config"] }`.

### Fix Focus Areas (locations)
- docs/config-module.md[5-10]
- crates/nestforge/Cargo.toml[36-46]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Core config feature bypass 🐞 Bug ⚙ Maintainability
Description
nestforge-core now depends on and publicly re-exports nestforge-config unconditionally, which
bypasses the nestforge crate’s config feature-gating and increases baseline dependencies for
core consumers.
Code

crates/nestforge-core/src/lib.rs[R65-67]

+pub use nestforge_config::{
+    Config, ConfigError, ConfigModule, ConfigOptions, ConfigService,
+};
Evidence
nestforge-core adds a hard dependency on nestforge-config and re-exports config types without
cfg(feature = "config"). In contrast, the top-level nestforge crate keeps config behind an
optional feature, so this change undermines that feature boundary.

crates/nestforge-core/Cargo.toml[14-23]
crates/nestforge-core/src/lib.rs[65-67]
crates/nestforge/Cargo.toml[36-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`nestforge-core` pulls config in unconditionally, weakening intended feature-gating.

### Fix Focus Areas
- Make `nestforge-config` an optional dependency in `nestforge-core` and add a `config` feature in `nestforge-core`.
- Gate `pub use nestforge_config::{...}` behind `#[cfg(feature = "config")]`.
- Adjust `nestforge` crate feature wiring if needed.

### Fix Focus Areas (locations)
- crates/nestforge-core/Cargo.toml[14-23]
- crates/nestforge-core/src/lib.rs[65-67]
- crates/nestforge/Cargo.toml[36-46]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@vernonthedev vernonthedev linked an issue Mar 23, 2026 that may be closed by this pull request
4 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

Refactored NestForge configuration system from schema-based validation to a ConfigService API. Introduces typed getters, simplified EnvSchema, new register_config function for DI integration, a Config derive macro, and updates CLI templates and all examples to use the new configuration loading pattern.

Changes

Cohort / File(s) Summary
Root & Core Dependencies
Cargo.toml, crates/nestforge-core/Cargo.toml
Added validator 0.16 workspace dependency and nestforge-config + validator to core crate dependencies.
Configuration Module Implementation
crates/nestforge-config/Cargo.toml, crates/nestforge-config/src/lib.rs
Replaced EnvStore-based validation with ConfigService offering typed getters (get_string, get_u16, get_bool, etc.), simplified EnvSchema to a requirements collector, and refactored ConfigModule to builder pattern (for_root(), for_root_with_options()) with dotenvy integration. Added register_config registration API and Config<T> wrapper.
Core Module & DI Integration
crates/nestforge-core/src/config.rs, crates/nestforge-core/src/lib.rs
New register_config function to integrate config into DI container; added module export and public re-exports of Config, ConfigError, ConfigModule, ConfigOptions, ConfigService.
Derive Macro
crates/nestforge-macros/src/lib.rs
Added Config procedural derive macro implementing FromEnv and Default with compile-time error stubs requiring manual implementation.
Framework Re-exports
crates/nestforge/src/lib.rs
Updated config feature exports to include Config and ConfigService; reorganized prelude with conditional re-exports for extension types (NestForgeFactoryOpenApiExt, NestForgeFactoryWebSocketExt, NestForgeGrpcFactory, TestFactory).
CLI Template Generation
crates/nestforge-cli/src/main.rs
Changed template generation to type-alias AppConfig = ConfigService and use ConfigModule::for_root_with_options(...) with .env loading instead of struct validation.
Documentation & Examples
docs/config-module.md, examples/*/src/app_config.rs
Updated configuration module docs with CLI integration flow, typed getters, .env support, and register_config patterns; refreshed all five example projects to use ConfigModule::for_root_with_options(...) with get_string_or(...) fallbacks instead of schema validation.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant CM as ConfigModule
    participant CS as ConfigService
    participant Env as Process Env +<br/>Dotenvy File
    participant DI as DI Container
    
    App->>CM: for_root_with_options(options)
    CM->>CS: load_with_options()
    CS->>Env: env::vars() + dotenvy::from_path_iter()
    Env-->>CS: key-value pairs
    CS-->>CM: ConfigService instance
    CM-->>App: ConfigService (ready)
    
    App->>DI: register_config(container, options)
    DI->>CS: load()
    CS->>Env: env::vars() + dotenvy::from_path_iter()
    Env-->>CS: key-value pairs
    CS-->>DI: ConfigService singleton
    DI-->>App: registration complete
    
    App->>CS: get_string("APP_NAME")
    CS-->>App: typed value + default
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Poem

🐰 Hops through configs with glee,
Types and services, validation-free!
No more schemas, just getters so clean,
The prettiest config I've ever seen!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly describes the main change: adding a NestJS-inspired configuration system with reference to Rikita similarity.
Description check ✅ Passed Description comprehensively covers summary, changes, validation, performance impact, documentation, architecture, and linked issues per template.
Linked Issues check ✅ Passed PR fulfills issue #23 objectives: type-safe config (ConfigService, structs), validation (EnvSchema), modularity (for_root/for_feature), injection (Config), and CLI scaffolding updates.
Out of Scope Changes check ✅ Passed All changes directly align with issue #23: new nestforge-config crate, DI integration, CLI updates, example updates, and docs. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nestforge-config-module

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vernonthedev vernonthedev changed the title feat(config): add NestJS-inspired configuration system(rikita config similarity) feat(config): add NestJS-inspired configuration system(rikta config similarity) Mar 23, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56d0f39ddd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +103 to +105
.for_each(|result| {
if let Ok((key, value)) = result {
values.insert(key, value);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve process env precedence over .env values

When include_process_env is true, values is preloaded from env::vars(), but this loop unconditionally overwrites those entries with .env contents. The previous loader preserved runtime overrides via entry(...).or_insert_with(...), so deployments that inject PORT, DATABASE_URL, etc. through the process environment and also ship a default .env will now silently boot with the file's values instead of the intended runtime configuration.

Useful? React with 👍 / 👎.

fn load_app_config() -> anyhow::Result<AppConfig> {{
Ok(ConfigModule::for_root::<AppConfig>(ConfigOptions::new().env_file(".env"))?)
}}
pub type AppConfig = ConfigService;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep scaffolded AppConfig injectable

This changes the generated AppConfig provider into a plain alias of ConfigService, but template_app_module_rs still emits providers = [AppConfig] (crates/nestforge-cli/src/main.rs:2571-2600), and the module macro expands bare type names to register_injectable::<T> (crates/nestforge-macros/src/lib.rs:1749-1755). Any project created with nestforge new will therefore fail to compile because ConfigService does not implement Injectable.

Useful? React with 👍 / 👎.

Comment on lines +275 to +277
pub struct Config<T> {
_phantom: std::marker::PhantomData<T>,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register a real Config<T> instead of a marker

Config<T> is only a PhantomData marker here, and neither load_config() nor nestforge_core::register_config() ever registers that wrapper type—only ConfigService. Because the container resolves exact TypeIds (crates/nestforge-core/src/container.rs:259-297), any provider that follows the advertised Config<AppConfig> pattern and resolves Config<AppConfig> will fail with TypeNotRegistered at startup.

Useful? React with 👍 / 👎.

Comment on lines +275 to 291
pub struct Config<T> {
_phantom: std::marker::PhantomData<T>,
}

if issues.is_empty() {
Ok(())
} else {
Err(ConfigError::Validation { issues })
impl<T> Config<T> {
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}

/**
* EnvStore
*
* A store for environment variable key-value pairs.
*
* # Loading
* - Loads from .env file if present
* - Includes process environment variables by default
* - Supports custom loading options
*/
#[derive(Clone, Debug, Default)]
pub struct EnvStore {
values: HashMap<String, String>,
impl<T> Default for Config<T> {
fn default() -> Self {
Self::new()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Config wrapper unusable 🐞 Bug ✓ Correctness

Config<T> is documented as the injectable config handle, but it is only a PhantomData wrapper
and exposes none of ConfigService’s getter APIs, so documented usage like
Config<AppConfig>.get_string_or(...) cannot compile.
Agent Prompt
### Issue description
`Config<T>` is advertised/used as the DI-friendly config handle, but it has no functionality. Any code following the README/docs (`self.config.get_string_or(...)`) will not compile.

### Issue Context
- `ConfigService` contains the getter methods.
- `Config<T>` currently contains only `PhantomData<T>`.

### Fix Focus Areas
- Implement `Config<T>` as a real wrapper around `ConfigService` (e.g., `struct Config<T>(Arc<ConfigService>, PhantomData<T>)` or `struct Config<T>(ConfigService, PhantomData<T>)`) and forward/Deref getter methods.
- Alternatively, remove `Config<T>` from public API and update docs/README to inject/resolve `ConfigService` (or `AppConfig = ConfigService`) directly.

### Fix Focus Areas (locations)
- crates/nestforge-config/src/lib.rs[75-223]
- crates/nestforge-config/src/lib.rs[275-291]
- docs/config-module.md[54-75]
- crates/nestforge-config/README.md[25-41]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +2027 to +2052
#[proc_macro_derive(Config)]
pub fn derive_config(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();

let Data::Struct(_data) = &input.data else {
return syn::Error::new(input.ident.span(), "Config can only be derived on structs")
.to_compile_error()
.into();
};

let expanded = quote! {
impl #impl_generics nestforge_config::FromEnv for #name #ty_generics #where_clause {
fn from_env(env: &nestforge_config::EnvStore) -> Result<Self, nestforge_config::ConfigError> {
std::compile_error!(
"Config derive requires manual FromEnv implementation. \
Use `impl FromEnv for YourConfig` with `env.get(\"KEY\")` to read values."
);
}

fn config_key() -> &'static str {
stringify!(#name)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. Broken derive config 🐞 Bug ✓ Correctness

#[derive(Config)] generates an impl of nestforge_config::FromEnv that declares a non-existent
trait method (config_key) and intentionally emits compile_error! inside function bodies, so any
use of the derive fails compilation.
Agent Prompt
### Issue description
`#[derive(Config)]` currently expands to code that cannot compile against the current `FromEnv` trait (extra `config_key()` method) and contains deliberate `compile_error!`.

### Issue Context
If this derive is intended as a placeholder, it should not be shipped in a way that breaks builds when used. If it is intended to work, it must match the `FromEnv` trait and generate a valid `from_env` implementation.

### Fix Focus Areas
- Either delete the derive macro entirely until implemented.
- Or update the macro to:
  - Only implement existing trait methods (`from_env`).
  - Remove `compile_error!` bodies.
  - (Optional) generate `from_env` by reading fields via attributes.

### Fix Focus Areas (locations)
- crates/nestforge-macros/src/lib.rs[2027-2063]
- crates/nestforge-config/src/lib.rs[71-73]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +2727 to +2734
fn template_app_config_rs(_transport: AppTransport) -> String {
r#"use nestforge_config::{ConfigService, ConfigModule};

fn load_app_config() -> anyhow::Result<AppConfig> {{
Ok(ConfigModule::for_root::<AppConfig>(ConfigOptions::new().env_file(".env"))?)
}}
pub type AppConfig = ConfigService;

impl nestforge::FromEnv for AppConfig {{
fn from_env(env: &nestforge::EnvStore) -> Result<Self, nestforge::ConfigError> {{
let app_name = env.get("APP_NAME").unwrap_or("{default_app_name}").to_string();
Ok(Self {{ app_name }})
}}
}}
pub fn load_config() -> AppConfig {
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Scaffolded app won't compile 🐞 Bug ✓ Correctness

The CLI now generates app_config.rs that imports nestforge_config directly and defines
AppConfig = ConfigService, but the generated Cargo.toml doesn’t add a nestforge-config
dependency and other templates still register/consume AppConfig as if it were a struct
(register_injectable::<AppConfig>, config.app_name), causing immediate compilation failures in
new projects.
Agent Prompt
### Issue description
`nestforge new` scaffolding is internally inconsistent after switching `AppConfig` to a `ConfigService` alias. The generated project will not compile.

### Issue Context
- `#[module]` providers accept expressions; a bare path like `AppConfig` is treated as an injectable type and becomes `register_injectable::<AppConfig>`.
- With `pub type AppConfig = ConfigService;`, this is not injectable.

### Fix Focus Areas
- In `template_app_config_rs`, import from `nestforge` (re-export) instead of `nestforge_config`, OR add an explicit `nestforge-config` dependency in generated Cargo.toml.
- In `template_app_module_rs`, register config as a value/factory expression, e.g. `providers = [load_config()]` or `providers = [Provider::value(load_config())]`.
- Update `template_main_rs` (GraphQL) to use getters (e.g. `config.get_string_or("APP_NAME", ...)`) instead of `config.app_name`.
- Update `template_app_service_rs` to use the new API (resolve `AppConfig`/`ConfigService` from container and call getters), removing `FromEnv` + `EnvStore::load_with_options`.

### Fix Focus Areas (locations)
- crates/nestforge-cli/src/main.rs[2289-2342]
- crates/nestforge-cli/src/main.rs[2560-2604]
- crates/nestforge-cli/src/main.rs[2680-2705]
- crates/nestforge-cli/src/main.rs[2390-2405]
- crates/nestforge-cli/src/main.rs[2727-2736]
- crates/nestforge-macros/src/lib.rs[1749-1756]
- crates/nestforge-macros/src/lib.rs[1783-1792]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +262 to 264
pub fn for_root_with_options(options: ConfigOptions) -> ConfigService {
ConfigService::load_with_options(&options).expect("Failed to load configuration")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Config loading panics 🐞 Bug ⛯ Reliability

ConfigModule::for_root_with_options calls expect, so failures to load/parse the .env file
crash the process; nestforge-core::register_config returns Result<()> but cannot report config
load failures because it will panic first.
Agent Prompt
### Issue description
Config loading panics via `expect`, bypassing callers’ `Result`-based error handling.

### Issue Context
`nestforge-core::register_config` returns `anyhow::Result<()>` but cannot surface config load failures while `for_root_with_options` panics.

### Fix Focus Areas
- Change `ConfigModule::for_root_with_options` to return `Result<ConfigService, ConfigError>`.
- Update `load_config()` and `nestforge-core::register_config` to propagate errors instead of panicking.

### Fix Focus Areas (locations)
- crates/nestforge-config/src/lib.rs[85-112]
- crates/nestforge-config/src/lib.rs[257-273]
- crates/nestforge-core/src/config.rs[5-8]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

@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: 15

🧹 Nitpick comments (1)
examples/hello-nestforge-grpc/src/app_config.rs (1)

10-23: FromEnv implementation appears unused.

The load_app_config function creates a ConfigModule and reads values directly via get_string_or, but the FromEnv trait implementation on lines 18-23 is never called. This creates confusion about the intended pattern.

Consider either:

  1. Removing the unused FromEnv impl
  2. Using FromEnv within load_app_config to demonstrate the trait-based approach
  3. Adding a comment explaining when each approach should be used
Option 1: Remove unused impl
 fn load_app_config() -> anyhow::Result<AppConfig> {
     let options = ConfigOptions::new().env_file(".env");
     let config = ConfigModule::for_root_with_options(options);
     Ok(AppConfig {
         app_name: config.get_string_or("APP_NAME", "NestForge gRPC"),
     })
 }
-
-impl FromEnv for AppConfig {
-    fn from_env(env: &EnvStore) -> Result<Self, ConfigError> {
-        Ok(Self {
-            app_name: env.get("APP_NAME").unwrap_or("NestForge gRPC").to_string(),
-        })
-    }
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/hello-nestforge-grpc/src/app_config.rs` around lines 10 - 23, The
FromEnv impl for AppConfig is unused; either remove the impl block (impl FromEnv
for AppConfig { ... }) to avoid dead code, or update load_app_config to
construct AppConfig via the trait (e.g., obtain the EnvStore from ConfigModule
and call AppConfig::from_env(env) or equivalent) so FromEnv is actually invoked;
pick one and apply consistently to the AppConfig, load_app_config, FromEnv and
EnvStore symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Cargo.toml`:
- Line 48: Remove the unused workspace dependency declaration for the validator
crate (the line declaring validator = { version = "0.16", features = ["derive"]
} in Cargo.toml) since the project uses its own Validate trait and custom
#[derive(Validate)] implementation; delete that dependency entry from Cargo.toml
and run cargo update / cargo metadata to ensure the workspace lockfile and
dependency tree are cleaned up.

In `@crates/nestforge-cli/src/main.rs`:
- Around line 2727-2736: The generated app_config.rs template
(template_app_config_rs) now imports nestforge_config and exposes
load_config/ConfigService, which is inconsistent with template_app_service_rs
and template_app_module_rs that expect AppConfig as a struct/FromEnv with fields
like config.app_name and do not call load_config; fix by making the templates
consistent: either (A) revert template_app_config_rs to generate the original
AppConfig struct implementing FromEnv with the expected fields (e.g., app_name)
so it matches template_app_service_rs and keep template_app_cargo_toml depending
on nestforge, or (B) update template_app_cargo_toml to add nestforge_config and
modify template_app_service_rs and template_app_module_rs to call load_config()
and use the ConfigService API; pick one approach and apply it across
template_app_config_rs, template_app_cargo_toml, template_app_service_rs, and
template_app_module_rs so all references to AppConfig/load_config/nestforge vs
nestforge_config are consistent.

In `@crates/nestforge-config/README.md`:
- Around line 20-22: Update the README's example to match the project's standard
initialization pattern by replacing the use of
ConfigModule::for_root().env_file(".env") with the
ConfigOptions::new().env_file(".env") pattern shown in examples; specifically,
in the load_config() example (returning AppConfig) use ConfigOptions::new() to
build the options (instead of calling ConfigModule::for_root()) so the docs
align with examples and the code paths that ultimately call
ConfigOptions::new().

In `@crates/nestforge-config/src/lib.rs`:
- Around line 275-290: Config<T> currently only stores PhantomData and thus
holds no underlying value; change Config<T> to actually contain the wrapped
configuration (e.g., a field like inner: T or cfg: T) and update the constructor
and Default impl to initialize that field (for Default require T: Default). Also
implement or forward the accessor methods used in docs (e.g., get_string_or) by
either impl Deref<Target=T> for Config<T> or by adding matching wrapper methods
on Config that call through to the inner T (referencing Config<T>, Config::new,
and the Default impl to locate where to change).
- Around line 14-15: The accessors currently swallow missing keys and parse
failures (returning ""/0/false) instead of surfacing the error; change each
getter (e.g., the string/uint/bool accessors referenced around lines 117–217) to
return Result<T, ConfigError> and: 1) return Err(ConfigError::MissingKey { key:
key.to_string() }) when the environment key is absent, 2) return a distinct
parse error (or extend ConfigError with a Parse variant) when conversion fails,
and 3) remove the silent defaulting to empty/0/false so callers must handle the
Result; update call sites to propagate or handle these errors.
- Around line 18-32: EnvSchema currently accumulates required keys in the
requirements Vec but never enforces them; add a validation step that fails
startup when a required env var is missing. Implement a method on EnvSchema like
validate_required(&self) -> Result<(), anyhow::Error> (or suitable error type)
that iterates self.requirements and checks std::env::var(key).map_err(...) for
missing keys, returning an error listing all missing keys, and then invoke this
method from the code path that constructs/applies the schema (where
EnvSchema::new() / .required(...) is used) so required-key validation runs
during startup.
- Around line 71-73: The trait FromEnv currently only declares fn
from_env(&EnvStore) -> Result<Self, ConfigError> but the derive macro for Config
generates an impl that also defines fn config_key() -> &'static str; update the
trait by adding the missing signature fn config_key() -> &'static str; so that
implementations produced by the macro match the trait (i.e., modify trait
FromEnv to include the config_key method), or alternatively remove the generated
config_key method from the derive macro in crates/nestforge-macros if you prefer
keeping the trait unchanged; reference the trait name FromEnv, the existing
method from_env and the generated method config_key to locate and reconcile the
mismatch.
- Around line 89-110: In load_with_options, do not silently drop per-entry parse
errors from dotenvy::from_path_iter; replace the current .for_each block with
.try_for_each and map any per-item error into a ConfigError variant (e.g.,
create/return ConfigError::ParseEnvEntry with the path and source) so malformed
.env lines cause load_with_options to return Err instead of being ignored;
ensure the mapping happens after dotenvy::from_path_iter(...) and before merging
into values.

In `@crates/nestforge-core/Cargo.toml`:
- Around line 22-23: Remove the unused dependency declaration "validator = {
workspace = true }" from the Cargo.toml so only the actively used path
dependency "nestforge-config = { path = \"../nestforge-config\" }" remains;
update the Cargo.toml by deleting the validator line and then run cargo metadata
or cargo build to verify no other references to the validator crate exist.

In `@crates/nestforge-core/src/config.rs`:
- Around line 14-29: The test test_register_config_loads currently mutates
process-wide environment via std::env::set_var/remove_var which is not
thread-safe; instead modify the test to avoid touching std::env by constructing
ConfigOptions with test-specific values (e.g., use
ConfigOptions::new().env_file("tests/fixtures/test.env") or a builder that sets
APP_NAME directly) and pass that into register_config(&container, options), then
assert via container.resolve::<ConfigService>() without calling
std::env::set_var/remove_var; alternatively mark the test to run serially if
changing ConfigOptions is not possible.
- Around line 5-8: The current register_config function calls
ConfigModule::for_root_with_options which panics on load failure; change to use
a fallible loader instead: add a try_for_root_with_options on ConfigModule that
delegates to ConfigService::load_with_options returning Result<ConfigService,
ConfigError>, then update register_config to call
ConfigModule::try_for_root_with_options(options) and propagate the error (return
Err) instead of allowing a panic; ensure the signature of register_config stays
Result<()> and that ConfigOptions and ConfigError types are used for the
fallible path.

In `@crates/nestforge-macros/src/lib.rs`:
- Around line 2027-2063: The public proc-macro derive Config (function
derive_config and the #[proc_macro_derive(Config)] export) is incomplete and
emits compile-time errors; remove the public derive by deleting the
derive_config function and its #[proc_macro_derive(Config)] attribute (or if you
prefer to keep it temporarily, remove the proc-macro export and mark it hidden
by removing #[proc_macro_derive(Config)] and adding a non-exported helper),
ensuring there are no remaining references to Config derive in the crate;
alternatively, fully implement derive_config to generate valid FromEnv and
Default impls if you intend to keep the API — in either case update Cargo export
so the public proc-macro is no longer exposed in its broken form.

In `@docs/config-module.md`:
- Around line 23-31: The examples import crate symbols directly from
nestforge_config (ConfigService, ConfigModule) which aren’t provided by the
scaffolded Cargo.toml; update the snippets to either re-export these symbols
from the top-level crate (use nestforge::{ConfigService, ConfigModule}) or
explicitly document/add the nestforge_config dependency in the manifest;
specifically change the import lines used by AppConfig and load_config (and the
other occurrences around the examples that reference ConfigService/ConfigModule)
so the examples compile when copy-pasted.
- Around line 214-219: The example function load_config incorrectly calls the
infallible helper
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
while declaring a fallible signature Result<AppConfig, ConfigError>; update the
snippet to call the fallible API instead—invoke
ConfigService::load_with_options(...) (passing the options from
ConfigModule::for_root().env_file(".env")) so the return type matches
Result<AppConfig, ConfigError>, or alternatively change the example to show an
infallible signature that returns ConfigService if you intend to keep
ConfigModule::for_root_with_options; reference
ConfigModule::for_root_with_options, ConfigModule::for_root,
ConfigService::load_with_options, AppConfig, and ConfigError when making the
fix.
- Around line 129-147: The example using static DATABASE_CONFIG is invalid
because Rust statics can't call non-const functions or allocate Strings at
compile time; replace the static with a lazy-initialized value using
once_cell::sync::Lazy or std::sync::OnceLock (e.g., create a Lazy/OnceLock named
DATABASE_CONFIG that calls register_config("database", || DatabaseSettings { ...
}) so the allocation happens at runtime), or alternatively show a non-static
local variable example; update the docs to reference the DatabaseSettings struct
and the register_config call and mention using Lazy/OnceLock (not a plain
static) to make the snippet compile.

---

Nitpick comments:
In `@examples/hello-nestforge-grpc/src/app_config.rs`:
- Around line 10-23: The FromEnv impl for AppConfig is unused; either remove the
impl block (impl FromEnv for AppConfig { ... }) to avoid dead code, or update
load_app_config to construct AppConfig via the trait (e.g., obtain the EnvStore
from ConfigModule and call AppConfig::from_env(env) or equivalent) so FromEnv is
actually invoked; pick one and apply consistently to the AppConfig,
load_app_config, FromEnv and EnvStore symbols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 175d8bdb-0201-4b6d-8eab-9d2410eaba9d

📥 Commits

Reviewing files that changed from the base of the PR and between cffafa5 and 56d0f39.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • Cargo.toml
  • crates/nestforge-cli/src/main.rs
  • crates/nestforge-config/Cargo.toml
  • crates/nestforge-config/README.md
  • crates/nestforge-config/src/lib.rs
  • crates/nestforge-core/Cargo.toml
  • crates/nestforge-core/src/config.rs
  • crates/nestforge-core/src/lib.rs
  • crates/nestforge-macros/src/lib.rs
  • crates/nestforge/src/lib.rs
  • docs/config-module.md
  • examples/hello-nestforge-graphql/src/app_config.rs
  • examples/hello-nestforge-grpc/src/app_config.rs
  • examples/hello-nestforge-microservices/src/app_config.rs
  • examples/hello-nestforge-websockets/src/app_config.rs
  • examples/hello-nestforge/src/app_config.rs

Comment thread Cargo.toml
http = "1"
anyhow = "1"
thiserror = "2"
validator = { version = "0.16", features = ["derive"] }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if validator is actually used in nestforge-config or nestforge-core
rg -l 'validator' --type=toml -g '*/nestforge-config/*' -g '*/nestforge-core/*'
rg 'use.*validator|validator::' --type=rust

Repository: vernonthedev/nestforge

Length of output: 98


🏁 Script executed:

# Check Cargo.toml files to see where validator is declared
cat crates/nestforge-core/Cargo.toml | grep -A 2 -B 2 validator

# Also search for any validator references in the entire Rust codebase
rg 'validator' --type=rust -i

Repository: vernonthedev/nestforge

Length of output: 179


🏁 Script executed:

# Search for any usage of validator in the entire Rust codebase
rg 'validator' --type=rust

# Also check if it's only declared but not used (check for imports more broadly)
rg -i 'validate|validator' --type=rust -A 2

Repository: vernonthedev/nestforge

Length of output: 14611


🏁 Script executed:

# Confirm by checking the workspace Cargo.toml to see if validator is truly a workspace dependency
cat Cargo.toml | grep -A 5 "\[workspace"

Repository: vernonthedev/nestforge

Length of output: 639


🏁 Script executed:

# Check if validator is in workspace.dependencies section
cat Cargo.toml | grep -A 50 "\[workspace.dependencies\]" | grep -i validator

# Also check the full nestforge-core Cargo.toml dependencies section
cat crates/nestforge-core/Cargo.toml | grep -A 30 "\[dependencies\]"

Repository: vernonthedev/nestforge

Length of output: 432


Remove unused validator dependency.

The validator crate is declared as a workspace dependency but is never used. The codebase implements a custom validation system with the Validate trait and #[derive(Validate)] macro instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Cargo.toml` at line 48, Remove the unused workspace dependency declaration
for the validator crate (the line declaring validator = { version = "0.16",
features = ["derive"] } in Cargo.toml) since the project uses its own Validate
trait and custom #[derive(Validate)] implementation; delete that dependency
entry from Cargo.toml and run cargo update / cargo metadata to ensure the
workspace lockfile and dependency tree are cleaned up.

Comment on lines +2727 to +2736
fn template_app_config_rs(_transport: AppTransport) -> String {
r#"use nestforge_config::{ConfigService, ConfigModule};

fn load_app_config() -> anyhow::Result<AppConfig> {{
Ok(ConfigModule::for_root::<AppConfig>(ConfigOptions::new().env_file(".env"))?)
}}
pub type AppConfig = ConfigService;

impl nestforge::FromEnv for AppConfig {{
fn from_env(env: &nestforge::EnvStore) -> Result<Self, nestforge::ConfigError> {{
let app_name = env.get("APP_NAME").unwrap_or("{default_app_name}").to_string();
Ok(Self {{ app_name }})
}}
}}
pub fn load_config() -> AppConfig {
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}
"#
)
.to_string()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

This generated app_config.rs no longer matches the rest of the scaffold.

Line 2728 switches the template to nestforge_config, but template_app_cargo_toml() still only adds nestforge as a dependency. More importantly, the other generated files in this same source file still treat AppConfig as a struct/FromEnv type (template_app_service_rs reads config.app_name, and template_app_module_rs never calls load_config()), so fresh projects will not compile or load .env through this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-cli/src/main.rs` around lines 2727 - 2736, The generated
app_config.rs template (template_app_config_rs) now imports nestforge_config and
exposes load_config/ConfigService, which is inconsistent with
template_app_service_rs and template_app_module_rs that expect AppConfig as a
struct/FromEnv with fields like config.app_name and do not call load_config; fix
by making the templates consistent: either (A) revert template_app_config_rs to
generate the original AppConfig struct implementing FromEnv with the expected
fields (e.g., app_name) so it matches template_app_service_rs and keep
template_app_cargo_toml depending on nestforge, or (B) update
template_app_cargo_toml to add nestforge_config and modify
template_app_service_rs and template_app_module_rs to call load_config() and use
the ConfigService API; pick one approach and apply it across
template_app_config_rs, template_app_cargo_toml, template_app_service_rs, and
template_app_module_rs so all references to AppConfig/load_config/nestforge vs
nestforge_config are consistent.

Comment on lines +20 to +22
pub fn load_config() -> AppConfig {
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual for_root method signature and usage
ast-grep --pattern 'fn for_root($$$) -> $_ {
  $$$
}'

# Check how ConfigOptions is created
rg 'ConfigModule::for_root\(' --type=rust
rg 'ConfigOptions::new\(\)' --type=rust

Repository: vernonthedev/nestforge

Length of output: 1225


🏁 Script executed:

# Check the ConfigModule::for_root() method signature and what it returns
ast-grep --pattern 'impl ConfigModule {
  $$$
  fn for_root($$$) {
    $$$
  }
  $$$
}'

# Also check for ConfigModule::for_root_with_options signature
ast-grep --pattern 'fn for_root_with_options($$$) {
  $$$
}'

Repository: vernonthedev/nestforge

Length of output: 48


🏁 Script executed:

# Get the README.md content around lines 20-22 for full context
sed -n '15,30p' crates/nestforge-config/README.md

Repository: vernonthedev/nestforge

Length of output: 373


🏁 Script executed:

# Check what ConfigModule::for_root() actually returns by looking at the implementation
rg -A 5 'fn for_root\(' crates/nestforge-config/src/lib.rs --type=rust

Repository: vernonthedev/nestforge

Length of output: 308


Standardize configuration initialization pattern across documentation and examples.

The example code is correct—ConfigModule::for_root() returns ConfigOptions, and calling .env_file(".env") on it works as expected. However, the README uses ConfigModule::for_root().env_file(".env") while all example projects use ConfigOptions::new().env_file(".env") directly (both patterns are functionally equivalent since for_root() internally calls ConfigOptions::new()). For consistency and clarity, align the README to use the same pattern as the actual examples.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-config/README.md` around lines 20 - 22, Update the README's
example to match the project's standard initialization pattern by replacing the
use of ConfigModule::for_root().env_file(".env") with the
ConfigOptions::new().env_file(".env") pattern shown in examples; specifically,
in the load_config() example (returning AppConfig) use ConfigOptions::new() to
build the options (instead of calling ConfigModule::for_root()) so the docs
align with examples and the code paths that ultimately call
ConfigOptions::new().

Comment on lines +14 to 15
#[error("Missing config key: {key}")]
MissingKey { key: String },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These getters hide bad config instead of surfacing it.

Line 14 introduces MissingKey, but none of the accessors use it: missing keys and parse failures both collapse to empty string/0/false. That makes values like PORT=abc indistinguishable from a missing key and defeats the fail-fast startup validation this module is supposed to provide.

Also applies to: 117-217

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-config/src/lib.rs` around lines 14 - 15, The accessors
currently swallow missing keys and parse failures (returning ""/0/false) instead
of surfacing the error; change each getter (e.g., the string/uint/bool accessors
referenced around lines 117–217) to return Result<T, ConfigError> and: 1) return
Err(ConfigError::MissingKey { key: key.to_string() }) when the environment key
is absent, 2) return a distinct parse error (or extend ConfigError with a Parse
variant) when conversion fails, and 3) remove the silent defaulting to
empty/0/false so callers must handle the Result; update call sites to propagate
or handle these errors.

Comment on lines +18 to +32
#[derive(Clone, Debug, Default)]
pub struct EnvSchema {
requirements: Vec<String>,
}

impl EnvSchema {
pub fn new() -> Self {
Self::default()
}

pub fn required(&mut self, key: &str) -> &mut Self {
self.requirements.push(key.to_string());
self
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

EnvSchema is a no-op in the new flow.

Lines 28-30 collect required keys, but nothing in Lines 257-268 or the rest of this file ever consumes requirements. Existing code can still build a schema, yet required-key validation never runs, which drops the startup-validation/backward-compatibility contract instead of preserving it.

Also applies to: 255-269

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-config/src/lib.rs` around lines 18 - 32, EnvSchema currently
accumulates required keys in the requirements Vec but never enforces them; add a
validation step that fails startup when a required env var is missing. Implement
a method on EnvSchema like validate_required(&self) -> Result<(), anyhow::Error>
(or suitable error type) that iterates self.requirements and checks
std::env::var(key).map_err(...) for missing keys, returning an error listing all
missing keys, and then invoke this method from the code path that
constructs/applies the schema (where EnvSchema::new() / .required(...) is used)
so required-key validation runs during startup.

Comment on lines +14 to +29
#[test]
fn test_register_config_loads() {
std::env::set_var("APP_NAME", "TestApp");

let container = Container::new();
let options = ConfigOptions::new();

let result = register_config(&container, options);
assert!(result.is_ok());

let resolved = container.resolve::<ConfigService>();
assert!(resolved.is_ok());
assert_eq!(resolved.unwrap().get("APP_NAME"), Some("TestApp"));

std::env::remove_var("APP_NAME");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test uses std::env::set_var/remove_var which is not thread-safe.

Modifying environment variables in tests can cause flaky behavior when tests run in parallel, as std::env is process-global. Consider using a test-specific ConfigOptions that doesn't rely on process environment, or use a serial test harness.

Alternative: use ConfigOptions with explicit values

If ConfigOptions supports setting values directly (or via a test .env file), that would be more robust:

#[test]
fn test_register_config_loads() {
    let options = ConfigOptions::new()
        .env_file("tests/fixtures/test.env"); // contains APP_NAME=TestApp
    // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-core/src/config.rs` around lines 14 - 29, The test
test_register_config_loads currently mutates process-wide environment via
std::env::set_var/remove_var which is not thread-safe; instead modify the test
to avoid touching std::env by constructing ConfigOptions with test-specific
values (e.g., use ConfigOptions::new().env_file("tests/fixtures/test.env") or a
builder that sets APP_NAME directly) and pass that into
register_config(&container, options), then assert via
container.resolve::<ConfigService>() without calling
std::env::set_var/remove_var; alternatively mark the test to run serially if
changing ConfigOptions is not possible.

Comment on lines +2027 to +2063
#[proc_macro_derive(Config)]
pub fn derive_config(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();

let Data::Struct(_data) = &input.data else {
return syn::Error::new(input.ident.span(), "Config can only be derived on structs")
.to_compile_error()
.into();
};

let expanded = quote! {
impl #impl_generics nestforge_config::FromEnv for #name #ty_generics #where_clause {
fn from_env(env: &nestforge_config::EnvStore) -> Result<Self, nestforge_config::ConfigError> {
std::compile_error!(
"Config derive requires manual FromEnv implementation. \
Use `impl FromEnv for YourConfig` with `env.get(\"KEY\")` to read values."
);
}

fn config_key() -> &'static str {
stringify!(#name)
}
}

impl #impl_generics std::default::Default for #name #ty_generics #where_clause {
fn default() -> Self {
std::compile_error!(
"Config derive requires manual Default implementation or provide default values."
);
}
}
};

TokenStream::from(expanded)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Config derive is actually used anywhere
rg '#\[derive\(.*Config.*\)\]' --type=rust

Repository: vernonthedev/nestforge

Length of output: 48


🏁 Script executed:

# Check visibility and documentation of Config derive macro
head -n 2100 crates/nestforge-macros/src/lib.rs | tail -n 100

Repository: vernonthedev/nestforge

Length of output: 3110


🏁 Script executed:

# Search for test files or tests related to Config
find . -type f -name "*.rs" -path "*/tests/*" | head -20

Repository: vernonthedev/nestforge

Length of output: 959


🏁 Script executed:

# Check if there's any documentation or examples for Config derive
rg -i "config.*derive|derive.*config" --type=rust -A 2 -B 2 | head -50

Repository: vernonthedev/nestforge

Length of output: 1722


🏁 Script executed:

# Check module structure to see what's exported
rg "^pub " crates/nestforge-macros/src/lib.rs | head -20

Repository: vernonthedev/nestforge

Length of output: 1503


🏁 Script executed:

# Look for any documentation files mentioning Config derive
find . -type f \( -name "*.md" -o -name "*.txt" \) | xargs grep -l "Config" 2>/dev/null | head -10

Repository: vernonthedev/nestforge

Length of output: 282


🏁 Script executed:

# Check the config module documentation
cat crates/nestforge-config/README.md

Repository: vernonthedev/nestforge

Length of output: 1802


🏁 Script executed:

# Check the config-module documentation
cat docs/config-module.md

Repository: vernonthedev/nestforge

Length of output: 5038


🏁 Script executed:

# Look for any TODO/FIXME comments related to Config derive
rg -i "todo|fixme" crates/nestforge-macros/src/lib.rs | grep -i config

Repository: vernonthedev/nestforge

Length of output: 48


🏁 Script executed:

# Check what's exported from the macros crate in Cargo.toml
head -30 crates/nestforge-macros/Cargo.toml

Repository: vernonthedev/nestforge

Length of output: 598


🏁 Script executed:

# Check if Config derive is mentioned in main crate docs or examples
rg -i "derive.*config|config.*derive" docs/ README.md 2>/dev/null

Repository: vernonthedev/nestforge

Length of output: 48


Remove or implement the unused Config derive macro.

The #[derive(Config)] macro is public and exported, but completely undocumented, unused in the codebase, and generates std::compile_error! in both required method implementations. This appears to be incomplete/unfinished work accidentally exposed as a public API.

The documented configuration approach (shown in docs/config-module.md) uses ConfigModule::for_root() and Config<AppConfig> injection, not a derive macro. For typed configs, the docs recommend register_config() instead.

Since the macro is neither used internally nor documented, and it's non-functional as written, either remove it or mark it as private while completing the implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/nestforge-macros/src/lib.rs` around lines 2027 - 2063, The public
proc-macro derive Config (function derive_config and the
#[proc_macro_derive(Config)] export) is incomplete and emits compile-time
errors; remove the public derive by deleting the derive_config function and its
#[proc_macro_derive(Config)] attribute (or if you prefer to keep it temporarily,
remove the proc-macro export and mark it hidden by removing
#[proc_macro_derive(Config)] and adding a non-exported helper), ensuring there
are no remaining references to Config derive in the crate; alternatively, fully
implement derive_config to generate valid FromEnv and Default impls if you
intend to keep the API — in either case update Cargo export so the public
proc-macro is no longer exposed in its broken form.

Comment thread docs/config-module.md
Comment on lines 23 to 31
```rust
fn load_app_config() -> anyhow::Result<AppConfig> {
let levels = vec!["trace", "debug", "info", "warn", "error"];
let schema = nestforge::EnvSchema::new()
.required("APP_NAME")
.min_len("APP_NAME", 2)
.one_of("LOG_LEVEL", &levels);
// src/app_config.rs
use nestforge_config::{ConfigService, ConfigModule};

pub type AppConfig = ConfigService;

Ok(nestforge::ConfigModule::for_root::<AppConfig>(
nestforge::ConfigOptions::new().env_file(".env").validate_with(schema),
)?)
pub fn load_config() -> AppConfig {
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

These snippets use a crate the documented install path never adds.

Line 25, Line 116, and Line 132 import nestforge_config, but the page says config support is included through nestforge, and the scaffolded manifest only depends on nestforge. Copy-pasting these examples into a generated app yields unresolved imports unless you either document the extra dependency or switch the snippets to the nestforge re-exports.

Also applies to: 115-123, 131-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/config-module.md` around lines 23 - 31, The examples import crate
symbols directly from nestforge_config (ConfigService, ConfigModule) which
aren’t provided by the scaffolded Cargo.toml; update the snippets to either
re-export these symbols from the top-level crate (use nestforge::{ConfigService,
ConfigModule}) or explicitly document/add the nestforge_config dependency in the
manifest; specifically change the import lines used by AppConfig and load_config
(and the other occurrences around the examples that reference
ConfigService/ConfigModule) so the examples compile when copy-pasted.

Comment thread docs/config-module.md
Comment on lines +129 to +147
For strongly-typed configuration structures, use `register_config`:

You can customize this with `ConfigOptions`.
```rust
use nestforge_config::register_config;

#[derive(Debug, Clone)]
pub struct DatabaseSettings {
pub host: String,
pub port: u16,
pub username: String,
pub password: String,
}

pub static DATABASE_CONFIG = register_config("database", || DatabaseSettings {
host: "localhost".to_string(),
port: 5432,
username: "postgres".to_string(),
password: "password".to_string(),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Can a Rust staticinitializer call a non-const function or allocateStrings?

💡 Result:

No, a Rust static initializer cannot call a non-const function or allocate Strings. Static initializers must be constant expressions evaluated at compile time. They can only call const fns (constant functions), not regular non-const functions. This is explicitly stated in the Rust Reference: "The static initializer is a constant expression evaluated at compile time." (Result 16,17). Compiler errors confirm this, e.g., "cannot call non-const fn Mutex::new in statics" with note "calls in statics are limited to constant functions" (Result 1). String::new requires heap allocation and is not const until Rust 1.39 for the empty case (Result 7,8), but even then, non-empty Strings from non-const operations fail. String allocation violates compile-time evaluation requirements. Use &'static str for static strings instead (Result 6,12). Workarounds like lazy_static, once_cell, or static mut with runtime init exist but are not true static initializers (Results 1,2,4). const items share similar restrictions but are inlined, not single-address like static (Result 12,16).

Citations:


The static DATABASE_CONFIG example does not compile as written.

Rust static initializers cannot call non-const functions like register_config(...), and this example allocates Strings via .to_string(). Static initializers require compile-time constant expressions. Use LazyLock/once_cell instead, or provide a non-static example.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/config-module.md` around lines 129 - 147, The example using static
DATABASE_CONFIG is invalid because Rust statics can't call non-const functions
or allocate Strings at compile time; replace the static with a lazy-initialized
value using once_cell::sync::Lazy or std::sync::OnceLock (e.g., create a
Lazy/OnceLock named DATABASE_CONFIG that calls register_config("database", ||
DatabaseSettings { ... }) so the allocation happens at runtime), or
alternatively show a non-static local variable example; update the docs to
reference the DatabaseSettings struct and the register_config call and mention
using Lazy/OnceLock (not a plain static) to make the snippet compile.

Comment thread docs/config-module.md
Comment on lines +214 to +219
For custom error handling:

```rust
pub fn load_config() -> Result<AppConfig, ConfigError> {
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This fallible-loading example is calling the infallible helper.

ConfigModule::for_root_with_options(...) returns ConfigService, not Result<_, ConfigError>, so Lines 217-219 do not type-check. If you want to document custom error handling, the snippet needs to call ConfigService::load_with_options(&...) instead, or the crate needs a fallible ConfigModule helper before this example is documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/config-module.md` around lines 214 - 219, The example function
load_config incorrectly calls the infallible helper
ConfigModule::for_root_with_options(ConfigModule::for_root().env_file(".env"))
while declaring a fallible signature Result<AppConfig, ConfigError>; update the
snippet to call the fallible API instead—invoke
ConfigService::load_with_options(...) (passing the options from
ConfigModule::for_root().env_file(".env")) so the return type matches
Result<AppConfig, ConfigError>, or alternatively change the example to show an
infallible signature that returns ConfigService if you intend to keep
ConfigModule::for_root_with_options; reference
ConfigModule::for_root_with_options, ConfigModule::for_root,
ConfigService::load_with_options, AppConfig, and ConfigError when making the
fix.

Repository owner deleted a comment from qodo-code-review Bot Mar 23, 2026
@vernonthedev vernonthedev merged commit cbc0726 into main Mar 24, 2026
5 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in Nestforge Mar 24, 2026
@vernonthedev vernonthedev deleted the feat/nestforge-config-module branch March 24, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli core documentation Improvements or additions to documentation enhancement New feature or request feature-update A new update to an already existing feature modules

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feat(core): Implement nestforge-config for Type-Safe, Modular Configuration

1 participant