Skip to content

Absolve kdl-rs of syn #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
rust: [1.71.1, stable]
rust: [1.81, stable]
os: [ubuntu-latest, macOS-latest, windows-latest]

steps:
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ on:
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
Expand Down Expand Up @@ -168,7 +168,7 @@ jobs:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
Expand Down Expand Up @@ -278,7 +278,7 @@ jobs:
needs:
- plan
- host
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PLAN: ${{ needs.plan.outputs.val }}
Expand Down Expand Up @@ -311,7 +311,7 @@ jobs:
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' && (needs.publish-npm.result == 'skipped' || needs.publish-npm.result == 'success') }}
runs-on: "ubuntu-20.04"
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
Expand Down
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ readme = "README.md"
homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "serialization", "config"]
rust-version = "1.71.1"
rust-version = "1.81"
edition = "2021"

[features]
Expand All @@ -22,17 +22,16 @@ members = ["tools/*"]

[dependencies]
miette.workspace = true
thiserror.workspace = true
num = "0.4.2"
winnow = { version = "=0.6.24", features = ["alloc", "unstable-recover"] }
kdlv1 = { package = "kdl", version = "4.7.0", optional = true }

[workspace.dependencies]
miette = "7.2.0"
thiserror = "1.0.40"
miette = { version = "7.6.0", default-features = false }

[dev-dependencies]
miette = { workspace = true, features = ["fancy"] }
miette = { workspace = true, features = ["derive", "fancy"] }
thiserror = "2.0.12"
pretty_assertions = "1.3.0"

# The profile that 'dist' will build with
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ Error:

* `span` (default) - Includes spans in the various document-related structs.
* `v1` - Adds support for v1 parsing. This will pull in the entire previous
version of `kdl-rs`, and so may be fairly heavy.
version of `kdl-rs`, and so may be fairly heavy.
* `v1-fallback` - Implies `v1`. Makes it so the various `*::parse()` and
`FromStr` implementations try to parse their inputs as `v2`, and, if that
fails, try again with `v1`. For `KdlDocument`, a heuristic will be applied
if both `v1` and `v2` parsers fail, to pick which error(s) to return. For
other types, only the `v2` parser's errors will be returned.
`FromStr` implementations try to parse their inputs as `v2`, and, if that
fails, try again with `v1`. For `KdlDocument`, a heuristic will be applied
if both `v1` and `v2` parsers fail, to pick which error(s) to return. For
other types, only the `v2` parser's errors will be returned.

### Quirks

Expand All @@ -140,9 +140,9 @@ means a few things:
representation will be thrown away and the actual value will be used when
serializing.

### Minimum Supported Rust Version
### Minimum Supported Rust Version (MSRV)

You must be at least `1.71.1` tall to get on this ride.
You must be at least `1.81` tall to get on this ride.

### License

Expand Down
3 changes: 3 additions & 0 deletions dist-workspace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ install-path = "CARGO_HOME"
install-updater = false
# Publish jobs to run in CI
publish-jobs = ["npm"]

[dist.github-custom-runners]
global = "ubuntu-22.04"
148 changes: 133 additions & 15 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::sync::Arc;
use std::{error::Error, fmt::Display, iter, sync::Arc};

use miette::{Diagnostic, SourceSpan};
use thiserror::Error;
use miette::{Diagnostic, LabeledSpan, Severity, SourceSpan};

#[cfg(doc)]
use {
Expand Down Expand Up @@ -34,30 +33,43 @@ use {
/// ╰────
/// help: Floating point numbers must be base 10, and have numbers after the decimal point.
/// ```
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
#[error("Failed to parse KDL document")]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KdlError {
/// Original input that this failure came from.
#[source_code]
pub input: Arc<String>,

/// Sub-diagnostics for this failure.
#[related]
pub diagnostics: Vec<KdlDiagnostic>,
}

impl Display for KdlError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed to parse KDL document")
}
}
impl Error for KdlError {}

impl Diagnostic for KdlError {
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.input)
}

fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
Some(Box::new(
self.diagnostics.iter().map(|d| d as &dyn Diagnostic),
))
}
}

/// An individual diagnostic message for a KDL parsing issue.
///
/// While generally signifying errors, they can also be treated as warnings.
#[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)]
#[error("{}", message.clone().unwrap_or_else(|| "Unexpected error".into()))]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KdlDiagnostic {
/// Shared source for the diagnostic.
#[source_code]
pub input: Arc<String>,

/// Offset in chars of the error.
#[label("{}", label.clone().unwrap_or_else(|| "here".into()))]
pub span: SourceSpan,

/// Message for the error itself.
Expand All @@ -67,12 +79,42 @@ pub struct KdlDiagnostic {
pub label: Option<String>,

/// Suggestion for fixing the parser error.
#[help]
pub help: Option<String>,

/// Severity level for the Diagnostic.
#[diagnostic(severity)]
pub severity: miette::Severity,
pub severity: Severity,
}

