Skip to content

Instantly share code, notes, and snippets.

@lmmx
Last active October 31, 2025 10:47
Show Gist options
  • Save lmmx/0c8b157c58365c18663a57a7a0c050dc to your computer and use it in GitHub Desktop.
Save lmmx/0c8b157c58365c18663a57a7a0c050dc to your computer and use it in GitHub Desktop.
Rust-script to parse cargo doc diagnostics as JSON in Rust
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! anstream = "0.6"
//! serde = { version = "1", features = ["derive"] }
//! serde_json = "1"
//! ```
use serde::{Deserialize, Serialize};
use std::io::{self, BufRead};
#[derive(Serialize, Deserialize)]
pub struct Coordinate {
cfgs: Option<Vec<String>>,
env: Option<Vec<Option<serde_json::Value>>>,
executable: Option<serde_json::Value>,
features: Option<Vec<String>>,
filenames: Option<Vec<String>>,
fresh: Option<bool>,
linked_libs: Option<Vec<String>>,
linked_paths: Option<Vec<Option<serde_json::Value>>>,
manifest_path: Option<String>,
message: Option<Message>,
out_dir: Option<String>,
package_id: Option<String>,
profile: Option<Profile>,
reason: String,
success: Option<bool>,
target: Option<Target>,
}
#[derive(Serialize, Deserialize)]
pub struct Message {
#[serde(rename = "$message_type")]
message_type: String,
children: Vec<Child>,
code: Code,
level: String,
message: String,
rendered: String,
spans: Vec<Span>,
}
#[derive(Serialize, Deserialize)]
pub struct Child {
children: Vec<Option<serde_json::Value>>,
code: Option<serde_json::Value>,
level: String,
message: String,
rendered: Option<serde_json::Value>,
spans: Vec<Option<serde_json::Value>>,
}
#[derive(Serialize, Deserialize)]
pub struct Code {
code: String,
explanation: Option<serde_json::Value>,
}
#[derive(Serialize, Deserialize)]
pub struct Span {
byte_end: i64,
byte_start: i64,
column_end: i64,
column_start: i64,
expansion: Option<serde_json::Value>,
file_name: String,
is_primary: bool,
label: Option<serde_json::Value>,
line_end: i64,
line_start: i64,
suggested_replacement: Option<serde_json::Value>,
suggestion_applicability: Option<serde_json::Value>,
text: Vec<Text>,
}
#[derive(Serialize, Deserialize)]
pub struct Text {
highlight_end: i64,
highlight_start: i64,
text: String,
}
#[derive(Serialize, Deserialize)]
pub struct Profile {
debug_assertions: bool,
debuginfo: i64,
opt_level: String,
overflow_checks: bool,
test: bool,
}
#[derive(Serialize, Deserialize)]
pub struct Target {
crate_types: Vec<String>,
doc: bool,
doctest: bool,
edition: String,
kind: Vec<String>,
name: String,
src_path: String,
test: bool,
}
fn main() {
let stdin = io::stdin();
let reader = stdin.lock();
for (line_num, line) in reader.lines().enumerate() {
match line {
Ok(line_content) => {
if line_content.trim().is_empty() {
continue;
}
match serde_json::from_str::<Coordinate>(&line_content) {
Ok(coord) => {
anstream::println!("=== Entry {} ===", line_num + 1);
anstream::println!("Reason: {}", coord.reason);
if let Some(pkg_id) = &coord.package_id {
anstream::println!("Package ID: {}", pkg_id);
}
if let Some(target) = &coord.target {
anstream::println!("Target: {} ({})", target.name, target.kind.join(", "));
anstream::println!("Source: {}", target.src_path);
}
if let Some(success) = coord.success {
anstream::println!("Success: {}", success);
}
if let Some(msg) = &coord.message {
anstream::println!("Message Level: {}", msg.level);
anstream::println!("Message: {}", msg.message);
if let Some(code) = msg.code.explanation.as_ref() {
anstream::println!("Code: {} ({})", msg.code.code, code);
} else {
anstream::println!("Code: {}", msg.code.code);
}
}
anstream::println!();
}
Err(e) => {
anstream::eprintln!("Error parsing line {}: {}", line_num + 1, e);
anstream::eprintln!("Line content: {}", line_content);
}
}
}
Err(e) => {
anstream::eprintln!("Error reading line {}: {}", line_num + 1, e);
}
}
}
}
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! anstream = "0.6"
//! serde = { version = "1", features = ["derive"] }
//! serde_json = "1"
//! ```
use serde::{Deserialize, Serialize};
use std::io::{self, BufRead};
#[derive(Serialize, Deserialize)]
pub struct Coordinate {
cfgs: Option<Vec<String>>,
env: Option<Vec<Option<serde_json::Value>>>,
executable: Option<serde_json::Value>,
features: Option<Vec<String>>,
filenames: Option<Vec<String>>,
fresh: Option<bool>,
linked_libs: Option<Vec<String>>,
linked_paths: Option<Vec<Option<serde_json::Value>>>,
manifest_path: Option<String>,
message: Option<Message>,
out_dir: Option<String>,
package_id: Option<String>,
profile: Option<Profile>,
reason: String,
success: Option<bool>,
target: Option<Target>,
}
#[derive(Serialize, Deserialize)]
pub struct Message {
#[serde(rename = "$message_type")]
message_type: String,
children: Vec<Child>,
code: Code,
level: String,
message: String,
rendered: String,
spans: Vec<Span>,
}
#[derive(Serialize, Deserialize)]
pub struct Child {
children: Vec<Option<serde_json::Value>>,
code: Option<serde_json::Value>,
level: String,
message: String,
rendered: Option<serde_json::Value>,
spans: Vec<Option<serde_json::Value>>,
}
#[derive(Serialize, Deserialize)]
pub struct Code {
code: String,
explanation: Option<serde_json::Value>,
}
#[derive(Serialize, Deserialize)]
pub struct Span {
byte_end: i64,
byte_start: i64,
column_end: i64,
column_start: i64,
expansion: Option<serde_json::Value>,
file_name: String,
is_primary: bool,
label: Option<serde_json::Value>,
line_end: i64,
line_start: i64,
suggested_replacement: Option<serde_json::Value>,
suggestion_applicability: Option<serde_json::Value>,
text: Vec<Text>,
}
#[derive(Serialize, Deserialize)]
pub struct Text {
highlight_end: i64,
highlight_start: i64,
text: String,
}
#[derive(Serialize, Deserialize)]
pub struct Profile {
debug_assertions: bool,
debuginfo: i64,
opt_level: String,
overflow_checks: bool,
test: bool,
}
#[derive(Serialize, Deserialize)]
pub struct Target {
crate_types: Vec<String>,
doc: bool,
doctest: bool,
edition: String,
kind: Vec<String>,
name: String,
src_path: String,
test: bool,
}
fn main() {
let stdin = io::stdin();
let reader = stdin.lock();
for (line_num, line) in reader.lines().enumerate() {
match line {
Ok(line_content) => {
if line_content.trim().is_empty() {
continue;
}
match serde_json::from_str::<Coordinate>(&line_content) {
Ok(coord) => {
anstream::println!("╔═══ Entry {} ═══", line_num + 1);
anstream::println!("║ Reason: {}", coord.reason);
if let Some(pkg_id) = &coord.package_id {
anstream::println!("║ Package ID: {}", pkg_id);
}
if let Some(manifest) = &coord.manifest_path {
anstream::println!("║ Manifest: {}", manifest);
}
if let Some(target) = &coord.target {
anstream::println!("║ Target: {} ({})", target.name, target.kind.join(", "));
anstream::println!("║ Edition: {}", target.edition);
anstream::println!("║ Source: {}", target.src_path);
anstream::println!("║ Crate types: {}", target.crate_types.join(", "));
}
if let Some(profile) = &coord.profile {
anstream::println!("║ Profile: opt_level={}, debuginfo={}, debug_assertions={}",
profile.opt_level, profile.debuginfo, profile.debug_assertions);
}
if let Some(features) = &coord.features {
if !features.is_empty() {
anstream::println!("║ Features: {}", features.join(", "));
}
}
if let Some(filenames) = &coord.filenames {
if !filenames.is_empty() {
anstream::println!("║ Output files:");
for file in filenames {
anstream::println!("║ - {}", file);
}
}
}
if let Some(success) = coord.success {
anstream::println!("║ Success: {}", success);
}
if let Some(fresh) = coord.fresh {
anstream::println!("║ Fresh: {}", fresh);
}
if let Some(out_dir) = &coord.out_dir {
anstream::println!("║ Out dir: {}", out_dir);
}
if let Some(msg) = &coord.message {
anstream::println!("║");
anstream::println!("║ ┌─ Compiler Message ─");
anstream::println!("║ │ Level: {}", msg.level);
anstream::println!("║ │ Type: {}", msg.message_type);
anstream::println!("║ │ Code: {}", msg.code.code);
anstream::println!("║ │ Message: {}", msg.message);
if !msg.spans.is_empty() {
anstream::println!("║ │");
anstream::println!("║ │ Code Spans:");
for (i, span) in msg.spans.iter().enumerate() {
anstream::println!("║ │ Span {} [{}]:", i + 1,
if span.is_primary { "PRIMARY" } else { "secondary" });
anstream::println!("║ │ File: {}", span.file_name);
anstream::println!("║ │ Lines: {}-{}, Cols: {}-{}",
span.line_start, span.line_end,
span.column_start, span.column_end);
if let Some(label) = &span.label {
anstream::println!("║ │ Label: {}", label);
}
if !span.text.is_empty() {
anstream::println!("║ │ Code:");
for text in &span.text {
anstream::println!("║ │ {}", text.text);
// Show highlight if applicable
if text.highlight_start > 0 || text.highlight_end > 0 {
let spaces = " ".repeat(text.highlight_start as usize);
let carets = "^".repeat(
(text.highlight_end - text.highlight_start).max(1) as usize
);
anstream::println!("║ │ {}{}", spaces, carets);
}
}
}
if let Some(suggestion) = &span.suggested_replacement {
anstream::println!("║ │ Suggested replacement: {}", suggestion);
}
}
}
if !msg.children.is_empty() {
anstream::println!("║ │");
anstream::println!("║ │ Additional notes:");
for (i, child) in msg.children.iter().enumerate() {
anstream::println!("║ │ [{}] {}: {}", i + 1, child.level, child.message);
}
}
anstream::println!("║ │");
anstream::println!("║ │ Rendered output:");
for line in msg.rendered.lines() {
anstream::println!("║ │ {}", line);
}
anstream::println!("║ └─");
}
anstream::println!("╚═══════════════════════════════");
anstream::println!();
}
Err(e) => {
anstream::eprintln!("❌ Error parsing line {}: {}", line_num + 1, e);
anstream::eprintln!("Line content: {}", line_content);
}
}
}
Err(e) => {
anstream::eprintln!("❌ Error reading line {}: {}", line_num + 1, e);
}
}
}
}
cargo doc --message-format=json 2>/dev/null | ./cdoc.rs -
@lmmx
Copy link
Author

