concinnity_engine/app/
startup_error.rs1use crate::result::CnResult;
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum StartupError {
12 MissingData {
15 blob: PathBuf,
17 },
18 UnreadableData {
21 blob: PathBuf,
23 cause: CnResult,
25 },
26 OverflowUnsupported {
31 blob: PathBuf,
33 needed: u32,
35 },
36 NoStateRoot,
38}
39
40impl StartupError {
41 pub fn from_blob_failure(blob: PathBuf, cause: CnResult) -> Self {
46 if blob.exists() {
47 StartupError::UnreadableData { blob, cause }
48 } else {
49 StartupError::MissingData { blob }
50 }
51 }
52
53 pub(crate) fn io_kind(&self) -> std::io::ErrorKind {
55 match self {
56 StartupError::MissingData { .. } | StartupError::NoStateRoot => {
57 std::io::ErrorKind::NotFound
58 }
59 StartupError::UnreadableData { .. } | StartupError::OverflowUnsupported { .. } => {
60 std::io::ErrorKind::InvalidData
61 }
62 }
63 }
64
65 pub(crate) fn user_message(&self) -> String {
68 match self {
69 StartupError::MissingData { blob } => {
70 format!("Failed to find the data blob:\n{}", blob.display())
71 }
72 StartupError::UnreadableData { blob, .. } => {
73 format!("Failed to read the data blob:\n{}", blob.display())
74 }
75 StartupError::OverflowUnsupported { blob, .. } => {
76 format!("This app's data is incomplete:\n{}", blob.display())
77 }
78 StartupError::NoStateRoot => "Failed to find this app's data.".to_string(),
79 }
80 }
81
82 pub(crate) fn log_line(&self) -> String {
84 match self {
85 StartupError::MissingData { blob } => {
86 format!(
87 "no compiled world data at {} -- run `concinnity build` first",
88 blob.display()
89 )
90 }
91 StartupError::UnreadableData { blob, cause } => {
92 format!(
93 "compiled world data at {} failed to load: {cause}",
94 blob.display()
95 )
96 }
97 StartupError::OverflowUnsupported { blob, needed } => {
98 format!(
99 "{} is a single blob file, but this world spans {} more; \
100 re-export it so the player ships a `data/` directory",
101 blob.display(),
102 needed
103 )
104 }
105 StartupError::NoStateRoot => {
106 "no state directory was installed, so there is nowhere to read world data from"
107 .to_string()
108 }
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn a_missing_blob_classifies_as_missing() {
119 let dir = tempfile::tempdir().expect("tempdir");
120 let blob = dir.path().join("data").join("0");
121
122 let err = StartupError::from_blob_failure(blob, CnResult::FileIo);
123 assert!(matches!(err, StartupError::MissingData { .. }));
124 assert!(err.user_message().contains("Failed to find"));
125 assert!(err.log_line().contains("concinnity build"));
126 }
127
128 #[test]
129 fn a_present_but_broken_blob_classifies_as_unreadable() {
130 let dir = tempfile::tempdir().expect("tempdir");
131 std::fs::create_dir_all(dir.path().join("data")).expect("data dir");
132 let blob = dir.path().join("data").join("0");
133 std::fs::write(&blob, b"garbage").expect("write blob");
134
135 let err = StartupError::from_blob_failure(blob, CnResult::FileIo);
136 assert!(matches!(err, StartupError::UnreadableData { .. }));
137 assert!(err.user_message().contains("Failed to read"));
138 assert!(err.log_line().contains(&CnResult::FileIo.to_string()));
140 }
141
142 #[test]
144 fn every_message_names_the_blob_path() {
145 for err in [
146 StartupError::MissingData {
147 blob: PathBuf::from("/somewhere/data/0"),
148 },
149 StartupError::UnreadableData {
150 blob: PathBuf::from("/somewhere/data/0"),
151 cause: CnResult::FileIo,
152 },
153 StartupError::OverflowUnsupported {
154 blob: PathBuf::from("/somewhere/data/0"),
155 needed: 2,
156 },
157 ] {
158 assert!(err.user_message().contains("/somewhere/data/0"));
159 assert!(err.log_line().contains("/somewhere/data/0"));
160 }
161 }
162
163 #[test]
166 fn the_overflow_refusal_names_the_fix() {
167 let err = StartupError::OverflowUnsupported {
168 blob: PathBuf::from("/apps/MyGame/data"),
169 needed: 3,
170 };
171 assert!(err.log_line().contains("single blob file"), "{err:?}");
172 assert!(err.log_line().contains("`data/` directory"), "{err:?}");
173 assert!(err.log_line().contains('3'), "{err:?}");
174 assert_eq!(err.io_kind(), std::io::ErrorKind::InvalidData);
175 }
176
177 #[test]
180 fn no_state_root_reports_not_found_without_a_path() {
181 let err = StartupError::NoStateRoot;
182 assert_eq!(err.io_kind(), std::io::ErrorKind::NotFound);
183 assert!(err.log_line().contains("no state directory"));
184 }
185}