Skip to main content

pfx_mod/
error.rs

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 {}