diff --git a/Cargo.toml b/Cargo.toml index d94ea9b..5e71da1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ name = "shvproto" description = "Rust implementation of the SHV protocol" license = "MIT" repository = "https://github.com/silicon-heaven/libshvproto-rs" -version = "4.0.1" +version = "5.0.0" edition = "2024" [dependencies] @@ -38,3 +38,67 @@ specialization = [] [[bin]] name = "cp2cp" required-features = ["cp2cp"] + +[lints.clippy] +pedantic = {priority = -1, level = "warn"} + +allow_attributes = "warn" +allow_attributes_without_reason = "warn" +box_collection = "warn" +dbg_macro = "warn" +enum_variant_names = "warn" +equatable_if_let = "warn" +exit = "warn" +indexing_slicing = "warn" +infinite_loop = "warn" +iter_on_single_items = "warn" +large_types_passed_by_value = "warn" +let_underscore_must_use = "warn" +linkedlist = "warn" +map_err_ignore = "warn" +missing_assert_message = "warn" +needless_pass_by_ref_mut = "warn" +option-if-let-else = "warn" +option_option = "warn" +or_fun_call = "warn" +owned_cow = "warn" +panic = "warn" +print_stderr = "warn" +print_stdout = "warn" +rc_buffer = "warn" +rc_mutex = "warn" +redundant_allocation = "warn" +redundant_clone = "warn" +redundant_type_annotations = "warn" +ref_option = "warn" +rest_pat_in_fully_bound_structs = "warn" +return_and_then = "warn" +string_lit_as_bytes = "warn" +string_slice = "warn" +trivially_copy_pass_by_ref = "warn" +try_err = "warn" +undocumented_unsafe_blocks = "warn" +unimplemented = "warn" +unnecessary_box_returns = "warn" +unnecessary_wraps = "warn" +unneeded_field_pattern = "warn" +unused_self = "warn" +unwrap_used = "warn" +upper_case_acronyms = "warn" +useless_let_if_seq = "warn" +vec_box = "warn" +wrong_self_convention = "warn" + +doc_markdown = "allow" +expect_used = "allow" +explicit_iter_loop = "allow" +float-cmp = "allow" +format_push_string = "allow" +items_after_statements = "allow" +manual_string_new = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +must_use_candidate = "allow" +should_panic_without_expect = "allow" +todo = "allow" +too_many_lines = "allow" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..f01facb --- /dev/null +++ b/clippy.toml @@ -0,0 +1,8 @@ +allow-expect-in-consts = true +allow-expect-in-tests = true +allow-indexing-slicing-in-tests = true +allow-one-hash-in-raw-strings = true +allow-panic-in-tests = true +allow-print-in-tests = true +allow-unwrap-in-tests = true +avoid-breaking-exported-api = false diff --git a/libshvproto-macros/Cargo.toml b/libshvproto-macros/Cargo.toml index 8ba7c99..4632f47 100644 --- a/libshvproto-macros/Cargo.toml +++ b/libshvproto-macros/Cargo.toml @@ -3,7 +3,7 @@ name = "libshvproto-macros" description = "Derive macros for libshvproto" license = "MIT" repository = "https://github.com/silicon-heaven/libshvproto-rs" -version = "0.2.1" +version = "0.2.2" edition = "2021" [lib] diff --git a/libshvproto-macros/src/lib.rs b/libshvproto-macros/src/lib.rs index 6f16adf..f5c3d05 100644 --- a/libshvproto-macros/src/lib.rs +++ b/libshvproto-macros/src/lib.rs @@ -15,7 +15,7 @@ fn is_option(ty: &syn::Type) -> bool { if !(typepath.qself.is_none() && typepath.path.segments.len() == 1) { return false } - let segment = &typepath.path.segments[0]; + let segment = typepath.path.segments.first().expect("len() is 1"); if segment.ident != "Option" { return false; @@ -27,7 +27,7 @@ fn is_option(ty: &syn::Type) -> bool { return false; } - matches!(data.args[0], syn::GenericArgument::Type(_)) + matches!(data.args.first().expect("len() is 1"), syn::GenericArgument::Type(_)) } fn get_type(ty: &syn::Type) -> Option { @@ -48,8 +48,7 @@ fn get_field_name(field: &syn::Field) -> String { .and_then(|attr| attr.meta.require_name_value().ok()) .filter(|meta_name_value| meta_name_value.path.is_ident("field_name")) .map(|meta_name_value| if let syn::Expr::Lit(expr) = &meta_name_value.value { expr } else { panic!("Expected a string literal for 'field_name'") }) - .map(|literal| if let syn::Lit::Str(expr) = &literal.lit { expr.value() } else { panic!("Expected a string literal for 'field_name'") }) - .unwrap_or_else(|| field.ident.as_ref().unwrap().to_string().to_case(Case::Camel)) + .map_or_else(|| field.ident.as_ref().unwrap().to_string().to_case(Case::Camel), |literal| if let syn::Lit::Str(expr) = &literal.lit { expr.value() } else { panic!("Expected a string literal for 'field_name'") }) } fn field_to_initializers( @@ -63,11 +62,7 @@ fn field_to_initializers( let is_option = is_option(&field.ty); let struct_initializer; let rpcvalue_insert; - let identifier_at_value = if let Some(value) = from_value { - quote! { #value.#identifier } - } else { - quote! { #identifier } - }; + let identifier_at_value = from_value.map_or_else(|| quote! { #identifier }, |value| quote! { #value.#identifier }); if is_option { struct_initializer = quote!{ #identifier: match get_key(#field_name).ok() { @@ -235,9 +230,7 @@ pub fn derive_from_rpcvalue(item: TokenStream) -> TokenStream { }; match &variant.fields { syn::Fields::Unnamed(variant_types) => { - if variant_types.unnamed.len() != 1 { - panic!("Only single element variant tuples are supported for FromRpcValue"); - } + assert!(variant_types.unnamed.len() == 1, "Only single element variant tuples are supported for FromRpcValue"); let source_variant_type = &variant_types.unnamed.first().expect("No tuple elements").ty; let deref_code = quote!((*x)); let unbox_code = quote!((x.as_ref().clone())); @@ -431,9 +424,7 @@ pub fn derive_to_rpcvalue(item: TokenStream) -> TokenStream { let variant_ident = &variant.ident; match &variant.fields { syn::Fields::Unnamed(variant_types) => { - if variant_types.unnamed.len() != 1 { - panic!("Only single element variant tuples are supported for ToRpcValue"); - } + assert!(variant_types.unnamed.len() == 1, "Only single element variant tuples are supported for ToRpcValue"); match_arms_ser.extend(quote!{ #struct_identifier::#variant_ident(val) => shvproto::RpcValue::from(val), }); diff --git a/src/bin/cp2cp.rs b/src/bin/cp2cp.rs index a386d62..8b61132 100644 --- a/src/bin/cp2cp.rs +++ b/src/bin/cp2cp.rs @@ -1,3 +1,4 @@ +#![allow(clippy::print_stderr, clippy::print_stdout, clippy::exit, reason = "Fine for a binary")] use clap::Parser; use log::LevelFilter; use shvproto::reader::ReadErrorReason; @@ -15,6 +16,7 @@ use jaq_all::{jaq_core::{Ctx, Vars, data::JustLut}, jaq_std}; #[derive(Parser, Debug)] #[structopt(name = "cp2cp", version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"), about = "ChainPack to Cpon and back utility")] +#[expect(clippy::struct_excessive_bools, reason = "Fine for cli args")] struct Cli { #[arg(short, long, help = "Cpon indentation string")] indent: Option, @@ -53,24 +55,20 @@ struct ChainPackRpcBlockResult { } fn print_option(n: Option) { if let Some(n) = n { - println!("{}", n); + println!("{n}"); } else { println!(); } } fn exit_with_result_and_code(result: &ChainPackRpcBlockResult, error: Option) -> ! { - let exit_code = if let Some(error) = &error { - match error { - ReadErrorReason::UnexpectedEndOfStream => CODE_NOT_ENOUGH_DATA, - ReadErrorReason::NotEnoughPrecision => CODE_READ_ERROR, - ReadErrorReason::InvalidCharacter => { - eprintln!("Parse input error: {:?}", error); - CODE_READ_ERROR - } + let exit_code = error.map_or(CODE_SUCCESS, |error| match error { + ReadErrorReason::UnexpectedEndOfStream => CODE_NOT_ENOUGH_DATA, + ReadErrorReason::NotEnoughPrecision => CODE_READ_ERROR, + ReadErrorReason::InvalidCharacter => { + eprintln!("Parse input error: {error:?}"); + CODE_READ_ERROR } - } else { - CODE_SUCCESS - }; + }); print_option(result.block_length); print_option(result.frame_length); print_option(result.proto); @@ -85,6 +83,7 @@ fn process_chainpack_rpc_block(mut reader: Box) -> ! { cpon: "".to_string(), }; let mut rd = ChainPackReader::new(&mut reader); + #[expect(clippy::cast_possible_truncation, reason = "We assume pointer size is 64-bit")] match rd.read_uint_data() { Ok(frame_length) => { result.block_length = Some(frame_length as usize + rd.position()); @@ -106,10 +105,10 @@ fn process_chainpack_rpc_block(mut reader: Box) -> ! { Err(e) => { exit_with_result_and_code(&result, Some(e.reason)); } - }; + } match rd.read() { Ok(rv) => { - result.cpon = rv.to_cpon().to_string(); + result.cpon = rv.to_cpon(); exit_with_result_and_code(&result, None); } Err(e) => { @@ -133,7 +132,7 @@ fn main() { logger = logger.with_module_level(&module_name, LevelFilter::Trace); } } - logger.init().unwrap(); + logger.init().expect("Logger must work"); if opts.chainpack_rpc_block { opts.indent = None; @@ -143,7 +142,7 @@ fn main() { let mut reader: Box = match opts.file { None => Box::new(BufReader::new(io::stdin())), - Some(filename) => Box::new(BufReader::new(fs::File::open(filename).unwrap())), + Some(filename) => Box::new(BufReader::new(fs::File::open(filename).expect("Opening files must work"))), }; let read_result = if opts.cpon_input { @@ -158,7 +157,7 @@ fn main() { let input_value = match read_result { Err(e) => { - eprintln!("Parse input error: {:?}", e); + eprintln!("Parse input error: {e:?}"); process::exit(CODE_READ_ERROR); } Ok(rv) => rv, @@ -201,7 +200,7 @@ fn main() { wr.set_no_oneliners(opts.no_oneliners); if let Some(s) = &opts.indent { if s == "\\t" { - wr.set_indent("\t".as_bytes()); + wr.set_indent(b"\t"); } else { wr.set_indent(s.as_bytes()); } @@ -210,8 +209,8 @@ fn main() { }; if let Err(e) = res { - eprintln!("Write output error: {:?}", e); + eprintln!("Write output error: {e:?}"); process::exit(CODE_WRITE_ERROR); - }; + } } } diff --git a/src/chainpack.rs b/src/chainpack.rs index 6dc9bf5..21dab0a 100644 --- a/src/chainpack.rs +++ b/src/chainpack.rs @@ -1,3 +1,5 @@ +#![allow(clippy::cast_possible_truncation, reason = "Lots of casting here")] +#![allow(clippy::indexing_slicing, reason = "Lots of indexing here")] use crate::reader::{ByteReader, ReadError, ReadErrorReason, Reader}; use crate::rpcvalue::{IMap, Map}; use crate::writer::{ByteWriter, Writer}; @@ -6,9 +8,8 @@ use std::collections::BTreeMap; use std::io; use std::io::{Read, Write}; -#[allow(clippy::upper_case_acronyms)] +#[expect(clippy::upper_case_acronyms, reason = "We just want these")] #[warn(non_camel_case_types)] -#[allow(dead_code)] pub enum PackingSchema { Null = 128, UInt, @@ -30,7 +31,7 @@ pub enum PackingSchema { TERM = 255, } -const SHV_EPOCH_MSEC: i64 = 1517529600000; +const SHV_EPOCH_MSEC: i64 = 1_517_529_600_000; pub struct ChainPackWriter<'a, W> where @@ -60,11 +61,11 @@ where fn significant_bits_part_length(num: u64) -> u32 { let mut len = 0; let mut n = num; - if (n & 0xFFFFFFFF00000000) != 0 { + if (n & 0xFFFF_FFFF_0000_0000) != 0 { len += 32; n >>= 32; } - if (n & 0xFFFF0000) != 0 { + if (n & 0xFFFF_0000) != 0 { len += 16; n >>= 16; } @@ -78,7 +79,7 @@ where } const SIG_TABLE_4BIT: [u8; 16] = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4]; len += SIG_TABLE_4BIT[n as usize]; - len as u32 + u32::from(len) } /// number of bytes needed to encode bit_len fn bytes_needed(bit_len: u32) -> u32 { @@ -117,8 +118,7 @@ where let byte_cnt = Self::bytes_needed(bit_len); assert!( byte_cnt <= BYTE_CNT_MAX, - "Max int byte size {} exceeded", - BYTE_CNT_MAX + "Max int byte size {BYTE_CNT_MAX} exceeded" ); let mut bytes: [u8; BYTE_CNT_MAX as usize] = [0; BYTE_CNT_MAX as usize]; let mut num = number; @@ -148,15 +148,8 @@ where Ok(self.byte_writer.count() - cnt) } fn write_int_data(&mut self, number: i64) -> WriteResult { - let mut num: u64; - let neg; - if number < 0 { - num = (-number) as u64; - neg = true; - } else { - num = number as u64; - neg = false; - }; + let neg = number < 0; + let mut num = number.abs().cast_unsigned(); let bitlen = Self::significant_bits_part_length(num) + 1; // add sign bit if neg { @@ -171,6 +164,7 @@ where fn write_int(&mut self, n: i64) -> WriteResult { let cnt = self.byte_writer.count(); if (0..64).contains(&n) { + #[expect(clippy::cast_sign_loss, reason = "n is already between 0 and 64")] self.write_byte(((n % 64) + 64) as u8)?; } else { self.write_byte(PackingSchema::Int as u8)?; @@ -194,13 +188,13 @@ where self.write_bytes(&bytes)?; Ok(self.byte_writer.count() - cnt) } - fn write_decimal(&mut self, decimal: &Decimal) -> WriteResult { + fn write_decimal(&mut self, decimal: Decimal) -> WriteResult { let cnt = self.write_byte(PackingSchema::Decimal as u8)?; self.write_int_data(decimal.mantissa())?; - self.write_int_data(decimal.exponent() as i64)?; + self.write_int_data(i64::from(decimal.exponent()))?; Ok(self.byte_writer.count() - cnt) } - fn write_datetime(&mut self, dt: &DateTime) -> WriteResult { + fn write_datetime(&mut self, dt: DateTime) -> WriteResult { let cnt = self.write_byte(PackingSchema::DateTime as u8)?; let mut msecs = dt.epoch_msec() - SHV_EPOCH_MSEC; let offset = (dt.utc_offset() / 60 / 15) & 0x7F; @@ -210,7 +204,7 @@ where } if offset != 0 { msecs <<= 7; - msecs |= offset as i64; + msecs |= i64::from(offset); } msecs <<= 2; if offset != 0 { @@ -242,7 +236,7 @@ where fn write_imap(&mut self, map: &IMap) -> WriteResult { let cnt = self.write_byte(PackingSchema::IMap as u8)?; for (k, v) in map { - self.write_int(*k as i64)?; + self.write_int(i64::from(*k))?; self.write(v)?; } self.write_byte(PackingSchema::TERM as u8)?; @@ -273,7 +267,7 @@ where for k in map.0.iter() { match &k.key { MetaKey::Str(s) => self.write_string(s)?, - MetaKey::Int(i) => self.write_int(*i as i64)?, + MetaKey::Int(i) => self.write_int(i64::from(*i))?, }; self.write(&k.value)?; } @@ -305,8 +299,8 @@ where Value::String(s) => self.write_string(s)?, Value::Blob(b) => self.write_blob(b)?, Value::Double(n) => self.write_double(*n)?, - Value::Decimal(d) => self.write_decimal(d)?, - Value::DateTime(d) => self.write_datetime(d)?, + Value::Decimal(d) => self.write_decimal(*d)?, + Value::DateTime(d) => self.write_datetime(*d)?, Value::List(lst) => self.write_list(lst)?, Value::Map(map) => self.write_map(map)?, Value::IMap(map) => self.write_imap(map)?, @@ -342,7 +336,7 @@ where } fn make_error(&self, msg: &str, reason: ReadErrorReason) -> ReadError { self.byte_reader - .make_error(&format!("ChainPack read error - {}", msg), reason) + .make_error(&format!("ChainPack read error - {msg}"), reason) } /// return (n, bitlen) @@ -354,19 +348,19 @@ where let bitlen; if (head & 128) == 0 { bytes_to_read_cnt = 0; - num = (head & 127) as u64; + num = u64::from(head & 127); bitlen = 7; } else if (head & 64) == 0 { bytes_to_read_cnt = 1; - num = (head & 63) as u64; + num = u64::from(head & 63); bitlen = 6 + 8; } else if (head & 32) == 0 { bytes_to_read_cnt = 2; - num = (head & 31) as u64; + num = u64::from(head & 31); bitlen = 5 + 2 * 8; } else if (head & 16) == 0 { bytes_to_read_cnt = 3; - num = (head & 15) as u64; + num = u64::from(head & 15); bitlen = 4 + 3 * 8; } else if head == 0xFF { return Err(self.make_error( @@ -379,7 +373,7 @@ where } for _ in 0..bytes_to_read_cnt { let r = self.get_byte()?; - num = (num << 8) + (r as u64); + num = (num << 8) + u64::from(r); } Ok((num, bitlen)) } @@ -391,9 +385,9 @@ where let (num, bitlen) = self.read_uint_data_helper()?; let sign_bit_mask = (1_u64) << (bitlen - 1); let neg = (num & sign_bit_mask) != 0; - let mut snum = num as i64; + let mut snum = num.cast_signed(); if neg { - snum &= !(sign_bit_mask as i64); + snum &= !(sign_bit_mask.cast_signed()); snum = -snum; } Ok(snum) @@ -412,7 +406,7 @@ where match s { Ok(s) => Ok(Value::from(s)), Err(e) => Err(self.make_error( - &format!("Invalid string, Utf8 error: {}", e), + &format!("Invalid string, Utf8 error: {e}"), ReadErrorReason::InvalidCharacter, )), } @@ -428,7 +422,7 @@ where match s { Ok(s) => Ok(Value::from(s)), Err(e) => Err(self.make_error( - &format!("Invalid string, Utf8 error: {}", e), + &format!("Invalid string, Utf8 error: {e}"), ReadErrorReason::InvalidCharacter, )), } @@ -468,7 +462,7 @@ where k.as_str() } else { return Err(self.make_error( - &format!("Invalid Map key '{}'", k), + &format!("Invalid Map key '{k}'"), ReadErrorReason::InvalidCharacter, )); }; @@ -490,7 +484,7 @@ where k.as_i32() } else { return Err(self.make_error( - &format!("Invalid IMap key '{}'", k), + &format!("Invalid IMap key '{k}'"), ReadErrorReason::InvalidCharacter, )); }; @@ -515,7 +509,7 @@ where d *= 1000; } d += SHV_EPOCH_MSEC; - let dt = DateTime::from_epoch_msec_tz(d, (offset as i32 * 15) * 60); + let dt = DateTime::from_epoch_msec_tz(d, (i32::from(offset) * 15) * 60); Ok(Value::from(dt)) } fn read_double_data(&mut self) -> Result { @@ -578,10 +572,10 @@ where let v = if b < 128 { if (b & 64) == 0 { // tiny UInt - Value::from((b & 63) as u64) + Value::from(u64::from(b & 63)) } else { // tiny Int - Value::from((b & 63) as i64) + Value::from(i64::from(b & 63)) } } else if b == PackingSchema::Int as u8 { let n = self.read_int_data()?; @@ -615,7 +609,7 @@ where Value::from(()) } else { return Err(self.make_error( - &format!("Invalid Packing schema: {}", b), + &format!("Invalid Packing schema: {b}"), ReadErrorReason::InvalidCharacter, )); }; @@ -633,10 +627,10 @@ fn chainpack_to_rpcvalue(data: &str) -> Result { } #[cfg(test)] -fn rpcvalue_to_chainpack(value: RpcValue) -> String { +fn rpcvalue_to_chainpack(value: &RpcValue) -> String { let mut data = Vec::new(); let mut wr = ChainPackWriter::new(&mut data); - wr.write(&value).expect("Write must work"); + wr.write(value).expect("Write must work"); hex::encode(data).to_uppercase() } @@ -646,28 +640,28 @@ fn test_int() { assert_eq!(chainpack_to_rpcvalue("02").unwrap(), 2_u64.into()); assert_eq!(chainpack_to_rpcvalue("8178").unwrap(), 120_u64.into()); assert_eq!(chainpack_to_rpcvalue("8181FC").unwrap(), 508_u64.into()); - assert_eq!(chainpack_to_rpcvalue("81CFFFFA").unwrap(), 1048570_u64.into()); - assert_eq!(chainpack_to_rpcvalue("81E1FFFFE0").unwrap(), 33554400_u64.into()); - assert_eq!(chainpack_to_rpcvalue("82F3138083FD18A37C").unwrap(), 5489328932823932_i64.into()); - assert_eq!(chainpack_to_rpcvalue("82F47FFFFFFFFFFFFFFF").unwrap(), 9223372036854775807_i64.into()); - - assert_eq!(rpcvalue_to_chainpack(120_u64.into()), "8178"); - assert_eq!(rpcvalue_to_chainpack(2_u64.into()), "02"); - assert_eq!(rpcvalue_to_chainpack(508_u64.into()), "8181FC"); - assert_eq!(rpcvalue_to_chainpack(1048570_u64.into()), "81CFFFFA"); - assert_eq!(rpcvalue_to_chainpack(33554400_u64.into()), "81E1FFFFE0"); - assert_eq!(rpcvalue_to_chainpack(5489328932823932_i64.into()), "82F3138083FD18A37C"); - assert_eq!(rpcvalue_to_chainpack(9223372036854775807_i64.into()), "82F47FFFFFFFFFFFFFFF"); + assert_eq!(chainpack_to_rpcvalue("81CFFFFA").unwrap(), 1_048_570_u64.into()); + assert_eq!(chainpack_to_rpcvalue("81E1FFFFE0").unwrap(), 33_554_400_u64.into()); + assert_eq!(chainpack_to_rpcvalue("82F3138083FD18A37C").unwrap(), 5_489_328_932_823_932_i64.into()); + assert_eq!(chainpack_to_rpcvalue("82F47FFFFFFFFFFFFFFF").unwrap(), 9_223_372_036_854_775_807_i64.into()); + + assert_eq!(rpcvalue_to_chainpack(&120_u64.into()), "8178"); + assert_eq!(rpcvalue_to_chainpack(&2_u64.into()), "02"); + assert_eq!(rpcvalue_to_chainpack(&508_u64.into()), "8181FC"); + assert_eq!(rpcvalue_to_chainpack(&1_048_570_u64.into()), "81CFFFFA"); + assert_eq!(rpcvalue_to_chainpack(&33_554_400_u64.into()), "81E1FFFFE0"); + assert_eq!(rpcvalue_to_chainpack(&5_489_328_932_823_932_i64.into()), "82F3138083FD18A37C"); + assert_eq!(rpcvalue_to_chainpack(&9_223_372_036_854_775_807_i64.into()), "82F47FFFFFFFFFFFFFFF"); // Negative int - assert_eq!(chainpack_to_rpcvalue("82E9FFFFE0").unwrap(), (-33554400).into()); - assert_eq!(rpcvalue_to_chainpack((-33554400).into()), "82E9FFFFE0"); + assert_eq!(chainpack_to_rpcvalue("82E9FFFFE0").unwrap(), (-33_554_400).into()); + assert_eq!(rpcvalue_to_chainpack(&(-33_554_400).into()), "82E9FFFFE0"); } #[test] fn test_string() { assert_eq!(chainpack_to_rpcvalue("860541484F4A21").unwrap(), "AHOJ!".into()); - assert_eq!(rpcvalue_to_chainpack("AHOJ!".into()), "860541484F4A21"); + assert_eq!(rpcvalue_to_chainpack(&"AHOJ!".into()), "860541484F4A21"); // Invalid UTF-8 string assert!(chainpack_to_rpcvalue("8602C328").is_err()); @@ -676,16 +670,16 @@ fn test_string() { #[test] fn test_true_false_packing_schema() { assert_eq!(chainpack_to_rpcvalue("FE").unwrap(), true.into()); - assert_eq!(rpcvalue_to_chainpack(true.into()), "FE"); + assert_eq!(rpcvalue_to_chainpack(&true.into()), "FE"); assert_eq!(chainpack_to_rpcvalue("FD").unwrap(), false.into()); - assert_eq!(rpcvalue_to_chainpack(false.into()), "FD"); + assert_eq!(rpcvalue_to_chainpack(&false.into()), "FD"); } #[test] fn test_cstring() { assert_eq!(chainpack_to_rpcvalue("8E41484F4A2100").unwrap(), "AHOJ!".into()); - assert_eq!(rpcvalue_to_chainpack("AHOJ!".into()), "860541484F4A21"); + assert_eq!(rpcvalue_to_chainpack(&"AHOJ!".into()), "860541484F4A21"); // Invalid UTF-8 string assert!(chainpack_to_rpcvalue("8EC32800").is_err()); @@ -695,14 +689,14 @@ fn test_cstring() { fn test_blob() { let blob = vec![170u8; 10]; assert_eq!(chainpack_to_rpcvalue("850AAAAAAAAAAAAAAAAAAAAA").unwrap(), blob.clone().into()); - assert_eq!(rpcvalue_to_chainpack(blob.into()), "850AAAAAAAAAAAAAAAAAAAAA"); + assert_eq!(rpcvalue_to_chainpack(&blob.into()), "850AAAAAAAAAAAAAAAAAAAAA"); } #[test] fn test_list() { let list = crate::make_list!["a", 123, true, crate::make_list![1, 2, 3], RpcValue::null()]; assert_eq!(chainpack_to_rpcvalue("8886016182807BFE88414243FF80FF").unwrap(), RpcValue::from(list.clone())); - assert_eq!(rpcvalue_to_chainpack(RpcValue::from(list)), "8886016182807BFE88414243FF80FF"); + assert_eq!(rpcvalue_to_chainpack(&RpcValue::from(list)), "8886016182807BFE88414243FF80FF"); } #[test] @@ -713,7 +707,7 @@ fn test_map() { "foo" => vec![11,12,13] }; assert_eq!(chainpack_to_rpcvalue("89860362617242860362617A438603666F6F884B4C4DFFFF").unwrap(), map.clone().into()); - assert_eq!(rpcvalue_to_chainpack(map.into()), "89860362617242860362617A438603666F6F884B4C4DFFFF"); + assert_eq!(rpcvalue_to_chainpack(&map.into()), "89860362617242860362617A438603666F6F884B4C4DFFFF"); // Invalid key assert_eq!(chainpack_to_rpcvalue("898200").unwrap_err().msg, "ChainPack read error - Invalid Map key '0'"); @@ -728,7 +722,7 @@ fn test_imap() { }; assert_eq!(chainpack_to_rpcvalue("8A418603666F6F42860362617282814D4FFF").unwrap(), imap.clone().into()); - assert_eq!(rpcvalue_to_chainpack(imap.into()), "8A418603666F6F42860362617282814D4FFF"); + assert_eq!(rpcvalue_to_chainpack(&imap.into()), "8A418603666F6F42860362617282814D4FFF"); // Invalid key assert_eq!(chainpack_to_rpcvalue("8A8603626172").unwrap_err().msg, "ChainPack read error - Invalid IMap key '\"bar\"'"); @@ -736,56 +730,56 @@ fn test_imap() { #[test] fn test_datetime() { - assert_eq!(chainpack_to_rpcvalue("8D04").unwrap(), DateTime::from_epoch_msec_tz(1517529600001, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8D8211").unwrap(), DateTime::from_epoch_msec_tz(1517529600001, 3600).into()); - assert_eq!(chainpack_to_rpcvalue("8DE63DDA02").unwrap(), DateTime::from_epoch_msec_tz(1543708800000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DE8A8BFFE").unwrap(), DateTime::from_epoch_msec_tz(1514764800000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DE6DC0E02").unwrap(), DateTime::from_epoch_msec_tz(1546300800000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF00E60DC02").unwrap(), DateTime::from_epoch_msec_tz(1577836800000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF015EAF002").unwrap(), DateTime::from_epoch_msec_tz(1609459200000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF061258802").unwrap(), DateTime::from_epoch_msec_tz(1924992000000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF100AC656602").unwrap(), DateTime::from_epoch_msec_tz(2240611200000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF156D74D495F").unwrap(), DateTime::from_epoch_msec_tz(2246004900000, -36900).into()); - assert_eq!(chainpack_to_rpcvalue("8DF301533905E2375D").unwrap(), DateTime::from_epoch_msec_tz(2246004900123, -36900).into()); + assert_eq!(chainpack_to_rpcvalue("8D04").unwrap(), DateTime::from_epoch_msec_tz(1_517_529_600_001, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8D8211").unwrap(), DateTime::from_epoch_msec_tz(1_517_529_600_001, 3600).into()); + assert_eq!(chainpack_to_rpcvalue("8DE63DDA02").unwrap(), DateTime::from_epoch_msec_tz(1_543_708_800_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DE8A8BFFE").unwrap(), DateTime::from_epoch_msec_tz(1_514_764_800_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DE6DC0E02").unwrap(), DateTime::from_epoch_msec_tz(1_546_300_800_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF00E60DC02").unwrap(), DateTime::from_epoch_msec_tz(1_577_836_800_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF015EAF002").unwrap(), DateTime::from_epoch_msec_tz(1_609_459_200_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF061258802").unwrap(), DateTime::from_epoch_msec_tz(1_924_992_000_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF100AC656602").unwrap(), DateTime::from_epoch_msec_tz(2_240_611_200_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF156D74D495F").unwrap(), DateTime::from_epoch_msec_tz(2_246_004_900_000, -36900).into()); + assert_eq!(chainpack_to_rpcvalue("8DF301533905E2375D").unwrap(), DateTime::from_epoch_msec_tz(2_246_004_900_123, -36900).into()); assert_eq!(chainpack_to_rpcvalue("8DF18169CEA7FE").unwrap(), DateTime::from_epoch_msec_tz(0, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DEDA8E7F2").unwrap(), DateTime::from_epoch_msec_tz(1493790723000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF1961334BEB4").unwrap(), DateTime::from_epoch_msec_tz(1493826723923, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF28B0DE42CD95F").unwrap(), DateTime::from_epoch_msec_tz(1493790751123, 36000).into()); - assert_eq!(chainpack_to_rpcvalue("8DEDA6B572").unwrap(), DateTime::from_epoch_msec_tz(1493826723000, 0).into()); - assert_eq!(chainpack_to_rpcvalue("8DF182D3308815").unwrap(), DateTime::from_epoch_msec_tz(1493832123000, -5400).into()); - assert_eq!(chainpack_to_rpcvalue("8DF1961334BEB4").unwrap(), DateTime::from_epoch_msec_tz(1493826723923, 0).into()); - - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1517529600001, 0).into()), "8D04"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1517529600001, 3600).into()), "8D8211"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1543708800000, 0).into()), "8DE63DDA02"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1514764800000, 0).into()), "8DE8A8BFFE"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1546300800000, 0).into()), "8DE6DC0E02"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1577836800000, 0).into()), "8DF00E60DC02"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1609459200000, 0).into()), "8DF015EAF002"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1924992000000, 0).into()), "8DF061258802"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(2240611200000, 0).into()), "8DF100AC656602"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(2246004900000, -36900).into()), "8DF156D74D495F"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(2246004900123, -36900).into()), "8DF301533905E2375D"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(0, 0).into()), "8DF18169CEA7FE"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493790723000, 0).into()), "8DEDA8E7F2"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493826723923, 0).into()), "8DF1961334BEB4"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493790751123, 36000).into()), "8DF28B0DE42CD95F"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493826723000, 0).into()), "8DEDA6B572"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493832123000, -5400).into()), "8DF182D3308815"); - assert_eq!(rpcvalue_to_chainpack(DateTime::from_epoch_msec_tz(1493826723923, 0).into()), "8DF1961334BEB4"); + assert_eq!(chainpack_to_rpcvalue("8DEDA8E7F2").unwrap(), DateTime::from_epoch_msec_tz(1_493_790_723_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF1961334BEB4").unwrap(), DateTime::from_epoch_msec_tz(1_493_826_723_923, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF28B0DE42CD95F").unwrap(), DateTime::from_epoch_msec_tz(1_493_790_751_123, 36000).into()); + assert_eq!(chainpack_to_rpcvalue("8DEDA6B572").unwrap(), DateTime::from_epoch_msec_tz(1_493_826_723_000, 0).into()); + assert_eq!(chainpack_to_rpcvalue("8DF182D3308815").unwrap(), DateTime::from_epoch_msec_tz(1_493_832_123_000, -5400).into()); + assert_eq!(chainpack_to_rpcvalue("8DF1961334BEB4").unwrap(), DateTime::from_epoch_msec_tz(1_493_826_723_923, 0).into()); + + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_517_529_600_001, 0).into()), "8D04"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_517_529_600_001, 3600).into()), "8D8211"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_543_708_800_000, 0).into()), "8DE63DDA02"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_514_764_800_000, 0).into()), "8DE8A8BFFE"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_546_300_800_000, 0).into()), "8DE6DC0E02"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_577_836_800_000, 0).into()), "8DF00E60DC02"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_609_459_200_000, 0).into()), "8DF015EAF002"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_924_992_000_000, 0).into()), "8DF061258802"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(2_240_611_200_000, 0).into()), "8DF100AC656602"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(2_246_004_900_000, -36900).into()), "8DF156D74D495F"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(2_246_004_900_123, -36900).into()), "8DF301533905E2375D"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(0, 0).into()), "8DF18169CEA7FE"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_790_723_000, 0).into()), "8DEDA8E7F2"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_826_723_923, 0).into()), "8DF1961334BEB4"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_790_751_123, 36000).into()), "8DF28B0DE42CD95F"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_826_723_000, 0).into()), "8DEDA6B572"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_832_123_000, -5400).into()), "8DF182D3308815"); + assert_eq!(rpcvalue_to_chainpack(&DateTime::from_epoch_msec_tz(1_493_826_723_923, 0).into()), "8DF1961334BEB4"); } #[test] fn test_double() { assert_eq!(chainpack_to_rpcvalue("830000000000C208B8").unwrap(), RpcValue::from(-9.094_583_978_896_067E-39_f64)); - assert_eq!(rpcvalue_to_chainpack(RpcValue::from(-9.094_583_978_896_067E-39_f64)), "830000000000C208B8"); + assert_eq!(rpcvalue_to_chainpack(&RpcValue::from(-9.094_583_978_896_067E-39_f64)), "830000000000C208B8"); } #[test] fn test_decimal() { let dec = crate::decimal::Decimal::new(0, 0); assert_eq!(chainpack_to_rpcvalue("8C0000").unwrap(), dec.into()); - assert_eq!(rpcvalue_to_chainpack(dec.into()), "8C0000"); + assert_eq!(rpcvalue_to_chainpack(&dec.into()), "8C0000"); } #[test] diff --git a/src/cpon.rs b/src/cpon.rs index 7d78448..aa18bf3 100644 --- a/src/cpon.rs +++ b/src/cpon.rs @@ -24,7 +24,7 @@ impl<'a, W> CponWriter<'a, W> pub fn new(write: &'a mut W) -> Self { CponWriter { byte_writer: ByteWriter::new(write), - indent: "".as_bytes().to_vec(), + indent: b"".to_vec(), no_oneliners: false, nest_count: 0, } @@ -41,11 +41,8 @@ impl<'a, W> CponWriter<'a, W> return false; } for it in lst.iter() { - match &it.value { - Value::List(_) => return false, - Value::Map(_) => return false, - Value::IMap(_) => return false, - _ => continue, + if matches!(&it.value, Value::List(_) | Value::Map(_) | Value::IMap(_)) { + return false; } } true @@ -62,14 +59,12 @@ impl<'a, W> CponWriter<'a, W> match iter.next() { Some(val) => { match &val.1.value { - Value::List(_) => return false, - Value::Map(_) => return false, - Value::IMap(_) => return false, + Value::List(_) | Value::Map(_) | Value::IMap(_) => return false, _ => (), } }, None => break, - }; + } n += 1; }; true @@ -80,11 +75,8 @@ impl<'a, W> CponWriter<'a, W> return false; } for k in map.0.iter() { - match &k.value.value { - Value::List(_) => return false, - Value::Map(_) => return false, - Value::IMap(_) => return false, - _ => continue, + if matches!(&k.value.value, Value::List(_) | Value::Map(_) | Value::IMap(_)) { + return false; } } true @@ -203,8 +195,8 @@ impl<'a, W> CponWriter<'a, W> self.write_byte(b'"')?; Ok(self.byte_writer.count() - cnt) } - fn write_datetime(&mut self, dt: &DateTime) -> WriteResult { - let cnt = self.write_bytes("d\"".as_bytes())?; + fn write_datetime(&mut self, dt: DateTime) -> WriteResult { + let cnt = self.write_bytes(b"d\"")?; let s = dt.to_iso_string_opt(&ToISOStringOptions { include_millis: IncludeMilliseconds::WhenNonZero, include_timezone: true @@ -258,7 +250,7 @@ impl<'a, W> CponWriter<'a, W> self.write_byte(b',')?; } self.indent_element(is_oneliner, n == 0)?; - self.write_int(*k as i64)?; + self.write_int(i64::from(*k))?; self.write_byte(b':')?; self.write(v)?; } @@ -322,11 +314,11 @@ impl Writer for CponWriter<'_, W> fn write_value(&mut self, val: &Value) -> WriteResult { let cnt: usize = self.byte_writer.count(); match val { - Value::Null => self.write_bytes("null".as_bytes()), + Value::Null => self.write_bytes(b"null"), Value::Bool(b) => if *b { - self.write_bytes("true".as_bytes()) + self.write_bytes(b"true") } else { - self.write_bytes("false".as_bytes()) + self.write_bytes(b"false") }, Value::Int(n) => self.write_int(*n), Value::UInt(n) => { @@ -336,8 +328,8 @@ impl Writer for CponWriter<'_, W> Value::String(s) => self.write_string(s), Value::Blob(b) => self.write_blob(b), Value::Double(n) => self.write_double(*n), - Value::Decimal(d) => self.write_decimal(d), - Value::DateTime(d) => self.write_datetime(d), + Value::Decimal(d) => self.write_decimal(*d), + Value::DateTime(d) => self.write_datetime(*d), Value::List(lst) => self.write_list(lst), Value::Map(map) => self.write_map(map), Value::IMap(map) => self.write_imap(map), @@ -363,7 +355,7 @@ impl<'a, R> CponReader<'a, R> b'A' ..= b'F' => Ok(b - b'A' + 10), b'a' ..= b'f' => Ok(b - b'a' + 10), b'0' ..= b'9' => Ok(b - b'0'), - c => Err(self.make_error(&format!("Illegal hex encoding character: {}", c), ReadErrorReason::InvalidCharacter)), + c => Err(self.make_error(&format!("Illegal hex encoding character: {c}"), ReadErrorReason::InvalidCharacter)), } } fn read_blob_esc(&mut self) -> Result { @@ -385,7 +377,7 @@ impl<'a, R> CponReader<'a, R> let hi = b; let lo = self.get_byte()?; let b = self.decode_hex_byte(hi)? * 16 + self.decode_hex_byte(lo)?; - buff.push(b) + buff.push(b); }, } } @@ -440,6 +432,7 @@ impl<'a, R> CponReader<'a, R> let key = if is_negative { -value } else { value }; self.skip_white_insignificant()?; let val = self.read()?; + #[expect(clippy::cast_possible_truncation, reason = "We hope that the key is small enough to fit")] map.insert(key as i32, val); } Ok(Value::from(map)) @@ -483,7 +476,7 @@ where R: Read self.byte_reader.get_byte() } fn make_error(&self, msg: &str, reason: ReadErrorReason) -> ReadError { - self.byte_reader.make_error(&format!("Cpon read error - {}", msg), reason) + self.byte_reader.make_error(&format!("Cpon read error - {msg}"), reason) } fn read_string(&mut self) -> Result { let mut buff: Vec = Vec::new(); @@ -515,7 +508,7 @@ where R: Read let s = std::str::from_utf8(&buff); match s { Ok(s) => Ok(Value::from(s)), - Err(e) => Err(self.make_error(&format!("Invalid String, Utf8 error: {}", e), ReadErrorReason::InvalidCharacter)), + Err(e) => Err(self.make_error(&format!("Invalid String, Utf8 error: {e}"), ReadErrorReason::InvalidCharacter)), } } } @@ -604,12 +597,12 @@ mod test test_cpon_round_trip("123.4", Decimal::new(1234, -1)); test_cpon_round_trip("0.123", Decimal::new(123, -3)); test_cpon_round_trip("-0.123", Decimal::new(-123, -3)); - test_cpon_round_trip("123000000e2", Decimal::new(123000000, 2)); + test_cpon_round_trip("123000000e2", Decimal::new(123_000_000, 2)); assert_eq!(Decimal::new(10000, 3).to_cpon_string(), "10000000."); assert_eq!(RpcValue::from_cpon("0e0").unwrap().as_decimal(), Decimal::new(0, 0)); assert_eq!(RpcValue::from_cpon("0.123e3").unwrap().as_decimal(), Decimal::new(123, 0)); - test_cpon_round_trip("1000000.", Decimal::new(1000000, 0)); - test_cpon_round_trip("50.03138741402532", Decimal::new(5003138741402532, -14)); + test_cpon_round_trip("1000000.", Decimal::new(1_000_000, 0)); + test_cpon_round_trip("50.03138741402532", Decimal::new(5_003_138_741_402_532, -14)); // We do not support such high precision. assert!(RpcValue::from_cpon("36.028797018963968").is_err()); assert_eq!(RpcValue::from_cpon(r#""foo""#).unwrap().as_str(), "foo"); @@ -635,8 +628,7 @@ mod test assert_eq!(RpcValue::from_cpon(r#"d"2022-01-02T12:59:06Z""#).unwrap().as_datetime(), DateTime::from_datetime(&dt)); - // Allow in tests - #[allow(clippy::too_many_arguments)] + #[expect(clippy::too_many_arguments, reason = "Allow in tests")] fn dt_from_ymd_hms_milli_tz_offset(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, milli: i64, tz_offset: i32) -> chrono::DateTime { if let LocalResult::Single(dt) = FixedOffset::east_opt(tz_offset).unwrap() .with_ymd_and_hms(year, month, day, hour, min, sec) { @@ -696,21 +688,21 @@ mod test // read very long decimal without overflow error, value is capped assert_eq!(RpcValue::from_cpon("123456789012345678901234567890123456789012345678901234567890").unwrap().as_int(), i64::MAX); - assert_eq!(RpcValue::from_cpon("9223372036854775806").unwrap().as_int(), 9223372036854775806_i64); + assert_eq!(RpcValue::from_cpon("9223372036854775806").unwrap().as_int(), 9_223_372_036_854_775_806_i64); assert_eq!(RpcValue::from_cpon("9223372036854775807").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_cpon("9223372036854775808").unwrap().as_int(), i64::MAX); - assert_eq!(RpcValue::from_cpon("0x7FFFFFFFFFFFFFFE").unwrap().as_int(), 0x7FFFFFFFFFFFFFFE_i64); + assert_eq!(RpcValue::from_cpon("0x7FFFFFFFFFFFFFFE").unwrap().as_int(), 0x7FFF_FFFF_FFFF_FFFE_i64); assert_eq!(RpcValue::from_cpon("0x7FFFFFFFFFFFFFFF").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_cpon("0x8000000000000000").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_cpon("-123456789012345678901234567890123456789012345678901234567890").unwrap().as_int(), i64::MIN); - assert_eq!(RpcValue::from_cpon("-9223372036854775807").unwrap().as_int(), -9223372036854775807_i64); + assert_eq!(RpcValue::from_cpon("-9223372036854775807").unwrap().as_int(), -9_223_372_036_854_775_807_i64); assert_eq!(RpcValue::from_cpon("-9223372036854775808").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_cpon("-9223372036854775809").unwrap().as_int(), i64::MIN); - assert_eq!(RpcValue::from_cpon("-0x7FFFFFFFFFFFFFFF").unwrap().as_int(), -0x7FFFFFFFFFFFFFFF_i64); + assert_eq!(RpcValue::from_cpon("-0x7FFFFFFFFFFFFFFF").unwrap().as_int(), -0x7FFF_FFFF_FFFF_FFFF_i64); assert_eq!(RpcValue::from_cpon("-0x8000000000000000").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_cpon("-0x8000000000000001").unwrap().as_int(), i64::MIN); diff --git a/src/datetime.rs b/src/datetime.rs index 8e97841..78897a7 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,4 +1,4 @@ -//use crate::rpcvalue::RpcValue; +#![allow(clippy::string_slice, reason = "strings are not utf8")] use std::cmp::Ordering; use std::fmt; @@ -9,7 +9,6 @@ use chrono::{FixedOffset, NaiveDateTime, Offset}; /// I'm storing whole DateTime in one i64 to keep size_of RpcValue == 24 const TZ_MASK: i64 = 127; pub enum IncludeMilliseconds { - #[allow(dead_code)] Never, Always, WhenNonZero, @@ -64,7 +63,7 @@ impl DateTime { let mut msec = epoch_msec; // offset in quarters of hour msec *= TZ_MASK + 1; - let offset = (utc_offset_sec / 60 / 15) as i64; + let offset = i64::from(utc_offset_sec / 60 / 15); msec |= offset & TZ_MASK; DateTime(msec) } @@ -80,7 +79,7 @@ impl DateTime { let mut msec = 0; let mut offset = 0; let mut rest = &s[PATTERN.len()..]; - if !rest.is_empty() && rest.as_bytes()[0] == b'.' { + if matches!(rest.as_bytes().first(), Some(b'.')) { rest = &rest[1..]; if rest.len() >= 3 { match rest[..3].parse::() { @@ -89,73 +88,72 @@ impl DateTime { rest = &rest[3..]; } Err(err) => { - return Err(format!("Parsing DateTime msec part error: {}, in '{}", err, iso_str)) + return Err(format!("Parsing DateTime msec part error: {err}, in '{iso_str}")) } } } } if !rest.is_empty() { - if rest.len() == 1 && rest.as_bytes()[0] == b'Z' { + if rest.len() == 1 && *rest.as_bytes().first().expect("len() is 1") == b'Z' { } else if rest.len() == 3 { if let Ok(hrs) = rest.parse::() { offset = 60 * 60 * hrs; } else { - return Err(format!("Invalid DateTime TZ(3) part: '{}, date time: {}", rest, iso_str)) + return Err(format!("Invalid DateTime TZ(3) part: '{rest}, date time: {iso_str}")) } } else if rest.len() == 5 { if let Ok(hrs) = rest.parse::() { offset = 60 * (60 * (hrs / 100) + (hrs % 100)); } else { - return Err(format!("Invalid DateTime TZ(5) part: '{}, date time: {}", rest, iso_str)) + return Err(format!("Invalid DateTime TZ(5) part: '{rest}, date time: {iso_str}")) } } else { - return Err(format!("Invalid DateTime TZ part: '{}, date time: {}", rest, iso_str)) + return Err(format!("Invalid DateTime TZ part: '{rest}, date time: {iso_str}")) } } - let epoch_msec = (ndt.and_utc().timestamp() - (offset as i64)) * 1000 + (msec as i64); + let epoch_msec = (ndt.and_utc().timestamp() - i64::from(offset)) * 1000 + i64::from(msec); let dt = DateTime::from_epoch_msec_tz(epoch_msec, offset); return Ok(dt) } } - Err(format!("Invalid DateTime: '{:?}", iso_str)) + Err(format!("Invalid DateTime: '{iso_str:?}")) } - pub fn epoc_msec_utc_offset(&self) -> (i64, i32) { + pub fn epoc_msec_utc_offset(self) -> (i64, i32) { let msec= self.0 / (TZ_MASK + 1); let mut offset = self.0 & TZ_MASK; if (offset & ((TZ_MASK + 1) / 2)) != 0 { // sign extension offset |= !TZ_MASK; } + #[expect(clippy::cast_possible_truncation, reason = "We hope that the offset is small enough to fit")] let offset = (offset * 15 * 60) as i32; (msec, offset) } - pub fn epoch_msec(&self) -> i64 { self.epoc_msec_utc_offset().0 } - pub fn utc_offset(&self) -> i32 { self.epoc_msec_utc_offset().1 } + pub fn epoch_msec(self) -> i64 { self.epoc_msec_utc_offset().0 } + pub fn utc_offset(self) -> i32 { self.epoc_msec_utc_offset().1 } - pub fn to_chrono_naivedatetime(&self) -> chrono::NaiveDateTime { + pub fn to_chrono_naivedatetime(self) -> chrono::NaiveDateTime { let msec = self.epoch_msec(); chrono::DateTime::from_timestamp_millis(msec).unwrap_or_default().naive_utc() } - pub fn to_chrono_datetime(&self) -> chrono::DateTime { - let offset = match FixedOffset::east_opt(self.utc_offset()) { - None => {FixedOffset::east_opt(0).unwrap()} - Some(o) => {o} - }; + pub fn to_chrono_datetime(self) -> chrono::DateTime { + let offset = FixedOffset::east_opt(self.utc_offset()) + .unwrap_or_else(|| FixedOffset::east_opt(0).expect("Zero is within the range")); chrono::DateTime::from_naive_utc_and_offset(self.to_chrono_naivedatetime(), offset) } - pub fn to_iso_string(&self) -> String { + pub fn to_iso_string(self) -> String { self.to_iso_string_opt(&ToISOStringOptions::default()) } - pub fn to_iso_string_opt(&self, opts: &ToISOStringOptions) -> String { + pub fn to_iso_string_opt(self, opts: &ToISOStringOptions) -> String { let dt = self.to_chrono_datetime(); let mut s = format!("{}", dt.format("%Y-%m-%dT%H:%M:%S")); let ms = self.epoch_msec() % 1000; match opts.include_millis { IncludeMilliseconds::Never => {} - IncludeMilliseconds::Always => { s.push_str(&format!(".{:03}", ms)); } + IncludeMilliseconds::Always => { s.push_str(&format!(".{ms:03}")); } IncludeMilliseconds::WhenNonZero => { if ms > 0 { - s.push_str(&format!(".{:03}", ms)); + s.push_str(&format!(".{ms:03}")); } } } @@ -173,32 +171,37 @@ impl DateTime { } let offset_hr = offset / 60 / 60; let offset_min = offset / 60 % 60; - s += &format!("{:02}", offset_hr); + s += &format!("{offset_hr:02}"); if offset_min > 0 { - s += &format!("{:02}", offset_min); + s += &format!("{offset_min:02}"); } } } s } - pub fn add_days(&self, days: i64) -> Self { + #[must_use] + pub fn add_days(self, days: i64) -> Self { let (msec, offset) = self.epoc_msec_utc_offset(); Self::from_epoch_msec_tz(msec + (days * 24 * 60 * 60 * 1000), offset) } - pub fn add_hours(&self, hours: i64) -> Self { + #[must_use] + pub fn add_hours(self, hours: i64) -> Self { let (msec, offset) = self.epoc_msec_utc_offset(); Self::from_epoch_msec_tz(msec + (hours * 60 * 60 * 1000), offset) } - pub fn add_minutes(&self, minutes: i64) -> Self { + #[must_use] + pub fn add_minutes(self, minutes: i64) -> Self { let (msec, offset) = self.epoc_msec_utc_offset(); Self::from_epoch_msec_tz(msec + (minutes * 60 * 1000), offset) } - pub fn add_seconds(&self, seconds: i64) -> Self { + #[must_use] + pub fn add_seconds(self, seconds: i64) -> Self { let (msec, offset) = self.epoc_msec_utc_offset(); Self::from_epoch_msec_tz(msec + (seconds * 1000), offset) } - pub fn add_millis(&self, millis: i64) -> Self { + #[must_use] + pub fn add_millis(self, millis: i64) -> Self { let (msec, offset) = self.epoc_msec_utc_offset(); Self::from_epoch_msec_tz(msec + millis, offset) } diff --git a/src/decimal.rs b/src/decimal.rs index 3cfaa57..2915bc9 100644 --- a/src/decimal.rs +++ b/src/decimal.rs @@ -1,3 +1,4 @@ +#![allow(clippy::cast_sign_loss, clippy::cast_possible_truncation, clippy::cast_precision_loss, reason = "Lots of casting here")] /// mantisa: 56, exponent: 8; /// I'm storing whole Decimal in one i64 to keep size_of RpcValue == 24 #[derive(Debug, Copy, Clone)] @@ -7,10 +8,11 @@ impl Decimal { pub fn new(mantissa: i64, exponent: i8) -> Decimal { let mut n = mantissa << 8; - n |= (exponent as i64) & 0xff; + n |= i64::from(exponent) & 0xff; Decimal(n) } - pub fn normalize(&self) -> Decimal { + #[must_use] + pub fn normalize(self) -> Decimal { let (mut mantissa, mut exponent) = self.decode(); if mantissa == 0 { return Decimal::new(0, 0); @@ -24,18 +26,18 @@ impl Decimal { Decimal::new(mantissa, exponent) } - pub fn decode(&self) -> (i64, i8) { + pub fn decode(self) -> (i64, i8) { let m = self.0 >> 8; - let e = self.0 as i8; + let e = (self.0 & 0xFF) as i8; (m, e) } - pub fn mantissa(&self) -> i64 { + pub fn mantissa(self) -> i64 { self.decode().0 } - pub fn exponent(&self) -> i8 { + pub fn exponent(self) -> i8 { self.decode().1 } - pub fn to_cpon_string(&self) -> String { + pub fn to_cpon_string(self) -> String { let mut neg = false; let (mut mantissa, exponent) = self.decode(); if mantissa < 0 { @@ -77,12 +79,11 @@ impl Decimal { } s } - pub fn to_f64(&self) -> f64 { + pub fn to_f64(self) -> f64 { let decoded = self.decode(); let mut d = decoded.0 as f64; let exp = decoded.1; // We probably don't want to call .cmp() because of performance loss - #[allow(clippy::comparison_chain)] if exp < 0 { for _ in exp .. 0 { d /= 10.; @@ -155,17 +156,17 @@ mod tests { #[test] fn decimal_normalization_removes_trailing_zeros() { let d1 = Decimal::new(1000, -3); - let n1 = Decimal::normalize(&d1); + let n1 = Decimal::normalize(d1); assert_eq!(n1.mantissa(), 1); assert_eq!(n1.exponent(), 0); let d2 = Decimal::new(1200, -3); - let n2 = Decimal::normalize(&d2); + let n2 = Decimal::normalize(d2); assert_eq!(n2.mantissa(), 12); assert_eq!(n2.exponent(), -1); let d3 = Decimal::new(500, -1); - let n3 = Decimal::normalize(&d3); + let n3 = Decimal::normalize(d3); assert_eq!(n3.mantissa(), 5); assert_eq!(n3.exponent(), 1); } @@ -173,7 +174,7 @@ mod tests { #[test] fn decimal_normalization_zero() { let zero = Decimal::new(0, -10); - let n = Decimal::normalize(&zero); + let n = Decimal::normalize(zero); assert_eq!(n.mantissa(), 0); assert_eq!(n.exponent(), 0); } @@ -181,7 +182,7 @@ mod tests { #[test] fn decimal_normalization_negative() { let d = Decimal::new(-5000, -3); - let n = Decimal::normalize(&d); + let n = Decimal::normalize(d); assert_eq!(n.mantissa(), -5); assert_eq!(n.exponent(), 0); } @@ -191,7 +192,7 @@ mod tests { // This also tests conversion to f64 with a positive/negative/zero exponent. for exp in [-3, 0, 3] { let d = Decimal::new(1200, exp); - let n = Decimal::normalize(&d); + let n = Decimal::normalize(d); let diff = (d.to_f64() - n.to_f64()).abs(); assert!(diff < 1e-12); } diff --git a/src/jaq.rs b/src/jaq.rs index f5b4b22..709f797 100644 --- a/src/jaq.rs +++ b/src/jaq.rs @@ -126,7 +126,7 @@ impl From>> for RpcValue { fn from(value: core::ops::Range>) -> Self { let kv = |(k, v): (&str, Option<_>)| v.map(|v| (k.to_owned(), v)); let kvs = [("start", value.start), ("end", value.end)]; - kvs.into_iter().flat_map(kv).collect::>().into() + kvs.into_iter().filter_map(kv).collect::>().into() } } @@ -150,6 +150,7 @@ impl jaq_all::jaq_std::ValT for RpcValue { fn as_isize(&self) -> Option { match self.value { + #[expect(clippy::cast_possible_truncation, reason = "We assume pointer size is 64-bit")] Value::Int(num) => Some(num as isize), _ => None, } @@ -178,7 +179,7 @@ impl jaq_all::jaq_std::ValT for RpcValue { impl jaq_all::jaq_core::ValT for RpcValue { fn from_num(n: &str) -> ValR { - Ok(n.parse::().map(RpcValue::from).unwrap_or(0_i64.into())) + Ok(n.parse::().map_or_else(|_| 0_i64.into(), RpcValue::from)) } fn from_map>(iter: I) -> ValR { @@ -191,7 +192,7 @@ impl jaq_all::jaq_core::ValT for RpcValue { Box::new(list .into_iter() .enumerate() - .map(|(idx, val)| Ok((RpcValue::from(idx as isize), val)))) + .map(|(idx, val)| Ok((RpcValue::from(idx.cast_signed()), val)))) } Value::Map(map) => { Box::new(map @@ -231,9 +232,11 @@ impl jaq_all::jaq_core::ValT for RpcValue { fn index(self, index: &Self) -> ValR { match (&self.value, &index.value) { (Value::Null, _) => Ok(RpcValue::null()), + #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "We assume pointer size is 64-bit")] (Value::String(rv), Value::Int(i)) => Ok(rv.chars().nth(*i as usize).map(|cha| cha.to_string()).into()), - (Value::List(list), Value::Int(i)) => Ok((*list).get(*i as usize).cloned().unwrap_or(RpcValue::null())), - (Value::Map(o), Value::String(key)) => Ok(o.get(key.as_str()).cloned().unwrap_or(RpcValue::null())), + #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "We assume pointer size is 64-bit")] + (Value::List(list), Value::Int(i)) => Ok((*list).get(*i as usize).cloned().unwrap_or_else(RpcValue::null)), + (Value::Map(o), Value::String(key)) => Ok(o.get(key.as_str()).cloned().unwrap_or_else(RpcValue::null)), (_s, _) => Err(Error::typ(self, "")), } } diff --git a/src/json.rs b/src/json.rs index ab461f6..29ea44c 100644 --- a/src/json.rs +++ b/src/json.rs @@ -64,7 +64,7 @@ impl<'a, W> JsonWriter<'a, W> self.write_byte(b'"')?; Ok(self.byte_writer.count() - cnt) } - fn write_datetime(&mut self, dt: &DateTime) -> WriteResult { + fn write_datetime(&mut self, dt: DateTime) -> WriteResult { let cnt = self.byte_writer.count(); self.add_wrap_type_tag("DateTime")?; self.write_byte(b'"')?; @@ -111,7 +111,7 @@ impl<'a, W> JsonWriter<'a, W> self.write_byte(b',')?; } self.write_byte(b'"')?; - self.write_int(*k as i64)?; + self.write_int(i64::from(*k))?; self.write_byte(b'"')?; self.write_byte(b':')?; self.write(v)?; @@ -202,19 +202,19 @@ where W: Write fn write_value(&mut self, val: &Value) -> WriteResult { let cnt: usize = self.byte_writer.count(); match val { - Value::Null => self.write_bytes("null".as_bytes()), + Value::Null => self.write_bytes(b"null"), Value::Bool(b) => if *b { - self.write_bytes("true".as_bytes()) + self.write_bytes(b"true") } else { - self.write_bytes("false".as_bytes()) + self.write_bytes(b"false") }, Value::Int(n) => self.write_int(*n), - Value::UInt(n) => self.write_int(*n as i64), + Value::UInt(n) => self.write_int(n.cast_signed()), Value::String(s) => self.write_string(s), Value::Blob(b) => self.write_blob(b), Value::Double(n) => self.write_double(*n), - Value::Decimal(d) => self.write_decimal(d), - Value::DateTime(d) => self.write_datetime(d), + Value::Decimal(d) => self.write_decimal(*d), + Value::DateTime(d) => self.write_datetime(*d), Value::List(lst) => self.write_list(lst), Value::Map(map) => self.write_map(map), Value::IMap(map) => self.write_imap(map), @@ -251,7 +251,7 @@ impl<'a, R> JsonReader<'a, R> let val = match t { Some("Blob") => { if let Value::String(hex) = v { - let data = hex::decode(hex.as_str()).map_err(|e| self.make_error(&format!("Hex blob decode error: {}.", e), ReadErrorReason::InvalidCharacter))?; + let data = hex::decode(hex.as_str()).map_err(|e| self.make_error(&format!("Hex blob decode error: {e}."), ReadErrorReason::InvalidCharacter))?; Value::from(data) } else { return Err(self.make_error("Blob must be encoded as hex string.", ReadErrorReason::InvalidCharacter)) @@ -259,7 +259,7 @@ impl<'a, R> JsonReader<'a, R> } Some("DateTime") => { if let Value::String(dt) = v { - let dt = DateTime::from_iso_str(dt).map_err(|e| self.make_error(&format!("DateTime decode error: {}.", e), ReadErrorReason::InvalidCharacter))?; + let dt = DateTime::from_iso_str(dt).map_err(|e| self.make_error(&format!("DateTime decode error: {e}."), ReadErrorReason::InvalidCharacter))?; Value::from(dt) } else { return Err(self.make_error("DateTime must be encoded as ISO string.", ReadErrorReason::InvalidCharacter)) @@ -269,7 +269,7 @@ impl<'a, R> JsonReader<'a, R> if let Value::Map(im) = v { let mut imap = crate::IMap::default(); for (k, v) in im.iter() { - let ik = k.parse::().map_err(|e| self.make_error(&format!("IMap key decode error: {}.", e), ReadErrorReason::InvalidCharacter))?; + let ik = k.parse::().map_err(|e| self.make_error(&format!("IMap key decode error: {e}."), ReadErrorReason::InvalidCharacter))?; imap.insert(ik, v.clone()); } Value::from(imap) @@ -294,7 +294,7 @@ where R: Read self.byte_reader.get_byte() } fn make_error(&self, msg: &str, reason: ReadErrorReason) -> ReadError { - self.byte_reader.make_error(&format!("Cpon read error - {}", msg), reason) + self.byte_reader.make_error(&format!("Cpon read error - {msg}"), reason) } fn read_string(&mut self) -> Result { let mut buff: Vec = Vec::new(); @@ -321,16 +321,16 @@ where R: Read hex.push(self.get_byte()? as char); hex.push(self.get_byte()? as char); hex.push(self.get_byte()? as char); - let code_point = u32::from_str_radix(&hex, 16).map_err(|e| self.make_error(&format!("Invalid unicode escape sequence: {:?} - {}", hex, e), ReadErrorReason::InvalidCharacter))?; - let ch = char::from_u32(code_point).ok_or(self.make_error(&format!("Invalid code point: {code_point}"), ReadErrorReason::InvalidCharacter))?; + let code_point = u32::from_str_radix(&hex, 16).map_err(|e| self.make_error(&format!("Invalid unicode escape sequence: {hex:?} - {e}"), ReadErrorReason::InvalidCharacter))?; + let ch = char::from_u32(code_point).ok_or_else(|| self.make_error(&format!("Invalid code point: {code_point}"), ReadErrorReason::InvalidCharacter))?; let mut utf8 = [0; 4]; let s = ch.encode_utf8(&mut utf8); for b in s.as_bytes() { - buff.push(*b) + buff.push(*b); } } _ => { - buff.push(b) + buff.push(b); }, } } @@ -346,7 +346,7 @@ where R: Read let s = std::str::from_utf8(&buff); match s { Ok(s) => Ok(Value::from(s)), - Err(e) => Err(self.make_error(&format!("Invalid String, Utf8 error: {}", e), ReadErrorReason::InvalidCharacter)), + Err(e) => Err(self.make_error(&format!("Invalid String, Utf8 error: {e}"), ReadErrorReason::InvalidCharacter)), } } } @@ -386,7 +386,7 @@ impl Reader for JsonReader<'_, R> break 'a RpcValue::new(val, None); } } - }; + } break 'a RpcValue::new(val, None); }; Ok(rv) @@ -429,7 +429,7 @@ mod test let rv2 = RpcValue::from(val); assert_eq!(rv1, rv2); let json2 = rv1.to_json(); - assert_eq!(&json.replace(" ", ""), &json2); + assert_eq!(&json.replace(' ', ""), &json2); } fn test_cpon_cross_check(json: &str, cpon: &str) { let json1 = fix_tags(json); @@ -438,7 +438,7 @@ mod test assert_eq!(rv1, rv2); let json2 = rv1.to_json(); // println!("{json2} <=> {json2}"); - assert_eq!(&json1.replace(" ", ""), &json2); + assert_eq!(&json1.replace(' ', ""), &json2); } #[test] fn test_string() { @@ -509,7 +509,7 @@ mod test const MINUTE: i32 = 60; const HOUR: i32 = 60 * MINUTE; - #[allow(clippy::too_many_arguments)] + #[expect(clippy::too_many_arguments, reason = "Allow in tests")] fn dt_from_ymd_hms_milli_tz_offset(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32, milli: i64, tz_offset: i32) -> chrono::DateTime { if let LocalResult::Single(dt) = FixedOffset::east_opt(tz_offset).unwrap() .with_ymd_and_hms(year, month, day, hour, min, sec) { @@ -549,21 +549,21 @@ mod test // read very long decimal without overflow error, value is capped assert_eq!(RpcValue::from_json("123456789012345678901234567890123456789012345678901234567890").unwrap().as_int(), i64::MAX); - assert_eq!(RpcValue::from_json("9223372036854775806").unwrap().as_int(), 9223372036854775806_i64); + assert_eq!(RpcValue::from_json("9223372036854775806").unwrap().as_int(), 9_223_372_036_854_775_806_i64); assert_eq!(RpcValue::from_json("9223372036854775807").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_json("9223372036854775808").unwrap().as_int(), i64::MAX); - assert_eq!(RpcValue::from_json("0x7FFFFFFFFFFFFFFE").unwrap().as_int(), 0x7FFFFFFFFFFFFFFE_i64); + assert_eq!(RpcValue::from_json("0x7FFFFFFFFFFFFFFE").unwrap().as_int(), 0x7FFF_FFFF_FFFF_FFFE_i64); assert_eq!(RpcValue::from_json("0x7FFFFFFFFFFFFFFF").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_json("0x8000000000000000").unwrap().as_int(), i64::MAX); assert_eq!(RpcValue::from_json("-123456789012345678901234567890123456789012345678901234567890").unwrap().as_int(), i64::MIN); - assert_eq!(RpcValue::from_json("-9223372036854775807").unwrap().as_int(), -9223372036854775807_i64); + assert_eq!(RpcValue::from_json("-9223372036854775807").unwrap().as_int(), -9_223_372_036_854_775_807_i64); assert_eq!(RpcValue::from_json("-9223372036854775808").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_json("-9223372036854775809").unwrap().as_int(), i64::MIN); - assert_eq!(RpcValue::from_json("-0x7FFFFFFFFFFFFFFF").unwrap().as_int(), -0x7FFFFFFFFFFFFFFF_i64); + assert_eq!(RpcValue::from_json("-0x7FFFFFFFFFFFFFFF").unwrap().as_int(), -0x7FFF_FFFF_FFFF_FFFF_i64); assert_eq!(RpcValue::from_json("-0x8000000000000000").unwrap().as_int(), i64::MIN); assert_eq!(RpcValue::from_json("-0x8000000000000001").unwrap().as_int(), i64::MIN); diff --git a/src/metamap.rs b/src/metamap.rs index 14fda8b..8b7f4dc 100644 --- a/src/metamap.rs +++ b/src/metamap.rs @@ -42,11 +42,13 @@ impl GetIndex for i32 { } impl GetIndex for u32 { fn make_key(&self) -> GetKey<'_> { + #[expect(clippy::cast_possible_wrap, reason = "We hope that the key is small enough to fit")] GetKey::Int(*self as i32) } } impl GetIndex for usize { fn make_key(&self) -> GetKey<'_> { + #[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap, reason = "We hope that the key is small enough to fit")] GetKey::Int(*self as i32) } } @@ -70,6 +72,7 @@ impl MetaMap { pub fn len(&self) -> usize { self.0.len() } + #[expect(clippy::needless_pass_by_value, reason = "GetIndex is implemented for a lot of types, and some might not be owned")] pub fn insert(&mut self, key: I, value: RpcValue) -> &mut Self where I: GetIndex { @@ -77,12 +80,13 @@ impl MetaMap { None => { let key = key.make_key(); let key = MetaKey::from(&key); - self.0.push(MetaKeyVal{key, value }) + self.0.push(MetaKeyVal{key, value }); }, - Some(ix) => self.0[ix].value = value, + Some(ix) => self.0.get_mut(ix).expect("The value has been found with .find()").value = value, } self } + #[expect(clippy::needless_pass_by_value, reason = "GetIndex is implemented for a lot of types, and some might not be owned")] pub fn remove(&mut self, key: I) -> Option where I: GetIndex { @@ -91,13 +95,11 @@ impl MetaMap { Some(ix) => Some(self.0.remove(ix).value), } } + #[expect(clippy::needless_pass_by_value, reason = "GetIndex is implemented for a lot of types, and some might not be owned")] pub fn get(&self, key: I) -> Option<&RpcValue> where I: GetIndex { - match self.find(&key) { - Some(ix) => Some(&self.0[ix].value), - None => None, - } + self.find(&key).map(|ix| &self.0.get(ix).expect("The value has been found with .find()").value) } fn find(&self, key: &I) -> Option where I: GetIndex @@ -133,11 +135,11 @@ impl fmt::Display for MetaMap { let mut wr = CponWriter::new(&mut buff); let res = wr.write_meta(self); if let Err(e) = res { - log::warn!("to_cpon write with error: {}", e); + log::warn!("to_cpon write with error: {e}"); return write!(fmt, "") } match String::from_utf8(buff) { - Ok(s) => write!(fmt, "{}", s), + Ok(s) => write!(fmt, "{s}"), Err(_) => write!(fmt, ""), } } diff --git a/src/rpcvalue.rs b/src/rpcvalue.rs index 9f1c05f..adb79a8 100644 --- a/src/rpcvalue.rs +++ b/src/rpcvalue.rs @@ -57,6 +57,7 @@ pub enum Value { Bool(bool), DateTime(datetime::DateTime), Decimal(decimal::Decimal), + #[expect(clippy::box_collection, reason = "We're using the Box to lower stack size")] String(Box), Blob(Box), List(Box), @@ -100,7 +101,7 @@ impl Value { } impl From<()> for Value { - fn from(_: ()) -> Self { + fn from((): ()) -> Self { Value::Null } } @@ -124,7 +125,7 @@ impl From for Value { } impl From<&String> for Value { fn from(val: &String) -> Self { - Value::String(Box::new(val.to_string())) + Value::String(Box::new(val.clone())) } } @@ -858,7 +859,7 @@ where fn try_from(value: RpcValue) -> Result { let mut res = Self::new(); - for (key, val) in Map::try_from(value)?.into_iter() { + for (key, val) in Map::try_from(value)? { let val = val.try_into().map_err(|e| format!("Wrong item at key `{key}`: {e}"))?; res.insert(key,val); } @@ -893,7 +894,7 @@ where fn try_from(value: RpcValue) -> Result { let mut res = Self::new(); - for (key, val) in IMap::try_from(value)?.into_iter() { + for (key, val) in IMap::try_from(value)? { res.insert( key, val.try_into() @@ -934,11 +935,13 @@ impl GetIndex for i32 { } impl GetIndex for u32 { fn make_key(&self) -> GetKey<'_> { + #[expect(clippy::cast_possible_wrap, reason = "We hope that the key is small enough to fit")] GetKey::Int(*self as i32) } } impl GetIndex for usize { fn make_key(&self) -> GetKey<'_> { + #[expect(clippy::cast_possible_truncation, clippy::cast_possible_wrap, reason = "We hope that the key is small enough to fit")] GetKey::Int(*self as i32) } } @@ -994,22 +997,26 @@ impl RpcValue { pub fn as_i64(&self) -> i64 { match &self.value { Value::Int(d) => *d, - Value::UInt(d) => *d as i64, + Value::UInt(d) => d.cast_signed(), _ => 0, } } pub fn as_i32(&self) -> i32 { - self.as_i64() as i32 + #[expect(clippy::cast_possible_truncation, reason = "If the user wants an i32, we don't care about overflows")] + let res = self.as_i64() as i32; + res } pub fn as_u64(&self) -> u64 { match &self.value { - Value::Int(d) => *d as u64, + Value::Int(d) => d.cast_unsigned(), Value::UInt(d) => *d, _ => 0, } } pub fn as_u32(&self) -> u32 { - self.as_u64() as u32 + #[expect(clippy::cast_possible_truncation, reason = "If the user wants an u32, we don't care about overflows")] + let res = self.as_u64() as u32; + res } pub fn as_f64(&self) -> f64 { match &self.value { @@ -1019,7 +1026,9 @@ impl RpcValue { } pub fn as_usize(&self) -> usize { match &self.value { + #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "We expect a 64-bit platform")] Value::Int(d) => *d as usize, + #[expect(clippy::cast_possible_truncation, reason = "We expect a 64-bit platform")] Value::UInt(d) => *d as usize, _ => 0_usize, } @@ -1078,12 +1087,14 @@ impl RpcValue { _ => EMPTY_IMAP.get_or_init(IMap::new), } } + #[expect(clippy::needless_pass_by_value, reason = "GetIndex is implemented for a lot of types, and some might not be owned")] pub fn get(&self, key: I) -> Option<&RpcValue> where I: GetIndex, { match key.make_key() { GetKey::Int(ix) => match &self.value { + #[expect(clippy::cast_sign_loss, reason = "We expect the int to be positive")] Value::List(lst) => lst.get(ix as usize), Value::IMap(map) => map.get(&ix), _ => None, @@ -1242,8 +1253,8 @@ mod test { let dt = chrono::offset::Local::now(); let rv = RpcValue::from(dt); assert_eq!( - rv.as_datetime().epoch_msec() + rv.as_datetime().utc_offset() as i64 * 1000, - dt.timestamp_millis() + dt.offset().fix().local_minus_utc() as i64 * 1000 + rv.as_datetime().epoch_msec() + i64::from(rv.as_datetime().utc_offset()) * 1000, + dt.timestamp_millis() + i64::from(dt.offset().fix().local_minus_utc()) * 1000 ); let vec1 = vec![RpcValue::from(123), RpcValue::from("foo")]; diff --git a/src/serde/de.rs b/src/serde/de.rs index 30dbae6..e5c3930 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -11,7 +11,7 @@ pub struct ValueDeserializer<'a> { pub value: &'a Value, } -impl<'de, 'a> IntoDeserializer<'de> for ValueDeserializer<'a> { +impl IntoDeserializer<'_> for ValueDeserializer<'_> { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -19,7 +19,7 @@ impl<'de, 'a> IntoDeserializer<'de> for ValueDeserializer<'a> { } } -impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { +impl<'de> serde::Deserializer<'de> for ValueDeserializer<'_> { type Error = de::value::Error; fn deserialize_any(self, visitor: V) -> Result @@ -103,7 +103,7 @@ impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { { match self.value { Value::UInt(u) => visitor.visit_u64(*u), - Value::Int(i) if *i >= 0 => visitor.visit_u64(*i as u64), + Value::Int(i) if *i >= 0 => visitor.visit_u64(i.cast_unsigned()), // Value::Double(f) if *f >= 0.0 => visitor.visit_u64(*f as u64), _ => Err(de::Error::custom("expected unsigned integer-compatible value")), } @@ -137,7 +137,9 @@ impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { match self.value { Value::Double(f) => visitor.visit_f64(*f), Value::Decimal(decimal) => visitor.visit_f64(decimal.to_f64()), + #[expect(clippy::cast_precision_loss, reason = "We hope we don't lose precision by casting to f64")] Value::Int(i) => visitor.visit_f64(*i as f64), + #[expect(clippy::cast_precision_loss, reason = "We hope we don't lose precision by casting to f64")] Value::UInt(u) => visitor.visit_f64(*u as f64), _ => Err(de::Error::custom("expected float-compatible value")), } @@ -267,7 +269,7 @@ impl<'de, 'a> serde::Deserializer<'de> for ValueDeserializer<'a> { match self.value { // Externally tagged (e.g. { "Variant": {...} }) Value::Map(map) if map.len() == 1 => { - let (variant, value) = map.iter().next().unwrap(); + let (variant, value) = map.iter().next().expect("len() is 1"); let enum_access = SimpleEnumAccess { variant, value: Some(&value.value) @@ -324,7 +326,7 @@ struct SimpleEnumAccess<'a> { value: Option<&'a Value>, } -impl<'a, 'de> EnumAccess<'de> for SimpleEnumAccess<'a> { +impl<'de> EnumAccess<'de> for SimpleEnumAccess<'_> { type Error = serde::de::value::Error; type Variant = Self; @@ -335,22 +337,18 @@ impl<'a, 'de> EnumAccess<'de> for SimpleEnumAccess<'a> { } } -impl<'a, 'de> VariantAccess<'de> for SimpleEnumAccess<'a> { +impl<'de> VariantAccess<'de> for SimpleEnumAccess<'_> { type Error = serde::de::value::Error; fn unit_variant(self) -> Result<(), Self::Error> { - match self.value { - Some(v) => serde::Deserialize::deserialize(ValueDeserializer { value: v }), - None => Ok(()), - } + self.value + .map_or(Ok(()), |v| serde::Deserialize::deserialize(ValueDeserializer { value: v })) } fn newtype_variant_seed(self, seed: T) -> Result where T: DeserializeSeed<'de> { - match self.value { - Some(v) => seed.deserialize(ValueDeserializer { value: v }), - None => Err(de::Error::custom("Expected value for newtype variant")), - } + self.value + .map_or_else(|| Err(de::Error::custom("Expected value for newtype variant")), |v| seed.deserialize(ValueDeserializer { value: v })) } fn tuple_variant(self, _len: usize, visitor: V) -> Result @@ -366,7 +364,7 @@ impl<'a, 'de> VariantAccess<'de> for SimpleEnumAccess<'a> { struct UntaggedEnumAccess<'a>(&'a Value); -impl<'a, 'de> EnumAccess<'de> for UntaggedEnumAccess<'a> { +impl<'de> EnumAccess<'de> for UntaggedEnumAccess<'_> { type Error = serde::de::value::Error; type Variant = Self; @@ -380,7 +378,7 @@ impl<'a, 'de> EnumAccess<'de> for UntaggedEnumAccess<'a> { } } -impl<'a, 'de> VariantAccess<'de> for UntaggedEnumAccess<'a> { +impl<'de> VariantAccess<'de> for UntaggedEnumAccess<'_> { type Error = serde::de::value::Error; fn unit_variant(self) -> Result<(), Self::Error> { @@ -410,7 +408,7 @@ impl<'de> serde::Deserialize<'de> for crate::DateTime { { struct DateTimeVisitor; - impl<'de> Visitor<'de> for DateTimeVisitor { + impl Visitor<'_> for DateTimeVisitor { type Value = crate::DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -483,7 +481,7 @@ impl<'de> serde::Deserialize<'de> for Blob { struct BlobVisitor; - impl<'de> Visitor<'de> for BlobVisitor { + impl Visitor<'_> for BlobVisitor { type Value = Blob; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/serde/ser.rs b/src/serde/ser.rs index c3830c5..098d76b 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -44,43 +44,43 @@ impl serde::Serializer for ValueSerializer { } fn serialize_i8(self, v: i8) -> Result { - Ok(Value::Int(v as _)) + Ok(Value::Int(<_>::from(v))) } fn serialize_i16(self, v: i16) -> Result { - Ok(Value::Int(v as _)) + Ok(Value::Int(<_>::from(v))) } fn serialize_i32(self, v: i32) -> Result { - Ok(Value::Int(v as _)) + Ok(Value::Int(<_>::from(v))) } fn serialize_i64(self, v: i64) -> Result { - Ok(Value::Int(v as _)) + Ok(Value::Int(v)) } fn serialize_u8(self, v: u8) -> Result { - Ok(Value::UInt(v as _)) + Ok(Value::UInt(<_>::from(v))) } fn serialize_u16(self, v: u16) -> Result { - Ok(Value::UInt(v as _)) + Ok(Value::UInt(<_>::from(v))) } fn serialize_u32(self, v: u32) -> Result { - Ok(Value::UInt(v as _)) + Ok(Value::UInt(<_>::from(v))) } fn serialize_u64(self, v: u64) -> Result { - Ok(Value::UInt(v as _)) + Ok(Value::UInt(v)) } fn serialize_f32(self, v: f32) -> Result { - Ok(Value::Double(v as _)) + Ok(Value::Double(<_>::from(v))) } fn serialize_f64(self, v: f64) -> Result { - Ok(Value::Double(v as _)) + Ok(Value::Double(v)) } fn serialize_char(self, v: char) -> Result { @@ -669,12 +669,8 @@ impl serde::ser::SerializeMap for ValueSerializeIMap { fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> { if let Value::Int(i) = key.serialize(ValueSerializer)? { - if i <= i32::MAX as _ { - self.next_key = Some(i as _); - Ok(()) - } else { - Err(Error::custom("IMap keys must be i32-compatible")) - } + self.next_key = Some(i32::try_from(i).map_err(|_err| Error::custom("IMap keys must be i32-compatible"))?); + Ok(()) } else { Err(Error::custom("IMap key must be an integer")) } diff --git a/src/textrdwr.rs b/src/textrdwr.rs index dee0c8c..d5fdbf2 100644 --- a/src/textrdwr.rs +++ b/src/textrdwr.rs @@ -12,11 +12,11 @@ pub trait TextWriter : Writer { Ok(self.write_count() - cnt) } fn write_double(&mut self, n: f64) -> WriteResult { - let s = format!("{}", n); + let s = n.to_string(); let cnt = self.write_bytes(s.as_bytes())?; Ok(self.write_count() - cnt) } - fn write_decimal(&mut self, decimal: &Decimal) -> WriteResult { + fn write_decimal(&mut self, decimal: Decimal) -> WriteResult { let s = decimal.to_cpon_string(); let cnt = self.write_bytes(s.as_bytes())?; Ok(self.write_count() - cnt) @@ -94,7 +94,7 @@ pub trait TextReader : Reader { for c in token.as_bytes() { let b = self.get_byte()?; if b != *c { - return Err(self.make_error(&format!("Expected '{}'.", token), ReadErrorReason::InvalidCharacter)) + return Err(self.make_error(&format!("Expected '{token}'."), ReadErrorReason::InvalidCharacter)) } } Ok(()) @@ -120,13 +120,12 @@ pub trait TextReader : Reader { let mut is_overflow = false; fn add_digit(val: i64, base: i64, digit: u8) -> Option { let res = val.checked_mul(base)?; - let res = res.checked_add(digit as i64)?; + let res = res.checked_add(i64::from(digit))?; Some(res) } loop { let b = self.peek_byte(); let digit = match b { - 0 => break, b'+' | b'-' => { if n != 0 { break; @@ -234,10 +233,10 @@ pub trait TextReader : Reader { let ReadInt { value, digit_cnt, is_overflow, .. } = self.read_int(mantissa, true)?; decimal_overflow = decimal_overflow || is_overflow; mantissa = value; - if mantissa >= 36028797018963968 { + if mantissa >= 36_028_797_018_963_968 { decimal_overflow = true; } - dec_cnt = digit_cnt as i64; + dec_cnt = i64::from(digit_cnt); } b'e' | b'E' => { if state != State::Mantissa && state != State::Decimals { @@ -263,13 +262,14 @@ pub trait TextReader : Reader { if decimal_overflow { return Err(self.make_error("Not enough precision to read the Decimal", ReadErrorReason::InvalidCharacter)) } + #[expect(clippy::cast_possible_truncation, reason = "We hope that the new exponent is not big enough to truncate")] return Ok(Value::from(Decimal::new(mantissa, (exponent - dec_cnt) as i8))) } if is_uint { if decimal_overflow { return Ok(Value::from(i64::MAX as u64)) } - return Ok(Value::from(mantissa as u64)) + return Ok(Value::from(mantissa.cast_unsigned())) } if decimal_overflow { return Ok(Value::from(if is_negative { i64::MIN } else { i64::MAX })) @@ -312,7 +312,7 @@ pub trait TextReader : Reader { _ => return Err(self.make_error("Read MetaMap key internal error", ReadErrorReason::InvalidCharacter)), } }, - _ => return Err(self.make_error(&format!("Invalid Map key '{}'", b), ReadErrorReason::InvalidCharacter)), + _ => return Err(self.make_error(&format!("Invalid Map key '{b}'"), ReadErrorReason::InvalidCharacter)), }; self.skip_white_insignificant()?; let val = self.read()?; diff --git a/src/util.rs b/src/util.rs index 20b362d..bb62749 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,4 @@ +#![allow(clippy::indexing_slicing, reason = "Lots of indexing here")] use log::LevelFilter; pub fn parse_log_verbosity<'a>(verbosity: &'a str, module_path: &'a str) -> Vec<(Option<&'a str>, LevelFilter)> { @@ -34,15 +35,15 @@ pub fn hex_array(data: &[u8]) -> String { if ret.len() > 1 { ret += ","; } - ret += &format!("0x{:02x}", b); + ret += &format!("0x{b:02x}"); } ret += "]"; ret } pub fn hex_dump(data: &[u8]) -> String { - let mut ret: String = Default::default(); - let mut hex_line: String = Default::default(); - let mut char_line: String = Default::default(); + let mut ret = String::default(); + let mut hex_line = String::default(); + let mut char_line = String::default(); let box_size = (data.len() / 16 + 1) * 16 + 1; for i in 0..box_size { let byte = if i < data.len() { Some(data[i]) } else { None }; @@ -53,23 +54,17 @@ pub fn hex_dump(data: &[u8]) -> String { if i > 0 { ret += "\n"; } - ret += &format!("{:04x} ", i); + ret += &format!("{i:04x} "); } hex_line.clear(); char_line.clear(); } - let hex_str = match byte { - None => { " ".to_string() } - Some(b) => { format!("{:02x} ", b) } - }; - let c_str = match byte { - None => { " ".to_string() } - Some(b) => { - let c = b as char; - let c = if c >= ' ' && c < (127 as char) { c } else { '.' }; - format!("{}", c) - } - }; + let hex_str = byte.map_or_else(|| " ".to_string(), |b| format!("{b:02x} ")); + let c_str = byte.map_or_else(|| " ".to_string(), |b| { + let c = b as char; + let c = if c >= ' ' && c < (127 as char) { c } else { '.' }; + c.to_string() + }); hex_line += &hex_str; char_line += &c_str; } diff --git a/tests/test.rs b/tests/test.rs index 450ff8d..3ee2706 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -83,8 +83,8 @@ mod test { "oneFieldStruct" => shvproto::make_map!("x" => 4565), "vecIntField" => vec![1_i32, 2_i32].into_iter().map(shvproto::RpcValue::from).collect::>(), "vecEmptyStructField" => vec![shvproto::make_map!(), shvproto::make_map!()].into_iter().map(shvproto::RpcValue::from).collect::>(), - "mapIntField" => [("aaa".to_string(), 111)].into_iter().collect::>(), - "imapField" => [(420, 111)].into_iter().collect::>(), + "mapIntField" => std::iter::once(("aaa".to_string(), 111)).collect::>(), + "imapField" => std::iter::once((420, 111)).collect::>(), "rpcValueField" => RpcValue::from(42), ).into(); @@ -176,7 +176,7 @@ mod test { test_case(AllVariants::Blob(vec![1, 2, 3])); test_case(AllVariants::List(vec![shvproto::RpcValue::from("some_value")])); test_case(AllVariants::Map(shvproto::make_map!("key" => 1234))); - test_case(AllVariants::IMap([(420, 111.into())].into_iter().collect::>())); + test_case(AllVariants::IMap(std::iter::once((420, 111.into())).collect::>())); test_case(EnumWithUserStruct::OneFieldStructVariant(OneFieldStruct{x: 123})); test_case(EnumWithUserStruct::IntVariant(123)); @@ -200,7 +200,7 @@ mod test { fn generic_struct() { let int_struct_in: GenericStruct:: = shvproto::make_map!("x" => 123, "y" => vec![123, 456], "z" => "some_string").try_into().expect("Failed to parse"); let int_struct_rpcvalue: shvproto::RpcValue = int_struct_in.clone().into(); - let int_struct_out: GenericStruct:: = int_struct_rpcvalue.clone().try_into().expect("Failed to parse"); + let int_struct_out: GenericStruct:: = int_struct_rpcvalue.try_into().expect("Failed to parse"); assert_eq!(int_struct_in, int_struct_out); } diff --git a/tests/test_cp2cp.rs b/tests/test_cp2cp.rs index 05e2a6a..c5dd146 100644 --- a/tests/test_cp2cp.rs +++ b/tests/test_cp2cp.rs @@ -27,7 +27,7 @@ mod test { let output = run_cp2cp(data)?; let exit_code = output.status.code().unwrap(); let output = String::from_utf8(output.stdout).map_err(|e| e.to_string())?; - let mut lines = output.split("\n"); + let mut lines = output.split('\n'); let block_length = lines.next().unwrap().parse::().unwrap(); let frame_length = lines.next().unwrap().parse::().unwrap(); let proto = lines.next().unwrap().parse::().unwrap(); @@ -98,9 +98,8 @@ mod test { thread::spawn(move || { stdin.write_all(&block).expect("Failed to write to stdin"); }); - child.wait_with_output().map_err(|err| err.to_string()).and_then(|output| { - RpcValue::from_chainpack(output.stdout).map_err(|err| err.to_string()) - }) + let output = child.wait_with_output().map_err(|err| err.to_string())?; + RpcValue::from_chainpack(output.stdout).map_err(|err| err.to_string()) } #[test]