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