1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum DbError {
5 #[error(transparent)]
6 Sqlite(#[from] rusqlite::Error),
7 #[error(
8 "audio bounds out of range: offset {audio_offset} + length {audio_length} exceeds backing_size {backing_size}"
9 )]
10 AudioBoundsOutOfRange {
11 audio_offset: u64,
12 audio_length: u64,
13 backing_size: u64,
14 },
15 #[error(
16 "database schema does not match the version musefs expects (mismatch at {object}); \
17 regenerate the store by running `musefs scan` against the library"
18 )]
19 SchemaMismatch { object: String },
20 #[error("{table}.{field} length {len} exceeds the {max} cap (crafted or corrupt DB)")]
21 FieldTooLarge {
22 table: &'static str,
23 field: &'static str,
24 len: i64,
25 max: i64,
26 },
27 #[error("structural block for track {track_id} is invalid: {detail} (crafted or corrupt DB)")]
28 InvalidStructuralBlock { track_id: i64, detail: String },
29 #[error(
30 "track {track_id} has {count} tag rows, exceeds the {max}-row cap (crafted or corrupt DB)"
31 )]
32 TooManyValues {
33 track_id: i64,
34 count: usize,
35 max: usize,
36 },
37 #[error(
38 "track {track_id} has {count} track_art rows, exceeds the {max}-row cap (crafted or corrupt DB)"
39 )]
40 TooManyArtRows {
41 track_id: i64,
42 count: usize,
43 max: usize,
44 },
45}
46
47pub type Result<T> = std::result::Result<T, DbError>;
48
49pub(crate) fn check_field_len(
54 table: &'static str,
55 field: &'static str,
56 len: i64,
57 max: i64,
58) -> Result<()> {
59 if len > max {
60 return Err(DbError::FieldTooLarge {
61 table,
62 field,
63 len,
64 max,
65 });
66 }
67 Ok(())
68}
69
70pub(crate) fn check_tag_count(track_id: i64, count: usize) -> Result<()> {
74 if count > crate::limits::MAX_TAGS_PER_TRACK {
75 return Err(DbError::TooManyValues {
76 track_id,
77 count,
78 max: crate::limits::MAX_TAGS_PER_TRACK,
79 });
80 }
81 Ok(())
82}
83
84pub(crate) fn check_art_count(track_id: i64, count: usize) -> Result<()> {
90 if count > crate::limits::MAX_ART_ROWS_PER_TRACK {
91 return Err(DbError::TooManyArtRows {
92 track_id,
93 count,
94 max: crate::limits::MAX_ART_ROWS_PER_TRACK,
95 });
96 }
97 Ok(())
98}
99
100#[cfg(test)]
101mod guard_helper_tests {
102 use super::check_field_len;
103
104 #[test]
105 fn rejects_on_length_only_inclusive_boundary() {
106 assert!(check_field_len("tags", "value", 262_145, 262_144).is_err());
109 assert!(check_field_len("tags", "value", 262_144, 262_144).is_ok());
110 }
111
112 #[test]
113 fn tag_count_accepts_at_cap_rejects_above() {
114 use crate::limits::MAX_TAGS_PER_TRACK;
115 assert!(super::check_tag_count(1, MAX_TAGS_PER_TRACK).is_ok());
118 assert!(super::check_tag_count(1, MAX_TAGS_PER_TRACK + 1).is_err());
119 }
120
121 #[test]
122 fn art_count_accepts_at_cap_rejects_above() {
123 use crate::limits::MAX_ART_ROWS_PER_TRACK;
124 assert!(super::check_art_count(1, MAX_ART_ROWS_PER_TRACK).is_ok());
127 assert!(super::check_art_count(1, MAX_ART_ROWS_PER_TRACK + 1).is_err());
128 }
129}