diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b37c169..bf83f91 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -65,30 +65,6 @@ jobs: - run: cargo rustdoc --lib -- -D warnings - run: cargo rustdoc --bin pisa2ciff -- -D warnings - run: cargo rustdoc --bin ciff2pisa -- -D warnings - code-coverage: - name: Run code coverage - runs-on: ubuntu-latest - env: - RUSTFLAGS: -Zinstrument-coverage - LLVM_PROFILE_FILE: coverage-%p-%m.profraw - steps: - - uses: actions/checkout@v2 - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: nightly - - run: rustup component add llvm-tools-preview - - run: echo "PATH=/home/runner/.cargo/bin:$PATH" >> $GITHUB_ENV - - run: echo "7817b621f62dddfadd35fb84999b441bbce72b70cd8a61a9fe8e0998ccf75898 *grcov-v0.8.6-x86_64-unknown-linux-gnu.tar.gz" > checksums - - run: curl -sOL https://github.com/mozilla/grcov/releases/download/v0.8.6/grcov-v0.8.6-x86_64-unknown-linux-gnu.tar.gz - - run: sha256sum -c checksums --ignore-missing - - run: tar xf grcov-v0.8.6-x86_64-unknown-linux-gnu.tar.gz - - run: cargo test --verbose --workspace - - run: mkdir ./coverage - - run: ./grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "*cargo*" --ignore "build.rs" --ignore "*target*" --ignore "tests/*" --ignore "*ciff2pisa.rs" --ignore "*pisa2ciff.rs" -o ./coverage/lcov.info - - name: Coveralls - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} miri: name: Miri test runs-on: ubuntu-latest @@ -103,4 +79,4 @@ jobs: rustup toolchain install "nightly-$MIRI_NIGHTLY" rustup default "nightly-$MIRI_NIGHTLY" rustup component add miri - cargo miri test --lib + MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test --lib diff --git a/Cargo.lock b/Cargo.lock index 4fe313c..391fa06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -259,24 +259,24 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.27.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "protobuf-codegen" -version = "2.27.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec1632b7c8f2e620343439a7dfd1f3c47b18906c4be58982079911482b5d707" +checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" dependencies = [ "protobuf", ] [[package]] name = "protobuf-codegen-pure" -version = "2.27.1" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8122fdb18e55190c796b088a16bdb70cd7acdcd48f7a8b796b58c62e532cc6" +checksum = "95a29399fc94bcd3eeaa951c715f7bea69409b2445356b00519740bcd6ddd865" dependencies = [ "protobuf", "protobuf-codegen", diff --git a/Cargo.toml b/Cargo.toml index 3f91b57..2b01f19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ name = "jsonl2ciff" path = "src/jsonl2ciff.rs" [dependencies] -protobuf = "^2.27" +protobuf = "2.28.0" structopt = "0.3" num-traits = "0" indicatif = "0.15" diff --git a/src/jsonl2ciff.rs b/src/jsonl2ciff.rs index df904e4..a196b82 100644 --- a/src/jsonl2ciff.rs +++ b/src/jsonl2ciff.rs @@ -15,13 +15,21 @@ struct Args { input: PathBuf, #[structopt(short, long, help = "Output basename")] output: PathBuf, + #[structopt(short, long, help = "Quantize scores to integers")] + quantize: bool, + #[structopt(short, long, help = "Quantization bits", default_value = "8")] + bits: u32, } fn main() { let args = Args::from_args(); let mut converter = JsonlToCiff::default(); - converter.input_path(args.input).output_path(args.output); + converter + .input_path(args.input) + .output_path(args.output) + .quantize(args.quantize) + .quantization_bits(args.bits); if let Err(error) = converter.convert() { eprintln!("ERROR: {error}"); diff --git a/src/lib.rs b/src/lib.rs index 4bfdbb5..1cf0ff4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,74 @@ type Result = anyhow::Result; const DEFAULT_PROGRESS_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {count}/{total} ({eta})"; +/// Minimum value for quantized scores. +const MIN_QUANTIZED_VALUE: i32 = 1; + +/// A quantizer for converting floating-point scores to 8-bit integer representation. +/// Maps the score range [min, max] to [MIN_QUANTIZED_VALUE, MAX_QUANTIZED_VALUE]. +#[derive(Debug, Clone)] +struct ScoreQuantizer { + min: f64, + max: f64, + quantization_bits: u32, +} + +impl ScoreQuantizer { + /// Create a new ScoreQuantizer with the given min and max values. + /// + /// # Arguments + /// * `min` - The minimum score in the dataset (must be > 0) + /// * `max` - The maximum score in the dataset (must be >= min and > 0) + /// + /// # Returns + /// * `Ok(ScoreQuantizer)` if the parameters are valid + /// * `Err(String)` if the parameters are invalid + /// + /// # Errors + /// Returns an error if min <= 0, max <= 0, or max < min + fn new(min: f64, max: f64, quantization_bits: u32) -> Result { + if min <= 0.0 { + return Err(anyhow!("min must be greater than 0, got {}", min)); + } + if max <= 0.0 { + return Err(anyhow!("max must be greater than 0, got {}", max)); + } + if max < min { + return Err(anyhow!("max ({}) must be >= min ({})", max, min)); + } + if quantization_bits < 1 || quantization_bits > 32 { + return Err(anyhow!("quantization_bits must be between 1 and 32, got {}", quantization_bits)); + } + + Ok(ScoreQuantizer { min, max, quantization_bits }) + } + + /// Quantize a score to an 8-bit integer representation. + /// + /// # Arguments + /// * `score` - The score to quantize + /// + /// # Returns + /// * `0` if score <= 0 (will be filtered out) + /// * `MAX_QUANTIZED_VALUE` if min == max (all scores identical) + /// * Quantized value in range [MIN_QUANTIZED_VALUE, MAX_QUANTIZED_VALUE] otherwise + fn quantize(&self, score: f64) -> i32 { + let max_quantized_value = (1 << self.quantization_bits) - 1; + if score <= 0.0 { + 0 // Will be filtered out + } else if self.min == self.max { + // If all scores are the same, map to max value + MIN_QUANTIZED_VALUE + } else { + let normalized = (score - self.min) / (self.max - self.min); + let quantization_range = max_quantized_value - MIN_QUANTIZED_VALUE; + let quantized = (normalized * quantization_range as f64 + MIN_QUANTIZED_VALUE as f64) + .round() as i32; + quantized.clamp(MIN_QUANTIZED_VALUE, max_quantized_value) + } + } +} + /// Wraps [`proto::Header`] and additionally provides some important counts that are already cast /// to an unsigned type. #[derive(PartialEq, Clone, Default)] @@ -778,7 +846,9 @@ fn pisa_to_ciff_from_paths( #[derive(Debug, Deserialize)] struct JsonDoc { /// Collection docid. CIFF will automatically assign an integer ID to this. + /// Can be provided as either string or integer, will be converted to string. #[serde(default)] + #[serde(deserialize_with = "deserialize_id_to_string")] id: String, /// Optional textual content for the document. @@ -790,11 +860,37 @@ struct JsonDoc { vector: HashMap, } +fn deserialize_id_to_string<'de, D>(deserializer: D) -> std::result::Result +where + D: serde::Deserializer<'de>, +{ + use serde::de::Error; + use serde_json::Value; + + match Value::deserialize(deserializer) + .map_err(|e| Error::custom(format!("failed to deserialize id: {e}")))? + { + Value::String(s) => Ok(s), + Value::Number(n) => { + if n.is_i64() || n.is_u64() { + Ok(n.to_string()) + } else { + Err(Error::custom("id must be an integer")) + } + } + _ => Err(Error::custom( + "id must be a string or a number, but found an unsupported type", + )), + } +} + /// PISA to CIFF converter. #[derive(Debug, Default, Clone)] pub struct JsonlToCiff { input: Option, output: Option, + quantize: bool, + quantization_bits: u32, } impl JsonlToCiff { @@ -810,6 +906,68 @@ impl JsonlToCiff { self } + /// Set whether to quantize scores to integers. + /// If false, scores are assumed to be already pre-quantized and are cast directly to integers. + /// If true, performs 8-bit scalar quantization by mapping the score range [min, max] to [1, 256]. + /// Default is false. + pub fn quantize(&mut self, quantize: bool) -> &mut Self { + self.quantize = quantize; + self + } + + /// Set the number of bits to use for quantization. + /// Default is 8. + pub fn quantization_bits(&mut self, quantization_bits: u32) -> &mut Self { + self.quantization_bits = quantization_bits; + self + } + + /// Find the minimum and maximum scores across all documents in a JSONL file. + /// Only considers positive scores (> 0.0). + /// + /// # Errors + /// + /// Returns an error if: + /// - The file cannot be opened or read + /// - No valid scores are found (all scores <= 0 or no documents) + /// - JSON parsing fails + fn find_score_range>(input_path: P) -> Result<(f64, f64)> { + let input_file = File::open(&input_path) + .with_context(|| format!("Cannot open {:?}", input_path.as_ref()))?; + let total_input_size = input_file.metadata()?.len(); + + eprintln!("Finding score range for quantization"); + let reader = BufReader::new(input_file); + let mut min_val = f64::INFINITY; + let mut max_val = f64::NEG_INFINITY; + + let pb = ProgressBar::new(total_input_size); + pb.set_style(pb_style()); + + for line_result in reader.lines() { + let line = line_result?; + let jdoc: JsonDoc = serde_json::from_str(&line) + .map_err(|e| anyhow!("Invalid JSON line:\n `{}`\n Error: {}", line, e))?; + + for (_, score) in jdoc.vector { + if score > 0.0 { + // Only consider positive scores + min_val = min_val.min(score); + max_val = max_val.max(score); + } + } + pb.inc(line.len() as u64 + 1); + } + pb.finish(); + + if min_val.is_infinite() || max_val.is_infinite() { + return Err(anyhow!("No valid scores found for quantization")); + } + + eprintln!("Score range: {min_val} to {max_val}"); + Ok((min_val, max_val)) + } + /// Performs the conversion from JSONL to CIFF. /// /// # Errors @@ -826,18 +984,27 @@ impl JsonlToCiff { .as_ref() .ok_or_else(|| anyhow!("No CIFF output path was set"))?; - // Open the JSONL file + // Create quantizer if quantization is enabled + let quantizer = if self.quantize { + Some( + Self::find_score_range(input_path) + .and_then(|(min, max)| ScoreQuantizer::new(min, max, self.quantization_bits))?, + ) + } else { + None + }; + + // Open the JSONL file for processing let input_file = File::open(input_path).with_context(|| format!("Cannot open {input_path:?}"))?; let total_input_size = input_file.metadata()?.len(); - let reader = BufReader::new(input_file); // We'll store doc-level info: let mut doc_records: Vec = Vec::new(); // We'll map "term" -> (docid, tf). - // Because CIFF uses integer tf, we round any float to i32. + // Because CIFF uses integer tf, we convert scores to i32. let mut postings_map: HashMap> = HashMap::new(); let mut total_terms_in_collection: i64 = 0; @@ -873,7 +1040,14 @@ impl JsonlToCiff { // Sum of tf's in this doc => doc_length let mut doc_length = 0i64; for (term, score) in jdoc.vector { - let tf = score.round() as i32; + let tf = match &quantizer { + // 8-bit scalar quantization: map [min_score, max_score] to [1, 2^quantization_bits - 1] + // We use 1-2^quantization_bits-1 to avoid zero values which get filtered out + Some(quantizer) => quantizer.quantize(score), + // Assume scores are already pre-quantized integers + None => score as i32, + }; + if tf <= 0 { continue; // skip zero or negative } @@ -1017,6 +1191,294 @@ mod test { ); } + #[test] + fn test_jsondoc_id_deserialization() -> Result<()> { + // Test string ID + let json_with_string_id = r#"{"id": "doc123", "vector": {"term1": 1.5}}"#; + let doc: JsonDoc = serde_json::from_str(json_with_string_id)?; + assert_eq!(doc.id, "doc123"); + + // Test integer ID + let json_with_int_id = r#"{"id": 42, "vector": {"term1": 1.5}}"#; + let doc: JsonDoc = serde_json::from_str(json_with_int_id)?; + assert_eq!(doc.id, "42"); + + // Test float ID (should fail) + let json_with_float_id = r#"{"id": 3.14, "vector": {"term1": 1.5}}"#; + let result: serde_json::Result = serde_json::from_str(json_with_float_id); + assert!(result.is_err()); + + // Test invalid ID type (should fail) + let json_with_invalid_id = r#"{"id": true, "vector": {"term1": 1.5}}"#; + let result: serde_json::Result = serde_json::from_str(json_with_invalid_id); + assert!(result.is_err()); + + Ok(()) + } + + #[test] + fn test_find_score_range() -> Result<()> { + use std::io::Write; + use tempfile::NamedTempFile; + + // Test case 1: No documents (truly empty file) - should fail + { + let temp_file = NamedTempFile::new()?; + // Don't write anything, keep it truly empty + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_err()); + } + + // Test case 2: Documents with only empty vectors - should fail + { + let mut temp_file = NamedTempFile::new()?; + writeln!(temp_file, r#"{{"id": 0, "vector": {{}}}}"#)?; + writeln!(temp_file, r#"{{"id": 1, "vector": {{}}}}"#)?; + temp_file.flush()?; + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_err()); + } + + // Test case 3: Only vectors with scores <= 0 - should fail + { + let mut temp_file = NamedTempFile::new()?; + writeln!( + temp_file, + r#"{{"id": 0, "vector": {{"a": 0.0, "b": -1.0}}}}"# + )?; + writeln!( + temp_file, + r#"{{"id": 1, "vector": {{"c": -0.001, "d": -777}}}}"# + )?; + temp_file.flush()?; + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_err()); + } + + // Test case 4: Some scores > 0 - should succeed + { + let mut temp_file = NamedTempFile::new()?; + writeln!( + temp_file, + r#"{{"id": 0, "vector": {{"a": 0.0, "b": 1.0}}}}"# + )?; + writeln!( + temp_file, + r#"{{"id": 1, "vector": {{"c": 0.001, "d": -777}}}}"# + )?; + temp_file.flush()?; + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_ok()); + let (min_score, max_score) = result.unwrap(); + assert_eq!(min_score, 0.001); + assert_eq!(max_score, 1.0); + } + + // Test case 5: Mixed positive and negative scores + { + let mut temp_file = NamedTempFile::new()?; + writeln!( + temp_file, + r#"{{"id": 0, "vector": {{"a": -5.0, "b": 2.5, "c": 0.1}}}}"# + )?; + writeln!( + temp_file, + r#"{{"id": 1, "vector": {{"d": 10.0, "e": -1.0, "f": 7.3}}}}"# + )?; + temp_file.flush()?; + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_ok()); + let (min_score, max_score) = result.unwrap(); + assert_eq!(min_score, 0.1); + assert_eq!(max_score, 10.0); + } + + // Test case 6: All positive scores are identical (min == max) + { + let mut temp_file = NamedTempFile::new()?; + writeln!( + temp_file, + r#"{{"id": 0, "vector": {{"a": 0.0, "b": 5.0, "c": -1.0}}}}"# + )?; + writeln!( + temp_file, + r#"{{"id": 1, "vector": {{"d": 5.0, "e": -2.0, "f": 0.0}}}}"# + )?; + writeln!( + temp_file, + r#"{{"id": 2, "vector": {{"g": 5.0, "h": -10.0}}}}"# + )?; + temp_file.flush()?; + + let result = JsonlToCiff::find_score_range(temp_file.path()); + assert!(result.is_ok()); + let (min_score, max_score) = result.unwrap(); + assert_eq!(min_score, 5.0); + assert_eq!(max_score, 5.0); + } + + Ok(()) + } + + #[test] + fn test_quantization_with_identical_scores() -> Result<()> { + use std::io::Write; + use tempfile::NamedTempFile; + + // Create a JSONL file with identical positive scores + let mut temp_input = NamedTempFile::new()?; + writeln!( + temp_input, + r#"{{"id": "doc1", "vector": {{"term1": 3.14, "term2": -1.0}}}}"# + )?; + writeln!( + temp_input, + r#"{{"id": "doc2", "vector": {{"term3": 3.14, "term4": 0.0}}}}"# + )?; + temp_input.flush()?; + + let temp_output = NamedTempFile::new()?; + + // Test with quantization enabled + let mut converter = JsonlToCiff::default(); + converter + .input_path(temp_input.path()) + .output_path(temp_output.path()) + .quantize(true) + .quantization_bits(8); + + // This should succeed and handle the min == max case correctly + let result = converter.convert(); + assert!( + result.is_ok(), + "Conversion should succeed with identical scores: {:?}", + result.err() + ); + + Ok(()) + } + + #[test] + fn test_quantize_score() { + // Test with ScoreQuantizer + let quantizer = ScoreQuantizer::new(1.0, 10.0, 8).unwrap(); + + // Test case 1: Negative scores should return 0 + assert_eq!(quantizer.quantize(-1.0), 0); + assert_eq!(quantizer.quantize(0.0), 0); + + // Test case 2: When min == max (all scores identical) + let quantizer_same = ScoreQuantizer::new(5.0, 5.0, 8).unwrap(); + assert_eq!(quantizer_same.quantize(5.0), MIN_QUANTIZED_VALUE); + + let quantizer_pi = ScoreQuantizer::new(std::f64::consts::PI, std::f64::consts::PI, 8).unwrap(); + assert_eq!( + quantizer_pi.quantize(std::f64::consts::PI), + MIN_QUANTIZED_VALUE + ); + + // Test case 3: Score equals minimum should give MIN_QUANTIZED_VALUE + let quantizer = ScoreQuantizer::new(1.0, 10.0, 8).unwrap(); + assert_eq!(quantizer.quantize(1.0), MIN_QUANTIZED_VALUE); + + let quantizer = ScoreQuantizer::new(0.5, 2.0, 8).unwrap(); + assert_eq!(quantizer.quantize(0.5), MIN_QUANTIZED_VALUE); + + // Test case 4: Score equals maximum should give MAX_QUANTIZED_VALUE + let quantizer = ScoreQuantizer::new(1.0, 10.0, 8).unwrap(); + assert_eq!(quantizer.quantize(10.0), 255); + + let quantizer = ScoreQuantizer::new(0.5, 2.0, 8).unwrap(); + assert_eq!(quantizer.quantize(2.0), 255); + + // Test case 5: Middle values should map proportionally + // For range [1.0, 10.0], midpoint 5.5 should map to middle of [1, 255] + let mid_score = 5.5; + let min = 1.0; + let max = 10.0; + let quantizer = ScoreQuantizer::new(min, max, 8).unwrap(); + let quantized = quantizer.quantize(mid_score); + let expected_mid = (MIN_QUANTIZED_VALUE + 255) / 2; + assert_eq!(quantized, expected_mid); + + // Test case 6: Specific values with known expected results + let quarter_score = 3.25; // 25% of the way from 1.0 to 10.0 + let quantizer = ScoreQuantizer::new(min, max, 8).unwrap(); + let quantized_quarter = quantizer.quantize(quarter_score); + // Normalized: (3.25 - 1.0) / (10.0 - 1.0) = 0.25 + // Quantized: 0.25 * 254 + 1 = 64.5 -> rounds to 65 + assert_eq!(quantized_quarter, 65); + + let three_quarter_score = 7.75; // 75% of the way from 1.0 to 10.0 + let quantized_three_quarter = quantizer.quantize(three_quarter_score); + // Normalized: (7.75 - 1.0) / (10.0 - 1.0) = 0.75 + // Quantized: 0.75 * 254 + 1 = 191.5 -> rounds to 192 + assert_eq!(quantized_three_quarter, 192); + + // Test case 7: Values outside the range should be clamped + assert_eq!(quantizer.quantize(15.0), 255); // Above max + + // Test case 8: Very small range + let small_min = 1.0; + let small_max = 1.1; + let quantizer = ScoreQuantizer::new(small_min, small_max, 8).unwrap(); + assert_eq!(quantizer.quantize(small_min), MIN_QUANTIZED_VALUE); + assert_eq!(quantizer.quantize(small_max), 255); + + // Test case 9: Very large range + let large_min = 0.001; + let large_max = 1000000.0; + let quantizer = ScoreQuantizer::new(large_min, large_max, 8).unwrap(); + assert_eq!(quantizer.quantize(large_min), MIN_QUANTIZED_VALUE); + assert_eq!(quantizer.quantize(large_max), 255); + + // Test case 10: Edge case - score just above minimum + let just_above_min = 1.01; + let quantizer = ScoreQuantizer::new(min, max, 8).unwrap(); + let quantized_just_above = quantizer.quantize(just_above_min); + assert!(quantized_just_above >= MIN_QUANTIZED_VALUE); + assert!(quantized_just_above <= 255); + } + + #[test] + #[should_panic(expected = "min must be greater than 0")] + fn test_quantize_score_invalid_min() { + ScoreQuantizer::new(0.0, 10.0, 8).unwrap(); // min = 0 should panic + } + + #[test] + #[should_panic(expected = "min must be greater than 0")] + fn test_quantize_score_negative_min() { + ScoreQuantizer::new(-1.0, 10.0, 8).unwrap(); // negative min should panic + } + + #[test] + #[should_panic(expected = "max must be greater than 0")] + fn test_quantize_score_invalid_max() { + ScoreQuantizer::new(1.0, 0.0, 8).unwrap(); // max = 0 should panic + } + + #[test] + #[should_panic(expected = "max must be greater than 0")] + fn test_quantize_score_negative_max() { + ScoreQuantizer::new(1.0, -1.0, 8).unwrap(); // negative max should panic + } + + #[test] + fn test_score_quantizer_validation() { + // Test validation errors + assert!(ScoreQuantizer::new(0.0, 10.0, 8).is_err()); // min = 0 should fail + assert!(ScoreQuantizer::new(-1.0, 10.0, 8).is_err()); // negative min should fail + assert!(ScoreQuantizer::new(1.0, 0.0, 8).is_err()); // max = 0 should fail + assert!(ScoreQuantizer::new(1.0, -1.0, 8).is_err()); // negative max should fail + assert!(ScoreQuantizer::new(1.0, 0.5, 8).is_err()); // max < min should fail + } + fn header_to_buf(header: &proto::Header) -> Result> { let mut buffer = Vec::::new(); let mut out = CodedOutputStream::vec(&mut buffer); @@ -1064,4 +1526,12 @@ mod test { assert!(Header::from_stream(&mut input).is_err()); Ok(()) } + + #[test] + // quantize with 16 bits + fn test_quantize_score_16_bits() { + let quantizer = ScoreQuantizer::new(1.0, 10.0, 16).unwrap(); + assert_eq!(quantizer.quantize(1.0), MIN_QUANTIZED_VALUE); + assert_eq!(quantizer.quantize(10.0), 65535); + } } diff --git a/src/proto/common_index_format_v1.rs b/src/proto/common_index_format_v1.rs index 62fde6f..f5f7cfb 100644 --- a/src/proto/common_index_format_v1.rs +++ b/src/proto/common_index_format_v1.rs @@ -10,4 +10,6 @@ #![allow(unsafe_code)] #![allow(unused_imports)] #![allow(unused_results)] +#![allow(unnecessary_parens)] +#![allow(unused_parens)] include!(concat!(env!("OUT_DIR"), "/common_index_format_v1.rs")); diff --git a/tests/toy.rs b/tests/toy.rs index 9538995..e26e489 100644 --- a/tests/toy.rs +++ b/tests/toy.rs @@ -119,24 +119,24 @@ fn test_to_and_from_ciff() -> anyhow::Result<()> { let copy_basename = pisa_copy.display().to_string(); assert_eq!( - read(format!("{}.sizes", coll_basename))?, - read(format!("{}.sizes", copy_basename))? + read(format!("{coll_basename}.sizes"))?, + read(format!("{copy_basename}.sizes"))? ); assert_eq!( - read(format!("{}.terms", coll_basename))?, - read(format!("{}.terms", copy_basename))? + read(format!("{coll_basename}.terms"))?, + read(format!("{copy_basename}.terms"))? ); assert_eq!( - read(format!("{}.documents", coll_basename))?, - read(format!("{}.documents", copy_basename))? + read(format!("{coll_basename}.documents"))?, + read(format!("{copy_basename}.documents"))? ); assert_eq!( - read(format!("{}.docs", coll_basename))?, - read(format!("{}.docs", copy_basename))? + read(format!("{coll_basename}.docs"))?, + read(format!("{copy_basename}.docs"))? ); assert_eq!( - read(format!("{}.freqs", coll_basename))?, - read(format!("{}.freqs", copy_basename))? + read(format!("{coll_basename}.freqs"))?, + read(format!("{copy_basename}.freqs"))? ); Ok(())