1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
5pub enum ApiItemKind {
6 Struct,
7 Enum,
8 Trait,
9 Function,
10 Method,
11 Module,
12 Constant,
13 TypeAlias,
14 Impl,
15}
16
17impl std::fmt::Display for ApiItemKind {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 Self::Struct => write!(f, "struct"),
21 Self::Enum => write!(f, "enum"),
22 Self::Trait => write!(f, "trait"),
23 Self::Function => write!(f, "function"),
24 Self::Method => write!(f, "method"),
25 Self::Module => write!(f, "module"),
26 Self::Constant => write!(f, "constant"),
27 Self::TypeAlias => write!(f, "type alias"),
28 Self::Impl => write!(f, "impl"),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
35pub enum Visibility {
36 Public,
37 Crate,
38 Restricted,
39 Private,
40}
41
42impl std::fmt::Display for Visibility {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 Self::Public => write!(f, "pub"),
46 Self::Crate => write!(f, "pub(crate)"),
47 Self::Restricted => write!(f, "pub(restricted)"),
48 Self::Private => write!(f, "private"),
49 }
50 }
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ApiItem {
56 pub kind: ApiItemKind,
57 pub name: String,
58 pub module_path: Vec<String>,
59 pub signature: String,
60 pub visibility: Visibility,
61 pub trait_associations: Vec<String>,
62 pub stability: Option<String>,
63 pub doc_summary: Option<String>,
64 pub span_file: Option<String>,
65 pub span_line: Option<u32>,
66}
67
68impl PartialEq for ApiItem {
69 fn eq(&self, other: &Self) -> bool {
70 self.kind == other.kind
71 && self.name == other.name
72 && self.module_path == other.module_path
73 && self.signature == other.signature
74 && self.visibility == other.visibility
75 }
76}
77
78impl Eq for ApiItem {}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct ApiSnapshot {
83 pub crate_name: String,
84 pub version: Option<String>,
85 pub items: Vec<ApiItem>,
86 pub extracted_at: String,
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn sample_item() -> ApiItem {
94 ApiItem {
95 kind: ApiItemKind::Function,
96 name: "do_thing".into(),
97 module_path: vec!["mymod".into()],
98 signature: "fn do_thing(x: i32) -> bool".into(),
99 visibility: Visibility::Public,
100 trait_associations: vec![],
101 stability: None,
102 doc_summary: Some("Does a thing.".into()),
103 span_file: Some("src/lib.rs".into()),
104 span_line: Some(10),
105 }
106 }
107
108 #[test]
109 fn serde_roundtrip_api_item() {
110 let item = sample_item();
111 let json = serde_json::to_string(&item).unwrap();
112 let back: ApiItem = serde_json::from_str(&json).unwrap();
113 assert_eq!(item, back);
114 }
115
116 #[test]
117 fn serde_roundtrip_api_snapshot() {
118 let snap = ApiSnapshot {
119 crate_name: "my-crate".into(),
120 version: Some("0.1.0".into()),
121 items: vec![sample_item()],
122 extracted_at: "2026-01-01T00:00:00Z".into(),
123 };
124 let json = serde_json::to_string_pretty(&snap).unwrap();
125 let back: ApiSnapshot = serde_json::from_str(&json).unwrap();
126 assert_eq!(snap.crate_name, back.crate_name);
127 assert_eq!(snap.items.len(), back.items.len());
128 assert_eq!(snap.items[0], back.items[0]);
129 }
130}