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(
21 "store schema version {found} is newer than this musefs build supports \
22 (max {supported}); upgrade musefs to read this store"
23 )]
24 StoreTooNew { found: i64, supported: i64 },
25 #[error("the store is in use — unmount the filesystem or stop any scan before vacuuming")]
26 StoreInUse(#[source] rusqlite::Error),
27 #[error("{table}.{field} length {len} exceeds the {max} cap (crafted or corrupt DB)")]
28 FieldTooLarge {
29 table: &'static str,
30 field: &'static str,
31 len: i64,
32 max: i64,
33 },
34 #[error("structural block for track {track_id} is invalid: {detail} (crafted or corrupt DB)")]
35 InvalidStructuralBlock { track_id: i64, detail: String },
36 #[error(
37 "track {track_id} has {count} tag rows, exceeds the {max}-row cap (crafted or corrupt DB)"
38 )]
39 TooManyValues {
40 track_id: i64,
41 count: usize,
42 max: usize,
43 },
44 #[error(
45 "track {track_id} has {count} track_art rows, exceeds the {max}-row cap (crafted or corrupt DB)"
46 )]
47 TooManyArtRows {
48 track_id: i64,
49 count: usize,
50 max: usize,
51 },
52}
53
54pub type Result<T> = std::result::Result<T, DbError>;
55
56pub(crate) fn check_field_len(
61 table: &'static str,
62 field: &'static str,
63 len: i64,
64 max: i64,
65) -> Result<()> {
66 if len > max {
67 return Err(DbError::FieldTooLarge {
68 table,
69 field,
70 len,
71 max,
72 });
73 }
74 Ok(())
75}
76
77pub(crate) fn check_tag_count(track_id: i64, count: usize) -> Result<()> {
81 if count > crate::limits::MAX_TAGS_PER_TRACK {
82 return Err(DbError::TooManyValues {
83 track_id,
84 count,
85 max: crate::limits::MAX_TAGS_PER_TRACK,
86 });
87 }
88 Ok(())
89}
90
91pub(crate) fn check_art_count(track_id: i64, count: usize) -> Result<()> {
97 if count > crate::limits::MAX_ART_ROWS_PER_TRACK {
98 return Err(DbError::TooManyArtRows {
99 track_id,
100 count,
101 max: crate::limits::MAX_ART_ROWS_PER_TRACK,
102 });
103 }
104 Ok(())
105}
106
107#[cfg(test)]
108mod guard_helper_tests {
109 use super::check_field_len;
110
111 #[test]
112 fn rejects_on_length_only_inclusive_boundary() {
113 assert!(check_field_len("tags", "value", 262_145, 262_144).is_err());
116 assert!(check_field_len("tags", "value", 262_144, 262_144).is_ok());
117 }
118
119 #[test]
120 fn tag_count_accepts_at_cap_rejects_above() {
121 use crate::limits::MAX_TAGS_PER_TRACK;
122 assert!(super::check_tag_count(1, MAX_TAGS_PER_TRACK).is_ok());
125 assert!(super::check_tag_count(1, MAX_TAGS_PER_TRACK + 1).is_err());
126 }
127
128 #[test]
129 fn art_count_accepts_at_cap_rejects_above() {
130 use crate::limits::MAX_ART_ROWS_PER_TRACK;
131 assert!(super::check_art_count(1, MAX_ART_ROWS_PER_TRACK).is_ok());
134 assert!(super::check_art_count(1, MAX_ART_ROWS_PER_TRACK + 1).is_err());
135 }
136}