Skip to main content

lexicon_api/
schema.rs

1use serde::{Deserialize, Serialize};
2
3/// The kind of API item.
4#[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/// Visibility level of an API item.
34#[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/// A single public API item extracted from source code.
54#[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/// A snapshot of all public API items in a crate.
81#[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}