diff --git a/CHANGELOG.md b/CHANGELOG.md index 743f7df..b52206f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - aux: Removed the `to_slow5_t` required method on `AuxField` since it wasn't necessary and making dealing with enums harder + +### Fixed + +- mem: Fixed potential memory leaks during error paths + ## [0.9.0] - 2022-12-10 ### Added diff --git a/tests/read_blow5.rs b/examples/read_blow5.rs similarity index 77% rename from tests/read_blow5.rs rename to examples/read_blow5.rs index a01701f..b514069 100644 --- a/tests/read_blow5.rs +++ b/examples/read_blow5.rs @@ -1,12 +1,12 @@ use slow5::{FileReader, RecordExt}; -#[test] fn main() -> anyhow::Result<()> { let file_path = "examples/example3.blow5"; let mut slow5 = FileReader::open(file_path)?; let mut records = slow5.records(); while let Some(Ok(rec)) = records.next() { - println!("{:?}", rec.read_id()); + let id = std::str::from_utf8(rec.read_id())?; + println!("{id}"); } Ok(()) } diff --git a/examples/slow5-serde/src/main.rs b/examples/slow5-serde/src/main.rs index 290a115..00a2490 100644 --- a/examples/slow5-serde/src/main.rs +++ b/examples/slow5-serde/src/main.rs @@ -1,6 +1,6 @@ use slow5::FileReader; -fn main() -> anyhow::Result<()>{ +fn main() -> anyhow::Result<()> { let mut reader = FileReader::open("examples/example.slow5")?; for read in reader.records() { let read = read?; diff --git a/src/auxiliary.rs b/src/auxiliary.rs index 0aadd42..57fc4ef 100644 --- a/src/auxiliary.rs +++ b/src/auxiliary.rs @@ -265,8 +265,7 @@ fn parse_aux_field_set_error(ret: i32) -> Slow5Error { -2 => Slow5Error::MissingAttribute, -3 => Slow5Error::AuxTypeMismatch, -4 => Slow5Error::EnumOutOfRange, - _ => unreachable!("Invalid ret: {ret}") - + _ => Slow5Error::SetAuxFieldError, } } @@ -438,7 +437,10 @@ mod test { assert!(rec.set_aux_field(&mut writer, "char", "a").is_err()); assert!(rec.set_aux_field(&mut writer, "enum", EnumField(1)).is_ok()); - assert!(rec.set_aux_field(&mut writer, "enum", EnumField(10)).is_err()); + assert!( + rec.set_aux_field(&mut writer, "enum", EnumField(10)) + .is_err() + ); Ok(()) } diff --git a/src/reader.rs b/src/reader.rs index da3edf4..daac98d 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -183,12 +183,31 @@ impl FileReader { ReadIdIter::new(self) } - /// Returns iterator over the labels for an enum auxiliary field + /// Returns iterator over the labels for an enum auxiliary field. Useful for + /// converting into an indexable collection + /// and using [`crate::EnumField`] to get the value for an auxiliary field /// /// # Errors /// Return Err if field's type is not an auxiliary enum or field is not - /// within in the header - // TODO Example + /// within in the header. This can be due to the wrong name being passed as + /// field, the field is not an enum field or more. + /// + /// # Example + /// ``` + /// # use slow5::FileReader; + /// # use slow5::EnumField; + /// # fn main() -> anyhow::Result<()> { + /// let mut reader = FileReader::open("examples/example3.blow5")?; + /// let labels = reader + /// .iter_aux_enum_labels("end_reason")? + /// .map(|x| String::from_utf8(x.to_vec())) + /// .collect::, _>>()?; + /// let rec = reader.get_record("0035aaf9-a746-4bbd-97c4-390ddc27c756")?; + /// let EnumField(idx) = rec.get_aux_field("end_reason")?; + /// assert_eq!(labels[idx], "unknown"); + /// # Ok(()) + /// # } + /// ``` pub fn iter_aux_enum_labels(&self, field: B) -> Result where B: Into>, @@ -205,7 +224,25 @@ impl FileReader { } /// Returns iterator over attribute keys for all read groups - // TODO Example + /// + /// # Example + /// ``` + /// # use slow5::FileReader; + /// # use slow5::EnumField; + /// # use std::collections::HashSet; + /// # fn main() -> anyhow::Result<()> { + /// let mut reader = FileReader::open("examples/example3.blow5")?; + /// let attr_keys = reader + /// .iter_attr_keys()? + /// .map(|attr| std::str::from_utf8(attr).unwrap()) + /// .collect::>(); + /// + /// assert!(attr_keys.contains("file_version")); + /// assert!(attr_keys.contains("asic_id")); + /// assert!(!attr_keys.contains("random attributefdsafs")); + /// # Ok(()) + /// # } + /// ``` pub fn iter_attr_keys(&self) -> Result { let mut n = 0; let keys = unsafe { slow5_get_hdr_keys(self.header().header, &mut n) }; diff --git a/src/record.rs b/src/record.rs index 4fe2d8a..7752aed 100644 --- a/src/record.rs +++ b/src/record.rs @@ -146,15 +146,15 @@ impl RecordBuilder { let read_id_cs = to_cstring(read_id.clone()).map_err(|_| { libc::free(record as *mut c_void); - BuilderError::ReadIDError})?; + BuilderError::ReadIDError + })?; let read_id_ptr = read_id_cs.into_raw(); let read_id_len = read_id.len(); (*record).read_id = libc::strdup(read_id_ptr as *const c_char); - (*record).read_id_len = read_id_len - .try_into() - .map_err(|_| { - libc::free(record as *mut c_void); - BuilderError::ConversionError})?; + (*record).read_id_len = read_id_len.try_into().map_err(|_| { + libc::free(record as *mut c_void); + BuilderError::ConversionError + })?; let _ = CString::from_raw(read_id_ptr); (*record).read_group = read_group; @@ -163,16 +163,14 @@ impl RecordBuilder { (*record).range = range; (*record).sampling_rate = sampling_rate; - let len_raw_signal = raw_signal - .len() - .try_into() - .map_err(|_| { - libc::free(record as *mut c_void); - BuilderError::ConversionError})?; + let len_raw_signal = raw_signal.len().try_into().map_err(|_| { + libc::free(record as *mut c_void); + BuilderError::ConversionError + })?; (*record).len_raw_signal = len_raw_signal; let raw_signal_ptr = allocate(size_of::() * raw_signal.len()).map_err(|e| { - libc::free(record as *mut c_void); - e + libc::free(record as *mut c_void); + e })? as *mut i16; for (idx, &signal) in raw_signal.iter().enumerate() { @@ -212,7 +210,8 @@ impl serde::Serialize for Record { { use serde::ser::SerializeMap; let mut state = serializer.serialize_map(Some(7))?; - let read_id = std::str::from_utf8(self.read_id()).map_err(|_| serde::ser::Error::custom("read_id contains non-UTF8 character"))?; + let read_id = std::str::from_utf8(self.read_id()) + .map_err(|_| serde::ser::Error::custom("read_id contains non-UTF8 character"))?; state.serialize_entry("read_id", read_id)?; state.serialize_entry("read_group", &self.read_group())?; state.serialize_entry("digitisation", &self.digitisation())?; @@ -625,7 +624,7 @@ mod test { #[cfg(feature = "serde")] #[test] fn test_serialize() -> anyhow::Result<()> { - use serde_test::{Token, assert_ser_tokens}; + use serde_test::{assert_ser_tokens, Token}; let rec = RecordBuilder::default() .read_id("test_id") .read_group(0) @@ -635,37 +634,32 @@ mod test { .sampling_rate(4000.0) .raw_signal(&[0, 1, 2, 3]) .build()?; - assert_ser_tokens(&rec, &[ - Token::Map { len: Some(7) }, - - Token::Str("read_id"), - Token::Str("test_id"), - - Token::Str("read_group"), - Token::U32(0), - - Token::Str("digitisation"), - Token::F64(4096.0), - - Token::Str("offset"), - Token::F64(4.0), - - Token::Str("range"), - Token::F64(12.0), - - Token::Str("sampling_rate"), - Token::F64(4000.0), - - Token::Str("raw_signal"), - Token::Seq { len: Some(4) }, - Token::I16(0), - Token::I16(1), - Token::I16(2), - Token::I16(3), - Token::SeqEnd, - - Token::MapEnd, - ]); + assert_ser_tokens( + &rec, + &[ + Token::Map { len: Some(7) }, + Token::Str("read_id"), + Token::Str("test_id"), + Token::Str("read_group"), + Token::U32(0), + Token::Str("digitisation"), + Token::F64(4096.0), + Token::Str("offset"), + Token::F64(4.0), + Token::Str("range"), + Token::F64(12.0), + Token::Str("sampling_rate"), + Token::F64(4000.0), + Token::Str("raw_signal"), + Token::Seq { len: Some(4) }, + Token::I16(0), + Token::I16(1), + Token::I16(2), + Token::I16(3), + Token::SeqEnd, + Token::MapEnd, + ], + ); Ok(()) } }