Skip to main content

openusd_build/
error.rs

1//! What generation can fail with.
2
3use std::io;
4use std::path::PathBuf;
5
6use crate::validate::Violation;
7
8/// What a configured [`TokenEnum`](crate::TokenEnum) asked for that could not
9/// be honoured.
10///
11/// Every one of these is a configuration mistake rather than a schema one, and
12/// each stops generation: an enum silently left out, or one quietly given a
13/// variant name nobody chose, is worse than a build that stops and says which.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum TokenEnumError {
17    /// A source not spelled `Class.property`.
18    #[error("names source `{spelling}`, which is not spelled `Class.property`")]
19    MalformedSource {
20        /// What was configured.
21        spelling: String,
22    },
23
24    /// A class the library does not declare.
25    #[error("names class `{class}`, which the library does not declare")]
26    UnknownClass {
27        /// The class named.
28        class: String,
29    },
30
31    /// A property the class does not declare.
32    #[error("names `{class}.{property}`, which the class does not declare")]
33    UnknownProperty {
34        /// The class that was found.
35        class: String,
36        /// The property that was not.
37        property: String,
38    },
39
40    /// A source property declaring no `allowedTokens`, so there are no variants
41    /// to generate.
42    #[error("sources `{class}.{property}`, which declares no allowedTokens")]
43    NoAllowedTokens {
44        /// The class holding the property.
45        class: String,
46        /// The property named.
47        property: String,
48    },
49
50    /// A default asked for from a property with no token fallback to take it
51    /// from.
52    #[error("asks for a default, but `{class}.{property}` declares no token fallback")]
53    NoFallback {
54        /// The class holding the property.
55        class: String,
56        /// The property named.
57        property: String,
58    },
59
60    /// A default asked for from a fallback the allowed tokens do not admit.
61    #[error("asks for a default of `{token}`, which is not among its allowed tokens")]
62    UnknownFallback {
63        /// The fallback the property declares.
64        token: String,
65    },
66
67    /// A token no casing rule turns into a Rust name — an empty spelling, one
68    /// opening with a digit, one that is punctuation alone.
69    #[error("has no Rust name for token `{token}`; give one with TokenEnum::variant")]
70    UnnameableToken {
71        /// The token that could not be named.
72        token: String,
73    },
74
75    /// Two tokens reaching one variant name. Suffixing one of them would put a
76    /// name nobody chose into a public API, so the caller is asked instead.
77    #[error("names both `{first}` and `{second}` `{variant}`; give one a name with TokenEnum::variant")]
78    VariantCollision {
79        /// The token that took the name.
80        first: String,
81        /// The token that wanted it too.
82        second: String,
83        /// The name they both reached.
84        variant: String,
85    },
86
87    /// A type name the caller spelled that Rust would not take.
88    #[error("is not a Rust identifier")]
89    InvalidName,
90
91    /// A type name the generated file already uses for something else.
92    #[error("takes the name of another item the generated file declares")]
93    ReservedName,
94
95    /// A library neither generated by this run nor declared with
96    /// [`extern_library`](crate::Builder::extern_library), which is a name
97    /// nothing will ever match.
98    #[error("names library `{library}`, which this build neither generates nor declares")]
99    UnknownLibrary {
100        /// The library named.
101        library: String,
102    },
103
104    /// A variant name the caller spelled that Rust would not take.
105    #[error("names token `{token}` `{variant}`, which is not a Rust identifier")]
106    InvalidVariant {
107        /// The token it was given for.
108        token: String,
109        /// The name that was given.
110        variant: String,
111    },
112
113    /// A variant override for a token the source property does not admit,
114    /// which is a name that would silently do nothing.
115    #[error("names a variant for token `{token}`, which is not among its allowed tokens")]
116    UnknownVariantToken {
117        /// The token the override named.
118        token: String,
119    },
120
121    /// Two enums of one library under one name.
122    #[error("is declared more than once")]
123    Duplicate,
124}
125
126/// A failure while generating a schema library.
127///
128/// A build script runs this, so a variant that has a file to name names it:
129/// the message is all a contributor sees when `cargo build` stops.
130#[derive(Debug, thiserror::Error)]
131#[non_exhaustive]
132pub enum Error {
133    /// Generation had schemas to build but nowhere to write them.
134    #[error("no output directory: call Builder::out_dir, or run this from a build script, which sets OUT_DIR")]
135    NoOutDir,
136
137    /// Reading a schema through `openusd` failed.
138    #[error(transparent)]
139    Core(#[from] openusd::Error),
140
141    /// A schema's layers did not compose, so what it declares cannot be read.
142    #[error("{schema} does not compose: {diagnostic}")]
143    Composition {
144        /// The schema that was being read.
145        schema: PathBuf,
146        /// What composition reported. An unresolved sublayer usually wants a
147        /// `search_path`.
148        diagnostic: String,
149    },
150
151    /// A schema library did not name itself, so nothing can refer to it.
152    #[error("{schema}: no /GLOBAL prim declares customData.libraryName")]
153    NoLibraryName {
154        /// The schema that was being read.
155        schema: PathBuf,
156    },
157
158    /// A schema declares something the generator cannot honour.
159    #[error("{origin}: {violation}")]
160    Definition {
161        /// Where the offending declaration lives, layer and path.
162        origin: String,
163        /// Which rule it broke.
164        violation: Violation,
165    },
166
167    /// A configured [`TokenEnum`](crate::TokenEnum) the schema cannot supply.
168    ///
169    /// Configuration is what asked for it, so this names the enum rather than a
170    /// layer: nothing in the schema is wrong, and the file to open is the build
171    /// script.
172    #[error("token enum `{name}` {cause}")]
173    TokenEnum {
174        /// The enum the configuration asked for.
175        name: String,
176        /// What it asked for that could not be honoured.
177        cause: TokenEnumError,
178    },
179
180    /// A field holds a value the generated declaration has no Rust literal for.
181    ///
182    /// Only a fallback or a piece of metadata reaches the table, so this is a
183    /// value type nothing generated has needed yet rather than an unusual
184    /// schema. It stops the build: a fallback that went missing would leave the
185    /// generated schemas answering differently from the ones they came from.
186    #[error("{schema}.{field} holds a {kind} value, which the generated declaration cannot express")]
187    UnwritableValue {
188        /// The schema declaring it.
189        schema: String,
190        /// The field on it.
191        field: String,
192        /// The value type that has no literal.
193        kind: &'static str,
194    },
195
196    /// The generator wrote Rust that does not parse.
197    ///
198    /// A defect in this crate rather than anything a schema can ask for, but it
199    /// surfaces in a consumer's build, so it says which schema was being
200    /// generated and what the parser made of it.
201    #[error("generated Rust for {schema} does not parse: {cause}")]
202    Malformed {
203        /// The schema being generated.
204        schema: String,
205        /// What parsing the emitted tokens reported.
206        cause: String,
207    },
208
209    /// Two schemas declare the same `libraryName`, so both would be written to
210    /// one `<library>.rs` and the second would silently replace the first.
211    #[error("{first} and {second} both declare libraryName `{library}`, so both would write {library}.rs")]
212    DuplicateLibrary {
213        /// The library name they share.
214        library: String,
215        /// The schema configured first.
216        first: PathBuf,
217        /// The schema configured second.
218        second: PathBuf,
219    },
220
221    /// A shader-definition layer does not declare its nodes in a form this
222    /// crate generates views of.
223    #[error("invalid shader definitions {path}: {cause}")]
224    ShaderDefs {
225        /// The layer.
226        path: PathBuf,
227        /// What is wrong with it.
228        cause: String,
229    },
230
231    /// A `plugInfo.json` does not declare its metadata fields or kinds in a
232    /// form this crate reads.
233    #[error("invalid plugInfo {path}: {cause}")]
234    PlugInfo {
235        /// The file.
236        path: PathBuf,
237        /// What is wrong with it.
238        cause: String,
239    },
240
241    /// A `plugInfo.json` declares a plugin that is neither a configured
242    /// schema's `libraryName` nor a requested declaration family, so what it
243    /// declares would reach no generated file.
244    #[error("{path} declares plugin `{plugin}`, which is neither a configured schema library nor a declaration family")]
245    UnknownPlugin {
246        /// The plugin name.
247        plugin: String,
248        /// The file declaring it.
249        path: PathBuf,
250    },
251
252    /// A declaration family's name is not an identifier, so it could not name
253    /// the file generated for it.
254    #[error("declaration family `{family}` is not a valid identifier")]
255    InvalidDeclarationFamily {
256        /// The requested family.
257        family: String,
258    },
259
260    /// A declaration family was requested for a plugin no configured
261    /// `plugInfo.json` declares.
262    #[error("no configured plugInfo declares a plugin called `{family}`, requested as a declaration family")]
263    UnknownDeclarationFamily {
264        /// The requested family.
265        family: String,
266    },
267
268    /// Two libraries declare one kind, so the registry would refuse the
269    /// second.
270    #[error("kind `{kind}` is declared by both {first} and {second}")]
271    DuplicateKind {
272        /// The kind.
273        kind: String,
274        /// The library declaring it first.
275        first: String,
276        /// The library declaring it again.
277        second: String,
278    },
279
280    /// Two libraries declare one metadata field, so the registry would refuse
281    /// the second.
282    #[error("metadata field `{field}` is declared by both {first} and {second}")]
283    DuplicateMetadataField {
284        /// The field.
285        field: String,
286        /// The library declaring it first.
287        first: String,
288        /// The library declaring it again.
289        second: String,
290    },
291
292    /// Two of a library's metadata fields would be read or written by one
293    /// method of the trait they share.
294    #[error("metadata fields `{first}` and `{second}` of {library} both need a method called `{method}`")]
295    MetadataMethodCollision {
296        /// The library declaring them.
297        library: String,
298        /// The method both need.
299        method: String,
300        /// The field named first.
301        first: String,
302        /// The field named second.
303        second: String,
304    },
305
306    /// A file or directory could not be read, written or created.
307    #[error("cannot access {path}")]
308    Io {
309        /// What was being read or written.
310        path: PathBuf,
311        /// What the filesystem reported.
312        #[source]
313        source: io::Error,
314    },
315}
316
317impl Error {
318    /// The first diagnostic `stage` raised, named against the schema it came
319    /// from.
320    ///
321    /// Composition reports many; the first is the one a contributor fixes,
322    /// and the rest usually follow from it.
323    pub(crate) fn composition(schema: &std::path::Path, stage: &openusd::usd::Stage) -> Option<Self> {
324        let diagnostic = stage.composition_errors().first()?.to_string();
325        Some(Error::Composition {
326            schema: schema.to_path_buf(),
327            diagnostic,
328        })
329    }
330}
331
332/// A malformed path names nothing to read.
333///
334/// Written out rather than derived: `#[from]` builds a conversion into the
335/// variant holding that type, and this belongs in [`Error::Core`] with every
336/// other core failure. Its own variant would let one failure arrive two ways —
337/// as itself, or nested in a `Core` — and leave a caller matching in two
338/// places for it. `openusd::Error` already tells them apart.
339impl From<openusd::sdf::PathParseError> for Error {
340    fn from(source: openusd::sdf::PathParseError) -> Self {
341        Error::Core(source.into())
342    }
343}
344
345/// Authoring the layers this crate writes goes through the same door every
346/// other edit does. Written out for the reason above.
347impl From<openusd::sdf::EditError> for Error {
348    fn from(source: openusd::sdf::EditError) -> Self {
349        Error::Core(source.into())
350    }
351}
352
353/// A composed query a reader asks of a stage. Written out for the reason
354/// above.
355impl From<openusd::pcp::QueryError> for Error {
356    fn from(source: openusd::pcp::QueryError) -> Self {
357        Error::Core(source.into())
358    }
359}
360
361/// A field that will not decode is schema data this crate cannot use. Written
362/// out for the reason above.
363impl From<openusd::sdf::DataError> for Error {
364    fn from(source: openusd::sdf::DataError) -> Self {
365        Error::Core(source.into())
366    }
367}