Skip to content

Commit 88c3135

Browse files
authored
Merge pull request #41 from zircote/refactor/full-codebase-2026-03-19
refactor: improve API design, CI enforcement, and test coverage
2 parents b5da71c + f8d5417 commit 88c3135

7 files changed

Lines changed: 231 additions & 33 deletions

File tree

.github/workflows/ci-coverage.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,12 @@ jobs:
136136
env:
137137
COVERAGE: ${{ steps.coverage.outputs.percentage }}
138138
run: |
139-
THRESHOLD=80
139+
THRESHOLD=90
140140
141141
if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) ))
142142
then
143-
echo "Coverage ${COVERAGE}% is below ${THRESHOLD}%"
144-
echo "Consider adding more tests."
143+
echo "Coverage ${COVERAGE}% is below ${THRESHOLD}% — failing CI."
144+
exit 1
145145
else
146146
echo "Coverage ${COVERAGE}% meets ${THRESHOLD}%"
147147
fi

.github/workflows/pipeline.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ jobs:
198198
run: |
199199
set -euo pipefail
200200
HASHES=$(sha256sum \
201-
target/release/rust-template \
201+
target/release/rust_template \
202202
| base64 -w0)
203203
echo "hashes=${HASHES}" >> "$GITHUB_OUTPUT"
204204

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ USER nonroot:nonroot
3939

4040
# Health check
4141
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
42-
CMD ["/usr/local/bin/rust_template", "--version"]
42+
CMD ["/usr/local/bin/rust_template"]
4343

4444
# Run the binary
4545
ENTRYPOINT ["/usr/local/bin/rust_template"]

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ use rust_template::{add, divide, Config};
4545
fn main() -> Result<(), rust_template::Error> {
4646
// Basic arithmetic
4747
let sum = add(2, 3);
48-
println!("2 + 3 = {}", sum);
48+
println!("2 + 3 = {sum}");
4949

5050
// Safe division with error handling
5151
let quotient = divide(10, 2)?;
52-
println!("10 / 2 = {}", quotient);
52+
println!("10 / 2 = {quotient}");
5353

5454
// Using configuration builder
5555
let config = Config::new()
@@ -58,7 +58,7 @@ fn main() -> Result<(), rust_template::Error> {
5858
.with_timeout(60);
5959

6060
println!("Config: verbose={}, retries={}, timeout={}s",
61-
config.verbose, config.max_retries, config.timeout_secs);
61+
config.verbose(), config.max_retries(), config.timeout_secs());
6262

6363
Ok(())
6464
}

crates/lib.rs

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use thiserror::Error;
44

