Skip to content

Commit 38410db

Browse files
Emmshssoichiro
authored andcommitted
Add Deserialize implementation to the ser feature.
Addresses #74. This is a non-backward compatible change, as the commit changes `Pattern.sequence_name` and `RegexPattern.regex_name` from `&'static str` to `String` in order to avoid static bounds during deserialization.
1 parent 7af59fd commit 38410db

9 files changed

+92
-29
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Consider using zxcvbn as an algorithmic alternative to password composition poli
2222
zxcvbn = "2"
2323
```
2424

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

2828
zxcvbn follows Semantic Versioning.

src/feedback.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::fmt;
1010

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

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

134134
/// Verbal feedback to help choose better passwords
135-
#[derive(Debug, Clone, Default)]
136-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
135+
#[derive(Debug, PartialEq, Clone, Default)]
136+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
137137
pub struct Feedback {
138138
/// Explains what's wrong, e.g. "This is a top-10 common password". Not always set.
139139
warning: Option<Warning>,

src/frequency_lists.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const US_TV_AND_FILM: &str = "you,i,to,that,it,me,what,this,know,i'm,no,have,my,
88
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";
99

1010
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
11-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
11+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
1212
pub enum DictionaryType {
1313
#[default]
1414
Passwords,

src/lib.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ pub mod matching;
3030
mod scoring;
3131
pub mod time_estimates;
3232

33+
#[cfg(feature = "ser")]
34+
mod serialization_utils;
35+
3336
#[cfg(not(target_arch = "wasm32"))]
3437
fn time_scoped<F, R>(f: F) -> (R, Duration)
3538
where
@@ -78,12 +81,16 @@ where
7881
}
7982

8083
/// Contains the results of an entropy calculation
81-
#[derive(Debug, Clone)]
82-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
84+
#[derive(Debug, PartialEq, Clone)]
85+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
8386
pub struct Entropy {
8487
/// Estimated guesses needed to crack the password
8588
guesses: u64,
8689
/// Order of magnitude of `guesses`
90+
#[cfg_attr(
91+
feature = "ser",
92+
serde(deserialize_with = "crate::serialization_utils::deserialize_f64_null_as_nan")
93+
)]
8794
guesses_log10: f64,
8895
/// List of back-of-the-envelope crack time estimations based on a few scenarios.
8996
crack_times: time_estimates::CrackTimes,
@@ -202,6 +209,54 @@ mod tests {
202209
serde_json::to_string(&zxcvbn(&password, &inputs)).ok();
203210
TestResult::from_bool(true)
204211
}
212+
213+
#[cfg(feature = "ser")]
214+
fn test_zxcvbn_serialization_roundtrip(password: String, user_inputs: Vec<String>) -> TestResult {
215+
let inputs = user_inputs.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
216+
let entropy = zxcvbn(&password, &inputs);
217+
// When the entropy is not a finite number (otherwise our equality test fails). We test
218+
// this scenario separately
219+
if !entropy.guesses_log10.is_finite() {
220+
//panic!("infinite guesses_log10: {} => {}", password, entropy.guesses_log10);
221+
return TestResult::discard();
222+
}
223+
let serialized_entropy = serde_json::to_string(&entropy);
224+
assert!(serialized_entropy.is_ok());
225+
let serialized_entropy = serialized_entropy.expect("serialized entropy");
226+
let deserialized_entropy = serde_json::from_str::<Entropy>(&serialized_entropy);
227+
assert!(deserialized_entropy.is_ok());
228+
let deserialized_entropy = deserialized_entropy.expect("deserialized entropy");
229+
230+
// Apply a mask to trim the last bit when comparing guesses_log10, since Serde loses
231+
// precision when deserializing
232+
const MASK: u64 = 0x1111111111111110;
233+
234+
let original_equal_to_deserialized_version =
235+
(entropy.guesses == deserialized_entropy.guesses) &&
236+
(entropy.crack_times == deserialized_entropy.crack_times) &&
237+
(entropy.score == deserialized_entropy.score) &&
238+
(entropy.feedback == deserialized_entropy.feedback) &&
239+
(entropy.sequence == deserialized_entropy.sequence) &&
240+
(entropy.calc_time == deserialized_entropy.calc_time) &&
241+
(entropy.guesses_log10.to_bits() & MASK == deserialized_entropy.guesses_log10.to_bits() & MASK);
242+
243+
TestResult::from_bool(original_equal_to_deserialized_version)
244+
}
245+
}
246+
247+
#[test]
248+
#[cfg(feature = "ser")]
249+
fn test_zxcvbn_serialization_non_finite_guesses_log10() {
250+
let entropy = zxcvbn("", &[]);
251+
assert!(!entropy.guesses_log10.is_finite());
252+
253+
let serialized_entropy = serde_json::to_string(&entropy);
254+
assert!(serialized_entropy.is_ok());
255+
let serialized_entropy = serialized_entropy.expect("serialized entropy");
256+
let deserialized_entropy = serde_json::from_str::<Entropy>(&serialized_entropy);
257+
assert!(deserialized_entropy.is_ok());
258+
let deserialized_entropy = deserialized_entropy.expect("deserialized entropy");
259+
assert!(!deserialized_entropy.guesses_log10.is_finite());
205260
}
206261

207262
#[cfg_attr(not(target_arch = "wasm32"), test)]

src/matching/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::collections::HashMap;
1414
#[derive(Debug, Clone, PartialEq, Default)]
1515
#[cfg_attr(feature = "builder", derive(Builder))]
1616
#[cfg_attr(feature = "builder", builder(default))]
17-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
17+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
1818
pub struct Match {
1919
/// Beginning of the match.
2020
pub i: usize,
@@ -517,7 +517,7 @@ impl Matcher for SequenceMatch {
517517
("unicode", 26)
518518
};
519519
let pattern = MatchPattern::Sequence(SequencePattern {
520-
sequence_name,
520+
sequence_name: sequence_name.to_owned(),
521521
sequence_space,
522522
ascending: delta > 0,
523523
});
@@ -570,7 +570,7 @@ impl Matcher for RegexMatch {
570570
for capture in regex.captures_iter(password) {
571571
let m = capture.get(0).unwrap();
572572
let pattern = MatchPattern::Regex(RegexPattern {
573-
regex_name: name,
573+
regex_name: name.to_owned(),
574574
regex_match: capture
575575
.iter()
576576
.map(|x| x.unwrap().as_str().to_string())

src/matching/patterns.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::collections::HashMap;
44

55
/// Pattern type used to detect a match
66
#[derive(Debug, Clone, PartialEq, Default)]
7-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
7+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
88
#[cfg_attr(feature = "ser", serde(tag = "pattern"))]
99
#[cfg_attr(feature = "ser", serde(rename_all = "lowercase"))]
1010
pub enum MatchPattern {
@@ -44,7 +44,7 @@ impl MatchPattern {
4444
#[derive(Debug, Clone, PartialEq, Default)]
4545
#[cfg_attr(feature = "builder", derive(Builder))]
4646
#[cfg_attr(feature = "builder", builder(default))]
47-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
47+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
4848
pub struct DictionaryPattern {
4949
/// Word that has been found in a dictionary.
5050
pub matched_word: String,
@@ -72,7 +72,7 @@ pub struct DictionaryPattern {
7272
#[derive(Debug, Clone, PartialEq, Default)]
7373
#[cfg_attr(feature = "builder", derive(Builder))]
7474
#[cfg_attr(feature = "builder", builder(default))]
75-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
75+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
7676
pub struct SpatialPattern {
7777
/// Name of the graph for which a spatial match has been found.
7878
pub graph: String,
@@ -86,7 +86,7 @@ pub struct SpatialPattern {
8686
#[derive(Debug, Clone, PartialEq, Default)]
8787
#[cfg_attr(feature = "builder", derive(Builder))]
8888
#[cfg_attr(feature = "builder", builder(default))]
89-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
89+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
9090
pub struct RepeatPattern {
9191
/// Base token that repeats in the matched pattern.
9292
pub base_token: String,
@@ -102,10 +102,10 @@ pub struct RepeatPattern {
102102
#[derive(Debug, Clone, PartialEq, Default)]
103103
#[cfg_attr(feature = "builder", derive(Builder))]
104104
#[cfg_attr(feature = "builder", builder(default))]
105-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
105+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
106106
pub struct SequencePattern {
107107
/// Name of the sequence that was matched.
108-
pub sequence_name: &'static str,
108+
pub sequence_name: String,
109109
/// Size of the sequence that was matched.
110110
pub sequence_space: u8,
111111
/// Whether the matched sequence is ascending.
@@ -116,10 +116,10 @@ pub struct SequencePattern {
116116
#[derive(Debug, Clone, PartialEq, Default)]
117117
#[cfg_attr(feature = "builder", derive(Builder))]
118118
#[cfg_attr(feature = "builder", builder(default))]
119-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
119+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
120120
pub struct RegexPattern {
121121
/// Name of the regular expression that was matched.
122-
pub regex_name: &'static str,
122+
pub regex_name: String,
123123
/// Matches of the regular expression.
124124
pub regex_match: Vec<String>,
125125
}
@@ -128,7 +128,7 @@ pub struct RegexPattern {
128128
#[derive(Debug, Clone, PartialEq, Default)]
129129
#[cfg_attr(feature = "builder", derive(Builder))]
130130
#[cfg_attr(feature = "builder", builder(default))]
131-
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
131+
#[cfg_attr(feature = "ser", derive(serde::Deserialize, serde::Serialize))]
132132
pub struct DatePattern {
133133
/// Separator of a date that was matched.
134134
pub separator: String,

src/scoring.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -493,9 +493,9 @@ impl Estimator for SequencePattern {
493493
impl Estimator for RegexPattern {
494494
fn estimate(&mut self, token: &str) -> u64 {
495495
if CHAR_CLASS_BASES.keys().any(|x| *x == self.regex_name) {
496-
CHAR_CLASS_BASES[self.regex_name].pow(token.chars().count() as u32)
496+
CHAR_CLASS_BASES[&*self.regex_name].pow(token.chars().count() as u32)
497497
} else {
498-
match self.regex_name {
498+
match &*self.regex_name {
499499
"recent_year" => {
500500
let year_space =
501501
(self.regex_match[0].parse::<i32>().unwrap() - *REFERENCE_YEAR).abs();
@@ -829,7 +829,7 @@ mod tests {
829829
fn test_regex_guesses_lowercase() {
830830
let token = "aizocdk";
831831
let mut p = RegexPattern {
832-
regex_name: "alpha_lower",
832+
regex_name: "alpha_lower".to_owned(),
833833
regex_match: vec![token.to_string()],
834834
};
835835
assert_eq!(p.estimate(token), 26u64.pow(7));
@@ -839,7 +839,7 @@ mod tests {
839839
fn test_regex_guesses_alphanumeric() {
840840
let token = "ag7C8";
841841
let mut p = RegexPattern {
842-
regex_name: "alphanumeric",
842+
regex_name: "alphanumeric".to_owned(),
843843
regex_match: vec![token.to_string()],
844844
};
845845
assert_eq!(p.estimate(token), 62u64.pow(5));
@@ -849,7 +849,7 @@ mod tests {
849849
fn test_regex_guesses_distant_year() {
850850
let token = "1972";
851851
let mut p = RegexPattern {
852-
regex_name: "recent_year",
852+
regex_name: "recent_year".to_owned(),
853853
regex_match: vec![token.to_string()],
854854
};
855855
assert_eq!(
@@ -862,7 +862,7 @@ mod tests {
862862
fn test_regex_guesses_recent_year() {
863863
let token = "2005";
864864
let mut p = RegexPattern {
865-
regex_name: "recent_year",
865+
regex_name: "recent_year".to_owned(),
866866
regex_match: vec![token.to_string()],
867867
};
868868
assert_eq!(p.estimate(token), scoring::MIN_YEAR_SPACE as u64);
@@ -872,7 +872,7 @@ mod tests {
872872
fn test_regex_guesses_current_year() {
873873
let token = time::OffsetDateTime::now_utc().year().to_string();
874874
let mut p = RegexPattern {
875-
regex_name: "recent_year",
875+
regex_name: "recent_year".to_owned(),
876876
regex_match: vec![token.to_string()],
877877
};
878878
assert_eq!(p.estimate(&token), scoring::MIN_YEAR_SPACE as u64);

src/serialization_utils.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use serde::de::{Deserialize, Deserializer};
2+
3+
pub(crate) fn deserialize_f64_null_as_nan<'de, D: Deserializer<'de>>(
4+
des: D,
5+
) -> Result<f64, D::Error> {
6+
let optional = Option::<f64>::deserialize(des)?;
7+
Ok(optional.unwrap_or(f64::NAN))
8+
}

0 commit comments

Comments
 (0)