flow_gates/
types.rs

1use crate::error::{GateError, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::sync::Arc;
5
6/// A node in a gate, representing a control point with coordinates in raw data space.
7///
8/// Gate nodes store coordinates for multiple channels, allowing gates to be defined
9/// in multi-dimensional space. Coordinates are stored as `f32` values in raw data units.
10///
11/// # Example
12///
13/// ```rust
14/// use flow_gates::GateNode;
15///
16/// let node = GateNode::new("node1")
17///     .with_coordinate("FSC-A", 1000.0)
18///     .with_coordinate("SSC-A", 2000.0);
19///
20/// assert_eq!(node.get_coordinate("FSC-A"), Some(1000.0));
21/// ```
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct GateNode {
24    /// Unique identifier for this node
25    pub id: Arc<str>,
26    /// Coordinates in raw data space, keyed by channel name.
27    ///
28    /// Using `Arc<str>` for channel names reduces allocations since they're shared
29    /// across multiple nodes and gates.
30    #[serde(with = "arc_str_hashmap")]
31    pub coordinates: HashMap<Arc<str>, f32>,
32}
33
34impl GateNode {
35    /// Create a new gate node with the given ID.
36    ///
37    /// The node starts with no coordinates. Use `with_coordinate` or `set_coordinate`
38    /// to add coordinate values.
39    pub fn new(id: impl Into<Arc<str>>) -> Self {
40        Self {
41            id: id.into(),
42            coordinates: HashMap::new(),
43        }
44    }
45
46    /// Add a coordinate value using the builder pattern.
47    ///
48    /// Returns `self` for method chaining.
49    pub fn with_coordinate(mut self, channel: impl Into<Arc<str>>, value: f32) -> Self {
50        self.coordinates.insert(channel.into(), value);
51        self
52    }
53
54    /// Get a coordinate value for the specified channel.
55    ///
56    /// Returns `None` if the channel is not present in this node.
57    pub fn get_coordinate(&self, channel: &str) -> Option<f32> {
58        self.coordinates.get(channel).copied()
59    }
60
61    pub fn set_coordinate(&mut self, channel: impl Into<Arc<str>>, value: f32) {
62        self.coordinates.insert(channel.into(), value);
63    }
64}
65
66/// The geometry of a gate, defining its shape in 2D parameter space.
67///
68/// Gates can be one of three geometric types:
69/// - **Polygon**: A closed or open polygonal region defined by vertices
70/// - **Rectangle**: An axis-aligned rectangular region
71/// - **Ellipse**: An elliptical region with optional rotation
72///
73/// All geometries operate in raw data coordinate space and are parameterized
74/// by two channel names (x and y parameters).
75///
76/// # Example
77///
78/// ```rust
79/// use flow_gates::{GateGeometry, GateNode};
80///
81/// // Create a rectangle gate
82/// let min = GateNode::new("min")
83///     .with_coordinate("FSC-A", 100.0)
84///     .with_coordinate("SSC-A", 200.0);
85/// let max = GateNode::new("max")
86///     .with_coordinate("FSC-A", 500.0)
87///     .with_coordinate("SSC-A", 600.0);
88///
89/// let geometry = GateGeometry::Rectangle { min, max };
90///
91/// // Check if a point is inside
92/// let inside = geometry.contains_point(300.0, 400.0, "FSC-A", "SSC-A")?;
93/// ```
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(tag = "type")]
96pub enum GateGeometry {
97    Polygon {
98        nodes: Vec<GateNode>,
99        closed: bool,
100    },
101    Rectangle {
102        min: GateNode,
103        max: GateNode,
104    },
105    Ellipse {
106        center: GateNode,
107        radius_x: f32,
108        radius_y: f32,
109        angle: f32, // rotation angle in radians
110    },
111}
112
113impl GateGeometry {
114    /// Calculate the bounding box for this geometry in the specified parameter space
115    pub fn bounding_box(&self, x_param: &str, y_param: &str) -> Option<(f32, f32, f32, f32)> {
116        match self {
117            GateGeometry::Polygon { nodes, .. } => {
118                let mut min_x = f32::MAX;
119                let mut min_y = f32::MAX;
120                let mut max_x = f32::MIN;
121                let mut max_y = f32::MIN;
122
123                for node in nodes {
124                    if let (Some(x), Some(y)) =
125                        (node.get_coordinate(x_param), node.get_coordinate(y_param))
126                    {
127                        min_x = min_x.min(x);
128                        min_y = min_y.min(y);
129                        max_x = max_x.max(x);
130                        max_y = max_y.max(y);
131                    }
132                }
133
134                if min_x < max_x && min_y < max_y {
135                    Some((min_x, min_y, max_x, max_y))
136                } else {
137                    None
138                }
139            }
140            GateGeometry::Rectangle { min, max } => {
141                if let (Some(min_x), Some(min_y), Some(max_x), Some(max_y)) = (
142                    min.get_coordinate(x_param),
143                    min.get_coordinate(y_param),
144                    max.get_coordinate(x_param),
145                    max.get_coordinate(y_param),
146                ) {
147                    Some((min_x, min_y, max_x, max_y))
148                } else {
149                    None
150                }
151            }
152            GateGeometry::Ellipse {
153                center,
154                radius_x,
155                radius_y,
156                angle,
157            } => {
158                if let (Some(cx), Some(cy)) = (
159                    center.get_coordinate(x_param),
160                    center.get_coordinate(y_param),
161                ) {
162                    let cos_a = angle.cos();
163                    let sin_a = angle.sin();
164
165                    // Maximum extents along each axis after rotation
166                    let extent_x = ((radius_x * cos_a).powi(2) + (radius_y * sin_a).powi(2)).sqrt();
167                    let extent_y = ((radius_x * sin_a).powi(2) + (radius_y * cos_a).powi(2)).sqrt();
168
169                    Some((cx - extent_x, cy - extent_y, cx + extent_x, cy + extent_y))
170                } else {
171                    None
172                }
173            }
174        }
175    }
176
177    /// Calculate the center point in raw data coordinates
178    pub fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
179        match self {
180            GateGeometry::Polygon { nodes, .. } => {
181                let (sum_x, sum_y, count) = nodes
182                    .iter()
183                    .filter_map(|node| {
184                        let x = node.get_coordinate(x_param)?;
185                        let y = node.get_coordinate(y_param)?;
186                        Some((x, y))
187                    })
188                    .fold((0.0, 0.0, 0), |(sx, sy, c), (x, y)| (sx + x, sy + y, c + 1));
189
190                if count > 0 {
191                    Ok((sum_x / count as f32, sum_y / count as f32))
192                } else {
193                    Err(GateError::invalid_geometry(
194                        "Polygon has no valid coordinates",
195                    ))
196                }
197            }
198            GateGeometry::Rectangle { min, max } => {
199                let min_x = min
200                    .get_coordinate(x_param)
201                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
202                let min_y = min
203                    .get_coordinate(y_param)
204                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
205                let max_x = max
206                    .get_coordinate(x_param)
207                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
208                let max_y = max
209                    .get_coordinate(y_param)
210                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
211
212                Ok(((min_x + max_x) / 2.0, (min_y + max_y) / 2.0))
213            }
214            GateGeometry::Ellipse { center, .. } => {
215                let cx = center
216                    .get_coordinate(x_param)
217                    .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
218                let cy = center
219                    .get_coordinate(y_param)
220                    .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
221
222                Ok((cx, cy))
223            }
224        }
225    }
226
227    /// Check if a point (in raw coordinates) is inside the gate
228    pub fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
229        match self {
230            GateGeometry::Polygon { nodes, closed } => {
231                if !closed {
232                    return Ok(false);
233                }
234
235                // Extract coordinates
236                let coords: Vec<(f32, f32)> = nodes
237                    .iter()
238                    .filter_map(|node| {
239                        Some((node.get_coordinate(x_param)?, node.get_coordinate(y_param)?))
240                    })
241                    .collect();
242
243                if coords.len() < 3 {
244                    return Ok(false);
245                }
246
247                // Ray casting algorithm
248                Ok(point_in_polygon(x, y, &coords))
249            }
250            GateGeometry::Rectangle { min, max } => {
251                let min_x = min
252                    .get_coordinate(x_param)
253                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
254                let min_y = min
255                    .get_coordinate(y_param)
256                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
257                let max_x = max
258                    .get_coordinate(x_param)
259                    .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
260                let max_y = max
261                    .get_coordinate(y_param)
262                    .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
263
264                Ok(x >= min_x && x <= max_x && y >= min_y && y <= max_y)
265            }
266            GateGeometry::Ellipse {
267                center,
268                radius_x,
269                radius_y,
270                angle,
271            } => {
272                let cx = center
273                    .get_coordinate(x_param)
274                    .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
275                let cy = center
276                    .get_coordinate(y_param)
277                    .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
278
279                // Rotate point around center by -angle
280                let cos_a = angle.cos();
281                let sin_a = angle.sin();
282                let dx = x - cx;
283                let dy = y - cy;
284                let rotated_x = dx * cos_a + dy * sin_a;
285                let rotated_y = -dx * sin_a + dy * cos_a;
286
287                // Check if point is inside axis-aligned ellipse
288                let normalized = (rotated_x / radius_x).powi(2) + (rotated_y / radius_y).powi(2);
289                Ok(normalized <= 1.0)
290            }
291        }
292    }
293
294    /// Check if the gate has valid geometry and coordinates
295    pub fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
296        match self {
297            GateGeometry::Polygon { nodes, .. } => {
298                // Need at least 3 nodes for a valid polygon
299                if nodes.len() < 3 {
300                    return Ok(false);
301                }
302
303                // All nodes must have valid coordinates
304                let valid_coords = nodes.iter().all(|node| {
305                    node.get_coordinate(x_param).is_some() && node.get_coordinate(y_param).is_some()
306                });
307
308                Ok(valid_coords)
309            }
310            GateGeometry::Rectangle { min, max } => {
311                // Must have valid coordinates
312                if min.get_coordinate(x_param).is_none()
313                    || min.get_coordinate(y_param).is_none()
314                    || max.get_coordinate(x_param).is_none()
315                    || max.get_coordinate(y_param).is_none()
316                {
317                    return Ok(false);
318                }
319
320                // Min must be less than max
321                let min_x = min.get_coordinate(x_param).unwrap();
322                let min_y = min.get_coordinate(y_param).unwrap();
323                let max_x = max.get_coordinate(x_param).unwrap();
324                let max_y = max.get_coordinate(y_param).unwrap();
325
326                Ok(min_x < max_x && min_y < max_y)
327            }
328            GateGeometry::Ellipse {
329                center,
330                radius_x,
331                radius_y,
332                ..
333            } => {
334                // Must have valid center coordinates
335                if center.get_coordinate(x_param).is_none()
336                    || center.get_coordinate(y_param).is_none()
337                {
338                    return Ok(false);
339                }
340
341                // Radii must be positive
342                Ok(radius_x > &0.0 && radius_y > &0.0)
343            }
344        }
345    }
346
347    /// Get a descriptive name for this gate type
348    pub fn gate_type_name(&self) -> &'static str {
349        match self {
350            GateGeometry::Polygon { .. } => "Polygon",
351            GateGeometry::Rectangle { .. } => "Rectangle",
352            GateGeometry::Ellipse { .. } => "Ellipse",
353        }
354    }
355}
356
357// Implement geometry traits for GateGeometry enum by delegating to struct implementations
358use crate::traits::*;
359
360impl GateCenter for GateGeometry {
361    fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
362        match self {
363            GateGeometry::Polygon { nodes, closed } => {
364                let poly = crate::polygon::PolygonGateGeometry {
365                    nodes: nodes.clone(),
366                    closed: *closed,
367                };
368                poly.calculate_center(x_param, y_param)
369            }
370            GateGeometry::Rectangle { min, max } => {
371                let rect = crate::rectangle::RectangleGateGeometry {
372                    min: min.clone(),
373                    max: max.clone(),
374                };
375                rect.calculate_center(x_param, y_param)
376            }
377            GateGeometry::Ellipse {
378                center,
379                radius_x,
380                radius_y,
381                angle,
382            } => {
383                let ellipse = crate::ellipse::EllipseGateGeometry {
384                    center: center.clone(),
385                    radius_x: *radius_x,
386                    radius_y: *radius_y,
387                    angle: *angle,
388                };
389                ellipse.calculate_center(x_param, y_param)
390            }
391        }
392    }
393}
394
395impl GateContainment for GateGeometry {
396    fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
397        match self {
398            GateGeometry::Polygon { nodes, closed } => {
399                let poly = crate::polygon::PolygonGateGeometry {
400                    nodes: nodes.clone(),
401                    closed: *closed,
402                };
403                poly.contains_point(x, y, x_param, y_param)
404            }
405            GateGeometry::Rectangle { min, max } => {
406                let rect = crate::rectangle::RectangleGateGeometry {
407                    min: min.clone(),
408                    max: max.clone(),
409                };
410                rect.contains_point(x, y, x_param, y_param)
411            }
412            GateGeometry::Ellipse {
413                center,
414                radius_x,
415                radius_y,
416                angle,
417            } => {
418                let ellipse = crate::ellipse::EllipseGateGeometry {
419                    center: center.clone(),
420                    radius_x: *radius_x,
421                    radius_y: *radius_y,
422                    angle: *angle,
423                };
424                ellipse.contains_point(x, y, x_param, y_param)
425            }
426        }
427    }
428}
429
430impl GateBounds for GateGeometry {
431    fn bounding_box(&self, x_param: &str, y_param: &str) -> Result<(f32, f32, f32, f32)> {
432        match self {
433            GateGeometry::Polygon { nodes, closed } => {
434                let poly = crate::polygon::PolygonGateGeometry {
435                    nodes: nodes.clone(),
436                    closed: *closed,
437                };
438                poly.bounding_box(x_param, y_param)
439            }
440            GateGeometry::Rectangle { min, max } => {
441                let rect = crate::rectangle::RectangleGateGeometry {
442                    min: min.clone(),
443                    max: max.clone(),
444                };
445                rect.bounding_box(x_param, y_param)
446            }
447            GateGeometry::Ellipse {
448                center,
449                radius_x,
450                radius_y,
451                angle,
452            } => {
453                let ellipse = crate::ellipse::EllipseGateGeometry {
454                    center: center.clone(),
455                    radius_x: *radius_x,
456                    radius_y: *radius_y,
457                    angle: *angle,
458                };
459                ellipse.bounding_box(x_param, y_param)
460            }
461        }
462    }
463}
464
465impl GateValidation for GateGeometry {
466    fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
467        match self {
468            GateGeometry::Polygon { nodes, closed } => {
469                let poly = crate::polygon::PolygonGateGeometry {
470                    nodes: nodes.clone(),
471                    closed: *closed,
472                };
473                poly.is_valid(x_param, y_param)
474            }
475            GateGeometry::Rectangle { min, max } => {
476                let rect = crate::rectangle::RectangleGateGeometry {
477                    min: min.clone(),
478                    max: max.clone(),
479                };
480                rect.is_valid(x_param, y_param)
481            }
482            GateGeometry::Ellipse {
483                center,
484                radius_x,
485                radius_y,
486                angle,
487            } => {
488                let ellipse = crate::ellipse::EllipseGateGeometry {
489                    center: center.clone(),
490                    radius_x: *radius_x,
491                    radius_y: *radius_y,
492                    angle: *angle,
493                };
494                ellipse.is_valid(x_param, y_param)
495            }
496        }
497    }
498}
499
500impl GateGeometryOps for GateGeometry {
501    fn gate_type_name(&self) -> &'static str {
502        match self {
503            GateGeometry::Polygon { .. } => "Polygon",
504            GateGeometry::Rectangle { .. } => "Rectangle",
505            GateGeometry::Ellipse { .. } => "Ellipse",
506        }
507    }
508}
509
510/// Point-in-polygon using ray casting algorithm
511fn point_in_polygon(x: f32, y: f32, polygon: &[(f32, f32)]) -> bool {
512    let mut inside = false;
513    let n = polygon.len();
514
515    for i in 0..n {
516        let (x1, y1) = polygon[i];
517        let (x2, y2) = polygon[(i + 1) % n];
518
519        if ((y1 > y) != (y2 > y)) && (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1) {
520            inside = !inside;
521        }
522    }
523
524    inside
525}
526
527/// The scope of a gate - determines which files it applies to.
528///
529/// Gates can be:
530/// - **Global**: Applies to all files
531/// - **FileSpecific**: Applies only to a single file (identified by GUID)
532/// - **FileGroup**: Applies to a specific set of files
533///
534/// This allows gates to be shared across multiple files or restricted to
535/// specific datasets.
536///
537/// # Example
538///
539/// ```rust
540/// use flow_gates::GateMode;
541///
542/// // Global gate (applies to all files)
543/// let global = GateMode::Global;
544/// assert!(global.applies_to("any-file-guid"));
545///
546/// // File-specific gate
547/// let specific = GateMode::FileSpecific { guid: "file-123".into() };
548/// assert!(specific.applies_to("file-123"));
549/// assert!(!specific.applies_to("file-456"));
550///
551/// // File group gate
552/// let group = GateMode::FileGroup { guids: vec!["file-1".into(), "file-2".into()] };
553/// assert!(group.applies_to("file-1"));
554/// assert!(group.applies_to("file-2"));
555/// assert!(!group.applies_to("file-3"));
556/// ```
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
558#[serde(tag = "name")]
559pub enum GateMode {
560    /// Gate applies to all files
561    Global,
562    /// Gate applies only to a specific file
563    FileSpecific {
564        /// File GUID
565        #[serde(with = "arc_str_serde")]
566        guid: Arc<str>,
567    },
568    /// Gate applies to a group of files
569    FileGroup {
570        /// List of file GUIDs
571        #[serde(with = "arc_str_vec")]
572        guids: Vec<Arc<str>>,
573    },
574}
575
576impl GateMode {
577    /// Check if this gate mode applies to the given file GUID.
578    ///
579    /// Returns `true` if the gate should be applied to the specified file.
580    pub fn applies_to(&self, file_guid: &str) -> bool {
581        match self {
582            GateMode::Global => true,
583            GateMode::FileSpecific { guid } => guid.as_ref() == file_guid,
584            GateMode::FileGroup { guids } => guids.iter().any(|g| g.as_ref() == file_guid),
585        }
586    }
587}
588
589/// Label position stored as offset from the first node in raw data coordinates
590/// This allows labels to move with gates when they are edited
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
592pub struct LabelPosition {
593    /// Offset in raw data coordinates from the first node
594    pub offset_x: f32,
595    pub offset_y: f32,
596}
597
598/// A gate represents a region of interest in flow cytometry data.
599///
600/// Gates define 2D regions in parameter space that can be used to filter
601/// and analyze cytometry events. Each gate has:
602///
603/// - A unique identifier
604/// - A human-readable name
605/// - A geometric shape (polygon, rectangle, or ellipse)
606/// - Two parameters (channels) it operates on
607/// - A scope (global, file-specific, or file group)
608///
609/// # Example
610///
611/// ```rust
612/// use flow_gates::{Gate, GateGeometry, GateNode, geometry::*};
613///
614/// // Create a polygon gate
615/// let coords = vec![
616///     (100.0, 200.0),
617///     (300.0, 200.0),
618///     (300.0, 400.0),
619///     (100.0, 400.0),
620/// ];
621/// let geometry = create_polygon_geometry(coords, "FSC-A", "SSC-A")?;
622///
623/// let gate = Gate::new(
624///     "lymphocytes",
625///     "Lymphocytes",
626///     geometry,
627///     "FSC-A",
628///     "SSC-A",
629/// );
630///
631/// // Get parameter names
632/// assert_eq!(gate.x_parameter_channel_name(), "FSC-A");
633/// assert_eq!(gate.y_parameter_channel_name(), "SSC-A");
634/// ```
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct Gate {
637    #[serde(with = "arc_str_serde")]
638    pub id: Arc<str>,
639    pub name: String,
640    pub geometry: GateGeometry,
641    pub mode: GateMode,
642    /// The parameters (channels) this gate operates on (x_channel, y_channel)
643    #[serde(with = "arc_str_pair")]
644    pub parameters: (Arc<str>, Arc<str>),
645    /// Optional label position as offset from first node in raw data coordinates
646    pub label_position: Option<LabelPosition>,
647}
648
649impl Gate {
650    /// Create a new gate with the specified properties.
651    ///
652    /// The gate is created with `GateMode::Global` by default. Set the `mode` field
653    /// to make it file-specific or part of a file group.
654    ///
655    /// # Arguments
656    ///
657    /// * `id` - Unique identifier for the gate
658    /// * `name` - Human-readable name for the gate
659    /// * `geometry` - The geometric shape of the gate
660    /// * `x_param` - Channel name for the x-axis parameter
661    /// * `y_param` - Channel name for the y-axis parameter
662    pub fn new(
663        id: impl Into<Arc<str>>,
664        name: impl Into<String>,
665        geometry: GateGeometry,
666        x_param: impl Into<Arc<str>>,
667        y_param: impl Into<Arc<str>>,
668    ) -> Self {
669        let x_param = x_param.into();
670        let y_param = y_param.into();
671
672        Self {
673            id: id.into(),
674            name: name.into(),
675            geometry,
676            mode: GateMode::Global, // Default to global
677            parameters: (x_param, y_param),
678            label_position: None,
679        }
680    }
681
682    /// Get the x parameter (channel name)
683    pub fn x_parameter_channel_name(&self) -> &str {
684        self.parameters.0.as_ref()
685    }
686
687    /// Get the y parameter (channel name)
688    pub fn y_parameter_channel_name(&self) -> &str {
689        self.parameters.1.as_ref()
690    }
691}
692
693// Custom serde helpers for Arc<str> types
694mod arc_str_serde {
695    use serde::{Deserialize, Deserializer, Serialize, Serializer};
696    use std::sync::Arc;
697
698    pub fn serialize<S>(arc: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
699    where
700        S: Serializer,
701    {
702        arc.as_ref().serialize(serializer)
703    }
704
705    pub fn deserialize<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
706    where
707        D: Deserializer<'de>,
708    {
709        let s = String::deserialize(deserializer)?;
710        Ok(Arc::from(s.as_str()))
711    }
712}
713
714mod arc_str_vec {
715    use serde::{Deserialize, Deserializer, Serialize, Serializer};
716    use std::sync::Arc;
717
718    pub fn serialize<S>(vec: &Vec<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
719    where
720        S: Serializer,
721    {
722        let strings: Vec<&str> = vec.iter().map(|arc| arc.as_ref()).collect();
723        strings.serialize(serializer)
724    }
725
726    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Arc<str>>, D::Error>
727    where
728        D: Deserializer<'de>,
729    {
730        let vec = Vec::<String>::deserialize(deserializer)?;
731        Ok(vec.into_iter().map(|s| Arc::from(s.as_str())).collect())
732    }
733}
734
735mod arc_str_pair {
736    use serde::{Deserialize, Deserializer, Serialize, Serializer};
737    use std::sync::Arc;
738
739    pub fn serialize<S>(pair: &(Arc<str>, Arc<str>), serializer: S) -> Result<S::Ok, S::Error>
740    where
741        S: Serializer,
742    {
743        (pair.0.as_ref(), pair.1.as_ref()).serialize(serializer)
744    }
745
746    pub fn deserialize<'de, D>(deserializer: D) -> Result<(Arc<str>, Arc<str>), D::Error>
747    where
748        D: Deserializer<'de>,
749    {
750        let (s1, s2) = <(String, String)>::deserialize(deserializer)?;
751        Ok((Arc::from(s1.as_str()), Arc::from(s2.as_str())))
752    }
753}
754
755mod arc_str_hashmap {
756    use serde::{Deserialize, Deserializer, Serialize, Serializer};
757    use std::collections::HashMap;
758    use std::sync::Arc;
759
760    pub fn serialize<S>(map: &HashMap<Arc<str>, f32>, serializer: S) -> Result<S::Ok, S::Error>
761    where
762        S: Serializer,
763    {
764        let string_map: HashMap<&str, f32> = map.iter().map(|(k, v)| (k.as_ref(), *v)).collect();
765        string_map.serialize(serializer)
766    }
767
768    pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<Arc<str>, f32>, D::Error>
769    where
770        D: Deserializer<'de>,
771    {
772        let map = HashMap::<String, f32>::deserialize(deserializer)?;
773        Ok(map
774            .into_iter()
775            .map(|(k, v)| (Arc::from(k.as_str()), v))
776            .collect())
777    }
778}