1use std::fmt;
2use std::path::PathBuf;
3
4use wasmtime::Trap;
5
6use crate::manifest::{ID_CHARS, MANIFEST};
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct HostError(pub String);
10
11impl fmt::Display for HostError {
12 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 write!(f, "mod host: {}", self.0)
14 }
15}
16
17impl std::error::Error for HostError {}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum Refusal {
21 Read(String),
22 Manifest(String),
23 Id {
24 found: String,
25 },
26 Folder {
27 id: String,
28 folder: String,
29 },
30 Entry {
31 found: String,
32 },
33 Api {
34 wanted: String,
35 world: String,
36 version: String,
37 },
38 Size {
39 what: &'static str,
40 bytes: u64,
41 cap: usize,
42 },
43 NotComponent,
44 Invalid(String),
45 Start,
46 Import {
47 name: String,
48 },
49 Types(String),
50 Module(String),
51}
52
53impl fmt::Display for Refusal {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Read(e) => write!(f, "cannot be read: {e}"),
57 Self::Manifest(e) => write!(f, "has a bad {MANIFEST}: {e}"),
58 Self::Id { found } => write!(
59 f,
60 "has id `{found}`; an id is 1 to {ID_CHARS} lowercase letters, digits, `-` or `_`, starting with a letter or digit"
61 ),
62 Self::Folder { id, folder } => write!(
63 f,
64 "has id `{id}` in folder `{folder}`; the folder must be named after the id"
65 ),
66 Self::Entry { found } => write!(
67 f,
68 "has entry `{found}`; the entry is a file name ending in .wasm beside {MANIFEST}, with no folders"
69 ),
70 Self::Api {
71 wanted,
72 world,
73 version,
74 } => write!(
75 f,
76 "was made for version {wanted} of the game's `{world}` mod API, but this game has {version}; it needs a mod made for a version compatible with {version}"
77 ),
78 Self::Size { what, bytes, cap } => {
79 write!(f, "has a {what} of {bytes} bytes, over the cap of {cap}")
80 }
81 Self::NotComponent => write!(
82 f,
83 "is not a WebAssembly component; core modules are refused, build the mod as a component"
84 ),
85 Self::Invalid(e) => write!(f, "is not a valid component: {e}"),
86 Self::Start => write!(
87 f,
88 "has a component start function, which this host refuses; run setup from an export the game calls"
89 ),
90 Self::Import { name } => write!(
91 f,
92 "imports `{name}`, which is not part of the game's mod API"
93 ),
94 Self::Types(e) => write!(f, "does not match the game's mod API: {e}"),
95 Self::Module(e) => write!(
96 f,
97 "is a core module that cannot be made a component: {e}; build it with wit-bindgen, or as a component"
98 ),
99 }
100 }
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct LoadError {
105 pub folder: PathBuf,
106 pub id: Option<String>,
107 pub refusal: Box<Refusal>,
108}
109
110impl fmt::Display for LoadError {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match &self.id {
113 Some(id) => write!(f, "mod `{id}` ({}) {}", self.folder.display(), self.refusal),
114 None => write!(f, "mod at {} {}", self.folder.display(), self.refusal),
115 }
116 }
117}
118
119impl std::error::Error for LoadError {}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
122pub enum Limit {
123 Memory { bytes: usize },
124 Table { elements: usize },
125}
126
127#[derive(Clone, Debug, PartialEq, Eq)]
128pub enum Cause {
129 Fuel,
130 Depth,
131 Stack,
132 Trap(Trap),
133 Limit(Limit),
134 Error(String),
135 Disabled,
136}
137
138impl fmt::Display for Cause {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::Fuel => write!(f, "ran out of fuel"),
142 Self::Depth => write!(f, "called deeper than its call-depth limit"),
143 Self::Stack => write!(f, "exhausted its stack"),
144 Self::Trap(t) => write!(f, "trapped ({t})"),
145 Self::Limit(Limit::Memory { bytes }) => {
146 write!(f, "tried to grow its memory to {bytes} bytes, over the cap")
147 }
148 Self::Limit(Limit::Table { elements }) => {
149 write!(
150 f,
151 "tried to grow a table to {elements} elements, over the cap"
152 )
153 }
154 Self::Error(e) => write!(f, "failed: {e}"),
155 Self::Disabled => write!(f, "is disabled for this run"),
156 }
157 }
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
161pub struct Frame {
162 pub func: u32,
163 pub offset: Option<usize>,
164}
165
166#[derive(Clone, Debug, PartialEq, Eq)]
167pub struct ModError {
168 pub id: String,
169 pub cause: Cause,
170 pub fuel: u64,
171 pub at: Option<Frame>,
172}
173
174impl fmt::Display for ModError {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 write!(f, "mod `{}` {}", self.id, self.cause)?;
177 if self.cause != Cause::Disabled {
178 write!(f, " after {} fuel", self.fuel)?;
179 }
180 if let Some(at) = self.at {
181 write!(f, " in function {}", at.func)?;
182 if let Some(offset) = at.offset {
183 write!(f, " at offset {offset:#x}")?;
184 }
185 }
186 if self.cause != Cause::Disabled {
187 write!(f, "; it is stopped and disabled for this run")?;
188 }
189 Ok(())
190 }
191}
192
193impl std::error::Error for ModError {}