55
/// Error type for `rust_template` operations.
66
#[derive(Error, Debug)]
7+
#[non_exhaustive]
78
pub enum Error {
89
/// Invalid input was provided.
910
#[error("invalid input: {0}")]
@@ -55,11 +56,12 @@ pub const fn add(a: i64, b: i64) -> i64 {
5556
///
5657
/// # Returns
5758
///
58-
/// The quotient, or an error if `divisor` is zero.
59+
/// The quotient, or an error if `divisor` is zero or the operation overflows.
5960
///
6061
/// # Errors
6162
///
6263
/// Returns [`Error::InvalidInput`] if `divisor` is zero.
64+
/// Returns [`Error::OperationFailed`] if the division overflows.
6365
///
6466
/// # Examples
6567
///
@@ -73,18 +75,60 @@ pub fn divide(dividend: i64, divisor: i64) -> Result<i64> {
7375
if divisor == 0 {
7476
return Err(Error::InvalidInput("divisor cannot be zero".to_string()));
7577
}
76-
Ok(dividend / divisor)
78+
79+
dividend.checked_div(divisor).ok_or_else(|| Error::OperationFailed {
80+
operation: "divide".to_string(),
81+
cause: "overflow when dividing i64 values".to_string(),
82+
})
83+
}
84+
85+
/// Parses `input` as a non-negative integer and returns it.
86+
///
87+
/// # Arguments
88+
///
89+
/// * `input` - A string slice to parse as an `i64`.
90+
///
91+
/// # Returns
92+
///
93+
/// The parsed non-negative integer value on success.
94+
///
95+
/// # Errors
96+
///
97+
/// - Returns [`Error::InvalidInput`] if `input` cannot be parsed as an `i64`.
98+
/// - Returns [`Error::OperationFailed`] if the parsed value is negative.
99+
///
100+
/// # Examples
101+
///
102+
/// ```rust
103+
/// use rust_template::process;
104+
///
105+
/// assert_eq!(process("42").unwrap(), 42);
106+
/// assert_eq!(process("0").unwrap(), 0);
107+
/// assert!(process("abc").is_err());
108+
/// assert!(process("-1").is_err());
109+
/// ```
110+
pub fn process(input: &str) -> Result<i64> {
111+
let value = input
112+
.parse::<i64>()
113+
.map_err(|e| Error::InvalidInput(format!("not a valid integer: {e}")))?;
114+
115+
if value < 0 {
116+
return Err(Error::OperationFailed {
117+
operation: "process".to_string(),
118+
cause: format!("value {value} is negative"),
119+
});
120+
}
121+
122+
Ok(value)
77123
}
78124

79125
/// Configuration for the crate.
80126
#[derive(Debug, Clone)]
127+
#[non_exhaustive]
81128
pub struct Config {
82-
/// Enable verbose logging.
83-
pub verbose: bool,
84-
/// Maximum number of retries.
85-
pub max_retries: u32,
86-
/// Timeout in seconds.
87-
pub timeout_secs: u64,
129+
verbose: bool,
130+
max_retries: u32,
131+
timeout_secs: u64,
88132
}
89133

90134
impl Default for Config {
@@ -104,6 +148,24 @@ impl Config {
104148
}
105149
}
106150

151+
/// Returns whether verbose logging is enabled.
152+
#[must_use]
153+
pub const fn verbose(&self) -> bool {
154+
self.verbose
155+
}
156+
157+
/// Returns the maximum number of retries.
158+
#[must_use]
159+
pub const fn max_retries(&self) -> u32 {
160+
self.max_retries
161+
}
162+
163+
/// Returns the timeout in seconds.
164+
#[must_use]
165+
pub const fn timeout_secs(&self) -> u64 {
166+
self.timeout_secs
167+
}
168+
107169
/// Sets the verbose flag.
108170
#[must_use]
109171
pub const fn with_verbose(mut self, verbose: bool) -> Self {
@@ -148,7 +210,6 @@ mod tests {
148210
#[test]
149211
fn test_divide_by_zero() {
150212
let result = divide(10, 0);
151-
assert!(result.is_err());
152213
assert!(matches!(result, Err(Error::InvalidInput(ref msg)) if msg.contains("zero")));
153214
}
154215

@@ -159,17 +220,39 @@ mod tests {
159220
.with_max_retries(5)
160221
.with_timeout(60);
161222

162-
assert!(config.verbose);
163-
assert_eq!(config.max_retries, 5);
164-
assert_eq!(config.timeout_secs, 60);
223+
assert!(config.verbose());
224+
assert_eq!(config.max_retries(), 5);
225+
assert_eq!(config.timeout_secs(), 60);
165226
}
166227

167228
#[test]
168229
fn test_config_default() {
169230
let config = Config::default();
170-
assert!(!config.verbose);
171-
assert_eq!(config.max_retries, 3);
172-
assert_eq!(config.timeout_secs, 30);
231+
assert!(!config.verbose());
232+
assert_eq!(config.max_retries(), 3);
233+
assert_eq!(config.timeout_secs(), 30);
234+
}
235+
236+
#[test]
237+
fn test_process_valid() {
238+
assert_eq!(process("42").unwrap(), 42);
239+
assert_eq!(process("0").unwrap(), 0);
240+
assert_eq!(process("100").unwrap(), 100);
241+
}
242+
243+
#[test]
244+
fn test_process_invalid_input() {
245+
let result = process("abc");
246+
assert!(matches!(result, Err(Error::InvalidInput(_))));
247+
}
248+
249+
#[test]
250+
fn test_process_negative() {
251+
let result = process("-1");
252+
assert!(matches!(
253+
result,
254+
Err(Error::OperationFailed { ref operation, .. }) if operation == "process"
255+
));
173256
}
174257

175258
#[test]

crates/main.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rust_template::{Config, add, divide};
1010
fn run() -> Result<(), rust_template::Error> {
1111
let config = Config::new().with_verbose(true);
1212

13-
if config.verbose {
13+
if config.verbose() {
1414
eprintln!("Running rust_template with verbose mode enabled");
1515
}
1616

@@ -33,3 +33,23 @@ fn main() -> ExitCode {
3333
},
3434
}
3535
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
41+
#[test]
42+
fn test_run_succeeds() {
43+
let result = run();
44+
assert!(
45+
result.is_ok(),
46+
"run() should succeed with the default implementation"
47+
);
48+
}
49+
50+
#[test]
51+
fn test_main_returns_success() {
52+
let exit_code = main();
53+
assert_eq!(exit_code, ExitCode::SUCCESS);
54+
}
55+
}

0 commit comments

Comments
 (0)