code_moniker_core/core/moniker/
builder.rs1use super::encoding::{EncodingError, HEADER_FIXED_LEN, VERSION, is_uri_kind, write_u16};
4use super::{Moniker, MonikerView};
5
6const MAX_COMPONENT_LEN: usize = u16::MAX as usize;
7
8#[derive(Default, Debug)]
13pub struct MonikerBuilder {
14 project: Vec<u8>,
15 segments: Vec<(Vec<u8>, Vec<u8>)>,
16}
17
18impl MonikerBuilder {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn from_view(view: MonikerView<'_>) -> Self {
24 let mut b = Self::new();
25 b.project(view.project());
26 for seg in view.segments() {
27 b.segment(seg.kind, seg.name);
28 }
29 b
30 }
31
32 pub fn truncate(&mut self, len: usize) {
33 self.segments.truncate(len);
34 }
35
36 pub fn project(&mut self, project: &[u8]) -> &mut Self {
37 self.project.clear();
38 self.project.extend_from_slice(project);
39 self
40 }
41
42 pub fn segment(&mut self, kind: &[u8], name: &[u8]) -> &mut Self {
43 self.segments.push((kind.to_vec(), name.to_vec()));
44 self
45 }
46
47 pub fn build(&self) -> Moniker {
48 self.try_build()
49 .unwrap_or_else(|error| panic!("invalid moniker builder input: {error}"))
50 }
51
52 pub fn try_build(&self) -> Result<Moniker, EncodingError> {
53 if self.project.is_empty() {
54 return Err(EncodingError::EmptyProject);
55 }
56 std::str::from_utf8(&self.project).map_err(|_| EncodingError::NonUtf8Project)?;
57 if self.project.len() > MAX_COMPONENT_LEN {
58 return Err(EncodingError::ProjectTooLong(self.project.len()));
59 }
60 let mut buf = Vec::with_capacity(self.estimated_size());
61 buf.push(VERSION);
62 write_u16(&mut buf, self.project.len() as u16);
63 buf.extend_from_slice(&self.project);
64 for (kind, name) in &self.segments {
65 std::str::from_utf8(kind).map_err(|_| EncodingError::NonUtf8SegmentKind)?;
66 if !is_uri_kind(kind) {
67 return Err(EncodingError::InvalidSegmentKind);
68 }
69 std::str::from_utf8(name).map_err(|_| EncodingError::NonUtf8SegmentName)?;
70 if kind.len() > MAX_COMPONENT_LEN {
71 return Err(EncodingError::SegmentKindTooLong(kind.len()));
72 }
73 if name.len() > MAX_COMPONENT_LEN {
74 return Err(EncodingError::SegmentNameTooLong(name.len()));
75 }
76 write_u16(&mut buf, kind.len() as u16);
77 buf.extend_from_slice(kind);
78 write_u16(&mut buf, name.len() as u16);
79 buf.extend_from_slice(name);
80 }
81 Ok(Moniker::from_encoded_unchecked(buf))
82 }
83
84 fn estimated_size(&self) -> usize {
85 HEADER_FIXED_LEN
86 + self.project.len()
87 + self
88 .segments
89 .iter()
90 .map(|(k, n)| 2 + k.len() + 2 + n.len())
91 .sum::<usize>()
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::super::Segment;
98 use super::*;
99
100 #[test]
101 #[should_panic(expected = "project must not be empty")]
102 fn builder_panics_on_empty_project() {
103 MonikerBuilder::new().build();
104 }
105
106 #[test]
107 fn builder_with_project_no_segments() {
108 let m = MonikerBuilder::new().project(b"my-app").build();
109 let v = m.as_view();
110 assert_eq!(v.project(), b"my-app");
111 assert_eq!(v.segment_count(), 0);
112 }
113
114 #[test]
115 fn builder_with_segments() {
116 let m = MonikerBuilder::new()
117 .project(b"my-app")
118 .segment(b"module", b"main")
119 .segment(b"package", b"com")
120 .segment(b"package", b"acme")
121 .segment(b"class", b"Foo")
122 .build();
123 let v = m.as_view();
124 assert_eq!(v.segment_count(), 4);
125 let segs: Vec<_> = v.segments().collect();
126 assert_eq!(
127 segs[0],
128 Segment {
129 kind: b"module",
130 name: b"main"
131 }
132 );
133 assert_eq!(
134 segs[3],
135 Segment {
136 kind: b"class",
137 name: b"Foo"
138 }
139 );
140 }
141
142 #[test]
143 fn builder_method_with_arity_in_name() {
144 let m = MonikerBuilder::new()
145 .project(b"app")
146 .segment(b"class", b"Foo")
147 .segment(b"method", b"bar()")
148 .segment(b"method", b"bar(2)")
149 .build();
150 let segs: Vec<_> = m.as_view().segments().collect();
151 assert_eq!(segs[1].name, b"bar()");
152 assert_eq!(segs[2].name, b"bar(2)");
153 }
154
155 #[test]
156 fn builder_accepts_max_length_component() {
157 let big = vec![b'a'; MAX_COMPONENT_LEN];
158 let m = MonikerBuilder::new()
159 .project(&big)
160 .segment(b"path", &big)
161 .build();
162 let v = m.as_view();
163 assert_eq!(v.project().len(), MAX_COMPONENT_LEN);
164 let seg = v.segments().next().unwrap();
165 assert_eq!(seg.name.len(), MAX_COMPONENT_LEN);
166 }
167
168 #[test]
169 #[should_panic(expected = "project longer than u16::MAX bytes")]
170 fn builder_panics_on_oversized_project() {
171 let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
172 MonikerBuilder::new().project(&oversize).build();
173 }
174
175 #[test]
176 #[should_panic(expected = "segment kind longer than u16::MAX bytes")]
177 fn builder_panics_on_oversized_segment_kind() {
178 let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
179 MonikerBuilder::new()
180 .project(b"app")
181 .segment(&oversize, b"x")
182 .build();
183 }
184
185 #[test]
186 #[should_panic(expected = "segment name longer than u16::MAX bytes")]
187 fn builder_panics_on_oversized_segment_name() {
188 let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
189 MonikerBuilder::new()
190 .project(b"app")
191 .segment(b"path", &oversize)
192 .build();
193 }
194}