impl Display for KdlDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = self
.message
.clone()
.unwrap_or_else(|| "Unexpected error".into());
write!(f, "{message}")
}
}
impl Error for KdlDiagnostic {}

impl Diagnostic for KdlDiagnostic {
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
Some(&self.input)
}

fn severity(&self) -> Option<Severity> {
Some(self.severity)
}

fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
self.help.as_ref().map(|s| Box::new(s) as Box<dyn Display>)
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
let label = self.label.clone().unwrap_or_else(|| "here".to_owned());
let labeled_span = LabeledSpan::new_with_span(Some(label), self.span);

Some(Box::new(iter::once(labeled_span)))
}
}

#[cfg(feature = "v1")]
Expand All @@ -87,8 +129,84 @@ impl From<kdlv1::KdlError> for KdlError {
message: Some(format!("{}", value.kind)),
label: value.label.map(|x| x.into()),
help: value.help.map(|x| x.into()),
severity: miette::Severity::Error,
severity: Severity::Error,
}],
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn kdl_error() {
let kdl_diagnostic = KdlDiagnostic {
input: Default::default(),
span: SourceSpan::new(0.into(), 0),
message: Default::default(),
label: Default::default(),
help: Default::default(),
severity: Default::default(),
};

let kdl_error = KdlError {
input: Arc::new("bark? i guess?".to_owned()),
diagnostics: vec![kdl_diagnostic.clone(), kdl_diagnostic],
};

// Test `Error` impl
assert_eq!(kdl_error.to_string(), "Failed to parse KDL document");
assert!(kdl_error.source().is_none());

// Test `Diagnostic` impl
let related: Vec<_> = kdl_error.related().unwrap().collect();
assert_eq!(related.len(), 2);
assert_eq!(
kdl_error
.source_code()
.unwrap()
.read_span(&SourceSpan::new(0.into(), 5), 0, 0)
.unwrap()
.data(),
b"bark?"
);
}

#[test]
fn kdl_diagnostic() {
let mut kdl_diagnostic = KdlDiagnostic {
input: Arc::new("Catastrophic failure!!!".to_owned()),
span: SourceSpan::new(0.into(), 3),
message: None,
label: Some("cute".to_owned()),
help: Some("try harder?".to_owned()),
severity: Severity::Error,
};

// Test `Error` impl
assert_eq!(kdl_diagnostic.to_string(), "Unexpected error");
assert!(kdl_diagnostic.source().is_none());

kdl_diagnostic.message = Some("mega bad news, kiddo".to_owned());

assert_eq!(kdl_diagnostic.to_string(), "mega bad news, kiddo");
assert!(kdl_diagnostic.source().is_none());

// Test `Diagnostic` impl
let labels: Vec<_> = kdl_diagnostic.labels().unwrap().collect();
assert_eq!(labels.len(), 1);
assert_eq!(labels[0].label().unwrap(), "cute");
assert_eq!(
kdl_diagnostic
.source_code()
.unwrap()
.read_span(labels[0].inner(), 0, 0)
.unwrap()
.data(),
b"Cat"
);
assert_eq!(kdl_diagnostic.help().unwrap().to_string(), "try harder?");
assert_eq!(kdl_diagnostic.severity().unwrap(), Severity::Error);
}
}
12 changes: 6 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@
//!
//! * `span` (default) - Includes spans in the various document-related structs.
//! * `v1` - Adds support for v1 parsing. This will pull in the entire previous
//! version of `kdl-rs`, and so may be fairly heavy.
//! version of `kdl-rs`, and so may be fairly heavy.
//! * `v1-fallback` - Implies `v1`. Makes it so the various `*::parse()` and
//! `FromStr` implementations try to parse their inputs as `v2`, and, if that
//! fails, try again with `v1`. For `KdlDocument`, a heuristic will be applied
//! if both `v1` and `v2` parsers fail, to pick which error(s) to return. For
//! other types, only the `v2` parser's errors will be returned.
//! `FromStr` implementations try to parse their inputs as `v2`, and, if that
//! fails, try again with `v1`. For `KdlDocument`, a heuristic will be applied
//! if both `v1` and `v2` parsers fail, to pick which error(s) to return. For
//! other types, only the `v2` parser's errors will be returned.
//!
//! ## Quirks
//!
Expand Down Expand Up @@ -140,7 +140,7 @@
//!
//! ## Minimum Supported Rust Version (MSRV)
//!
//! You must be at least `1.71.1` tall to get on this ride.
//! You must be at least `1.81` tall to get on this ride.
//!
//! ## License
//!
Expand Down
2 changes: 1 addition & 1 deletion tools/kdl-lsp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
homepage = "https://kdl.dev"
repository = "https://github.com/kdl-org/kdl-rs"
keywords = ["kdl", "document", "config", "lsp", "language-server"]
rust-version = "1.71.1"
rust-version = "1.81"

[dependencies]
miette.workspace = true
Expand Down
Loading