use std::fmt;
use std::str::FromStr;
use crate::Dialect;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Rule {
Syntax,
DuplicateKey,
InvalidNumber,
InvalidCodePoint,
ByteOrderMark,
MissingFinalNewline,
}
impl Rule {
pub const ALL: &'static [Rule] = &[
Rule::Syntax,
Rule::DuplicateKey,
Rule::InvalidNumber,
Rule::InvalidCodePoint,
Rule::ByteOrderMark,
Rule::MissingFinalNewline,
];
pub fn id(self) -> &'static str {
match self {
Rule::Syntax => "syntax",
Rule::DuplicateKey => "duplicate-key",
Rule::InvalidNumber => "invalid-number",
Rule::InvalidCodePoint => "invalid-code-point",
Rule::ByteOrderMark => "byte-order-mark",
Rule::MissingFinalNewline => "missing-final-newline",
}
}
pub fn description(self) -> &'static str {
match self {
Rule::Syntax => "the first syntax error; checking stops there",
Rule::DuplicateKey => "a key repeated in one object",
Rule::InvalidNumber => "a number a double cannot hold, or an integer past 2^53 - 1",
Rule::InvalidCodePoint => "a lone surrogate or a noncharacter in a string",
Rule::ByteOrderMark => "a byte order mark at the start of the file",
Rule::MissingFinalNewline => "a JSON Lines file that does not end with a newline",
}
}
pub fn default_severity(self, dialect: Dialect) -> Severity {
match (self, dialect) {
(Rule::Syntax | Rule::DuplicateKey, _)
| (Rule::InvalidNumber | Rule::InvalidCodePoint, Dialect::IJson)
| (Rule::ByteOrderMark, Dialect::JsonLines) => Severity::Error,
(Rule::ByteOrderMark, Dialect::Json | Dialect::IJson)
| (Rule::MissingFinalNewline, Dialect::JsonLines) => Severity::Warning,
_ => Severity::Off,
}
}
}
impl fmt::Display for Rule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.id())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnknownRule(String);
impl UnknownRule {
pub fn id(&self) -> &str {
&self.0
}
}
impl fmt::Display for UnknownRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ids: Vec<&str> = Rule::ALL.iter().map(|r| r.id()).collect();
write!(
f,
"unknown rule `{}`; expected one of {}",
self.0,
ids.join(", ")
)
}
}
impl std::error::Error for UnknownRule {}
impl FromStr for Rule {
type Err = UnknownRule;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Rule::ALL
.iter()
.copied()
.find(|r| r.id() == s)
.ok_or_else(|| UnknownRule(s.to_owned()))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Severity {
Off,
Warning,
Error,
}
impl Severity {
pub fn name(self) -> &'static str {
match self {
Severity::Off => "off",
Severity::Warning => "warning",
Severity::Error => "error",
}
}
pub(crate) fn is_on(self) -> bool {
self != Severity::Off
}
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Config {
dialect: Dialect,
overrides: [Option<Severity>; Rule::ALL.len()],
pub(crate) max_depth: usize,
}
impl Config {
pub const DEFAULT_MAX_DEPTH: usize = 10_000;
pub fn new(dialect: Dialect) -> Self {
Config {
dialect,
overrides: [None; Rule::ALL.len()],
max_depth: Self::DEFAULT_MAX_DEPTH,
}
}
#[must_use]
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
#[must_use]
pub fn rule(mut self, rule: Rule, severity: Severity) -> Self {
self.overrides[rule as usize] = Some(severity);
self
}
pub fn dialect(&self) -> Dialect {
self.dialect
}
pub fn severity(&self, rule: Rule) -> Severity {
if rule == Rule::Syntax {
return Severity::Error;
}
self.overrides[rule as usize].unwrap_or_else(|| rule.default_severity(self.dialect))
}
}
#[cfg(test)]
mod tests {
use super::Rule;
#[test]
fn all_lists_every_rule_at_its_index() {
for (i, &rule) in Rule::ALL.iter().enumerate() {
assert_eq!(rule as usize, i, "{rule}");
}
let last = Rule::MissingFinalNewline;
match last {
Rule::Syntax
| Rule::DuplicateKey
| Rule::InvalidNumber
| Rule::InvalidCodePoint
| Rule::ByteOrderMark
| Rule::MissingFinalNewline => {}
}
assert_eq!(Rule::ALL.len(), last as usize + 1);
}
}