Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Deserialize implementation to the ser feature. #89

Merged
merged 1 commit into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Consider using zxcvbn as an algorithmic alternative to password composition poli
zxcvbn = "2"
```

zxcvbn has a "ser" feature flag you can enable if you require serialization support via `serde`.
zxcvbn has a "ser" feature flag you can enable if you require serialization/deserialization support via `serde`.
It is disabled by default to reduce bloat.

zxcvbn follows Semantic Versioning.
Expand Down
8 changes: 4 additions & 4 deletions src/feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::fmt;

/// A warning explains what's wrong with the password.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
#[allow(missing_docs)]
pub enum Warning {
StraightRowsOfKeysAreEasyToGuess,
Expand Down Expand Up @@ -69,7 +69,7 @@ impl fmt::Display for Warning {

/// A suggestion helps to choose a better password.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
#[allow(missing_docs)]
pub enum Suggestion {
UseAFewWordsAvoidCommonPhrases,
Expand Down Expand Up @@ -132,8 +132,8 @@ impl fmt::Display for Suggestion {
}

/// Verbal feedback to help choose better passwords
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct Feedback {
/// Explains what's wrong, e.g. "This is a top-10 common password". Not always set.
warning: Option<Warning>,
Expand Down
2 changes: 1 addition & 1 deletion src/frequency_lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const US_TV_AND_FILM: &str = "you,i,to,that,it,me,what,this,know,i'm,no,have,my,
const MALE_NAMES: &str = "james,john,robert,michael,william,david,richard,charles,joseph,thomas,christopher,daniel,paul,mark,donald,george,kenneth,steven,edward,brian,ronald,anthony,kevin,jason,matthew,gary,timothy,jose,larry,jeffrey,frank,scott,eric,stephen,andrew,raymond,gregory,joshua,jerry,dennis,walter,patrick,peter,harold,douglas,henry,carl,arthur,ryan,roger,joe,juan,jack,albert,jonathan,justin,terry,gerald,keith,samuel,willie,ralph,lawrence,nicholas,roy,benjamin,bruce,brandon,adam,harry,fred,wayne,billy,steve,louis,jeremy,aaron,randy,eugene,carlos,russell,bobby,victor,ernest,phillip,todd,jesse,craig,alan,shawn,clarence,sean,philip,chris,johnny,earl,jimmy,antonio,danny,bryan,tony,luis,mike,stanley,leonard,nathan,dale,manuel,rodney,curtis,norman,marvin,vincent,glenn,jeffery,travis,jeff,chad,jacob,melvin,alfred,kyle,francis,bradley,jesus,herbert,frederick,ray,joel,edwin,don,eddie,ricky,troy,randall,barry,bernard,mario,leroy,francisco,marcus,micheal,theodore,clifford,miguel,oscar,jay,jim,tom,calvin,alex,jon,ronnie,bill,lloyd,tommy,leon,derek,darrell,jerome,floyd,leo,alvin,tim,wesley,dean,greg,jorge,dustin,pedro,derrick,dan,zachary,corey,herman,maurice,vernon,roberto,clyde,glen,hector,shane,ricardo,sam,rick,lester,brent,ramon,tyler,gilbert,gene,marc,reginald,ruben,brett,nathaniel,rafael,edgar,milton,raul,ben,cecil,duane,andre,elmer,brad,gabriel,ron,roland,jared,adrian,karl,cory,claude,erik,darryl,neil,christian,javier,fernando,clinton,ted,mathew,tyrone,darren,lonnie,lance,cody,julio,kurt,allan,clayton,hugh,max,dwayne,dwight,armando,felix,jimmie,everett,ian,ken,bob,jaime,casey,alfredo,alberto,dave,ivan,johnnie,sidney,byron,julian,isaac,clifton,willard,daryl,virgil,andy,salvador,kirk,sergio,seth,kent,terrance,rene,eduardo,terrence,enrique,freddie,stuart,fredrick,arturo,alejandro,joey,nick,luther,wendell,jeremiah,evan,julius,donnie,otis,trevor,luke,homer,gerard,doug,kenny,hubert,angelo,shaun,lyle,matt,alfonso,orlando,rex,carlton,ernesto,pablo,lorenzo,omar,wilbur,blake,horace,roderick,kerry,abraham,rickey,ira,andres,cesar,johnathan,malcolm,rudolph,damon,kelvin,rudy,preston,alton,archie,marco,pete,randolph,garry,geoffrey,jonathon,felipe,bennie,gerardo,dominic,loren,delbert,colin,guillermo,earnest,benny,noel,rodolfo,myron,edmund,salvatore,cedric,lowell,gregg,sherman,devin,sylvester,roosevelt,israel,jermaine,forrest,wilbert,leland,simon,irving,owen,rufus,woodrow,sammy,kristopher,levi,marcos,gustavo,jake,lionel,marty,gilberto,clint,nicolas,laurence,ismael,orville,drew,ervin,dewey,wilfred,josh,hugo,ignacio,caleb,tomas,sheldon,erick,frankie,darrel,rogelio,terence,alonzo,elias,bert,elbert,ramiro,conrad,noah,grady,phil,cornelius,lamar,rolando,clay,percy,bradford,merle,darin,amos,terrell,moses,irvin,saul,roman,darnell,randal,tommie,timmy,darrin,brendan,toby,van,abel,dominick,emilio,elijah,cary,domingo,aubrey,emmett,marlon,emanuel,jerald,edmond,emil,dewayne,otto,teddy,reynaldo,bret,jess,trent,humberto,emmanuel,stephan,louie,vicente,lamont,garland,micah,efrain,heath,rodger,demetrius,ethan,eldon,rocky,pierre,eli,bryce,antoine,robbie,kendall,royce,sterling,grover,elton,cleveland,dylan,chuck,damian,reuben,stan,leonardo,russel,erwin,benito,hans,monte,blaine,ernie,curt,quentin,agustin,jamal,devon,adolfo,tyson,wilfredo,bart,jarrod,vance,denis,damien,joaquin,harlan,desmond,elliot,darwin,gregorio,kermit,roscoe,esteban,anton,solomon,norbert,elvin,nolan,carey,rod,quinton,hal,brain,rob,elwood,kendrick,darius,moises,marlin,fidel,thaddeus,cliff,marcel,ali,raphael,bryon,armand,alvaro,jeffry,dane,joesph,thurman,ned,sammie,rusty,michel,monty,rory,fabian,reggie,kris,isaiah,gus,avery,loyd,diego,adolph,millard,rocco,gonzalo,derick,rodrigo,gerry,rigoberto,alphonso,rickie,noe,vern,elvis,bernardo,mauricio,hiram,donovan,basil,nickolas,scot,vince,quincy,eddy,sebastian,federico,ulysses,heriberto,donnell,denny,gavin,emery,romeo,jayson,dion,dante,clement,coy,odell,jarvis,bruno,issac,dudley,sanford,colby,carmelo,nestor,hollis,stefan,donny,linwood,beau,weldon,galen,isidro,truman,delmar,johnathon,silas,frederic,irwin,merrill,charley,marcelino,carlo,trenton,kurtis,aurelio,winfred,vito,collin,denver,leonel,emory,pasquale,mohammad,mariano,danial,landon,dirk,branden,adan,numbers,clair,buford,bernie,wilmer,emerson,zachery,jacques,errol,josue,edwardo,wilford,theron,raymundo,daren,tristan,robby,lincoln,jame,genaro,octavio,cornell,hung,arron,antony,herschel,alva,giovanni,garth,cyrus,cyril,ronny,stevie,lon,kennith,carmine,augustine,erich,chadwick,wilburn,russ,myles,jonas,mitchel,mervin,zane,jamel,lazaro,alphonse,randell,johnie,jarrett,ariel,abdul,dusty,luciano,seymour,scottie,eugenio,mohammed,arnulfo,lucien,ferdinand,thad,ezra,aldo,rubin,mitch,earle,abe,marquis,lanny,kareem,jamar,boris,isiah,emile,elmo,aron,leopoldo,everette,josef,eloy,dorian,rodrick,reinaldo,lucio,jerrod,weston,hershel,lemuel,lavern,burt,jules,gil,eliseo,ahmad,nigel,efren,antwan,alden,margarito,refugio,dino,osvaldo,les,deandre,normand,kieth,ivory,trey,norberto,napoleon,jerold,fritz,rosendo,milford,sang,deon,christoper,alfonzo,lyman,josiah,brant,wilton,rico,jamaal,dewitt,brenton,yong,olin,faustino,claudio,judson,gino,edgardo,alec,jarred,donn,trinidad,tad,porfirio,odis,lenard,chauncey,tod,mel,marcelo,kory,augustus,keven,hilario,bud,sal,orval,mauro,dannie,zachariah,olen,anibal,milo,jed,thanh,amado,lenny,tory,richie,horacio,brice,mohamed,delmer,dario,mac,jonah,jerrold,robt,hank,sung,rupert,rolland,kenton,damion,chi,antone,waldo,fredric,bradly,kip,burl,tyree,jefferey,ahmed,willy,stanford,oren,moshe,mikel,enoch,brendon,quintin,jamison,florencio,darrick,tobias,minh,hassan,giuseppe,demarcus,cletus,tyrell,lyndon,keenan,werner,theo,geraldo,columbus,chet,bertram,markus,huey,hilton,dwain,donte,tyron,omer,isaias,hipolito,fermin,chung,adalberto,jamey,teodoro,mckinley,maximo,raleigh,lawerence,abram,rashad,emmitt,daron,chong,samual,otha,miquel,eusebio,dong,domenic,darron,wilber,renato,hoyt,haywood,ezekiel,chas,florentino,elroy,clemente,arden,neville,edison,deshawn,carrol,shayne,nathanial,jordon,danilo,claud,sherwood,raymon,rayford,cristobal,ambrose,titus,hyman,felton,ezequiel,erasmo,lonny,milan,lino,jarod,herb,andreas,rhett,jude,douglass,cordell,oswaldo,ellsworth,virgilio,toney,nathanael,benedict,mose,hong,isreal,garret,fausto,arlen,zack,modesto,francesco,manual,gaylord,gaston,filiberto,deangelo,michale,granville,malik,zackary,tuan,nicky,cristopher,antione,malcom,korey,jospeh,colton,waylon,hosea,shad,santo,rudolf,rolf,renaldo,marcellus,lucius,kristofer,harland,arnoldo,rueben,leandro,kraig,jerrell,jeromy,hobert,cedrick,arlie,winford,wally,luigi,keneth,jacinto,graig,franklyn,edmundo,leif,jeramy,willian,vincenzo,shon,michal,lynwood,jere,elden,darell,broderick,alonso";

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub enum DictionaryType {
#[default]
Passwords,
Expand Down
59 changes: 57 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub mod matching;
mod scoring;
pub mod time_estimates;

#[cfg(feature = "ser")]
mod serialization_utils;

#[cfg(not(target_arch = "wasm32"))]
fn time_scoped<F, R>(f: F) -> (R, Duration)
where
Expand Down Expand Up @@ -78,12 +81,16 @@ where
}

/// Contains the results of an entropy calculation
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct Entropy {
/// Estimated guesses needed to crack the password
guesses: u64,
/// Order of magnitude of `guesses`
#[cfg_attr(
feature = "ser",
serde(deserialize_with = "crate::serialization_utils::deserialize_f64_null_as_nan")
)]
guesses_log10: f64,
/// List of back-of-the-envelope crack time estimations based on a few scenarios.
crack_times: time_estimates::CrackTimes,
Expand Down Expand Up @@ -202,6 +209,54 @@ mod tests {
serde_json::to_string(&zxcvbn(&password, &inputs)).ok();
TestResult::from_bool(true)
}

#[cfg(feature = "ser")]
fn test_zxcvbn_serialization_roundtrip(password: String, user_inputs: Vec<String>) -> TestResult {
let inputs = user_inputs.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
let entropy = zxcvbn(&password, &inputs);
// When the entropy is not a finite number (otherwise our equality test fails). We test
// this scenario separately
if !entropy.guesses_log10.is_finite() {
//panic!("infinite guesses_log10: {} => {}", password, entropy.guesses_log10);
return TestResult::discard();
}
let serialized_entropy = serde_json::to_string(&entropy);
assert!(serialized_entropy.is_ok());
let serialized_entropy = serialized_entropy.expect("serialized entropy");
let deserialized_entropy = serde_json::from_str::<Entropy>(&serialized_entropy);
assert!(deserialized_entropy.is_ok());
let deserialized_entropy = deserialized_entropy.expect("deserialized entropy");

// Apply a mask to trim the last bit when comparing guesses_log10, since Serde loses
// precision when deserializing
const MASK: u64 = 0x1111111111111110;

let original_equal_to_deserialized_version =
(entropy.guesses == deserialized_entropy.guesses) &&
(entropy.crack_times == deserialized_entropy.crack_times) &&
(entropy.score == deserialized_entropy.score) &&
(entropy.feedback == deserialized_entropy.feedback) &&
(entropy.sequence == deserialized_entropy.sequence) &&
(entropy.calc_time == deserialized_entropy.calc_time) &&
(entropy.guesses_log10.to_bits() & MASK == deserialized_entropy.guesses_log10.to_bits() & MASK);

TestResult::from_bool(original_equal_to_deserialized_version)
}
}

#[test]
#[cfg(feature = "ser")]
fn test_zxcvbn_serialization_non_finite_guesses_log10() {
let entropy = zxcvbn("", &[]);
assert!(!entropy.guesses_log10.is_finite());

let serialized_entropy = serde_json::to_string(&entropy);
assert!(serialized_entropy.is_ok());
let serialized_entropy = serialized_entropy.expect("serialized entropy");
let deserialized_entropy = serde_json::from_str::<Entropy>(&serialized_entropy);
assert!(deserialized_entropy.is_ok());
let deserialized_entropy = deserialized_entropy.expect("deserialized entropy");
assert!(!deserialized_entropy.guesses_log10.is_finite());
}

#[cfg_attr(not(target_arch = "wasm32"), test)]
Expand Down
6 changes: 3 additions & 3 deletions src/matching/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct Match {
/// Beginning of the match.
pub i: usize,
Expand Down Expand Up @@ -517,7 +517,7 @@
("unicode", 26)
};
let pattern = MatchPattern::Sequence(SequencePattern {
sequence_name,
sequence_name: sequence_name.to_owned(),
sequence_space,
ascending: delta > 0,
});
Expand Down Expand Up @@ -570,7 +570,7 @@
for capture in regex.captures_iter(password) {
let m = capture.get(0).unwrap();
let pattern = MatchPattern::Regex(RegexPattern {
regex_name: name,
regex_name: name.to_owned(),
regex_match: capture
.iter()
.map(|x| x.unwrap().as_str().to_string())
Expand Down Expand Up @@ -990,7 +990,7 @@
} else {
panic!("Wrong match pattern")
};
assert_eq!(p.reversed, true);

Check warning on line 993 in src/matching/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

used `assert_eq!` with a literal bool
}
}

Expand Down Expand Up @@ -1063,7 +1063,7 @@
} else {
panic!("Wrong match pattern")
};
assert_eq!(p.l33t, true);

Check warning on line 1066 in src/matching/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

used `assert_eq!` with a literal bool
}
}

Expand All @@ -1082,7 +1082,7 @@
} else {
panic!("Wrong match pattern")
};
assert_eq!(p.l33t, true);

Check warning on line 1085 in src/matching/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

used `assert_eq!` with a literal bool
}
}

Expand Down Expand Up @@ -1208,7 +1208,7 @@
panic!("Wrong match pattern")
};
assert_eq!(p.sequence_name, "lower");
assert_eq!(p.ascending, false);

Check warning on line 1211 in src/matching/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

used `assert_eq!` with a literal bool
}

#[test]
Expand Down
18 changes: 9 additions & 9 deletions src/matching/patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::collections::HashMap;

/// Pattern type used to detect a match
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "ser", serde(tag = "pattern"))]
#[cfg_attr(feature = "ser", serde(rename_all = "lowercase"))]
pub enum MatchPattern {
Expand Down Expand Up @@ -44,7 +44,7 @@ impl MatchPattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct DictionaryPattern {
/// Word that has been found in a dictionary.
pub matched_word: String,
Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct DictionaryPattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct SpatialPattern {
/// Name of the graph for which a spatial match has been found.
pub graph: String,
Expand All @@ -86,7 +86,7 @@ pub struct SpatialPattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct RepeatPattern {
/// Base token that repeats in the matched pattern.
pub base_token: String,
Expand All @@ -102,10 +102,10 @@ pub struct RepeatPattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct SequencePattern {
/// Name of the sequence that was matched.
pub sequence_name: &'static str,
pub sequence_name: String,
/// Size of the sequence that was matched.
pub sequence_space: u8,
/// Whether the matched sequence is ascending.
Expand All @@ -116,10 +116,10 @@ pub struct SequencePattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct RegexPattern {
/// Name of the regular expression that was matched.
pub regex_name: &'static str,
pub regex_name: String,
/// Matches of the regular expression.
pub regex_match: Vec<String>,
}
Expand All @@ -128,7 +128,7 @@ pub struct RegexPattern {
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builder", derive(Builder))]
#[cfg_attr(feature = "builder", builder(default))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
pub struct DatePattern {
/// Separator of a date that was matched.
pub separator: String,
Expand Down
14 changes: 7 additions & 7 deletions src/scoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,9 @@
impl Estimator for RegexPattern {
fn estimate(&mut self, token: &str) -> u64 {
if CHAR_CLASS_BASES.keys().any(|x| *x == self.regex_name) {
CHAR_CLASS_BASES[self.regex_name].pow(token.chars().count() as u32)
CHAR_CLASS_BASES[&*self.regex_name].pow(token.chars().count() as u32)
} else {
match self.regex_name {
match &*self.regex_name {
"recent_year" => {
let year_space =
(self.regex_match[0].parse::<i32>().unwrap() - *REFERENCE_YEAR).abs();
Expand Down Expand Up @@ -561,7 +561,7 @@

quickcheck! {
fn test_n_ck_mul_overflow(n: usize, k: usize) -> TestResult {
if n >= 63 && n <= 100 {

Check warning on line 564 in src/scoring.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

manual `RangeInclusive::contains` implementation
scoring::n_ck(n, k); // Must not panic
TestResult::from_bool(true)
} else {
Expand Down Expand Up @@ -829,7 +829,7 @@
fn test_regex_guesses_lowercase() {
let token = "aizocdk";
let mut p = RegexPattern {
regex_name: "alpha_lower",
regex_name: "alpha_lower".to_owned(),
regex_match: vec![token.to_string()],
};
assert_eq!(p.estimate(token), 26u64.pow(7));
Expand All @@ -839,7 +839,7 @@
fn test_regex_guesses_alphanumeric() {
let token = "ag7C8";
let mut p = RegexPattern {
regex_name: "alphanumeric",
regex_name: "alphanumeric".to_owned(),
regex_match: vec![token.to_string()],
};
assert_eq!(p.estimate(token), 62u64.pow(5));
Expand All @@ -849,12 +849,12 @@
fn test_regex_guesses_distant_year() {
let token = "1972";
let mut p = RegexPattern {
regex_name: "recent_year",
regex_name: "recent_year".to_owned(),
regex_match: vec![token.to_string()],
};
assert_eq!(
p.estimate(token),
(*scoring::REFERENCE_YEAR - 1972).abs() as u64

Check warning on line 857 in src/scoring.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

casting the result of `i32::abs()` to u64
);
}

Expand All @@ -862,7 +862,7 @@
fn test_regex_guesses_recent_year() {
let token = "2005";
let mut p = RegexPattern {
regex_name: "recent_year",
regex_name: "recent_year".to_owned(),
regex_match: vec![token.to_string()],
};
assert_eq!(p.estimate(token), scoring::MIN_YEAR_SPACE as u64);
Expand All @@ -872,7 +872,7 @@
fn test_regex_guesses_current_year() {
let token = time::OffsetDateTime::now_utc().year().to_string();
let mut p = RegexPattern {
regex_name: "recent_year",
regex_name: "recent_year".to_owned(),
regex_match: vec![token.to_string()],
};
assert_eq!(p.estimate(&token), scoring::MIN_YEAR_SPACE as u64);
Expand All @@ -889,7 +889,7 @@
let token = "1123";
assert_eq!(
p.estimate(token),
365 * (*scoring::REFERENCE_YEAR - p.year).abs() as u64

Check warning on line 892 in src/scoring.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

casting the result of `i32::abs()` to u64
);
}

Expand Down Expand Up @@ -917,7 +917,7 @@
let base_guesses = *scoring::KEYBOARD_STARTING_POSITIONS
* *scoring::KEYBOARD_AVERAGE_DEGREE
* (token.len() - 1) as u64;
assert_eq!(p.estimate(token), base_guesses as u64);

Check warning on line 920 in src/scoring.rs

View workflow job for this annotation

GitHub Actions / clippy-rustfmt

casting to the same type is unnecessary (`u64` -> `u64`)
}

#[test]
Expand Down
8 changes: 8 additions & 0 deletions src/serialization_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::de::{Deserialize, Deserializer};

pub(crate) fn deserialize_f64_null_as_nan<'de, D: Deserializer<'de>>(
des: D,
) -> Result<f64, D::Error> {
let optional = Option::<f64>::deserialize(des)?;
Ok(optional.unwrap_or(f64::NAN))
}
Loading
Loading