diff --git a/src/db/db_format.rs b/src/db/db_format.rs new file mode 100644 index 0000000000000000000000000000000000000000..1af9eefafc00903e21c6888f9e1b2c5ff486a6b2 --- /dev/null +++ b/src/db/db_format.rs @@ -0,0 +1,133 @@ +use std::ops::Deref; +use crate::db::db_format::ValueType::{K_TYPE_DELETION, K_TYPE_VALUE}; +use crate::util::slice::Slice; + +/// Maximum encoding length of a BlockHandle +pub const k_num_levels: usize = 7; + +// Level-0 compaction is started when we hit this many files. +pub const kl0_compaction_trigger: usize = 4; + +// Soft limit on number of level-0 files. We slow down writes at this point. +pub const kl0_slowdown_writes_trigger: usize = 8; + +// Maximum number of level-0 files. We stop writes at this point. +pub const kL0_stop_writes_trigger: usize = 12; + +// Maximum level to which a new compacted memtable is pushed if it +// does not create overlap. We try to push to level 2 to avoid the +// relatively expensive level 0=>1 compactions and to avoid some +// expensive manifest file operations. We do not push all the way to +// the largest level since that can generate a lot of wasted disk +// space if the same key space is being repeatedly overwritten. +pub const k_max_mem_compact_level: usize = 2; + +// Approximate gap in bytes between samples of data read during iteration. +pub const k_read_bytes_period: usize = 1048576; + +// kValueTypeForSeek defines the ValueType that should be passed when +// constructing a ParsedInternalKey object for seeking to a particular +// sequence number (since we sort sequence numbers in decreasing order +// and the value type is embedded as the low 8 bits in the sequence +// number in internal keys, we need to use the highest-numbered +// ValueType, not the lowest). +pub const k_value_type_for_seek: ValueType = ValueType::K_TYPE_VALUE; + +pub enum ValueType { + /// 0x0 + K_TYPE_DELETION, + + /// 0x1 + K_TYPE_VALUE, +} + +// typedef uint64_t SequenceNumber; + +pub struct ParsedInternalKey { + user_key: Slice, + // sequence: SequenceNumber, + value_type: ValueType +} + +impl ValueType { + pub fn get_value(&self) -> i32 { + let le = match self { + K_TYPE_DELETION => 0, + K_TYPE_VALUE => 1 + }; + + le + } +} + +impl TryFrom for ValueType { + type Error = String; + + /// i32 转 ValueType + /// + /// # Arguments + /// + /// * `value`: 值 + /// + /// returns: Result>::Error> + /// + /// # Examples + /// + /// ``` + /// let rs: ValueType = ValueType::try_from(1)?; + /// assert!(&rs == K_TYPE_DELETION); + /// ``` + #[inline] + fn try_from(value: i32) -> Result { + match value { + 0 => Ok(K_TYPE_DELETION), + 1 => Ok(K_TYPE_VALUE), + // all other numbers + _ => Err(String::from(format!("Unknown code: {}", value))) + } + } +} + +impl Default for ParsedInternalKey { + #[inline] + fn default() -> Self { + ParsedInternalKey { + user_key: Default::default(), + value_type: K_TYPE_DELETION, + } + } +} + +impl ParsedInternalKey { + fn new(u: Slice, /* seq: SequenceNumber, */ t: ValueType) -> ParsedInternalKey { + ParsedInternalKey { + user_key: u, + value_type: t, + } + } + + fn debug_string() -> Slice { + Slice::default() + } + + /// Return the length of the encoding of "key". + fn internal_key_encoding_length(key: ParsedInternalKey) -> usize { + key.user_key.size() + 8 + } + + fn append_internal_key(key : ParsedInternalKey) -> Slice { + todo!() + } + + /// Attempt to parse an internal key from "internal_key". On success, + /// stores the parsed data in "*result", and returns true. + /// On error, returns false, leaves "*result" in an undefined state. + fn parse_internal_key(internal_key : Slice, target: ParsedInternalKey) -> bool { + todo!() + } + + /// Returns the user key portion of an internal key. + fn extract_user_key(internal_key : Slice) -> Slice { + todo!() + } +} diff --git a/src/db/db_format_test.rs b/src/db/db_format_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f987ff82cd7e19d75c6b11c1fee08352783f08c --- /dev/null +++ b/src/db/db_format_test.rs @@ -0,0 +1,5 @@ + +#[test] +fn test_db_format() { + todo!() +} \ No newline at end of file diff --git a/src/db/file_meta_data.rs b/src/db/file_meta_data.rs new file mode 100644 index 0000000000000000000000000000000000000000..789c0bf5c87f1692933f3728b6b3e3f6a551d0b2 --- /dev/null +++ b/src/db/file_meta_data.rs @@ -0,0 +1,11 @@ + +pub struct FileMetaData { + refs: u32, + // Seeks allowed until compaction + allowed_seeks: u32, + number: u64, + // File size in bytes + file_size: u64, + // smallest: InternalKey, + +} \ No newline at end of file diff --git a/src/db/file_meta_data_test.rs b/src/db/file_meta_data_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..b40ab666c281871b98f1ff732759c7ad3ab61d44 --- /dev/null +++ b/src/db/file_meta_data_test.rs @@ -0,0 +1,5 @@ + +#[test] +fn test_file_meta_data() { + todo!() +} \ No newline at end of file diff --git a/src/db/log_writer.rs b/src/db/log_writer.rs index 42406db3f609f86e330a8e039f8f53ee9541a3c3..48e567cda808384f73fa3c57b73a2ebeb7eb0338 100644 --- a/src/db/log_writer.rs +++ b/src/db/log_writer.rs @@ -79,7 +79,7 @@ impl LogWriter { header[6] = record_type; let mut crc = CRC::extend(self.type_crc[record_type as usize], data); crc = CRC::mask(crc); - Coding::encode_fixed32(crc, header.as_mut(), 0); + Coding::encode_fixed32(&mut crc, header.as_mut(), 0); self.file_writer.write(header.as_ref())?; self.block_offset += K_HEADER_SIZE; if !data.is_empty() { diff --git a/src/db/mod.rs b/src/db/mod.rs index 5a34cf6ff1473e78a6cc2b7ce8c884ce44c5318f..a673123505cd4497fd109aee2da55ec1390c012a 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -9,6 +9,10 @@ mod log_wr_test; pub mod skip_list; pub mod mem_table; pub mod db; +pub mod db_format; +mod db_format_test; +pub mod file_meta_data; +mod file_meta_data_test; /// 默认调表 pub type DefaultSkipList = SkipList; diff --git a/src/table/format.rs b/src/table/format.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a64d4b8bbf2d5da987a1838645a74e9da56e6be --- /dev/null +++ b/src/table/format.rs @@ -0,0 +1,228 @@ +use crate::traits::coding_trait::CodingTrait; +use crate::util::coding; +use crate::util::slice::Slice; +use crate::util::Result; + +/// Maximum encoding length of a BlockHandle +pub const k_max_encoded_length: u32 = 10 + 10; + +/// Encoded length of a Footer. Note that the serialization of a +/// Footer will always occupy exactly this many bytes. It consists +/// of two block handles and a magic number. +pub const k_encoded_length: u32 = 2 * k_max_encoded_length + 8; + +// // kTableMagicNumber was picked by running +// // echo http://code.google.com/p/leveldb/ | sha1sum +// // and taking the leading 64 bits. +// pub const k_table_magic_number: &str = 0xdb4775248b80fb57ull; + +/// 1-byte type + 32-bit crc +pub const k_block_trailer_size: usize = 5; + +pub struct BlockHandle { + // 偏移量 + offset_: u64, + // + size_: u64 +} + +/// Footer encapsulates the fixed information stored at the tail +/// end of every table file. +pub struct Footer { + metaindex_handle_: BlockHandle, + index_handle_: BlockHandle +} + +pub struct BlockContents { + // Actual contents of data + data: Slice, + + // True if data can be cached + cachable: bool, + + // True if caller should delete[] data.data() + heap_allocated:bool, +} + +trait Block { + /// + /// The offset of the block in the file. + /// + fn offset(&self) -> u64; + + /// + /// set offset + /// # Arguments + /// + /// * `offset`: + /// + fn set_offset(&mut self, offset: u64); + + // The size of the stored block + fn size(&self) -> u64; + + /// + /// set size + /// # Arguments + /// + /// * `size`: + /// + fn set_size(&mut self, size: u64); + + /// + /// 将 Block 对象编码成 Slice + /// + /// # Arguments + /// + /// returns: Slice + /// + /// # Examples + /// + /// ``` + /// + /// ``` + fn encode_to(&self) -> Slice; + + /// + /// 将 Slice 对象解码后与BlockHandle比较,是否可以成功 + /// + /// # Arguments + /// * `input`: + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// + /// ``` + fn decode_from(&self, input: Slice) -> Result; +} + +trait Foot { + // The block handle for the metaindex block of the table + fn metaindex_handle() -> BlockHandle; + + fn set_metaindex_handle(h: BlockHandle); + + fn index_handle() -> BlockHandle; + + fn set_index_handle(h: BlockHandle); + + /// + /// 将 Foot 对象编码成 Slice + /// + /// # Arguments + /// + /// returns: Slice + /// + /// # Examples + /// + /// ``` + /// + /// ``` + fn encode_to(&self) -> Slice; + + /// + /// 将 Slice 对象解码后与 BlockHandle 比较,是否可以成功 + /// + /// # Arguments + /// * `input`: + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// + /// ``` + fn decode_from(&self, input: Slice) -> Result; +} + +trait BlockContent { + /// Read the block identified by "handle" from "file". On failure + /// return non-OK. On success fill *result and return OK. + fn read_block( + // todo RandomAccessFile, ReadOptions 未提供 + // file: RandomAccessFile, options: ReadOptions, + handle: BlockHandle + ) -> Result; + +} + +impl Block for BlockHandle { + fn offset(&self) -> u64 { + self.offset_ + } + + fn set_offset(&mut self, offset: u64) { + self.offset_ = offset; + } + + fn size(&self) -> u64 { + self.size_ + } + + fn set_size(&mut self, size: u64) { + self.size_ = size; + } + + fn encode_to(&self) -> Slice { + todo!() + + // // Sanity check that all fields have been set + // assert!(self.offset_ != 0); + // assert!(self.size_ != 0); + // + // let mut buf: [u8; 4] = [0, 0, 0, 0]; + // coding::Coding::put_varint64(&mut buf, 0, &self.offset_); + // + // Slice::default() + } + + fn decode_from(&self, input: Slice) -> Result { + todo!() + } +} + +impl Default for BlockHandle { + #[inline] + fn default() -> Self { + BlockHandle { + offset_: 0, + size_: 0, + } + } +} + +impl Foot for Footer { + fn metaindex_handle() -> BlockHandle { + todo!() + } + + fn set_metaindex_handle(h: BlockHandle) { + todo!() + } + + fn index_handle() -> BlockHandle { + todo!() + } + + fn set_index_handle(h: BlockHandle) { + todo!() + } + + fn encode_to(&self) -> Slice { + todo!() + } + + fn decode_from(&self, input: Slice) -> Result { + todo!() + } +} + +impl BlockContent for BlockContents { + fn read_block(handle: BlockHandle) -> Result { + todo!() + } +} diff --git a/src/table/format_test.rs b/src/table/format_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..e106bee8cbcc8205bb4a200e6d18dfe5f320a017 --- /dev/null +++ b/src/table/format_test.rs @@ -0,0 +1,7 @@ + + +// #################### BlockHandle test +#[test] +fn test_block_handle() { + todo!() +} \ No newline at end of file diff --git a/src/table/mod.rs b/src/table/mod.rs index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4a1f72c4b2a61afc943a8dfeb8e0c9cc6e1cf55 100644 --- a/src/table/mod.rs +++ b/src/table/mod.rs @@ -0,0 +1,4 @@ +pub mod format; +mod format_test; +pub mod ss_table; +mod ss_table_test; \ No newline at end of file diff --git a/src/table/ss_table.rs b/src/table/ss_table.rs new file mode 100644 index 0000000000000000000000000000000000000000..869c7a708fd72d4566dd5a18f02cb54253f1c437 --- /dev/null +++ b/src/table/ss_table.rs @@ -0,0 +1,4 @@ + +pub struct SSTable { + +} \ No newline at end of file diff --git a/src/table/ss_table_test.rs b/src/table/ss_table_test.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb5f800dbae1937e33f8194db84dc4adb0baa1a6 --- /dev/null +++ b/src/table/ss_table_test.rs @@ -0,0 +1,5 @@ + +#[test] +fn test_ss_table() { + todo!() +} \ No newline at end of file diff --git a/src/util/comparator.rs b/src/util/comparator.rs index 71560febc43ae7dcc5fa88e950541c95b55666a6..d28c48dca3864796d4c14fd939460dbdf01a597e 100644 --- a/src/util/comparator.rs +++ b/src/util/comparator.rs @@ -91,37 +91,29 @@ impl ComparatorTrait for BytewiseComparatorImpl { /// InternalKeyComparator pub struct InternalKeyComparator { - // fn user_comparator(&self) -> Box { - // todo!() - // } - - // fn Compare(InternalKey, InternalKey) -} - -impl Default for InternalKeyComparator { - fn default() -> Self { - Self {} - } + // user_comparator_: ComparatorTrait } -/// InternalKeyComparator 比较器: 用来比较内部键(Internal Key)。 -/// 内部键值是为了方便处理,将原普通键、序列号和值类型组成的新键。 -impl ComparatorTrait for InternalKeyComparator { - // todo InternalKeyComparator 的构造方法 - - fn compare(&self, _a: &Slice, _b: &Slice) -> Option { - todo!() - } - - fn get_name(&self) -> String { - String::from("leveldb.InternalKeyComparator") - } - - fn find_shortest_separator(&self, _start: &String, _limit: &Slice) -> String { - todo!() - } - - fn find_short_successor(&self, _key: &String) -> String { - todo!() - } -} +// /// InternalKeyComparator 比较器: 用来比较内部键(Internal Key)。 +// /// 内部键值是为了方便处理,将原普通键、序列号和值类型组成的新键。 +// impl ComparatorTrait for InternalKeyComparator { +// fn new(c: ComparatorTrait) -> InternalKeyComparator { +// todo!() +// } +// +// fn compare(&self, _a: &Slice, _b: &Slice) -> Option { +// todo!() +// } +// +// fn get_name(&self) -> String { +// String::from("leveldb.InternalKeyComparator") +// } +// +// fn find_shortest_separator(&self, _start: &String, _limit: &Slice) -> String { +// todo!() +// } +// +// fn find_short_successor(&self, _key: &String) -> String { +// todo!() +// } +// } diff --git a/src/util/comparator_test.rs b/src/util/comparator_test.rs index 121c49a024ac25436a2f5a8cf3d0e89bd70de7fc..8d5ec27e64d93426b5588042be40bd37be857112 100644 --- a/src/util/comparator_test.rs +++ b/src/util/comparator_test.rs @@ -20,7 +20,7 @@ mod test { let option_val = comp.compare(&Slice::from("a"), &Slice::from("ab")); assert_eq!(option_val.unwrap(), Ordering::Less); - // todo Slice 存在 bug 未修复 + // // todo Slice 存在 bug 未修复 // let comp = BytewiseComparatorImpl::default(); // let option_val = comp.compare(&Slice::from("b"), &Slice::from("abcd")); // assert_eq!(option_val.unwrap(), Ordering::Greater); @@ -123,10 +123,4 @@ mod test { String::from(Slice::from_buf(expect_u8_max_vec.as_slice()))); } - #[test] - fn test_internal_key_comparator_get_name() { - let name = InternalKeyComparator::default().get_name(); - assert_eq!("leveldb.InternalKeyComparator", name); - } - } \ No newline at end of file diff --git a/src/util/filter_policy.rs b/src/util/filter_policy.rs index 07b33b228f3fd65c17766efe2738abe1a020b64a..912dbaa0ff7e43da6cee5a529b06007b18a03066 100644 --- a/src/util/filter_policy.rs +++ b/src/util/filter_policy.rs @@ -1,35 +1,26 @@ use std::ops::{BitOr, Mul, Shl}; use crate::traits::filter_policy_trait::{FilterPolicy}; use crate::util::hash::{Hash, ToHash}; +use crate::util::r#const::HASH_DEFAULT_SEED; use crate::util::slice::Slice; pub trait FromPolicy { fn from_bits_per_key(&self) -> usize; + fn from_k(&self) -> usize; } +/// 其他成员的语义扩展 +pub trait AsBloomHash { + #[inline] + fn bloom_hash(&self) -> u32; +} + pub struct BloomFilterPolicy { bits_per_key: usize, k: usize } -impl<'a> BloomFilterPolicy { - pub fn bloom_hash(key: &Slice) -> u32 { - key.to_hash_with_seed(0xbc9f1d34) - } -} - -/// get struct BloomFilterPolicy 属性 -impl FromPolicy for BloomFilterPolicy { - fn from_bits_per_key(&self) -> usize { - self.bits_per_key - } - - fn from_k(&self) -> usize { - self.k - } -} - impl BloomFilterPolicy { pub fn new(bits_per_key: usize) -> Self { // We intentionally round down to reduce probing cost a little bit @@ -51,6 +42,23 @@ impl BloomFilterPolicy { } } +impl<'a> BloomFilterPolicy { + pub fn bloom_hash(key: &Slice) -> u32 { + key.to_hash_with_seed(HASH_DEFAULT_SEED) + } +} + +/// get struct BloomFilterPolicy 属性 +impl FromPolicy for BloomFilterPolicy { + fn from_bits_per_key(&self) -> usize { + self.bits_per_key + } + + fn from_k(&self) -> usize { + self.k + } +} + // dyn FilterPolicy + FromPolicy impl FilterPolicy for BloomFilterPolicy { @@ -76,7 +84,9 @@ impl FilterPolicy for BloomFilterPolicy { dst_chars[bytes] = self.k as u8; for i in 0..n { - let mut h : u32 = BloomFilterPolicy::bloom_hash(keys.get(i).unwrap()); + let slice = keys.get(i).unwrap(); + + let mut h : u32 = slice.bloom_hash(); let delta : u32 = (h >> 17) | (h << 15); for j in 0..self.k { @@ -113,7 +123,7 @@ impl FilterPolicy for BloomFilterPolicy { return true; } - let mut h : u32 = BloomFilterPolicy::bloom_hash(key); + let mut h : u32 = key.bloom_hash(); // Rotate right 17 bits let delta = (h >> 17) | (h << 15); @@ -128,4 +138,18 @@ impl FilterPolicy for BloomFilterPolicy { return true; } +} + +/// 实现了 Slice 转 bloom_hash 的特质 +/// Sample: +/// ``` +/// let val = "aabbccd"; +/// let slice: Slice = Slice::from_buf(val.as_bytes()); +/// let hash_val = slice.bloom_hash(); +/// ``` +impl AsBloomHash for Slice { + #[inline] + fn bloom_hash(&self) -> u32 { + BloomFilterPolicy::bloom_hash(self) + } } \ No newline at end of file diff --git a/src/util/filter_policy_test.rs b/src/util/filter_policy_test.rs index 280f19ea53104572012e0681c15a4a9576698125..e84ee121de60da5aa508025477dcad7d8ab595bd 100644 --- a/src/util/filter_policy_test.rs +++ b/src/util/filter_policy_test.rs @@ -1,7 +1,8 @@ use std::ptr::null; use crate::traits::filter_policy_trait::FilterPolicy; use crate::util::bloom_filter; -use crate::util::filter_policy::{BloomFilterPolicy, FromPolicy}; +use crate::util::filter_policy::{AsBloomHash, BloomFilterPolicy, FromPolicy}; +use crate::util::hash::ToHash; use crate::util::slice::Slice; // #################### BloomFilterPolicy test @@ -11,6 +12,8 @@ fn test_bloom_hash() { let slice: Slice = Slice::from_buf(val.as_bytes()); let hash_val = BloomFilterPolicy::bloom_hash(&slice); + let hash_val_1 = slice.bloom_hash(); + assert_eq!(hash_val, hash_val_1); assert_eq!(hash_val, 2085241752); } @@ -23,7 +26,6 @@ fn test_new() { let bloom_filter = BloomFilterPolicy::new(800); assert_eq!(bloom_filter.from_bits_per_key(), 800); assert_eq!(bloom_filter.from_k(), 30); - } // #################### FilterPolicy test diff --git a/src/util/mod.rs b/src/util/mod.rs index b3e53d7a409d35c4e4aa0f68e21bf58ae1bc91bc..ac618ba685878cfab66cf255b16a99db73c215b0 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -29,9 +29,9 @@ mod filter_policy_test; pub mod histogram; mod histogram_test; -mod hash; +pub mod hash; mod hash_test; -mod mutex_lock; +pub mod mutex_lock; mod mutex_lock_test; pub mod options; diff --git a/src/util/status.rs b/src/util/status.rs index e7607f580c7910c12a47e556322920b75b623363..4af55973969df732fb9aa9e9a58cd463d09bd2e1 100644 --- a/src/util/status.rs +++ b/src/util/status.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter}; use std::io; +use std::ops::Deref; use crate::util::r#const::COLON_WHITE_SPACE; use crate::util::slice::Slice; use crate::util::status::LevelError::{KCorruption, KIOError, KInvalidArgument, KNotSupported, KNotFound, KOk, KBadRecord}; @@ -288,6 +289,20 @@ impl LevelError { msg } } + + pub fn get_value(&self) -> i32 { + let le = match self { + KOk => 0, + KNotFound => 1, + KCorruption => 2, + KNotSupported => 3, + KInvalidArgument => 4, + KIOError => 5, + KBadRecord => 6 + }; + + le + } } impl Default for LevelError { @@ -355,21 +370,3 @@ impl Display for LevelError { write!(f, "{}", print) } } - -// impl Deref for LevelError { -// type Target = i32; -// -// /// StatusTrait 解引用到 i32 -// fn deref(&self) -> &Self::Target { -// let le = match self { -// KOk => 0, -// KNotFound(_) => 1, -// KCorruption(_) => 2, -// KNotSupported(_) => 3, -// KInvalidArgument(_) => 4, -// KIOError(_) => 5, -// }; -// -// &*le -// } -// } \ No newline at end of file diff --git a/src/util/status_test.rs b/src/util/status_test.rs index 2ac1030cd30292af50470c762a3b33658f52d5cd..baad8329e2ec23eb0947c41ace8fff6759b67772 100644 --- a/src/util/status_test.rs +++ b/src/util/status_test.rs @@ -32,12 +32,10 @@ mod test { let err: Status = LevelError::invalid_argument(String::from(msg1).into(), String::from(msg2).into()); assert!(&err.is_invalid_argument()); - // assert_eq!(err.into_code(), 4); let err: Status = LevelError::corruption(String::from(msg1).into(), String::from(msg2).into()); assert!(&err.is_corruption()); - // assert_eq!(err.into_code(), 2); let err: Status = LevelError::not_found(String::from(msg1).into(), String::from(msg2).into()); @@ -121,32 +119,38 @@ mod test { fn test_level_error_try_from() -> Result<(), String> { let rs = LevelError::try_from(1)?; assert!(&rs.is_not_found()); + assert_eq!(rs.get_value(), 1); let rs: Result = 1.try_into(); assert!(rs.ok().unwrap().is_not_found()); let rs = LevelError::try_from(0)?; assert!(&rs.is_ok()); + assert_eq!(rs.get_value(), 0); let rs: Result = 0.try_into(); assert!(rs.ok().unwrap().is_ok()); let rs = LevelError::try_from(2)?; assert!(&rs.is_corruption()); + assert_eq!(rs.get_value(), 2); let rs: LevelError = 2.try_into()?; assert!(rs.is_corruption()); let rs: LevelError = LevelError::try_from(3)?; assert!(&rs.is_not_supported_error()); + assert_eq!(rs.get_value(), 3); let rs: LevelError = 3.try_into()?; assert!(rs.is_not_supported_error()); let rs = LevelError::try_from(4)?; assert!(&rs.is_invalid_argument()); + assert_eq!(rs.get_value(), 4); let rs = LevelError::try_from(5)?; assert!(&rs.is_io_error()); + assert_eq!(rs.get_value(), 5); - let rs = LevelError::try_from(6); - assert_eq!("Unknown code: 6", rs.err().unwrap()); + let rs = LevelError::try_from(66); + assert_eq!("Unknown code: 66", rs.err().unwrap()); Ok(()) }