use crate::matches::MatchedSteps; use std::path::PathBuf; use std::process::Output; use thiserror::Error; /// Define all the kinds of errors any part of this crate can return. #[derive(Debug, Error)] pub enum SubplotError { /// Document has non-fatal errors. #[error("Document has {0} warnings.")] Warnings(usize), /// Subplot could not find a file named as a bindings file. #[error("binding file could not be found: {0}: {1}")] BindingsFileNotFound(PathBuf, std::io::Error), /// Subplot could not find a file named as a functions file. #[error("functions file could not be found: {0}: {1}")] FunctionsFileNotFound(PathBuf, std::io::Error), /// The simple pattern specifies a kind that is unknown. #[error("simple pattern kind {0} is unknown")] UnknownSimplePatternKind(String), /// The simple pattern and the type map disagree over the kind of a match #[error("simple pattern kind for {0} disagrees with the type map")] SimplePatternKindMismatch(String), /// The simple pattern contains a stray { or }. #[error("simple pattern contains a stray {{ or }}")] StrayBraceInSimplePattern(String), /// The simple pattern has regex metacharacters. /// /// Simple patterns are not permitted to have regex metacharacters in them /// unless the pattern is explicitly marked `regex: false` to indicate that /// the binding author understands what they're up to. #[error("simple pattern contains regex metacharacters: {0}")] SimplePatternHasMetaCharacters(String), /// Error while parsing a bindings file #[error("binding file failed to parse: {0}")] BindingFileParseError(PathBuf, #[source] Box), /// Scenario step does not match a known binding /// /// This may be due to the binding missing entirely, or that the /// step or the binding has a typo, or that a pattern in the /// binding doesn't match what the author thinks it matches. #[error("do not understand binding: {0}")] BindingUnknown(String), /// Scenario step matches more than one binding /// /// THis may be due to bindings being too general, or having unusual /// overlaps in their matching #[error("more than one binding matches step {0}:\n{1}")] BindingNotUnique(String, MatchedSteps), /// A binding in the bindings file doesn't specify a known keyword. #[error("binding doesn't specify known keyword: {0}")] BindingWithoutKnownKeyword(String), /// A binding has more than one keyword (given/when/then). #[error("binding has more than one keyword (given/when/then)")] BindingHasManyKeywords(String), /// A binding lists an unknown type in its type map #[error("binding has unknown type/kind {0}")] UnknownTypeInBinding(String), /// Subplot tried to use a program, but couldn't feed it data /// /// Subplot uses some helper programs to implement some of its /// functionality, for example the GraphViz dot program. This /// error means that when tried to start a helper program and /// write to the helper's standard input stream, it failed to get /// the stream. /// /// This probably implies there's something wrong on your system. #[error("couldn't get stdin of child process to write to it")] ChildNoStdin, /// Subplot helper program failed /// /// Subplot uses some helper programs to implement some of its /// functionality, for example the GraphViz dot program. This /// error means that the helper program failed (exit code was not /// zero). /// /// This probably implies there's something wrong in Subplot. /// Please report this error to the Subplot project. #[error("child process failed: {0}")] ChildFailed(String), /// Binding doesn't define a function /// /// All binding must define the name of the function that /// implements the step. The bindings file has at least one /// binding that doesn't define one. To fix, add a `function:` /// field to the binding. #[error("binding does not name a function: {0}")] NoFunction(String), /// Document has no title /// /// The document YAML metadata does not define a document title. /// To fix, add a `title` field. #[error("document has no title")] NoTitle, /// Document has no template /// /// The document YAML metadata does not define the template to use /// during code generation. /// /// To fix, ensure an appropriate `impl` entry is present. #[error("document has no template")] MissingTemplate, /// Document has more than one template /// /// The document YAML metadata specifies more than one possible /// template implementation to be used during code generation. /// /// To fix, specify `--template` on the codegen CLI. #[error("document has more than one template possibility")] AmbiguousTemplate, /// Document does not support the requested template /// /// The document YAML metadata does not specify support for the /// stated template. /// /// To fix, specify a template which is provided for in the document. #[error("document lacks specified template support")] TemplateSupportNotPresent, /// Pandoc AST is not JSON /// /// Subplot acts as a Pandoc filter, and as part of that Pandoc /// constructs an _abstract syntax tree_ from the input document, /// and feeds it to the filter as JSON. However, when Subplot was /// parsing the AST, it wasn't JSON. /// /// This probably means there's something wrong with Pandoc, it's /// Rust bindings, or Subplot. #[error("Pandoc produce AST not in JSON")] NotJson, /// First scenario is before first heading /// /// Subplot scenarios are group by the input document's structure. /// Each scenario must be in a chapter, section, subsection, or /// other structural element with a heading. Subplot found a /// scenario block before the first heading in the document. /// /// To fix, add a heading or move the scenario after a heading. #[error("first scenario is before first heading")] ScenarioBeforeHeading, /// Step does not have a keyword. #[error("step has no keyword: {0}")] NoStepKeyword(String), /// Unknown scenario step keyword. /// /// Each scenario step must start with a known keyword (given, /// when, then, and, but), but Subplot didn't find one it /// recognized. /// /// This is usually due to a typing mistake or similar. #[error("unknown step keyword: {0}")] UnknownStepKind(String), /// Scenario step uses continuation keyword too early /// /// If a continuation keyword (`and` or `but`) is used too early /// in a scenario (i.e. before any other keyword was used) then /// it cannot be resolved to whichever keyword it should have been. #[error("continuation keyword used too early")] ContinuationTooEarly, /// Embedded file has the same name as another embedded file /// /// Names of embedded files must be unique in the input document, /// but Subplot found at least one with the same name as another. #[error("Duplicate embedded file name: {0:?}")] DuplicateEmbeddedFilename(String), /// Embedded file has more than one `add-newline` attribute /// /// The `add-newline` attribute can only be specified once for any given /// embedded file #[error("Embedded file {0} has more than one `add-newline` attribute")] RepeatedAddNewlineAttribute(String), /// Unrecognised `add-newline` attribute value on an embedded file /// /// The `add-newline` attribute can only take the values `auto`, `yes`, /// and `no`. #[error("Embedded file {0} has unrecognised `add-newline={}` - valid values are auto/yes/no")] UnrecognisedAddNewline(String, String), /// Couldn't determine base directory from input file name. /// /// Subplot needs to to determine the base directory for files /// referred to by the markdown input file (e.g., bindings and /// functions files). It failed to do that from the name of the /// input file. Something weird is happening. #[error("Could not determine base directory for included files from {0:?}")] BasedirError(PathBuf), /// Output goes into a directory that does not exist. /// /// Subplot needs to know in which directory it should write its /// output file, since it writes a temporary file first, then /// renames it to the final output file. The temporary file is /// created in the same directory as the final output file. /// However, Subplot could not find that directory. #[error("Output going to a directory that does not exist: {0}")] OutputDirectoryNotFound(String), /// The template.yaml is not in a directory. /// /// Template specifications reference files relative to the /// template.yaml file, but Subplot could not find the name of the /// directory containing the template.yaml file. Something is very /// weird. #[error("Couldn't find name of directory containing template spec: {0}")] NoTemplateSpecDirectory(PathBuf), /// A code template has an error. #[error("Couldn't load template {0}: {1}")] TemplateError(String, tera::Error), /// Unknown classes in use in document #[error("Unknown classes found in the document: {0}")] UnknownClasses(String), /// Template does not specify how to run generated program /// /// The template.yaml file used does not specify how to run the /// generated program, but user asked codegen to run it. #[error("template.yaml does not specify how to run generated program")] TemplateNoRun, /// An embedded file was not found. #[error("embedded file {0} was not found in the subplot document")] EmbeddedFileNotFound(String), /// When rendering a pikchr, something went wrong. #[error("failure rendering pikchr diagram: {0}")] PikchrRenderError(String), /// When attempting to codegen, no scenarios matched the desired template language #[error("no scenarios were found matching the `{0}` template")] NoScenariosMatched(String), /// I/O error /// /// Subplot did some I/O, and it failed. This is a generic wrapper /// for any kind of I/O error. #[error(transparent)] IoError { /// The wrapped error. #[from] source: std::io::Error, }, /// Pandoc error /// /// Subplot got an error from Panoc. This is a generic wrapper for /// any kinds of Pandoc errors. #[error(transparent)] PandocError { /// The wrapped error. #[from] source: pandoc::PandocError, }, /// Regular expression error /// /// Subplot uses regular expressions. This is a generic wrapper for /// any kinds of errors related to that. #[error(transparent)] RegexError { /// The wrapped error. #[from] source: regex::Error, }, /// JSON error /// /// Subplot parses and generates JSON. This is a generic wrapper /// for any kinds of errors related to that. #[error(transparent)] JsonError { /// The wrapped error. #[from] source: serde_json::Error, }, /// YAML error /// /// Subplot parses YAML. This is a generic wrapper for any kinds /// of errors related to that. #[error(transparent)] YamlError { /// The wrapped error. #[from] source: serde_yaml::Error, }, /// Abstract syntax tree error. #[error(transparent)] Ast(#[from] crate::ast::Error), /// UTF8 conversion error. #[error(transparent)] Utf8Error(#[from] std::str::Utf8Error), } impl SubplotError { /// Construct a ChildFailed error. pub fn child_failed(msg: &str, output: &Output) -> SubplotError { let msg = format!( "{}: {}: {:?}", msg, output.status.code().unwrap_or(-1), String::from_utf8_lossy(&output.stderr) ); SubplotError::ChildFailed(msg) } } /// A warning, or non-fatal error. /// /// Errors prevent Subplot from producing output. Warnings don't do that. #[derive(Debug, Clone, thiserror::Error)] pub enum Warning { /// Document refers to an embedded file that doesn't exist. #[error( "Document refers to an embedded file that doesn't exist: \"{1}\"\n in scenario \"{0}\"" )] UnknownEmbeddedFile(String, String), /// Embedded file is not used by any scenario. #[error("Embedded file is not used by any scenario: \"{0}\"")] UnusedEmbeddedFile(String), /// Missing step implementation. #[error("Missing step implementation: \"{1}\"\n in scenario \"{0}\"")] MissingStepImplementation(String, String), /// Unknown binding when typesetting a scenario. #[error("Unknown binding: {0}")] UnknownBinding(String), /// Pikchr failed during typesetting. #[error("Markup using pikchr failed: {0}")] Pikchr(String), /// Dot failed during typesetting. #[error("Markup using dot failed: {0}")] Dot(String), /// Plantuml failed during typesetting. #[error("Markup using plantuml failed: {0}")] Plantuml(String), } /// A list of warnings. /// /// Subplot collects warnings into this structure so that they can be /// processed at the end. #[derive(Debug, Default)] pub struct Warnings { warnings: Vec, } impl Warnings { /// Append a warning to the list. pub fn push(&mut self, w: Warning) { self.warnings.push(w); } /// Append all warnings from one list to another. pub fn push_all(&mut self, mut other: Warnings) { self.warnings.append(&mut other.warnings); } /// Return a slice with all the warnings in the list. pub fn warnings(&self) -> &[Warning] { &self.warnings } }