lmmx commented Oct 31, 2025

Example output

=== Entry 1 ===
Reason: compiler-artifact
Package ID: registry+https://github.com/rust-lang/crates.io-index#[email protected]
Target: build-script-build (custom-build)
Source: /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/proc-macro2-1.0.103/build.rs

=== Entry 2 ===
Reason: build-script-executed
Package ID: registry+https://github.com/rust-lang/crates.io-index#[email protected]

=== Entry 3 ===
Reason: compiler-artifact
Package ID: registry+https://github.com/rust-lang/crates.io-index#[email protected]
Target: build-script-build (custom-build)
Source: /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/quote-1.0.41/build.rs

=== Entry 4 ===
Reason: compiler-artifact
Package ID: registry+https://github.com/rust-lang/crates.io-index#[email protected]
Target: unicode_ident (lib)
Source: /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/unicode-ident-1.0.22/src/lib.rs

=== Entry 5 ===
Reason: compiler-artifact
Package ID: registry+https://github.com/rust-lang/crates.io-index#[email protected]
Target: cfg_if (lib)
Source: /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cfg-if-1.0.4/src/lib.rs

@lmmx
Copy link
Author

lmmx commented Oct 31, 2025

Tweet tweet

Parsing cargo doc diagnostics output as Rust structs via the JSON message format ✨ https://gist.github.com/lmmx/0c8b157c58365c18663a57a7a0c050dc

Screenshot from 2025-10-31 10-39-46 Screenshot from 2025-10-31 10-40-19

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment