hubcio commented on code in PR #4350:
URL: https://github.com/apache/iggy/pull/4350#discussion_r4158611332


##########
core/configs/src/configs_impl/env_mapping.rs:
##########
@@ -59,3 +74,120 @@ pub trait ConfigEnvMappings {
             .collect()
     }
 }
+
+impl EnvVarTemplate {
+    /// Expands this template into every concrete env-var name it represents,
+    /// substituting each `<N>` placeholder left-to-right with `0..limit`.
+    /// A leaf template (`max_elements: &[]`) expands to itself.
+    pub fn expand_names(&self) -> Vec<String> {
+        if self.max_elements.is_empty() {
+            return vec![self.env_name.to_string()];
+        }
+
+        let mut results = vec![self.env_name.to_string()];
+
+        for &limit in self.max_elements {
+            let mut next = Vec::new();
+            for name in results {
+                for i in 0..limit {
+                    next.push(name.replacen("<N>", &i.to_string(), 1));
+                }
+            }
+            results = next;
+        }
+
+        results
+    }
+}
+
+#[cfg(test)]
+mod consistency_tests {
+    use super::*;
+    use std::collections::HashSet;
+    use crate::server::ServerConfig;
+    use crate::cluster::ClusterConfig;
+    use crate::McpServerConfig;

Review Comment:
   critical: `configs` cannot import `McpServerConfig` or the connectors config 
types, since those binaries depend on `configs` - the test target fails with 
E0432, so no `configs` test runs. move these four parity tests next to their 
types.



##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +132,50 @@ fn print_ascii_art(text: &str) {
     println!("{}", figure.unwrap());
 }
 
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+    let args = Args::parse();
+    if args.list_config_env_vars {
+        print_config_env_vars()?;
+        return Ok(());
+    }
     capture_allowed_cpus();
-    Args::parse();
+    Builder::new_multi_thread()
+        .enable_all()
+        .build()?
+        .block_on(run())
+}
+
+fn print_config_env_vars() -> std::io::Result<()> {
+    let prefix = ConnectorsRuntimeConfig::ENV_PREFIX;
+    let sink_source_templates: Vec<String> = [
+        ("SINK", SinkConfig::env_templates()),
+        ("SOURCE", SourceConfig::env_templates()),
+    ]
+    .iter()
+    .flat_map(|(kind, templates)| {
+        templates
+            .iter()
+            .map(move |template| format!("{prefix}{kind}_<KEY>_{}", 
template.env_name))
+            .chain(
+                [

Review Comment:
   warning: clippy rejects the `.into_iter()` on this array as 
`useless_conversion` - `chain` takes any `IntoIterator`, so pass it directly.
   
   also at line 173.



##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +132,50 @@ fn print_ascii_art(text: &str) {
     println!("{}", figure.unwrap());
 }
 
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+    let args = Args::parse();
+    if args.list_config_env_vars {
+        print_config_env_vars()?;
+        return Ok(());
+    }
     capture_allowed_cpus();
-    Args::parse();
+    Builder::new_multi_thread()
+        .enable_all()
+        .build()?
+        .block_on(run())
+}
+
+fn print_config_env_vars() -> std::io::Result<()> {
+    let prefix = ConnectorsRuntimeConfig::ENV_PREFIX;
+    let sink_source_templates: Vec<String> = [
+        ("SINK", SinkConfig::env_templates()),
+        ("SOURCE", SourceConfig::env_templates()),
+    ]
+    .iter()
+    .flat_map(|(kind, templates)| {
+        templates
+            .iter()
+            .map(move |template| format!("{prefix}{kind}_<KEY>_{}", 
template.env_name))

Review Comment:
   warning: the listing now advertises `IGGY_CONNECTORS_SOURCE_<KEY>_KEY`, and 
setting it renames the source, so it starts without its saved state. not new in 
this PR - worth an issue to mark `key` and `version` `#[config_env(skip)]` on 
sinks and sources.



##########
core/integration/tests/config_env_listing/mod.rs:
##########
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+use std::time::Duration;
+
+const LIST_ENV_VARS_TIMEOUT: Duration = Duration::from_secs(5);
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+    for (binary, config_env, dotenv_env) in [
+        ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+        (
+            "iggy-connectors",
+            "IGGY_CONNECTORS_CONFIG_PATH",
+            "IGGY_CONNECTORS_ENV_PATH",
+        ),
+        ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+    ] {
+        let directory = tempfile::tempdir().expect("temporary directory");
+        let output = Command::cargo_bin(binary)
+            .expect("binary should be built")
+            .current_dir(directory.path())
+            .arg("--list-config-env-vars")
+            .env(config_env, directory.path().join("missing-config.toml"))
+            .env(dotenv_env, directory.path().join("missing.env"))
+            .timeout(LIST_ENV_VARS_TIMEOUT)
+            .output()
+            .expect("listing command should run");
+
+        assert!(output.status.success(), "{binary} failed");
+        assert!(output.stderr.is_empty(), "{binary} wrote to stderr");
+
+        let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+        let names: Vec<_> = stdout.lines().collect();
+        assert!(!names.is_empty(), "{binary} returned no variables");
+        assert!(
+            names.windows(2).all(|pair| pair[0] < pair[1]),
+            "{binary} output must be sorted and deduplicated"
+        );
+        assert!(names.iter().all(|name| name.starts_with("IGGY_")));

Review Comment:
   warning: this only checks the `IGGY_` prefix, so no test pins the #4322 
exclusions. assert the server output has none of `SERVER_SCAN_ONLY_ENV_VARS` or 
the sibling prefixes, and connectors and mcp names use their own prefix or 
`IGGY_DISPLAY_CONFIG`.



##########
core/configs/src/server_config/server.rs:
##########
@@ -47,6 +47,34 @@ pub use crate::common::server::{
     TelemetryConfig, TelemetryLogsConfig, TelemetryTracesConfig, 
TelemetryTransport,
 };
 
+/// Vars safe to advertise to end users via `--list-config-env-vars`.
+pub const SERVER_RUNTIME_ENV_VARS: &[&str] = &[
+    "IGGY_CONFIG_PATH",
+    "IGGY_DISPLAY_CONFIG",
+    "IGGY_ENV_PATH",
+    "IGGY_ROOT_PASSWORD",
+    "IGGY_ROOT_USERNAME",
+    "IGGY_SHARD_EVENT_INTERVAL",
+    "IGGY_SHARD_RUNTIME_CAPACITY",
+];
+
+/// Test/CI-only vars: never advertised, but still non-config (suppress

Review Comment:
   nit: `IGGY_HOME`, `IGGY_USERNAME` and `IGGY_PASSWORD` are not test-only - 
the `iggy` CLI reads them, and the server reads none of the three. describe 
them as sibling-binary vars here and at line 73.



##########
core/ai/mcp/src/error.rs:
##########
@@ -45,4 +45,6 @@ pub enum McpRuntimeError {
     TokenFileReadError(String, String),
     #[error("Token file is empty: {0}")]
     TokenFileEmpty(String),
+    #[error("I/O error: {0}")]
+    Io(#[from] std::io::Error),

Review Comment:
   nit: same as the server - this context-free `#[from] std::io::Error` sits 
next to `RuntimeCreation` and `SignalRegistration`, which name their operation. 
use a named `#[source]` variant and `map_err` at main.rs:57.



##########
core/configs/src/configs_impl/env_mapping.rs:
##########
@@ -59,3 +74,120 @@ pub trait ConfigEnvMappings {
             .collect()
     }
 }
+
+impl EnvVarTemplate {
+    /// Expands this template into every concrete env-var name it represents,
+    /// substituting each `<N>` placeholder left-to-right with `0..limit`.
+    /// A leaf template (`max_elements: &[]`) expands to itself.
+    pub fn expand_names(&self) -> Vec<String> {
+        if self.max_elements.is_empty() {
+            return vec![self.env_name.to_string()];
+        }
+
+        let mut results = vec![self.env_name.to_string()];
+
+        for &limit in self.max_elements {
+            let mut next = Vec::new();
+            for name in results {
+                for i in 0..limit {
+                    next.push(name.replacen("<N>", &i.to_string(), 1));
+                }
+            }
+            results = next;
+        }
+
+        results
+    }
+}
+
+#[cfg(test)]
+mod consistency_tests {
+    use super::*;
+    use std::collections::HashSet;
+    use crate::server::ServerConfig;
+    use crate::cluster::ClusterConfig;
+    use crate::McpServerConfig;
+    use configs::runtime::ConnectorsRuntimeConfig;
+    use configs::connectors::{SinkConfig, SourceConfig};
+
+    #[test]
+    fn server_config_templates_and_mappings_align() {

Review Comment:
   simplification: the six tests share one body, and the `ClusterConfig` one 
repeats what the `ServerConfig` test covers. drop it, and after the move use 
one generic helper for the three connectors types.



##########
core/configs/src/server_config/server.rs:
##########
@@ -47,6 +47,34 @@ pub use crate::common::server::{
     TelemetryConfig, TelemetryLogsConfig, TelemetryTracesConfig, 
TelemetryTransport,
 };
 
+/// Vars safe to advertise to end users via `--list-config-env-vars`.
+pub const SERVER_RUNTIME_ENV_VARS: &[&str] = &[

Review Comment:
   simplification: `SERVER_RUNTIME_ENV_VARS` retypes 7 names of 
`SERVER_PROCESS_ENV_VARS`, and a test keeps them in sync. print 
`SERVER_PROCESS_ENV_VARS` minus `SERVER_SCAN_ONLY_ENV_VARS`, and drop this list 
and its test.



##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +132,50 @@ fn print_ascii_art(text: &str) {
     println!("{}", figure.unwrap());
 }
 
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+    let args = Args::parse();
+    if args.list_config_env_vars {
+        print_config_env_vars()?;
+        return Ok(());
+    }
     capture_allowed_cpus();
-    Args::parse();
+    Builder::new_multi_thread()
+        .enable_all()
+        .build()?
+        .block_on(run())
+}
+
+fn print_config_env_vars() -> std::io::Result<()> {
+    let prefix = ConnectorsRuntimeConfig::ENV_PREFIX;
+    let sink_source_templates: Vec<String> = [
+        ("SINK", SinkConfig::env_templates()),
+        ("SOURCE", SourceConfig::env_templates()),
+    ]
+    .iter()
+    .flat_map(|(kind, templates)| {
+        templates
+            .iter()
+            .map(move |template| format!("{prefix}{kind}_<KEY>_{}", 
template.env_name))

Review Comment:
   simplification: #4322 asks to reuse the provider naming rules, but this 
rebuilds the `<KEY>` prefix and `PLUGIN_CONFIG_` rule that 
local_provider.rs:351 and :825 build. move the rule into one `pub(crate)` fn 
and call it from both.



##########
core/integration/tests/config_env_listing/mod.rs:
##########
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+use std::time::Duration;
+
+const LIST_ENV_VARS_TIMEOUT: Duration = Duration::from_secs(5);
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+    for (binary, config_env, dotenv_env) in [
+        ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+        (
+            "iggy-connectors",
+            "IGGY_CONNECTORS_CONFIG_PATH",
+            "IGGY_CONNECTORS_ENV_PATH",
+        ),
+        ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+    ] {
+        let directory = tempfile::tempdir().expect("temporary directory");
+        let output = Command::cargo_bin(binary)
+            .expect("binary should be built")
+            .current_dir(directory.path())
+            .arg("--list-config-env-vars")
+            .env(config_env, directory.path().join("missing-config.toml"))
+            .env(dotenv_env, directory.path().join("missing.env"))
+            .timeout(LIST_ENV_VARS_TIMEOUT)
+            .output()
+            .expect("listing command should run");
+
+        assert!(output.status.success(), "{binary} failed");
+        assert!(output.stderr.is_empty(), "{binary} wrote to stderr");
+
+        let stdout = String::from_utf8(output.stdout).expect("UTF-8 output");
+        let names: Vec<_> = stdout.lines().collect();
+        assert!(!names.is_empty(), "{binary} returned no variables");
+        assert!(
+            names.windows(2).all(|pair| pair[0] < pair[1]),
+            "{binary} output must be sorted and deduplicated"
+        );
+        assert!(names.iter().all(|name| name.starts_with("IGGY_")));
+    }
+}
+
+#[test]
+fn config_env_listing_includes_vector_index_templates() {
+    // Verify that vector fields are correctly represented with <N> placeholder
+    let directory = tempfile::tempdir().expect("temporary directory");
+    let output = Command::cargo_bin("iggy-server")

Review Comment:
   simplification: the six `Command::cargo_bin` builders repeat - one helper 
that takes the binary, extra args and env pairs and runs in a fresh tempdir 
saves about 12 lines.



##########
core/server/src/server_error.rs:
##########
@@ -313,6 +313,8 @@ pub enum ServerError {
     /// as clean.
     #[error("server shut down after a panic: {description}")]
     Panicked { description: String },
+    #[error("I/O error: {0}")]
+    Io(#[from] std::io::Error),

Review Comment:
   warning: this `#[from]` lets any later `?` on an io error compile into a 
context-free `Io`, and a failed listing prints just `Error: Io(Os { .. })`. use 
a named variant like `ListConfigEnvVars(#[source] std::io::Error)` with 
`map_err` at main.rs:46.



##########
core/configs/src/server_config/server.rs:
##########
@@ -47,6 +47,34 @@ pub use crate::common::server::{
     TelemetryConfig, TelemetryLogsConfig, TelemetryTracesConfig, 
TelemetryTransport,
 };
 
+/// Vars safe to advertise to end users via `--list-config-env-vars`.
+pub const SERVER_RUNTIME_ENV_VARS: &[&str] = &[
+    "IGGY_CONFIG_PATH",
+    "IGGY_DISPLAY_CONFIG",
+    "IGGY_ENV_PATH",
+    "IGGY_ROOT_PASSWORD",
+    "IGGY_ROOT_USERNAME",
+    "IGGY_SHARD_EVENT_INTERVAL",
+    "IGGY_SHARD_RUNTIME_CAPACITY",
+];
+
+/// Test/CI-only vars: never advertised, but still non-config (suppress
+/// "unknown env var" warnings the same as `SERVER_RUNTIME_ENV_VARS` does).

Review Comment:
   nit: `SERVER_RUNTIME_ENV_VARS` does not suppress anything - only 
`SERVER_PROCESS_ENV_VARS` reaches the unknown-name scan near line 313. reword 
this, and drop "reads directly" at line 73.



##########
core/server/README.md:
##########
@@ -27,7 +27,7 @@ To run one node of a cluster, pass its replica ID from the 
`cluster.nodes` roste
 cargo run --bin iggy-server --release -- --replica-id 0
 ```
 
-`--replica-id` is the only command line argument; everything else is 
configuration.
+Command line arguments include `--replica-id`, `--fresh` (`-f`), 
`--with-default-root-credentials`, and `--list-config-env-vars`; everything 
else is configuration.

Review Comment:
   nit: #4322 also asks for the configuration docs - please document the flag 
and its `<N>`, `<KEY>` and `<FIELD>` placeholders on the website configuration 
pages too.



##########
core/server/src/args.rs:
##########
@@ -87,6 +87,21 @@ For more information, visit: 
https://iggy.apache.org/docs/introduction/getting-s
 // variable names and paths must stay unquoted rather than wear rustdoc 
backticks.
 #[allow(clippy::doc_markdown)]
 pub struct Args {
+    #[arg(long, long_help = r#"Print supported configuration environment 
variables and exit.

Review Comment:
   nit: with only `long_help`, clap prints this whole block for `-h` too. add a 
one-line `help` and keep the details in `long_help`.
   
   also at core/connectors/runtime/src/main.rs:73.



##########
core/server/src/main.rs:
##########
@@ -119,7 +123,7 @@ fn main() -> Result<(), ServerError> {
     if let Err(error) = &joined
         && descriptors_exhausted()
     {
-        // `fatal` skips destructors, and the log appenders flush on drop.
+        // `fatal` skips destructors, and the log flush must run

Review Comment:
   nit: this comment lost its reason - restore "`fatal` skips destructors, and 
the log appenders flush on drop." from master.



##########
core/connectors/runtime/src/main.rs:
##########
@@ -117,10 +132,50 @@ fn print_ascii_art(text: &str) {
     println!("{}", figure.unwrap());
 }
 
-#[tokio::main]
-async fn main() -> Result<(), RuntimeError> {
+fn main() -> Result<(), RuntimeError> {
+    let args = Args::parse();
+    if args.list_config_env_vars {
+        print_config_env_vars()?;
+        return Ok(());
+    }
     capture_allowed_cpus();
-    Args::parse();
+    Builder::new_multi_thread()
+        .enable_all()
+        .build()?
+        .block_on(run())
+}
+
+fn print_config_env_vars() -> std::io::Result<()> {
+    let prefix = ConnectorsRuntimeConfig::ENV_PREFIX;
+    let sink_source_templates: Vec<String> = [
+        ("SINK", SinkConfig::env_templates()),
+        ("SOURCE", SourceConfig::env_templates()),
+    ]
+    .iter()
+    .flat_map(|(kind, templates)| {
+        templates
+            .iter()
+            .map(move |template| format!("{prefix}{kind}_<KEY>_{}", 
template.env_name))
+            .chain(
+                [
+                    format!("{prefix}{kind}_<KEY>_ENABLED"),

Review Comment:
   simplification: `enabled` already derives `<KEY>_ENABLED`, and only the sort 
and dedup hide the duplicate. drop this entry, chain `std::iter::once` for 
`PLUGIN_CONFIG_<FIELD>`, take the array by value and skip the `Vec` - that also 
clears both clippy errors.



##########
core/connectors/runtime/src/main.rs:
##########
@@ -67,7 +69,20 @@ static GLOBAL: MiMalloc = MiMalloc;
 
 #[derive(Parser, Debug)]
 #[command(author = "Apache Iggy", version)]
-struct Args {}
+struct Args {
+    #[arg(long, long_help = r#"Print supported configuration environment 
variables and exit.
+
+Lists all supported IGGY_* environment variable names and templates,
+sorted and deduplicated. Template syntax:
+- <N> represents vector indices

Review Comment:
   nit: #4322 asks for per-field index limits - every `<N>` here comes from 
`streams`, which takes 0-255, so say that like args.rs:94 does for the server.



##########
core/integration/tests/config_env_listing/mod.rs:
##########
@@ -0,0 +1,219 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use assert_cmd::Command;
+use std::collections::HashSet;
+use std::time::Duration;
+
+const LIST_ENV_VARS_TIMEOUT: Duration = Duration::from_secs(5);
+
+#[test]
+fn config_env_listing_exits_before_startup_for_each_binary() {
+    for (binary, config_env, dotenv_env) in [
+        ("iggy-server", "IGGY_CONFIG_PATH", "IGGY_ENV_PATH"),
+        (
+            "iggy-connectors",
+            "IGGY_CONNECTORS_CONFIG_PATH",
+            "IGGY_CONNECTORS_ENV_PATH",
+        ),
+        ("iggy-mcp", "IGGY_MCP_CONFIG_PATH", "IGGY_MCP_ENV_PATH"),
+    ] {
+        let directory = tempfile::tempdir().expect("temporary directory");
+        let output = Command::cargo_bin(binary)
+            .expect("binary should be built")
+            .current_dir(directory.path())
+            .arg("--list-config-env-vars")
+            .env(config_env, directory.path().join("missing-config.toml"))
+            .env(dotenv_env, directory.path().join("missing.env"))
+            .timeout(LIST_ENV_VARS_TIMEOUT)
+            .output()
+            .expect("listing command should run");
+
+        assert!(output.status.success(), "{binary} failed");

Review Comment:
   nit: on failure this prints only the binary name - add `output.status` and 
the stderr text, so a red run says why.
   
   also at lines 163, 167, 191.



##########
core/configs_derive/src/config_env.rs:
##########
@@ -359,6 +389,49 @@ fn generate_struct_impl(
         }
     };
 
+    let own_template_entries = mappings.iter().map(|mapping| {

Review Comment:
   simplification: `env_mappings()` and `env_templates()` walk the same field 
tree, and the parity tests exist only to keep them in step. generate templates 
with `config_path` and `is_secret`, and expand them in `env_mappings()` - note 
the `Vec` entries then change order.



##########
core/configs/src/configs_impl/env_listing.rs:
##########
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::io::Write;
+
+/// Writes each name in `names`, sorted and deduplicated, to stdout — one
+/// call, one lock, one error policy. A closed stdout (e.g.
+/// `iggy-server --list-config-env-vars | head -1`) is expected, not a
+/// failure: on `BrokenPipe` this stops writing and returns `Ok(())`. Any
+/// other I/O error is real and is propagated so the caller exits non-zero.
+pub fn print_env_var_names<I, S>(names: I) -> std::io::Result<()>

Review Comment:
   nit: this hardcodes `stdout()`, so no test reaches the `BrokenPipe` branch 
or the error branch. take a `&mut impl Write` and unit-test both.



##########
core/configs/src/configs_impl/env_mapping.rs:
##########
@@ -59,3 +74,120 @@ pub trait ConfigEnvMappings {
             .collect()
     }
 }
+
+impl EnvVarTemplate {
+    /// Expands this template into every concrete env-var name it represents,
+    /// substituting each `<N>` placeholder left-to-right with `0..limit`.
+    /// A leaf template (`max_elements: &[]`) expands to itself.
+    pub fn expand_names(&self) -> Vec<String> {
+        if self.max_elements.is_empty() {

Review Comment:
   simplification: this early return is redundant - with an empty 
`max_elements`, the loop below already returns `vec![env_name]`.



##########
core/configs/src/configs_impl/env_listing.rs:
##########
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::io::Write;
+
+/// Writes each name in `names`, sorted and deduplicated, to stdout — one
+/// call, one lock, one error policy. A closed stdout (e.g.
+/// `iggy-server --list-config-env-vars | head -1`) is expected, not a
+/// failure: on `BrokenPipe` this stops writing and returns `Ok(())`. Any
+/// other I/O error is real and is propagated so the caller exits non-zero.
+pub fn print_env_var_names<I, S>(names: I) -> std::io::Result<()>
+where
+    I: IntoIterator<Item = S>,
+    S: Into<String>,
+{
+    let mut names: Vec<String> = names.into_iter().map(Into::into).collect();
+    names.sort_unstable();
+    names.dedup();
+
+    let stdout = std::io::stdout();
+    let mut lock = stdout.lock();
+    for name in names {
+        if let Err(err) = writeln!(lock, "{name}") {
+            return if err.kind() == std::io::ErrorKind::BrokenPipe {
+                Ok(())
+            } else {
+                Err(err)
+            };
+        }
+    }
+    Ok(())
+}
+
+/// Env vars `iggy-mcp --list-config-env-vars` advertises beyond the derived
+/// `McpServerConfig` templates.
+pub const MCP_RUNTIME_ENV_VARS: &[&str] =

Review Comment:
   simplification: these names are typed again at each `env::var` call in the 
mcp and connectors mains. give each name one constant that the list and the 
read share.



##########
core/configs/src/configs_impl/env_listing.rs:
##########
@@ -0,0 +1,59 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::io::Write;
+
+/// Writes each name in `names`, sorted and deduplicated, to stdout — one
+/// call, one lock, one error policy. A closed stdout (e.g.
+/// `iggy-server --list-config-env-vars | head -1`) is expected, not a
+/// failure: on `BrokenPipe` this stops writing and returns `Ok(())`. Any
+/// other I/O error is real and is propagated so the caller exits non-zero.
+pub fn print_env_var_names<I, S>(names: I) -> std::io::Result<()>
+where
+    I: IntoIterator<Item = S>,
+    S: Into<String>,
+{
+    let mut names: Vec<String> = names.into_iter().map(Into::into).collect();
+    names.sort_unstable();
+    names.dedup();
+
+    let stdout = std::io::stdout();
+    let mut lock = stdout.lock();
+    for name in names {
+        if let Err(err) = writeln!(lock, "{name}") {
+            return if err.kind() == std::io::ErrorKind::BrokenPipe {
+                Ok(())
+            } else {
+                Err(err)
+            };
+        }
+    }
+    Ok(())
+}
+
+/// Env vars `iggy-mcp --list-config-env-vars` advertises beyond the derived
+/// `McpServerConfig` templates.
+pub const MCP_RUNTIME_ENV_VARS: &[&str] =

Review Comment:
   simplification: these lists repeat the four path names of `IGNORED_ENV_VARS` 
(typed_env_provider.rs:49). let the scan at typed_env_provider.rs:294 read 
these lists, and delete `IGNORED_ENV_VARS`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to