jsonschema_annotator/schema/
annotation.rs1use std::collections::HashMap;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct Annotation {
6 pub path: String,
8 pub title: Option<String>,
10 pub description: Option<String>,
12}
13
14impl Annotation {
15 pub fn new(path: impl Into<String>) -> Self {
17 Self {
18 path: path.into(),
19 title: None,
20 description: None,
21 }
22 }
23
24 pub fn with_title(mut self, title: impl Into<String>) -> Self {
26 self.title = Some(title.into());
27 self
28 }
29
30 pub fn with_description(mut self, description: impl Into<String>) -> Self {
32 self.description = Some(description.into());
33 self
34 }
35
36 pub fn to_comment_lines(&self, max_width: Option<usize>) -> Vec<String> {
38 let mut lines = Vec::new();
39
40 if let Some(title) = &self.title {
41 lines.push(format!("# {}", title));
42 }
43
44 if let Some(desc) = &self.description {
45 let width = max_width.unwrap_or(78);
46 for line in textwrap::wrap(desc, width) {
47 lines.push(format!("# {}", line));
48 }
49 }
50
51 lines
52 }
53
54 pub fn is_empty(&self) -> bool {
56 self.title.is_none() && self.description.is_none()
57 }
58}
59
60#[derive(Debug, Clone, Default)]
62pub struct AnnotationMap {
63 inner: HashMap<String, Annotation>,
64}
65
66impl AnnotationMap {
67 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn get(&self, path: &str) -> Option<&Annotation> {
74 self.inner.get(path)
75 }
76
77 pub fn insert(&mut self, annotation: Annotation) {
79 if !annotation.is_empty() {
80 self.inner.insert(annotation.path.clone(), annotation);
81 }
82 }
83
84 pub fn iter(&self) -> impl Iterator<Item = (&String, &Annotation)> {
86 self.inner.iter()
87 }
88
89 pub fn len(&self) -> usize {
91 self.inner.len()
92 }
93
94 pub fn is_empty(&self) -> bool {
96 self.inner.is_empty()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_annotation_builder() {
106 let ann = Annotation::new("server.port")
107 .with_title("Port")
108 .with_description("The server port number");
109
110 assert_eq!(ann.path, "server.port");
111 assert_eq!(ann.title, Some("Port".to_string()));
112 assert_eq!(ann.description, Some("The server port number".to_string()));
113 }
114
115 #[test]
116 fn test_annotation_to_comment_lines() {
117 let ann = Annotation::new("test")
118 .with_title("Title")
119 .with_description("Description");
120
121 let lines = ann.to_comment_lines(None);
122 assert_eq!(lines, vec!["# Title", "# Description"]);
123 }
124
125 #[test]
126 fn test_annotation_to_comment_lines_wrapping() {
127 let ann = Annotation::new("test")
128 .with_description("This is a very long description that should be wrapped");
129
130 let lines = ann.to_comment_lines(Some(30));
131 assert!(lines.len() > 1);
132 for line in &lines {
133 assert!(line.len() <= 32); }
135 }
136
137 #[test]
138 fn test_annotation_map() {
139 let mut map = AnnotationMap::new();
140
141 map.insert(Annotation::new("a").with_title("A"));
142 map.insert(Annotation::new("b").with_title("B"));
143
144 assert_eq!(map.len(), 2);
145 assert_eq!(map.get("a").unwrap().title, Some("A".to_string()));
146 assert_eq!(map.get("b").unwrap().title, Some("B".to_string()));
147 assert!(map.get("c").is_none());
148 }
149
150 #[test]
151 fn test_empty_annotation_not_inserted() {
152 let mut map = AnnotationMap::new();
153 map.insert(Annotation::new("empty"));
154 assert!(map.is_empty());
155 }
156}