compose_lens/model/
port.rs1use super::{FieldReference, Located};
4use crate::source::SourceSpan;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ShortPort {
9 raw: Located<String>,
10 host_ip: Option<String>,
11 published: Option<String>,
12 target: String,
13 protocol: Option<String>,
14}
15
16impl ShortPort {
17 pub(crate) fn parse(raw: Located<String>) -> Self {
18 let (without_protocol, protocol) = raw
19 .value()
20 .rsplit_once('/')
21 .map_or((raw.value().as_str(), None), |(value, protocol)| {
22 (value, Some(protocol.to_owned()))
23 });
24 let fields = split_port_fields(without_protocol);
25 let (host_ip, published, target) = match fields.as_slice() {
26 [] => (None, None, String::new()),
27 [target] => (None, None, (*target).to_owned()),
28 [published, target] => (None, Some((*published).to_owned()), (*target).to_owned()),
29 [host @ .., published, target] => (
30 Some(host.join(":")),
31 Some((*published).to_owned()),
32 (*target).to_owned(),
33 ),
34 };
35 Self {
36 raw,
37 host_ip,
38 published,
39 target,
40 protocol,
41 }
42 }
43
44 #[must_use]
46 pub const fn raw(&self) -> &Located<String> {
47 &self.raw
48 }
49
50 #[must_use]
52 pub fn host_ip(&self) -> Option<&str> {
53 self.host_ip.as_deref()
54 }
55
56 #[must_use]
58 pub fn published(&self) -> Option<&str> {
59 self.published.as_deref()
60 }
61
62 #[must_use]
64 pub fn target(&self) -> &str {
65 &self.target
66 }
67
68 #[must_use]
70 pub fn protocol(&self) -> Option<&str> {
71 self.protocol.as_deref()
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct LongPort {
78 span: SourceSpan,
79 target: Option<Located<String>>,
80 published: Option<Located<String>>,
81 host_ip: Option<Located<String>>,
82 protocol: Option<Located<String>>,
83 app_protocol: Option<Located<String>>,
84 mode: Option<Located<String>>,
85 name: Option<Located<String>>,
86 extension_fields: Vec<FieldReference>,
87 unknown_fields: Vec<FieldReference>,
88}
89
90impl LongPort {
91 pub(crate) fn new(span: SourceSpan) -> Self {
92 Self {
93 span,
94 target: None,
95 published: None,
96 host_ip: None,
97 protocol: None,
98 app_protocol: None,
99 mode: None,
100 name: None,
101 extension_fields: Vec::new(),
102 unknown_fields: Vec::new(),
103 }
104 }
105
106 pub(crate) fn set_target(&mut self, value: Located<String>) {
107 self.target = Some(value);
108 }
109 pub(crate) fn set_published(&mut self, value: Located<String>) {
110 self.published = Some(value);
111 }
112 pub(crate) fn set_host_ip(&mut self, value: Located<String>) {
113 self.host_ip = Some(value);
114 }
115 pub(crate) fn set_protocol(&mut self, value: Located<String>) {
116 self.protocol = Some(value);
117 }
118 pub(crate) fn set_app_protocol(&mut self, value: Located<String>) {
119 self.app_protocol = Some(value);
120 }
121 pub(crate) fn set_mode(&mut self, value: Located<String>) {
122 self.mode = Some(value);
123 }
124 pub(crate) fn set_name(&mut self, value: Located<String>) {
125 self.name = Some(value);
126 }
127 pub(super) fn push_extension(&mut self, value: FieldReference) {
128 self.extension_fields.push(value);
129 }
130 pub(super) fn push_unknown(&mut self, value: FieldReference) {
131 self.unknown_fields.push(value);
132 }
133
134 #[must_use]
136 pub const fn span(&self) -> SourceSpan {
137 self.span
138 }
139 #[must_use]
141 pub const fn target(&self) -> Option<&Located<String>> {
142 self.target.as_ref()
143 }
144 #[must_use]
146 pub const fn published(&self) -> Option<&Located<String>> {
147 self.published.as_ref()
148 }
149 #[must_use]
151 pub const fn host_ip(&self) -> Option<&Located<String>> {
152 self.host_ip.as_ref()
153 }
154 #[must_use]
156 pub const fn protocol(&self) -> Option<&Located<String>> {
157 self.protocol.as_ref()
158 }
159 #[must_use]
161 pub const fn app_protocol(&self) -> Option<&Located<String>> {
162 self.app_protocol.as_ref()
163 }
164 #[must_use]
166 pub const fn mode(&self) -> Option<&Located<String>> {
167 self.mode.as_ref()
168 }
169 #[must_use]
171 pub const fn name(&self) -> Option<&Located<String>> {
172 self.name.as_ref()
173 }
174 #[must_use]
176 pub fn extension_fields(&self) -> &[FieldReference] {
177 &self.extension_fields
178 }
179 #[must_use]
181 pub fn unknown_fields(&self) -> &[FieldReference] {
182 &self.unknown_fields
183 }
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
188pub enum Port {
189 Short(ShortPort),
191 Long(Box<LongPort>),
193}
194
195impl Port {
196 #[must_use]
198 pub const fn span(&self) -> SourceSpan {
199 match self {
200 Self::Short(value) => value.raw().span(),
201 Self::Long(value) => value.span(),
202 }
203 }
204}
205
206fn split_port_fields(value: &str) -> Vec<&str> {
207 if let Some(close) = value.find(']').filter(|_| value.starts_with('[')) {
208 let host = &value[..=close];
209 let suffix = value[close + 1..].strip_prefix(':').unwrap_or(&value[close + 1..]);
210 let mut fields = vec![host];
211 fields.extend(suffix.split(':'));
212 fields
213 } else {
214 value.split(':').collect()
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::ShortPort;
221 use crate::model::Located;
222 use crate::source::{SourceId, SourceSpan};
223
224 fn located(value: &str) -> Located<String> {
225 Located::new(
226 value.to_owned(),
227 SourceSpan::from_valid_offsets(SourceId::new(1), 0, value.len()),
228 )
229 }
230
231 #[test]
232 fn splits_bracketed_ipv6_and_protocol() {
233 let port = ShortPort::parse(located("[::1]:6001:6001/udp"));
234 assert_eq!(port.host_ip(), Some("[::1]"));
235 assert_eq!(port.published(), Some("6001"));
236 assert_eq!(port.target(), "6001");
237 assert_eq!(port.protocol(), Some("udp"));
238 }
239}