use crate::output::path;
use num_traits::cast::FromPrimitive;
use std::sync::Arc;
use strum::EnumProperty;
#[derive(Debug, thiserror::Error)]
pub struct JsonSchemaValidationError {
pub message: String,
pub kind: jsonschema::error::ValidationErrorKind,
pub instance_path: jsonschema::paths::JSONPointer,
pub schema_path: jsonschema::paths::JSONPointer,
}
impl std::fmt::Display for JsonSchemaValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.message.fmt(f)
}
}
impl From<jsonschema::error::ValidationError<'_>> for JsonSchemaValidationError {
fn from(v: jsonschema::error::ValidationError) -> Self {
JsonSchemaValidationError {
message: v.to_string(),
kind: v.kind,
instance_path: v.instance_path,
schema_path: v.schema_path,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Message {
#[error("{0}")]
Untyped(String),
#[error("{0}")]
ProstDecodeError(#[from] prost::DecodeError),
#[error("{0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
UtfError(#[from] std::str::Utf8Error),
#[error("{0}")]
YamlError(#[from] serde_yaml::Error),
#[error("{0}")]
JsonSchemaValidationError(#[from] JsonSchemaValidationError),
#[error("{0}")]
UriError(#[from] uriparse::URIReferenceError),
#[error("{0}")]
GlobError(#[from] glob::PatternError),
}
impl From<&str> for Message {
fn from(s: &str) -> Self {
Message::Untyped(s.to_string())
}
}
impl From<String> for Message {
fn from(s: String) -> Self {
Message::Untyped(s)
}
}
impl From<jsonschema::error::ValidationError<'_>> for Message {
fn from(v: jsonschema::error::ValidationError<'_>) -> Self {
JsonSchemaValidationError::from(v).into()
}
}
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
strum_macros::EnumIter,
strum_macros::EnumProperty,
num_derive::FromPrimitive,
Default,
)]
pub enum Classification {
#[strum(props(HiddenDescription = "unclassified diagnostic"))]
#[default]
Unclassified = 0,
#[strum(props(Description = "not yet implemented"))]
NotYetImplemented = 1,
#[strum(props(Description = "illegal value"))]
IllegalValue = 2,
#[strum(props(Description = "illegal value in hint"))]
IllegalValueInHint = 3,
#[strum(props(Description = "illegal URI"))]
IllegalUri = 4,
#[strum(props(Description = "illegal glob"))]
IllegalGlob = 5,
#[strum(props(Description = "deprecation"))]
Deprecation = 6,
#[strum(props(HiddenDescription = "versioning"))]
Versioning = 7,
#[strum(props(HiddenDescription = "experimental"))]
Experimental = 999,
#[strum(props(HiddenDescription = "protobuf-related diagnostic"))]
Proto = 1000,
#[strum(props(HiddenDescription = "protobuf parsing failed"))]
ProtoParseFailed = 1001,
#[strum(props(Description = "missing required protobuf field"))]
ProtoMissingField = 1002,
#[strum(props(Description = "encountered a protobuf \"any\""))]
ProtoAny = 1004,
#[strum(props(Description = "missing protobuf \"any\" declaration"))]
ProtoMissingAnyDeclaration = 1006,
#[strum(props(HiddenDescription = "YAML-related diagnostic"))]
Yaml = 2000,
#[strum(props(Description = "did not attempt to resolve YAML"))]
YamlResolutionDisabled = 2001,
#[strum(props(Description = "failed to resolve YAML"))]
YamlResolutionFailed = 2002,
#[strum(props(Description = "failed to parse YAML"))]
YamlParseFailed = 2003,
#[strum(props(Description = "YAML does not conform to schema"))]
YamlSchemaValidationFailed = 2004,
#[strum(props(Description = "missing required YAML key"))]
YamlMissingKey = 2005,
#[strum(props(Description = "missing required YAML array element"))]
YamlMissingElement = 2007,
#[strum(props(Description = "invalid YAML value type"))]
YamlInvalidType = 2008,
#[strum(props(Description = "cyclic dependency"))]
YamlCyclicDependency = 2009,
#[strum(props(HiddenDescription = "link resolution diagnostic"))]
Link = 3000,
#[strum(props(Description = "failed to resolve anchor"))]
LinkMissingAnchor = 3001,
#[strum(props(HiddenDescription = "use of anchor zero"))]
LinkAnchorZero = 3005,
#[strum(props(Description = "failed to resolve type variation name & class pair"))]
LinkMissingTypeVariationNameAndClass = 3006,
#[strum(props(Description = "unresolved name lookup"))]
LinkUnresolvedName = 3007,
#[strum(props(Description = "ambiguous name lookup"))]
LinkAmbiguousName = 3008,
#[strum(props(Description = "duplicate definition"))]
LinkDuplicateDefinition = 3009,
#[strum(props(HiddenDescription = "invalid compound vs. simple function name usage"))]
LinkCompoundVsSimpleFunctionName = 3010,
#[strum(props(HiddenDescription = "type-related diagnostics"))]
Type = 4000,
#[strum(props(Description = "unknown type"))]
TypeUnknown = 4001,
#[strum(props(Description = "mismatched type parameters"))]
TypeMismatchedParameters = 4002,
#[strum(props(Description = "mismatched field name associations"))]
TypeMismatchedFieldNameAssociations = 4003,
#[strum(props(Description = "invalid swizzle operation"))]
TypeInvalidSwizzle = 4004,
#[strum(props(Description = "mismatched types"))]
TypeMismatch = 4005,
#[strum(props(Description = "struct type is required"))]
TypeStructRequired = 4006,
#[strum(props(Description = "mismatched type variation"))]
TypeMismatchedVariation = 4007,
#[strum(props(Description = "mismatched nullability"))]
TypeMismatchedNullability = 4008,
#[strum(props(Description = "invalid type pattern or derivation expression"))]
TypeDerivationInvalid = 4009,
#[strum(props(
Description = "type pattern or derivation expression failed to match or evaluate"
))]
TypeDerivationFailed = 4010,
#[strum(props(Description = "parse error in type pattern or derivation expression"))]
TypeParseError = 4011,
#[strum(props(
Description = "name resolution error in type pattern or derivation expression"
))]
TypeResolutionError = 4012,
#[strum(props(Description = "invalid field name"))]
TypeInvalidFieldName = 4013,
#[strum(props(Description = "unsupported type pattern or derivation construct"))]
TypeDerivationNotSupported = 4014,
#[strum(props(HiddenDescription = "relation-related diagnostics"))]
Relation = 5000,
#[strum(props(Description = "missing root relation"))]
RelationRootMissing = 5001,
#[strum(props(Description = "missing relation"))]
RelationMissing = 5002,
#[strum(props(Description = "invalid relation"))]
RelationInvalid = 5003,
#[strum(props(HiddenDescription = "expression-related diagnostics"))]
Expression = 6000,
#[strum(props(Description = "field reference into non-existent stream"))]
ExpressionFieldRefMissingStream = 6001,
#[strum(props(Description = "illegal literal value"))]
ExpressionIllegalLiteralValue = 6002,
#[strum(props(Description = "function definition unavailable"))]
ExpressionFunctionDefinitionUnavailable = 6003,
#[strum(props(Description = "illegal subquery"))]
ExpressionIllegalSubquery = 6004,
#[strum(props(
HiddenDescription = "diagnostics for pointing out parts of the plan that can be removed without changing its semantics"
))]
Redundant = 7000,
#[strum(props(Description = "redundant protobuf \"any\" declaration"))]
RedundantProtoAnyDeclaration = 7001,
#[strum(props(Description = "redundant extension URI definition"))]
RedundantExtensionDefition = 7002,
#[strum(props(Description = "redundant function declaration"))]
RedundantFunctionDeclaration = 7003,
#[strum(props(Description = "redundant type declaration"))]
RedundantTypeDeclaration = 7004,
#[strum(props(Description = "redundant type variation declaration"))]
RedundantTypeVariationDeclaration = 7005,
#[strum(props(Description = "redundant list slice"))]
RedundantListSlice = 7006,
#[strum(props(Description = "redundant field"))]
RedundantField = 7007,
#[strum(props(Description = "redundant enum variant"))]
RedundantEnumVariant = 7008,
}
impl Classification {
pub fn code(&self) -> u32 {
*self as u32
}
pub fn name(&self) -> String {
format!("{:?}", self)
}
pub fn group_code(&self) -> u32 {
(*self as u32) / 1000
}
pub fn group(&self) -> Classification {
Self::from_group(self.group_code())
.unwrap_or_else(|| panic!("missing group for {:?}", self))
}
pub fn sub_code(&self) -> u32 {
(*self as u32) % 1000
}
pub fn description(&self) -> &str {
self.get_str("Description")
.or_else(|| self.get_str("HiddenDescription"))
.unwrap_or_else(|| {
panic!(
"missing Description or HiddenDescription property for {:?}",
self
)
})
}
pub fn from_code(code: u32) -> Option<Self> {
Self::from_u32(code)
}
pub fn group_from_code(code: u32) -> Option<Self> {
Self::from_group(code / 1000)
}
pub fn from_group(group: u32) -> Option<Self> {
Self::from_u32(group * 1000)
}
pub fn parent(code: u32) -> u32 {
if code % 1000 != 0 {
(code / 1000) * 1000
} else {
0
}
}
pub fn format_message(
&self,
message: &Message,
f: &mut std::fmt::Formatter,
) -> std::fmt::Result {
if let Some(description) = self.get_str("Description") {
write!(f, "{description}: ")?;
}
write!(f, "{message} (code {:04})", self.code())
}
}
impl From<Classification> for u32 {
fn from(classification: Classification) -> Self {
classification.code()
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub struct Cause {
pub message: Arc<Message>,
pub classification: Classification,
}
impl PartialEq for Cause {
fn eq(&self, other: &Self) -> bool {
self.message.to_string() == other.message.to_string()
&& self.classification == other.classification
}
}
impl std::fmt::Display for Cause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.classification.format_message(&self.message, f)
}
}
impl Cause {
pub fn prefix<S: AsRef<str>>(self, prefix: S) -> Cause {
Cause {
message: Arc::new(Message::from(format!(
"{}: {}",
prefix.as_ref(),
self.message
))),
classification: self.classification,
}
}
}
macro_rules! ecause {
($class:ident, $message:expr) => {
crate::output::diagnostic::Cause {
message: std::sync::Arc::new($message.into()),
classification: crate::output::diagnostic::Classification::$class,
}
};
}
macro_rules! cause {
($class:ident, $($args:expr),*) => {
ecause!($class, format!($($args),*))
};
}
pub type Result<T> = std::result::Result<T, Cause>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Level {
Info,
Warning,
Error,
}
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub struct RawDiagnostic {
pub cause: Cause,
pub level: Level,
pub path: path::PathBuf,
}
impl std::fmt::Display for RawDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.level)?;
if !f.alternate() {
write!(f, " at {}", self.path)?;
}
write!(f, ": {}", self.cause)
}
}
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub struct Diagnostic {
pub cause: Cause,
pub original_level: Level,
pub adjusted_level: Level,
pub path: path::PathBuf,
}
impl std::fmt::Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.adjusted_level)?;
match self.original_level.cmp(&self.adjusted_level) {
std::cmp::Ordering::Less => write!(f, " (upgraded from {:?})", self.original_level)?,
std::cmp::Ordering::Equal => {}
std::cmp::Ordering::Greater => {
write!(f, " (downgraded from {:?})", self.original_level)?
}
}
if !f.alternate() {
write!(f, " at {}", self.path)?;
}
write!(f, ": {}", self.cause)
}
}
impl RawDiagnostic {
pub fn adjust_level(self, adjusted_level: Level) -> Diagnostic {
Diagnostic {
cause: self.cause,
original_level: self.level,
adjusted_level,
path: self.path,
}
}
}
macro_rules! diag {
($path:expr, $level:ident, $class:ident, $($args:expr),*) => {
diag!($path, $level, cause!($class, $($args),*))
};
($path:expr, $level:ident, $cause:expr) => {
crate::output::diagnostic::RawDiagnostic {
cause: $cause,
level: crate::output::diagnostic::Level::$level,
path: $path
}
};
}
pub type DiagResult<T> = std::result::Result<T, RawDiagnostic>;
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
use strum::IntoEnumIterator;
#[test]
fn test_diagnostic_classifications() {
let mut descriptions = HashSet::new();
for class in Classification::iter() {
let group = class.group();
if group != Classification::Unclassified {
assert!(
class.name().starts_with(&group.name()),
"incorrect group prefix for {:?}, should start with {:?}",
class,
group
);
}
assert!(
descriptions.insert(class.description().to_string()),
"duplicate description for {:?}",
class
);
}
}
}