1use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[derive(Default)]
13pub enum VerificationStatus {
14 #[default]
15 Unverified,
16 Decoded,
17 Screenshot,
18 Verified,
19}
20
21impl std::fmt::Display for VerificationStatus {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::Unverified => write!(f, "unverified"),
25 Self::Decoded => write!(f, "decoded"),
26 Self::Screenshot => write!(f, "screenshot"),
27 Self::Verified => write!(f, "verified"),
28 }
29 }
30}
31
32impl std::str::FromStr for VerificationStatus {
33 type Err = ParseError;
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 match s {
36 "unverified" => Ok(Self::Unverified),
37 "decoded" => Ok(Self::Decoded),
38 "screenshot" => Ok(Self::Screenshot),
39 "verified" => Ok(Self::Verified),
40 _ => Err(ParseError::InvalidVerificationStatus(s.to_string())),
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
47#[serde(rename_all = "snake_case")]
48#[derive(Default)]
49pub enum ValueSource {
50 InGame = 3,
52 #[default]
54 Decoder = 2,
55 CommunityTool = 1,
57}
58
59impl ValueSource {
60 pub fn priority(&self) -> u8 {
62 *self as u8
63 }
64}
65
66impl std::fmt::Display for ValueSource {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 Self::InGame => write!(f, "ingame"),
70 Self::Decoder => write!(f, "decoder"),
71 Self::CommunityTool => write!(f, "community_tool"),
72 }
73 }
74}
75
76impl std::str::FromStr for ValueSource {
77 type Err = ParseError;
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 match s {
80 "ingame" | "in_game" => Ok(Self::InGame),
81 "decoder" => Ok(Self::Decoder),
82 "community_tool" | "community" => Ok(Self::CommunityTool),
83 _ => Err(ParseError::InvalidValueSource(s.to_string())),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
90#[serde(rename_all = "snake_case")]
91#[derive(Default)]
92pub enum Confidence {
93 Verified = 3,
95 #[default]
97 Inferred = 2,
98 Uncertain = 1,
100}
101
102impl Confidence {
103 pub fn priority(&self) -> u8 {
105 *self as u8
106 }
107}
108
109impl std::fmt::Display for Confidence {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 match self {
112 Self::Verified => write!(f, "verified"),
113 Self::Inferred => write!(f, "inferred"),
114 Self::Uncertain => write!(f, "uncertain"),
115 }
116 }
117}
118
119impl std::str::FromStr for Confidence {
120 type Err = ParseError;
121 fn from_str(s: &str) -> Result<Self, Self::Err> {
122 match s {
123 "verified" => Ok(Self::Verified),
124 "inferred" => Ok(Self::Inferred),
125 "uncertain" => Ok(Self::Uncertain),
126 _ => Err(ParseError::InvalidConfidence(s.to_string())),
127 }
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
133#[serde(rename_all = "snake_case")]
134pub enum ItemField {
135 Name,
136 Prefix,
137 Manufacturer,
138 WeaponType,
139 ItemType,
140 Rarity,
141 Level,
142 Element,
143 Dps,
144 Damage,
145 Accuracy,
146 FireRate,
147 ReloadTime,
148 MagSize,
149 Value,
150 RedText,
151}
152
153impl ItemField {
154 pub const ALL: &'static [ItemField] = &[
156 ItemField::Name,
157 ItemField::Prefix,
158 ItemField::Manufacturer,
159 ItemField::WeaponType,
160 ItemField::ItemType,
161 ItemField::Rarity,
162 ItemField::Level,
163 ItemField::Element,
164 ItemField::Dps,
165 ItemField::Damage,
166 ItemField::Accuracy,
167 ItemField::FireRate,
168 ItemField::ReloadTime,
169 ItemField::MagSize,
170 ItemField::Value,
171 ItemField::RedText,
172 ];
173
174 pub fn display_width(&self) -> usize {
176 match self {
177 Self::Name => 20,
178 Self::Prefix => 15,
179 Self::Manufacturer => 12,
180 Self::WeaponType => 8,
181 Self::ItemType => 6,
182 Self::Rarity => 10,
183 Self::Level => 5,
184 Self::Element => 10,
185 Self::Dps => 6,
186 Self::Damage => 6,
187 Self::Accuracy => 8,
188 Self::FireRate => 10,
189 Self::ReloadTime => 11,
190 Self::MagSize => 8,
191 Self::Value => 8,
192 Self::RedText => 30,
193 }
194 }
195}
196
197impl std::fmt::Display for ItemField {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 match self {
200 Self::Name => write!(f, "name"),
201 Self::Prefix => write!(f, "prefix"),
202 Self::Manufacturer => write!(f, "manufacturer"),
203 Self::WeaponType => write!(f, "weapon_type"),
204 Self::ItemType => write!(f, "item_type"),
205 Self::Rarity => write!(f, "rarity"),
206 Self::Level => write!(f, "level"),
207 Self::Element => write!(f, "element"),
208 Self::Dps => write!(f, "dps"),
209 Self::Damage => write!(f, "damage"),
210 Self::Accuracy => write!(f, "accuracy"),
211 Self::FireRate => write!(f, "fire_rate"),
212 Self::ReloadTime => write!(f, "reload_time"),
213 Self::MagSize => write!(f, "mag_size"),
214 Self::Value => write!(f, "value"),
215 Self::RedText => write!(f, "red_text"),
216 }
217 }
218}
219
220impl std::str::FromStr for ItemField {
221 type Err = ParseError;
222 fn from_str(s: &str) -> Result<Self, Self::Err> {
223 match s {
224 "name" => Ok(Self::Name),
225 "prefix" => Ok(Self::Prefix),
226 "manufacturer" => Ok(Self::Manufacturer),
227 "weapon_type" => Ok(Self::WeaponType),
228 "item_type" => Ok(Self::ItemType),
229 "rarity" => Ok(Self::Rarity),
230 "level" => Ok(Self::Level),
231 "element" => Ok(Self::Element),
232 "dps" => Ok(Self::Dps),
233 "damage" => Ok(Self::Damage),
234 "accuracy" => Ok(Self::Accuracy),
235 "fire_rate" => Ok(Self::FireRate),
236 "reload_time" => Ok(Self::ReloadTime),
237 "mag_size" => Ok(Self::MagSize),
238 "value" => Ok(Self::Value),
239 "red_text" => Ok(Self::RedText),
240 _ => Err(ParseError::InvalidItemField(s.to_string())),
241 }
242 }
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct ItemValue {
248 pub id: i64,
249 pub item_serial: String,
250 pub field: String,
251 pub value: String,
252 pub source: ValueSource,
253 pub source_detail: Option<String>,
254 pub confidence: Confidence,
255 pub created_at: String,
256}
257
258#[derive(Debug, Clone, Default, Serialize, Deserialize)]
260pub struct Item {
261 pub serial: String,
262 pub name: Option<String>,
263 pub prefix: Option<String>,
264 pub manufacturer: Option<String>,
265 pub weapon_type: Option<String>,
266 pub item_type: Option<String>,
267 pub rarity: Option<String>,
268 pub level: Option<i32>,
269 pub element: Option<String>,
270 pub dps: Option<i32>,
271 pub damage: Option<i32>,
272 pub accuracy: Option<i32>,
273 pub fire_rate: Option<f64>,
274 pub reload_time: Option<f64>,
275 pub mag_size: Option<i32>,
276 pub value: Option<i32>,
277 pub red_text: Option<String>,
278 pub notes: Option<String>,
279 pub verification_status: VerificationStatus,
280 pub verification_notes: Option<String>,
281 pub verified_at: Option<String>,
282 pub legal: bool,
283 pub source: Option<String>,
284 pub created_at: String,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct ItemPart {
290 pub id: i64,
291 pub item_serial: String,
292 pub slot: String,
293 pub part_index: Option<i32>,
294 pub part_name: Option<String>,
295 pub manufacturer: Option<String>,
296 pub effect: Option<String>,
297 pub verified: bool,
298 pub verification_method: Option<String>,
299 pub verification_notes: Option<String>,
300 pub verified_at: Option<String>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct Attachment {
306 pub id: i64,
307 pub item_serial: String,
308 pub name: String,
309 pub mime_type: String,
310 pub view: String,
312}
313
314#[derive(Debug, Clone, Default, Serialize, Deserialize)]
316pub struct DbStats {
317 pub item_count: i64,
318 pub part_count: i64,
319 pub attachment_count: i64,
320 pub value_count: i64,
321}
322
323#[derive(Debug, Clone, Default, Serialize, Deserialize)]
325pub struct MigrationStats {
326 pub items_processed: usize,
327 pub values_migrated: usize,
328 pub values_skipped: usize,
329}
330
331#[derive(Debug, Default, Clone, Serialize, Deserialize)]
333pub struct ItemFilter {
334 pub manufacturer: Option<String>,
335 pub weapon_type: Option<String>,
336 pub element: Option<String>,
337 pub rarity: Option<String>,
338 pub limit: Option<u32>,
339 pub offset: Option<u32>,
340}
341
342#[derive(Debug, Default, Clone, Serialize, Deserialize)]
344pub struct ItemUpdate {
345 pub name: Option<String>,
346 pub prefix: Option<String>,
347 pub manufacturer: Option<String>,
348 pub weapon_type: Option<String>,
349 pub rarity: Option<String>,
350 pub level: Option<i32>,
351 pub element: Option<String>,
352 pub dps: Option<i32>,
353 pub damage: Option<i32>,
354 pub accuracy: Option<i32>,
355 pub fire_rate: Option<f64>,
356 pub reload_time: Option<f64>,
357 pub mag_size: Option<i32>,
358 pub value: Option<i32>,
359 pub red_text: Option<String>,
360 pub notes: Option<String>,
361}
362
363#[derive(Debug, Clone, thiserror::Error)]
365pub enum ParseError {
366 #[error("Invalid verification status: {0}")]
367 InvalidVerificationStatus(String),
368 #[error("Invalid value source: {0}")]
369 InvalidValueSource(String),
370 #[error("Invalid confidence level: {0}")]
371 InvalidConfidence(String),
372 #[error("Invalid item field: {0}")]
373 InvalidItemField(String),
374}
375
376pub fn pick_best_value(values: impl IntoIterator<Item = ItemValue>) -> Option<ItemValue> {
378 values
379 .into_iter()
380 .max_by(|a, b| match a.source.priority().cmp(&b.source.priority()) {
381 std::cmp::Ordering::Equal => a.confidence.priority().cmp(&b.confidence.priority()),
382 other => other,
383 })
384}
385
386pub fn hash_source(source: &str, salt: &str) -> String {
393 let mut hasher = Sha256::new();
394 hasher.update(salt.as_bytes());
395 hasher.update(source.as_bytes());
396 let result = hasher.finalize();
397 hex::encode(&result[..6]) }
399
400pub fn generate_salt() -> String {
402 use rand::Rng;
403 let mut rng = rand::thread_rng();
404 let bytes: [u8; 32] = rng.gen();
405 hex::encode(bytes)
406}
407
408pub fn lookup_source_hash<'a>(
411 hash: &str,
412 salt: &str,
413 known_sources: impl IntoIterator<Item = &'a str>,
414) -> Option<String> {
415 for source in known_sources {
416 if hash_source(source, salt) == hash {
417 return Some(source.to_string());
418 }
419 }
420 None
421}
422
423pub const BL4_NAMESPACE: uuid::Uuid = uuid::uuid!("b14c0de4-0000-4000-8000-000000000001");
426
427pub fn generate_item_uuid(serial: &str, hashed_source: &str) -> uuid::Uuid {
429 let name = format!("{}:{}", serial, hashed_source);
430 uuid::Uuid::new_v5(&BL4_NAMESPACE, name.as_bytes())
431}
432
433pub fn generate_random_uuid() -> uuid::Uuid {
435 uuid::Uuid::new_v4()
436}
437
438pub fn best_values_by_field(
444 values: impl IntoIterator<Item = ItemValue>,
445) -> HashMap<String, String> {
446 let mut best_by_field: HashMap<String, ItemValue> = HashMap::new();
447
448 for value in values {
449 let dominated = best_by_field
450 .get(&value.field)
451 .map(
452 |existing| match value.source.priority().cmp(&existing.source.priority()) {
453 std::cmp::Ordering::Greater => true,
454 std::cmp::Ordering::Equal => {
455 value.confidence.priority() > existing.confidence.priority()
456 }
457 std::cmp::Ordering::Less => false,
458 },
459 )
460 .unwrap_or(true);
461
462 if dominated {
463 best_by_field.insert(value.field.clone(), value);
464 }
465 }
466
467 best_by_field
468 .into_iter()
469 .map(|(k, v)| (k, v.value))
470 .collect()
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_verification_status_parse() {
479 assert_eq!(
480 "unverified".parse::<VerificationStatus>().unwrap(),
481 VerificationStatus::Unverified
482 );
483 assert_eq!(
484 "decoded".parse::<VerificationStatus>().unwrap(),
485 VerificationStatus::Decoded
486 );
487 assert_eq!(
488 "screenshot".parse::<VerificationStatus>().unwrap(),
489 VerificationStatus::Screenshot
490 );
491 assert_eq!(
492 "verified".parse::<VerificationStatus>().unwrap(),
493 VerificationStatus::Verified
494 );
495 assert!("invalid".parse::<VerificationStatus>().is_err());
496 }
497
498 #[test]
499 fn test_verification_status_display() {
500 assert_eq!(VerificationStatus::Unverified.to_string(), "unverified");
501 assert_eq!(VerificationStatus::Decoded.to_string(), "decoded");
502 assert_eq!(VerificationStatus::Screenshot.to_string(), "screenshot");
503 assert_eq!(VerificationStatus::Verified.to_string(), "verified");
504 }
505
506 #[test]
507 fn test_value_source_parse() {
508 assert_eq!(
509 "ingame".parse::<ValueSource>().unwrap(),
510 ValueSource::InGame
511 );
512 assert_eq!(
513 "in_game".parse::<ValueSource>().unwrap(),
514 ValueSource::InGame
515 );
516 assert_eq!(
517 "decoder".parse::<ValueSource>().unwrap(),
518 ValueSource::Decoder
519 );
520 assert_eq!(
521 "community_tool".parse::<ValueSource>().unwrap(),
522 ValueSource::CommunityTool
523 );
524 assert_eq!(
525 "community".parse::<ValueSource>().unwrap(),
526 ValueSource::CommunityTool
527 );
528 assert!("invalid".parse::<ValueSource>().is_err());
529 }
530
531 #[test]
532 fn test_value_source_display() {
533 assert_eq!(ValueSource::InGame.to_string(), "ingame");
534 assert_eq!(ValueSource::Decoder.to_string(), "decoder");
535 assert_eq!(ValueSource::CommunityTool.to_string(), "community_tool");
536 }
537
538 #[test]
539 fn test_value_source_priority() {
540 assert!(ValueSource::InGame.priority() > ValueSource::Decoder.priority());
541 assert!(ValueSource::Decoder.priority() > ValueSource::CommunityTool.priority());
542 }
543
544 #[test]
545 fn test_confidence_parse() {
546 assert_eq!(
547 "verified".parse::<Confidence>().unwrap(),
548 Confidence::Verified
549 );
550 assert_eq!(
551 "inferred".parse::<Confidence>().unwrap(),
552 Confidence::Inferred
553 );
554 assert_eq!(
555 "uncertain".parse::<Confidence>().unwrap(),
556 Confidence::Uncertain
557 );
558 assert!("invalid".parse::<Confidence>().is_err());
559 }
560
561 #[test]
562 fn test_confidence_display() {
563 assert_eq!(Confidence::Verified.to_string(), "verified");
564 assert_eq!(Confidence::Inferred.to_string(), "inferred");
565 assert_eq!(Confidence::Uncertain.to_string(), "uncertain");
566 }
567
568 #[test]
569 fn test_confidence_priority() {
570 assert!(Confidence::Verified.priority() > Confidence::Inferred.priority());
571 assert!(Confidence::Inferred.priority() > Confidence::Uncertain.priority());
572 }
573
574 #[test]
575 fn test_item_field_parse() {
576 assert_eq!("name".parse::<ItemField>().unwrap(), ItemField::Name);
577 assert_eq!("prefix".parse::<ItemField>().unwrap(), ItemField::Prefix);
578 assert_eq!(
579 "manufacturer".parse::<ItemField>().unwrap(),
580 ItemField::Manufacturer
581 );
582 assert_eq!(
583 "weapon_type".parse::<ItemField>().unwrap(),
584 ItemField::WeaponType
585 );
586 assert_eq!(
587 "item_type".parse::<ItemField>().unwrap(),
588 ItemField::ItemType
589 );
590 assert_eq!("rarity".parse::<ItemField>().unwrap(), ItemField::Rarity);
591 assert_eq!("level".parse::<ItemField>().unwrap(), ItemField::Level);
592 assert_eq!("element".parse::<ItemField>().unwrap(), ItemField::Element);
593 assert!("invalid".parse::<ItemField>().is_err());
594 }
595
596 #[test]
597 fn test_item_field_display() {
598 assert_eq!(ItemField::Name.to_string(), "name");
599 assert_eq!(ItemField::WeaponType.to_string(), "weapon_type");
600 }
601
602 fn make_value(
603 field: &str,
604 value: &str,
605 source: ValueSource,
606 confidence: Confidence,
607 ) -> ItemValue {
608 ItemValue {
609 id: 0,
610 item_serial: String::new(),
611 field: field.to_string(),
612 value: value.to_string(),
613 source,
614 source_detail: None,
615 confidence,
616 created_at: String::new(),
617 }
618 }
619
620 #[test]
621 fn test_pick_best_value_by_source() {
622 let values = vec![
623 make_value(
624 "name",
625 "Community Name",
626 ValueSource::CommunityTool,
627 Confidence::Verified,
628 ),
629 make_value(
630 "name",
631 "Decoder Name",
632 ValueSource::Decoder,
633 Confidence::Verified,
634 ),
635 make_value(
636 "name",
637 "InGame Name",
638 ValueSource::InGame,
639 Confidence::Verified,
640 ),
641 ];
642 let best = pick_best_value(values).unwrap();
643 assert_eq!(best.value, "InGame Name");
644 assert_eq!(best.source, ValueSource::InGame);
645 }
646
647 #[test]
648 fn test_pick_best_value_by_confidence() {
649 let values = vec![
650 make_value(
651 "name",
652 "Uncertain",
653 ValueSource::Decoder,
654 Confidence::Uncertain,
655 ),
656 make_value(
657 "name",
658 "Verified",
659 ValueSource::Decoder,
660 Confidence::Verified,
661 ),
662 make_value(
663 "name",
664 "Inferred",
665 ValueSource::Decoder,
666 Confidence::Inferred,
667 ),
668 ];
669 let best = pick_best_value(values).unwrap();
670 assert_eq!(best.value, "Verified");
671 assert_eq!(best.confidence, Confidence::Verified);
672 }
673
674 #[test]
675 fn test_pick_best_value_source_over_confidence() {
676 let values = vec![
678 make_value(
679 "name",
680 "Decoder Verified",
681 ValueSource::Decoder,
682 Confidence::Verified,
683 ),
684 make_value(
685 "name",
686 "InGame Uncertain",
687 ValueSource::InGame,
688 Confidence::Uncertain,
689 ),
690 ];
691 let best = pick_best_value(values).unwrap();
692 assert_eq!(best.value, "InGame Uncertain");
693 }
694
695 #[test]
696 fn test_pick_best_value_empty() {
697 let values: Vec<ItemValue> = vec![];
698 assert!(pick_best_value(values).is_none());
699 }
700
701 #[test]
702 fn test_best_values_by_field() {
703 let values = vec![
704 make_value(
705 "name",
706 "Bad Name",
707 ValueSource::CommunityTool,
708 Confidence::Uncertain,
709 ),
710 make_value(
711 "name",
712 "Good Name",
713 ValueSource::InGame,
714 Confidence::Verified,
715 ),
716 make_value("level", "50", ValueSource::Decoder, Confidence::Inferred),
717 make_value("level", "51", ValueSource::InGame, Confidence::Verified),
718 ];
719 let best = best_values_by_field(values);
720 assert_eq!(best.get("name"), Some(&"Good Name".to_string()));
721 assert_eq!(best.get("level"), Some(&"51".to_string()));
722 }
723
724 #[test]
725 fn test_best_values_by_field_empty() {
726 let values: Vec<ItemValue> = vec![];
727 let best = best_values_by_field(values);
728 assert!(best.is_empty());
729 }
730
731 #[test]
732 fn test_hash_source() {
733 let salt = "test_salt_12345";
734 let source = "monokrome";
735
736 let hash1 = hash_source(source, salt);
737 let hash2 = hash_source(source, salt);
738
739 assert_eq!(hash1, hash2);
741 assert_eq!(hash1.len(), 12);
743 assert!(hash1.chars().all(|c| c.is_ascii_hexdigit()));
745
746 let hash3 = hash_source("other_source", salt);
748 assert_ne!(hash1, hash3);
749
750 let hash4 = hash_source(source, "different_salt");
752 assert_ne!(hash1, hash4);
753 }
754
755 #[test]
756 fn test_generate_salt() {
757 let salt1 = generate_salt();
758 let salt2 = generate_salt();
759
760 assert_eq!(salt1.len(), 64);
762 assert_eq!(salt2.len(), 64);
763
764 assert!(salt1.chars().all(|c| c.is_ascii_hexdigit()));
766 assert!(salt2.chars().all(|c| c.is_ascii_hexdigit()));
767
768 assert_ne!(salt1, salt2);
770 }
771
772 #[test]
773 fn test_lookup_source_hash() {
774 let salt = "lookup_test_salt";
775 let sources = ["alice", "bob", "charlie"];
776
777 let alice_hash = hash_source("alice", salt);
779
780 let found = lookup_source_hash(&alice_hash, salt, sources.iter().copied());
782 assert_eq!(found, Some("alice".to_string()));
783
784 let fake_hash = "000000000000";
786 let not_found = lookup_source_hash(fake_hash, salt, sources.iter().copied());
787 assert!(not_found.is_none());
788 }
789
790 #[test]
791 fn test_generate_item_uuid() {
792 let serial = "@Ug12345678901234567890";
793 let hashed_source = "abc123def456";
794
795 let uuid1 = generate_item_uuid(serial, hashed_source);
796 let uuid2 = generate_item_uuid(serial, hashed_source);
797
798 assert_eq!(uuid1, uuid2);
800
801 assert_eq!(uuid1.get_version_num(), 5);
803
804 let uuid3 = generate_item_uuid("different_serial", hashed_source);
806 assert_ne!(uuid1, uuid3);
807
808 let uuid4 = generate_item_uuid(serial, "different_hash");
809 assert_ne!(uuid1, uuid4);
810 }
811
812 #[test]
813 fn test_generate_random_uuid() {
814 let uuid1 = generate_random_uuid();
815 let uuid2 = generate_random_uuid();
816
817 assert_eq!(uuid1.get_version_num(), 4);
819
820 assert_ne!(uuid1, uuid2);
822 }
823
824 #[test]
825 fn test_item_field_display_width() {
826 for field in ItemField::ALL {
828 let width = field.display_width();
829 assert!(width > 0);
830 assert!(width <= 30);
831 }
832
833 assert_eq!(ItemField::Name.display_width(), 20);
835 assert_eq!(ItemField::Level.display_width(), 5);
836 assert_eq!(ItemField::RedText.display_width(), 30);
837 }
838
839 #[test]
840 fn test_item_field_all_variants() {
841 assert_eq!(ItemField::ALL.len(), 16);
843
844 for field in ItemField::ALL {
846 let as_string = field.to_string();
847 let parsed: ItemField = as_string.parse().unwrap();
848 assert_eq!(parsed, *field);
849 }
850 }
851}