From f15a31a40c055215a71d02c60f21b153fb4f4be0 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 31 Jan 2026 14:25:23 -0500 Subject: [PATCH 1/3] feat: migrate to github actions --- .github/workflows/ci.yml | 70 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 32 ------------------ 2 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cf7f2f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: + - master + - 'ft*' + pull_request: + branches: + - master + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + name: Build, Lint, and Test + runs-on: ubuntu-latest + container: + image: rust:alpine + steps: + - name: Install Alpine dependencies + run: apk add --no-cache git musl-dev + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust components + run: rustup component add clippy rustfmt + + - name: Cache Cargo dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-alpine-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Check formatting + run: cargo fmt -- --check + + - name: Lint with Clippy + run: cargo clippy -- -D warnings + + - name: Run Tests + run: cargo test --verbose + + publish: + name: Publish to Crates.io + runs-on: ubuntu-latest + needs: test + environment: master + if: github.ref == 'refs/heads/master' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Build Release + run: cargo build --release + + - name: Publish to Crates.io + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish --token $CARGO_REGISTRY_TOKEN diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b64698f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: rust -cache: cargo -sudo: true - -env: - global: - - RUSTFLAGS="-C link-dead-code" - -addons: - apt: - packages: - - libcurl4-openssl-dev - - libelf-dev - - libdw-dev - - cmake - - gcc - - binutils-dev - - libiberty-dev -after_success: | - wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && - tar xzf master.tar.gz && - cd kcov-master && - mkdir build && - cd build && - cmake .. && - make && - make install DESTDIR=../../kcov-build && - cd ../.. && - rm -rf kcov-master && - for file in target/debug/pg_interval-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && - bash <(curl -s https://codecov.io/bash) && - echo "Uploaded code coverage" From 3109f2e9a51f46fa64980ef2d0e2794345c6b325 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 31 Jan 2026 14:34:26 -0500 Subject: [PATCH 2/3] style: fix clippy issues --- src/integrations/duration.rs | 37 +++++++++++++++---------------- src/integrations/mod.rs | 2 +- src/integrations/rust_postgres.rs | 2 +- src/interval_fmt/iso_8601.rs | 4 ++-- src/interval_fmt/mod.rs | 14 ++++++------ src/interval_fmt/postgres.rs | 12 +++++----- src/interval_norm.rs | 4 ++-- src/interval_parse/iso_8601.rs | 17 +++++++------- src/interval_parse/postgres.rs | 17 +++++++------- src/pg_interval.rs | 5 ++--- src/pg_interval_add.rs | 7 +++--- src/pg_interval_sub.rs | 7 +++--- 12 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/integrations/duration.rs b/src/integrations/duration.rs index 2b03b13..4962607 100644 --- a/src/integrations/duration.rs +++ b/src/integrations/duration.rs @@ -5,22 +5,22 @@ const NANOS_PER_SEC: i64 = 1_000_000_000; const NANOS_PER_MICRO: i64 = 1000; impl Interval { - /// Tries to convert from the `Duration` type to a `Interval`. Will - /// return `None` on a overflow. This is a lossy conversion in that + /// Tries to convert from the `Duration` type to a `Interval`. Will + /// return `None` on a overflow. This is a lossy conversion in that /// any units smaller than a microsecond will be lost. pub fn from_duration(duration: Duration) -> Option { - let mut days = duration.num_days(); - let mut new_dur = duration - Duration::days(days); - let mut hours = duration.num_hours(); - new_dur = new_dur - Duration::hours(hours); - let minutes = new_dur.num_minutes(); - new_dur = new_dur - Duration::minutes(minutes); + let mut days = duration.num_days(); + let mut new_dur = duration - Duration::days(days); + let mut hours = duration.num_hours(); + new_dur -= Duration::hours(hours); + let minutes = new_dur.num_minutes(); + new_dur -= Duration::minutes(minutes); let nano_secs = new_dur.num_nanoseconds()?; - if days > (i32::max_value() as i64) { - let overflow_days = days - (i32::max_value() as i64); + if days > (i32::MAX as i64) { + let overflow_days = days - (i32::MAX as i64); let added_hours = overflow_days.checked_mul(24)?; - hours = hours.checked_add(added_hours)?; - days -= overflow_days; + hours = hours.checked_add(added_hours)?; + days -= overflow_days; } let (seconds, remaining_nano) = reduce_by_units(nano_secs, NANOS_PER_SEC); // We have to discard any remaining nanoseconds @@ -39,9 +39,9 @@ impl Interval { } fn reduce_by_units(nano_secs: i64, unit: i64) -> (i64, i64) { - let new_time_unit = (nano_secs - (nano_secs % unit)) / unit; + let new_time_unit = (nano_secs - (nano_secs % unit)) / unit; let remaining_nano = nano_secs - (new_time_unit * unit); - (new_time_unit, remaining_nano) + (new_time_unit, remaining_nano) } #[cfg(test)] @@ -49,12 +49,11 @@ mod tests { use super::*; use chrono::Duration; - #[test] fn can_convert_small_amount_of_days() { let dur = Duration::days(5); let interval = Interval::from_duration(dur); - assert_eq!(interval, Some(Interval::new(0,5,0))) + assert_eq!(interval, Some(Interval::new(0, 5, 0))) } #[test] @@ -68,13 +67,13 @@ mod tests { fn can_convert_small_amount_of_secs() { let dur = Duration::seconds(1); let interval = Interval::from_duration(dur); - assert_eq!(interval, Some(Interval::new(0,0,1_000_000))) + assert_eq!(interval, Some(Interval::new(0, 0, 1_000_000))) } #[test] fn can_convert_one_micro() { let dur = Duration::nanoseconds(1000); let interval = Interval::from_duration(dur); - assert_eq!(interval, Some(Interval::new(0,0,1))) + assert_eq!(interval, Some(Interval::new(0, 0, 1))) } -} \ No newline at end of file +} diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs index 9cebf41..1cdf2e3 100644 --- a/src/integrations/mod.rs +++ b/src/integrations/mod.rs @@ -1,2 +1,2 @@ +mod duration; mod rust_postgres; -mod duration; \ No newline at end of file diff --git a/src/integrations/rust_postgres.rs b/src/integrations/rust_postgres.rs index 9ad0bc5..a841a7c 100644 --- a/src/integrations/rust_postgres.rs +++ b/src/integrations/rust_postgres.rs @@ -1,7 +1,7 @@ use crate::Interval; use bytes::{Buf, BufMut, BytesMut}; -use std::error::Error; use postgres_types::{to_sql_checked, FromSql, IsNull, ToSql, Type}; +use std::error::Error; impl<'a> FromSql<'a> for Interval { fn from_sql(_: &Type, mut raw: &'a [u8]) -> Result> { diff --git a/src/interval_fmt/iso_8601.rs b/src/interval_fmt/iso_8601.rs index cfcb888..e261386 100644 --- a/src/interval_fmt/iso_8601.rs +++ b/src/interval_fmt/iso_8601.rs @@ -36,8 +36,8 @@ impl IntervalNorm { if self.days != 0 { day_interval.push_str(&format!("{}D", self.days)); } - year_interval.push_str(&*day_interval); - year_interval.push_str(&*time_interval); + year_interval.push_str(&day_interval); + year_interval.push_str(&time_interval); year_interval } } diff --git a/src/interval_fmt/mod.rs b/src/interval_fmt/mod.rs index 9a8577f..16b8e0f 100644 --- a/src/interval_fmt/mod.rs +++ b/src/interval_fmt/mod.rs @@ -7,7 +7,7 @@ use std::ops::Neg; /// Safely maps a i64 value to a unsigned number /// without any overflow issues. fn safe_abs_u64(mut num: i64) -> u64 { - let max = i64::max_value(); + let max = i64::MAX; let max_min = max.neg(); if num <= max_min { let result = max as u64; @@ -15,14 +15,14 @@ fn safe_abs_u64(mut num: i64) -> u64 { num *= -1; result + num as u64 } else { - num.abs() as u64 + num.unsigned_abs() } } /// Safely maps a i32 value to a unsigned number /// without any overflow issues. fn safe_abs_u32(mut num: i32) -> u32 { - let max = i32::max_value(); + let max = i32::MAX; let max_min = max.neg(); if num <= max_min { let result = max as u32; @@ -30,7 +30,7 @@ fn safe_abs_u32(mut num: i32) -> u32 { num *= -1; result + num as u32 } else { - num.abs() as u32 + num.unsigned_abs() } } @@ -41,7 +41,7 @@ fn pad_i64(val: i64) -> String { } else { val as u64 }; - return format!("{:02}", num); + format!("{:02}", num) } #[cfg(test)] @@ -50,7 +50,7 @@ mod tests { #[test] fn abs_safe_u32() { - let min = i32::min_value(); + let min = i32::MIN; let actual = safe_abs_u32(min); let expected = 2147483648; assert_eq!(actual, expected); @@ -58,7 +58,7 @@ mod tests { #[test] fn abs_safe_u64() { - let min = i64::min_value(); + let min = i64::MIN; let actual = safe_abs_u64(min); let expected = 9_223_372_036_854_775_808; assert_eq!(actual, expected); diff --git a/src/interval_fmt/postgres.rs b/src/interval_fmt/postgres.rs index 7899a8c..66d8127 100644 --- a/src/interval_fmt/postgres.rs +++ b/src/interval_fmt/postgres.rs @@ -14,14 +14,14 @@ impl IntervalNorm { } if self.is_year_month_present() { if self.years != 0 { - year_interval.push_str(&*format!("{:#?} year ", self.years)) + year_interval.push_str(&format!("{:#?} year ", self.years)) } if self.months != 0 { - year_interval.push_str(&*format!("{:#?} mons ", self.months)); + year_interval.push_str(&format!("{:#?} mons ", self.months)); } } - year_interval.push_str(&*day_interval); - year_interval.push_str(&*time_interval); + year_interval.push_str(&day_interval); + year_interval.push_str(&time_interval); year_interval.trim().to_owned() } @@ -35,7 +35,7 @@ impl IntervalNorm { }; let hours = super::pad_i64(self.hours); time_interval.push_str( - &*(sign + &(sign + &hours + ":" + &super::pad_i64(self.minutes) @@ -43,7 +43,7 @@ impl IntervalNorm { + &super::pad_i64(self.seconds)), ); if self.microseconds != 0 { - time_interval.push_str(&*format!(".{:06}", super::safe_abs_u64(self.microseconds))) + time_interval.push_str(&format!(".{:06}", super::safe_abs_u64(self.microseconds))) } } time_interval diff --git a/src/interval_norm.rs b/src/interval_norm.rs index 0482c98..e9afc43 100644 --- a/src/interval_norm.rs +++ b/src/interval_norm.rs @@ -10,7 +10,7 @@ pub struct IntervalNorm { pub microseconds: i64, } -impl<'a> From<&'a Interval> for IntervalNorm { +impl From<&Interval> for IntervalNorm { fn from(val: &Interval) -> IntervalNorm { // grab the base values from the interval let months = val.months; @@ -62,7 +62,7 @@ impl IntervalNorm { })?, days: self.days, microseconds: microseconds - .ok_or_else(|| ParseError::from_time("Invalid time interval overflow detected."))?, + .ok_or_else(|| ParseError::from_time("Invalid time interval overflow detected."))?, }) } diff --git a/src/interval_parse/iso_8601.rs b/src/interval_parse/iso_8601.rs index 76f15f6..489f4ba 100644 --- a/src/interval_parse/iso_8601.rs +++ b/src/interval_parse/iso_8601.rs @@ -1,8 +1,8 @@ +use super::parse_error::ParseError; use super::{ scale_date, scale_time, DAYS_PER_MONTH, HOURS_PER_DAY, MICROS_PER_SECOND, MINUTES_PER_HOUR, MONTHS_PER_YEAR, SECONDS_PER_MIN, }; -use super::parse_error::ParseError; use crate::{interval_norm::IntervalNorm, Interval}; enum ParserCode { @@ -17,7 +17,7 @@ impl Interval { let delim = vec!['Y', 'M', 'D', 'H', 'S']; let mut number = String::new(); let mut interval_norm = IntervalNorm::default(); - if iso_str.rfind('P').map_or(false, |v| v == 1) { + if iso_str.rfind('P') == Some(1) { Err(ParseError::from_invalid_interval( "Invalid format must start with P.", )) @@ -102,9 +102,9 @@ impl Interval { } fn consume_number<'a>(val: &'a char, number: &'a mut String, delim: &[char]) -> ParserCode { - let is_first_char = number.is_empty() && *val == '-'; + let is_first_char = number.is_empty() && *val == '-'; let is_period_char = !number.is_empty() && *val == '.'; - if val.is_digit(10) || is_first_char || is_period_char { + if val.is_ascii_digit() || is_first_char || is_period_char { number.push(*val); ParserCode::Good } else if delim.contains(val) { @@ -116,7 +116,7 @@ fn consume_number<'a>(val: &'a char, number: &'a mut String, delim: &[char]) -> fn parse_number(number: &mut String) -> Result { let parse_num = number.parse::()?; - if parse_num > i32::max_value() as f64 { + if parse_num > i32::MAX as f64 { Err(ParseError::from_invalid_interval("Exceeded max value")) } else { *number = "".to_owned(); @@ -257,19 +257,19 @@ mod tests { #[test] fn test_from_8601_19() { let interval = Interval::from_iso("PTT"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] fn test_from_8601_20() { let interval = Interval::from_iso("PT-"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] fn test_from_8601_21() { let interval = Interval::from_iso("PT10"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] @@ -299,5 +299,4 @@ mod tests { let interval_exp = Interval::new(0, 0, 10000000); assert_eq!(interval, interval_exp); } - } diff --git a/src/interval_parse/postgres.rs b/src/interval_parse/postgres.rs index ed25a33..ae41c83 100644 --- a/src/interval_parse/postgres.rs +++ b/src/interval_parse/postgres.rs @@ -7,11 +7,11 @@ use super::{ }; impl Interval { - pub fn from_postgres(iso_str: &str) -> Result { + pub fn from_postgres(iso_str: &str) -> Result { let mut delim = vec![ "years", "months", "mons", "days", "hours", "minutes", "seconds", ]; - let mut time_tokens = iso_str.split(' ').collect::>(); // clean up empty values caused by n spaces between values. + let mut time_tokens = iso_str.split(' ').collect::>(); // clean up empty values caused by n spaces between values. time_tokens.retain(|&token| !token.is_empty()); // since there might not be space between the delim and the // value we need to scan each token. @@ -88,11 +88,11 @@ fn split_token(val: &str) -> Result<(String, String), ParseError> { } /// Consume the token parts and add to the normalized interval. -fn consume_token<'a>( +fn consume_token( interval: &mut IntervalNorm, val: f64, delim: String, - delim_list: &mut Vec<&'a str>, + delim_list: &mut Vec<&str>, ) -> Result<(), ParseError> { // Unlike iso8601 the delimiter can only appear once // so we need to check if the token can be found in @@ -325,19 +325,19 @@ mod tests { #[test] fn test_from_postgres_24() { let interval = Interval::from_postgres("years 1"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] fn test_from_postgres_25() { let interval = Interval::from_postgres("- years"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] fn test_from_postgres_26() { let interval = Interval::from_postgres("10"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } #[test] @@ -364,7 +364,6 @@ mod tests { #[test] fn test_from_postgres_30() { let interval = Interval::from_postgres("!"); - assert_eq!(interval.is_err(), true); + assert!(interval.is_err()); } - } diff --git a/src/pg_interval.rs b/src/pg_interval.rs index 1bafae4..34b8a08 100644 --- a/src/pg_interval.rs +++ b/src/pg_interval.rs @@ -56,7 +56,7 @@ mod tests { #[test] fn test_clone() { let interval = Interval::new(1, 1, 30); - let test_interval = interval.clone(); + let test_interval = interval; assert_eq!(interval, test_interval); } @@ -313,7 +313,7 @@ mod tests { } #[test] - fn test_postgres_19(){ + fn test_postgres_19() { let interval = Interval::new(0, 3, 0); let output = interval.to_postgres(); assert_eq!(String::from("3 days"), output); @@ -444,5 +444,4 @@ mod tests { let output = interval.to_sql(); assert_eq!(String::from("-1:10:15"), output); } - } diff --git a/src/pg_interval_add.rs b/src/pg_interval_add.rs index 2f1604f..c5eb87b 100644 --- a/src/pg_interval_add.rs +++ b/src/pg_interval_add.rs @@ -96,7 +96,7 @@ mod tests { #[test] fn test_checked_add_2() { let interval = Interval::new(13, 0, 0); - let interval_add = Interval::new(i32::max_value(), 1, 12); + let interval_add = Interval::new(i32::MAX, 1, 12); let result = interval.checked_add(interval_add); assert_eq!(result, None); } @@ -110,7 +110,7 @@ mod tests { #[test] fn test_checked_add_day_time_2() { - let interval = Interval::new(13, i32::max_value(), 0); + let interval = Interval::new(13, i32::MAX, 0); let result = interval.checked_add_day_time(200, 0, 0, 2.123456789); assert_eq!(result, None); } @@ -132,7 +132,7 @@ mod tests { #[test] fn checked_add_year_month_2() { let interval = Interval::new(15, 0, 0); - let result = interval.checked_add_year_month(i32::max_value(), 32); + let result = interval.checked_add_year_month(i32::MAX, 32); assert_eq!(result, None); } @@ -150,5 +150,4 @@ mod tests { let result = interval.add_day_time(2, 0, 0, 2.123456789); assert_eq!(result, Interval::new(13, 2, 2123456)); } - } diff --git a/src/pg_interval_sub.rs b/src/pg_interval_sub.rs index 21ea052..ed8baec 100644 --- a/src/pg_interval_sub.rs +++ b/src/pg_interval_sub.rs @@ -104,7 +104,7 @@ mod tests { #[test] fn test_checked_sub_day_time_2() { - let interval = Interval::new(13, i32::min_value(), 0); + let interval = Interval::new(13, i32::MIN, 0); println!("{:?}", interval.days); let result = interval.checked_sub_day_time(100, 0, 0, 2.12); println!("{:?}", result); @@ -128,7 +128,7 @@ mod tests { #[test] fn test_checked_sub_year_month_2() { let interval = Interval::new(-20, 0, 0); - let result = interval.checked_sub_year_month(i32::min_value(), 1); + let result = interval.checked_sub_year_month(i32::MIN, 1); assert_eq!(result, None); } @@ -151,9 +151,8 @@ mod tests { #[test] fn test_checked_sub_2() { let interval = Interval::new(-10, 0, 0); - let interval_sub = Interval::new(i32::max_value(), 0, 0); + let interval_sub = Interval::new(i32::MAX, 0, 0); let result = interval.checked_sub(interval_sub); assert_eq!(result, None); } - } From 72d63d02ddce5ac01f1f8b18f256818790309564 Mon Sep 17 00:00:00 2001 From: ryan Date: Sat, 31 Jan 2026 14:40:25 -0500 Subject: [PATCH 3/3] chore: bump minor version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4900ee9..440472c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pg_interval" -version = "0.4.2" +version = "0.4.3" edition = "2018" authors = ["Ryan Piper "] license = "MIT"