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/// Boolean operation for combining gates
67///
68/// Boolean gates combine multiple gates using logical operations:
69/// - **And**: Events must pass all operand gates
70/// - **Or**: Events must pass at least one operand gate
71/// - **Not**: Events must NOT pass the operand gate (complement)
72///
73/// # Example
74///
75/// ```rust
76/// use flow_gates::BooleanOperation;
77///
78/// let and_op = BooleanOperation::And;
79/// let or_op = BooleanOperation::Or;
80/// let not_op = BooleanOperation::Not;
81/// ```
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(rename_all = "lowercase")]
84pub enum BooleanOperation {
85 /// AND operation - events must pass all operand gates
86 And,
87 /// OR operation - events must pass at least one operand gate
88 Or,
89 /// NOT operation - events must NOT pass the operand gate (single operand)
90 Not,
91}
92
93impl BooleanOperation {
94 /// Get the expected number of operands for this operation
95 ///
96 /// # Returns
97 /// - `And`: `None` (any number >= 2)
98 /// - `Or`: `None` (any number >= 2)
99 /// - `Not`: `Some(1)` (exactly one operand)
100 pub fn expected_operand_count(&self) -> Option<usize> {
101 match self {
102 BooleanOperation::And | BooleanOperation::Or => None, // At least 2
103 BooleanOperation::Not => Some(1),
104 }
105 }
106
107 /// Get a string representation of the operation
108 pub fn as_str(&self) -> &'static str {
109 match self {
110 BooleanOperation::And => "and",
111 BooleanOperation::Or => "or",
112 BooleanOperation::Not => "not",
113 }
114 }
115}
116
117/// The geometry of a gate, defining its shape in 2D parameter space.
118///
119/// Gates can be one of four geometric types:
120/// - **Polygon**: A closed or open polygonal region defined by vertices
121/// - **Rectangle**: An axis-aligned rectangular region
122/// - **Ellipse**: An elliptical region with optional rotation
123/// - **Boolean**: A combination of other gates using boolean operations (AND, OR, NOT)
124///
125/// All geometries operate in raw data coordinate space and are parameterized
126/// by two channel names (x and y parameters).
127///
128/// # Example
129///
130/// ```rust
131/// use flow_gates::{GateGeometry, GateNode};
132///
133/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
134/// // Create a rectangle gate
135/// let min = GateNode::new("min")
136/// .with_coordinate("FSC-A", 100.0)
137/// .with_coordinate("SSC-A", 200.0);
138/// let max = GateNode::new("max")
139/// .with_coordinate("FSC-A", 500.0)
140/// .with_coordinate("SSC-A", 600.0);
141///
142/// let geometry = GateGeometry::Rectangle { min, max };
143///
144/// // Check if a point is inside
145/// let inside = geometry.contains_point(300.0, 400.0, "FSC-A", "SSC-A")?;
146/// # Ok(())
147/// # }
148/// ```
149#[derive(Debug, Clone, Serialize, Deserialize)]
150#[serde(tag = "type")]
151pub enum GateGeometry {
152 Polygon {
153 nodes: Vec<GateNode>,
154 closed: bool,
155 },
156 Rectangle {
157 min: GateNode,
158 max: GateNode,
159 },
160 Ellipse {
161 center: GateNode,
162 radius_x: f32,
163 radius_y: f32,
164 angle: f32, // rotation angle in radians
165 },
166 /// Boolean gate combining other gates with logical operations
167 ///
168 /// Boolean gates reference other gates by ID and combine their results
169 /// using AND, OR, or NOT operations. The referenced gates must be resolved
170 /// externally when filtering events.
171 Boolean {
172 /// The boolean operation to apply
173 operation: BooleanOperation,
174 /// IDs of the gates to combine (gate IDs, not the gates themselves)
175 operands: Vec<Arc<str>>,
176 },
177}
178
179impl GateGeometry {
180 /// Calculate the bounding box for this geometry in the specified parameter space
181 pub fn bounding_box(&self, x_param: &str, y_param: &str) -> Option<(f32, f32, f32, f32)> {
182 match self {
183 GateGeometry::Polygon { nodes, .. } => {
184 let mut min_x = f32::MAX;
185 let mut min_y = f32::MAX;
186 let mut max_x = f32::MIN;
187 let mut max_y = f32::MIN;
188
189 for node in nodes {
190 if let (Some(x), Some(y)) =
191 (node.get_coordinate(x_param), node.get_coordinate(y_param))
192 {
193 min_x = min_x.min(x);
194 min_y = min_y.min(y);
195 max_x = max_x.max(x);
196 max_y = max_y.max(y);
197 }
198 }
199
200 if min_x < max_x && min_y < max_y {
201 Some((min_x, min_y, max_x, max_y))
202 } else {
203 None
204 }
205 }
206 GateGeometry::Rectangle { min, max } => {
207 if let (Some(min_x), Some(min_y), Some(max_x), Some(max_y)) = (
208 min.get_coordinate(x_param),
209 min.get_coordinate(y_param),
210 max.get_coordinate(x_param),
211 max.get_coordinate(y_param),
212 ) {
213 Some((min_x, min_y, max_x, max_y))
214 } else {
215 None
216 }
217 }
218 GateGeometry::Ellipse {
219 center,
220 radius_x,
221 radius_y,
222 angle,
223 } => {
224 if let (Some(cx), Some(cy)) = (
225 center.get_coordinate(x_param),
226 center.get_coordinate(y_param),
227 ) {
228 let cos_a = angle.cos();
229 let sin_a = angle.sin();
230
231 // Maximum extents along each axis after rotation
232 let extent_x = ((radius_x * cos_a).powi(2) + (radius_y * sin_a).powi(2)).sqrt();
233 let extent_y = ((radius_x * sin_a).powi(2) + (radius_y * cos_a).powi(2)).sqrt();
234
235 Some((cx - extent_x, cy - extent_y, cx + extent_x, cy + extent_y))
236 } else {
237 None
238 }
239 }
240 GateGeometry::Boolean { .. } => {
241 // Boolean gates don't have a direct bounding box - would need to resolve operands
242 // For now, return None to indicate it can't be calculated directly
243 None
244 }
245 }
246 }
247
248 /// Calculate the center point in raw data coordinates
249 pub fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
250 match self {
251 GateGeometry::Polygon { nodes, .. } => {
252 let (sum_x, sum_y, count) = nodes
253 .iter()
254 .filter_map(|node| {
255 let x = node.get_coordinate(x_param)?;
256 let y = node.get_coordinate(y_param)?;
257 Some((x, y))
258 })
259 .fold((0.0, 0.0, 0), |(sx, sy, c), (x, y)| (sx + x, sy + y, c + 1));
260
261 if count > 0 {
262 Ok((sum_x / count as f32, sum_y / count as f32))
263 } else {
264 Err(GateError::invalid_geometry(
265 "Polygon has no valid coordinates",
266 ))
267 }
268 }
269 GateGeometry::Rectangle { min, max } => {
270 let min_x = min
271 .get_coordinate(x_param)
272 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
273 let min_y = min
274 .get_coordinate(y_param)
275 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
276 let max_x = max
277 .get_coordinate(x_param)
278 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
279 let max_y = max
280 .get_coordinate(y_param)
281 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
282
283 Ok(((min_x + max_x) / 2.0, (min_y + max_y) / 2.0))
284 }
285 GateGeometry::Ellipse { center, .. } => {
286 let cx = center
287 .get_coordinate(x_param)
288 .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
289 let cy = center
290 .get_coordinate(y_param)
291 .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
292
293 Ok((cx, cy))
294 }
295 GateGeometry::Boolean { .. } => {
296 // Boolean gates don't have a direct center - would need to resolve operands
297 Err(GateError::invalid_geometry(
298 "Boolean gates do not have a direct center point",
299 ))
300 }
301 }
302 }
303
304 /// Check if a point (in raw coordinates) is inside the gate
305 pub fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
306 match self {
307 GateGeometry::Polygon { nodes, closed } => {
308 if !closed {
309 return Ok(false);
310 }
311
312 // Extract coordinates
313 let coords: Vec<(f32, f32)> = nodes
314 .iter()
315 .filter_map(|node| {
316 Some((node.get_coordinate(x_param)?, node.get_coordinate(y_param)?))
317 })
318 .collect();
319
320 if coords.len() < 3 {
321 return Ok(false);
322 }
323
324 // Ray casting algorithm
325 Ok(point_in_polygon(x, y, &coords))
326 }
327 GateGeometry::Rectangle { min, max } => {
328 let min_x = min
329 .get_coordinate(x_param)
330 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
331 let min_y = min
332 .get_coordinate(y_param)
333 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
334 let max_x = max
335 .get_coordinate(x_param)
336 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
337 let max_y = max
338 .get_coordinate(y_param)
339 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
340
341 Ok(x >= min_x && x <= max_x && y >= min_y && y <= max_y)
342 }
343 GateGeometry::Ellipse {
344 center,
345 radius_x,
346 radius_y,
347 angle,
348 } => {
349 let cx = center
350 .get_coordinate(x_param)
351 .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
352 let cy = center
353 .get_coordinate(y_param)
354 .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
355
356 // Rotate point around center by -angle
357 let cos_a = angle.cos();
358 let sin_a = angle.sin();
359 let dx = x - cx;
360 let dy = y - cy;
361 let rotated_x = dx * cos_a + dy * sin_a;
362 let rotated_y = -dx * sin_a + dy * cos_a;
363
364 // Check if point is inside axis-aligned ellipse
365 let normalized = (rotated_x / radius_x).powi(2) + (rotated_y / radius_y).powi(2);
366 Ok(normalized <= 1.0)
367 }
368 GateGeometry::Boolean { .. } => {
369 // Boolean gates require resolving referenced gates - can't check containment directly
370 // This should be handled by the filtering functions that resolve gate references
371 Err(GateError::invalid_geometry(
372 "Boolean gates require gate resolution to check containment",
373 ))
374 }
375 }
376 }
377
378 /// Batch check if points (in raw coordinates) are inside the gate
379 ///
380 /// Uses optimized CPU-based batch filtering with Rayon parallelization.
381 pub fn contains_points_batch(
382 &self,
383 points: &[(f32, f32)],
384 x_param: &str,
385 y_param: &str,
386 ) -> Result<Vec<bool>> {
387 match self {
388 GateGeometry::Polygon { nodes, closed } => {
389 if !closed {
390 return Ok(vec![false; points.len()]);
391 }
392
393 // Extract coordinates
394 let coords: Vec<(f32, f32)> = nodes
395 .iter()
396 .filter_map(|node| {
397 Some((node.get_coordinate(x_param)?, node.get_coordinate(y_param)?))
398 })
399 .collect();
400
401 if coords.len() < 3 {
402 return Ok(vec![false; points.len()]);
403 }
404
405 crate::batch_filtering::filter_by_polygon_batch(points, &coords)
406 }
407 GateGeometry::Rectangle { min, max } => {
408 let min_x = min
409 .get_coordinate(x_param)
410 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle min"))?;
411 let min_y = min
412 .get_coordinate(y_param)
413 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle min"))?;
414 let max_x = max
415 .get_coordinate(x_param)
416 .ok_or_else(|| GateError::missing_parameter(x_param, "rectangle max"))?;
417 let max_y = max
418 .get_coordinate(y_param)
419 .ok_or_else(|| GateError::missing_parameter(y_param, "rectangle max"))?;
420
421 crate::batch_filtering::filter_by_rectangle_batch(
422 points,
423 (min_x, min_y, max_x, max_y),
424 )
425 }
426 GateGeometry::Ellipse {
427 center,
428 radius_x,
429 radius_y,
430 angle,
431 } => {
432 let cx = center
433 .get_coordinate(x_param)
434 .ok_or_else(|| GateError::missing_parameter(x_param, "ellipse center"))?;
435 let cy = center
436 .get_coordinate(y_param)
437 .ok_or_else(|| GateError::missing_parameter(y_param, "ellipse center"))?;
438
439 crate::batch_filtering::filter_by_ellipse_batch(
440 points,
441 (cx, cy),
442 *radius_x,
443 *radius_y,
444 *angle,
445 )
446 }
447 GateGeometry::Boolean { .. } => {
448 // Boolean gates require resolving referenced gates - can't check containment directly
449 Err(GateError::invalid_geometry(
450 "Boolean gates require gate resolution to check containment",
451 ))
452 }
453 }
454 }
455
456 /// Check if the gate has valid geometry and coordinates
457 pub fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
458 match self {
459 GateGeometry::Polygon { nodes, .. } => {
460 // Need at least 3 nodes for a valid polygon
461 if nodes.len() < 3 {
462 return Ok(false);
463 }
464
465 // All nodes must have valid coordinates
466 let valid_coords = nodes.iter().all(|node| {
467 node.get_coordinate(x_param).is_some() && node.get_coordinate(y_param).is_some()
468 });
469
470 Ok(valid_coords)
471 }
472 GateGeometry::Rectangle { min, max } => {
473 // Must have valid coordinates
474 if min.get_coordinate(x_param).is_none()
475 || min.get_coordinate(y_param).is_none()
476 || max.get_coordinate(x_param).is_none()
477 || max.get_coordinate(y_param).is_none()
478 {
479 return Ok(false);
480 }
481
482 // Min must be less than max
483 let min_x = min.get_coordinate(x_param).unwrap();
484 let min_y = min.get_coordinate(y_param).unwrap();
485 let max_x = max.get_coordinate(x_param).unwrap();
486 let max_y = max.get_coordinate(y_param).unwrap();
487
488 Ok(min_x < max_x && min_y < max_y)
489 }
490 GateGeometry::Ellipse {
491 center,
492 radius_x,
493 radius_y,
494 ..
495 } => {
496 // Must have valid center coordinates
497 if center.get_coordinate(x_param).is_none()
498 || center.get_coordinate(y_param).is_none()
499 {
500 return Ok(false);
501 }
502
503 // Radii must be positive
504 Ok(radius_x > &0.0 && radius_y > &0.0)
505 }
506 GateGeometry::Boolean {
507 operation,
508 operands,
509 } => {
510 // Validate operand count
511 match operation {
512 BooleanOperation::And | BooleanOperation::Or => {
513 if operands.len() < 2 {
514 return Ok(false);
515 }
516 }
517 BooleanOperation::Not => {
518 if operands.len() != 1 {
519 return Ok(false);
520 }
521 }
522 }
523 Ok(true)
524 }
525 }
526 }
527
528 /// Get a descriptive name for this gate type
529 pub fn gate_type_name(&self) -> &'static str {
530 match self {
531 GateGeometry::Polygon { .. } => "Polygon",
532 GateGeometry::Rectangle { .. } => "Rectangle",
533 GateGeometry::Ellipse { .. } => "Ellipse",
534 GateGeometry::Boolean { .. } => "Boolean",
535 }
536 }
537}
538
539// Implement geometry traits for GateGeometry enum by delegating to struct implementations
540use crate::traits::*;
541
542impl GateCenter for GateGeometry {
543 fn calculate_center(&self, x_param: &str, y_param: &str) -> Result<(f32, f32)> {
544 match self {
545 GateGeometry::Polygon { nodes, closed } => {
546 let poly = crate::polygon::PolygonGateGeometry {
547 nodes: nodes.clone(),
548 closed: *closed,
549 };
550 poly.calculate_center(x_param, y_param)
551 }
552 GateGeometry::Rectangle { min, max } => {
553 let rect = crate::rectangle::RectangleGateGeometry {
554 min: min.clone(),
555 max: max.clone(),
556 };
557 rect.calculate_center(x_param, y_param)
558 }
559 GateGeometry::Ellipse {
560 center,
561 radius_x,
562 radius_y,
563 angle,
564 } => {
565 let ellipse = crate::ellipse::EllipseGateGeometry {
566 center: center.clone(),
567 radius_x: *radius_x,
568 radius_y: *radius_y,
569 angle: *angle,
570 };
571 ellipse.calculate_center(x_param, y_param)
572 }
573 GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
574 "Boolean gates do not have a direct center point",
575 )),
576 }
577 }
578}
579
580impl GateContainment for GateGeometry {
581 fn contains_point(&self, x: f32, y: f32, x_param: &str, y_param: &str) -> Result<bool> {
582 match self {
583 GateGeometry::Polygon { nodes, closed } => {
584 let poly = crate::polygon::PolygonGateGeometry {
585 nodes: nodes.clone(),
586 closed: *closed,
587 };
588 poly.contains_point(x, y, x_param, y_param)
589 }
590 GateGeometry::Rectangle { min, max } => {
591 let rect = crate::rectangle::RectangleGateGeometry {
592 min: min.clone(),
593 max: max.clone(),
594 };
595 rect.contains_point(x, y, x_param, y_param)
596 }
597 GateGeometry::Ellipse {
598 center,
599 radius_x,
600 radius_y,
601 angle,
602 } => {
603 let ellipse = crate::ellipse::EllipseGateGeometry {
604 center: center.clone(),
605 radius_x: *radius_x,
606 radius_y: *radius_y,
607 angle: *angle,
608 };
609 ellipse.contains_point(x, y, x_param, y_param)
610 }
611 GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
612 "Boolean gates require gate resolution to check containment",
613 )),
614 }
615 }
616}
617
618impl GateBounds for GateGeometry {
619 fn bounding_box(&self, x_param: &str, y_param: &str) -> Result<(f32, f32, f32, f32)> {
620 match self {
621 GateGeometry::Polygon { nodes, closed } => {
622 let poly = crate::polygon::PolygonGateGeometry {
623 nodes: nodes.clone(),
624 closed: *closed,
625 };
626 poly.bounding_box(x_param, y_param)
627 }
628 GateGeometry::Rectangle { min, max } => {
629 let rect = crate::rectangle::RectangleGateGeometry {
630 min: min.clone(),
631 max: max.clone(),
632 };
633 rect.bounding_box(x_param, y_param)
634 }
635 GateGeometry::Ellipse {
636 center,
637 radius_x,
638 radius_y,
639 angle,
640 } => {
641 let ellipse = crate::ellipse::EllipseGateGeometry {
642 center: center.clone(),
643 radius_x: *radius_x,
644 radius_y: *radius_y,
645 angle: *angle,
646 };
647 ellipse.bounding_box(x_param, y_param)
648 }
649 GateGeometry::Boolean { .. } => Err(GateError::invalid_geometry(
650 "Boolean gates do not have a direct bounding box",
651 )),
652 }
653 }
654}
655
656impl GateValidation for GateGeometry {
657 fn is_valid(&self, x_param: &str, y_param: &str) -> Result<bool> {
658 match self {
659 GateGeometry::Polygon { nodes, closed } => {
660 let poly = crate::polygon::PolygonGateGeometry {
661 nodes: nodes.clone(),
662 closed: *closed,
663 };
664 poly.is_valid(x_param, y_param)
665 }
666 GateGeometry::Rectangle { min, max } => {
667 let rect = crate::rectangle::RectangleGateGeometry {
668 min: min.clone(),
669 max: max.clone(),
670 };
671 rect.is_valid(x_param, y_param)
672 }
673 GateGeometry::Ellipse {
674 center,
675 radius_x,
676 radius_y,
677 angle,
678 } => {
679 let ellipse = crate::ellipse::EllipseGateGeometry {
680 center: center.clone(),
681 radius_x: *radius_x,
682 radius_y: *radius_y,
683 angle: *angle,
684 };
685 ellipse.is_valid(x_param, y_param)
686 }
687 GateGeometry::Boolean {
688 operation,
689 operands,
690 } => {
691 match operation {
692 BooleanOperation::And | BooleanOperation::Or => {
693 if operands.len() < 2 {
694 return Err(GateError::invalid_boolean_operation(
695 operation.as_str(),
696 operands.len(),
697 2,
698 ));
699 }
700 }
701 BooleanOperation::Not => {
702 if operands.len() != 1 {
703 return Err(GateError::invalid_boolean_operation(
704 operation.as_str(),
705 operands.len(),
706 1,
707 ));
708 }
709 }
710 }
711 Ok(true)
712 }
713 }
714 }
715}
716
717impl GateGeometryOps for GateGeometry {
718 fn gate_type_name(&self) -> &'static str {
719 match self {
720 GateGeometry::Polygon { .. } => "Polygon",
721 GateGeometry::Rectangle { .. } => "Rectangle",
722 GateGeometry::Ellipse { .. } => "Ellipse",
723 GateGeometry::Boolean { .. } => "Boolean",
724 }
725 }
726}
727
728/// Point-in-polygon using ray casting algorithm
729fn point_in_polygon(x: f32, y: f32, polygon: &[(f32, f32)]) -> bool {
730 let mut inside = false;
731 let n = polygon.len();
732
733 for i in 0..n {
734 let (x1, y1) = polygon[i];
735 let (x2, y2) = polygon[(i + 1) % n];
736
737 if ((y1 > y) != (y2 > y)) && (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1) {
738 inside = !inside;
739 }
740 }
741
742 inside
743}
744
745/// The scope of a gate - determines which files it applies to.
746///
747/// Gates can be:
748/// - **Global**: Applies to all files
749/// - **FileSpecific**: Applies only to a single file (identified by GUID)
750/// - **FileGroup**: Applies to a specific set of files
751///
752/// This allows gates to be shared across multiple files or restricted to
753/// specific datasets.
754///
755/// # Example
756///
757/// ```rust
758/// use flow_gates::GateMode;
759///
760/// // Global gate (applies to all files)
761/// let global = GateMode::Global;
762/// assert!(global.applies_to("any-file-guid"));
763///
764/// // File-specific gate
765/// let specific = GateMode::FileSpecific { guid: "file-123".into() };
766/// assert!(specific.applies_to("file-123"));
767/// assert!(!specific.applies_to("file-456"));
768///
769/// // File group gate
770/// let group = GateMode::FileGroup { guids: vec!["file-1".into(), "file-2".into()] };
771/// assert!(group.applies_to("file-1"));
772/// assert!(group.applies_to("file-2"));
773/// assert!(!group.applies_to("file-3"));
774/// ```
775#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
776#[serde(tag = "name")]
777pub enum GateMode {
778 /// Gate applies to all files
779 Global,
780 /// Gate applies only to a specific file
781 FileSpecific {
782 /// File GUID
783 #[serde(with = "arc_str_serde")]
784 guid: Arc<str>,
785 },
786 /// Gate applies to a group of files
787 FileGroup {
788 /// List of file GUIDs
789 #[serde(with = "arc_str_vec")]
790 guids: Vec<Arc<str>>,
791 },
792}
793
794impl GateMode {
795 /// Check if this gate mode applies to the given file GUID.
796 ///
797 /// Returns `true` if the gate should be applied to the specified file.
798 pub fn applies_to(&self, file_guid: &str) -> bool {
799 match self {
800 GateMode::Global => true,
801 GateMode::FileSpecific { guid } => guid.as_ref() == file_guid,
802 GateMode::FileGroup { guids } => guids.iter().any(|g| g.as_ref() == file_guid),
803 }
804 }
805}
806
807/// Label position stored as offset from the first node in raw data coordinates
808/// This allows labels to move with gates when they are edited
809#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
810pub struct LabelPosition {
811 /// Offset in raw data coordinates from the first node
812 pub offset_x: f32,
813 pub offset_y: f32,
814}
815
816/// A gate represents a region of interest in flow cytometry data.
817///
818/// Gates define 2D regions in parameter space that can be used to filter
819/// and analyze cytometry events. Each gate has:
820///
821/// - A unique identifier
822/// - A human-readable name
823/// - A geometric shape (polygon, rectangle, or ellipse)
824/// - Two parameters (channels) it operates on
825/// - A scope (global, file-specific, or file group)
826///
827/// # Example
828///
829/// ```rust
830/// use flow_gates::{Gate, GateGeometry, GateNode, geometry::*};
831///
832/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
833/// // Create a polygon gate
834/// let coords = vec![
835/// (100.0, 200.0),
836/// (300.0, 200.0),
837/// (300.0, 400.0),
838/// (100.0, 400.0),
839/// ];
840/// let geometry = create_polygon_geometry(coords, "FSC-A", "SSC-A")?;
841///
842/// let gate = Gate::new(
843/// "lymphocytes",
844/// "Lymphocytes",
845/// geometry,
846/// "FSC-A",
847/// "SSC-A",
848/// );
849///
850/// // Get parameter names
851/// assert_eq!(gate.x_parameter_channel_name(), "FSC-A");
852/// assert_eq!(gate.y_parameter_channel_name(), "SSC-A");
853/// # Ok(())
854/// # }
855/// ```
856#[derive(Debug, Clone, Serialize, Deserialize)]
857pub struct Gate {
858 #[serde(with = "arc_str_serde")]
859 pub id: Arc<str>,
860 pub name: String,
861 pub geometry: GateGeometry,
862 pub mode: GateMode,
863 /// The parameters (channels) this gate operates on (x_channel, y_channel)
864 #[serde(with = "arc_str_pair")]
865 pub parameters: (Arc<str>, Arc<str>),
866 /// Optional label position as offset from first node in raw data coordinates
867 pub label_position: Option<LabelPosition>,
868}
869
870impl Gate {
871 /// Create a new gate with the specified properties.
872 ///
873 /// The gate is created with `GateMode::Global` by default. Set the `mode` field
874 /// to make it file-specific or part of a file group.
875 ///
876 /// # Arguments
877 ///
878 /// * `id` - Unique identifier for the gate
879 /// * `name` - Human-readable name for the gate
880 /// * `geometry` - The geometric shape of the gate
881 /// * `x_param` - Channel name for the x-axis parameter
882 /// * `y_param` - Channel name for the y-axis parameter
883 pub fn new(
884 id: impl Into<Arc<str>>,
885 name: impl Into<String>,
886 geometry: GateGeometry,
887 x_param: impl Into<Arc<str>>,
888 y_param: impl Into<Arc<str>>,
889 ) -> Self {
890 let x_param = x_param.into();
891 let y_param = y_param.into();
892
893 Self {
894 id: id.into(),
895 name: name.into(),
896 geometry,
897 mode: GateMode::Global, // Default to global
898 parameters: (x_param, y_param),
899 label_position: None,
900 }
901 }
902
903 /// Get the x parameter (channel name)
904 pub fn x_parameter_channel_name(&self) -> &str {
905 self.parameters.0.as_ref()
906 }
907
908 /// Get the y parameter (channel name)
909 pub fn y_parameter_channel_name(&self) -> &str {
910 self.parameters.1.as_ref()
911 }
912
913 /// Check if a point (in gate's parameter space) is inside the gate
914 ///
915 /// This is a convenience method that uses the gate's own parameters,
916 /// so you don't need to specify them explicitly.
917 ///
918 /// # Arguments
919 /// * `x` - X coordinate in raw data space
920 /// * `y` - Y coordinate in raw data space
921 ///
922 /// # Returns
923 /// `true` if the point is inside the gate, `false` otherwise
924 ///
925 /// # Errors
926 /// Returns an error if the gate geometry is invalid or parameters are missing
927 ///
928 /// # Example
929 /// ```rust
930 /// use flow_gates::Gate;
931 ///
932 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
933 /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
934 /// assert!(gate.contains_point(300.0, 400.0)?);
935 /// assert!(!gate.contains_point(50.0, 50.0)?);
936 /// # Ok(())
937 /// # }
938 /// ```
939 pub fn contains_point(&self, x: f32, y: f32) -> Result<bool> {
940 self.geometry.contains_point(
941 x,
942 y,
943 self.x_parameter_channel_name(),
944 self.y_parameter_channel_name(),
945 )
946 }
947
948 /// Get the bounding box in gate's parameter space
949 ///
950 /// This is a convenience method that uses the gate's own parameters.
951 ///
952 /// # Returns
953 /// `Some((min_x, min_y, max_x, max_y))` if the bounding box can be calculated,
954 /// `None` otherwise
955 ///
956 /// # Example
957 /// ```rust
958 /// use flow_gates::Gate;
959 ///
960 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
961 /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
962 /// let bbox = gate.bounding_box();
963 /// assert_eq!(bbox, Some((100.0, 200.0, 500.0, 600.0)));
964 /// # Ok(())
965 /// # }
966 /// ```
967 pub fn bounding_box(&self) -> Option<(f32, f32, f32, f32)> {
968 self.geometry.bounding_box(
969 self.x_parameter_channel_name(),
970 self.y_parameter_channel_name(),
971 )
972 }
973
974 /// Get x and y coordinates from a node for this gate's parameters
975 ///
976 /// This is a convenience method that extracts coordinates for the gate's
977 /// x and y parameters from a node.
978 ///
979 /// # Arguments
980 /// * `node` - The gate node to extract coordinates from
981 ///
982 /// # Returns
983 /// `Some((x, y))` if both coordinates are present, `None` otherwise
984 ///
985 /// # Example
986 /// ```rust
987 /// use flow_gates::{Gate, GateNode};
988 ///
989 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
990 /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
991 /// let node = GateNode::new("node1")
992 /// .with_coordinate("FSC-A", 300.0)
993 /// .with_coordinate("SSC-A", 400.0);
994 /// let coords = gate.get_node_coords(&node);
995 /// assert_eq!(coords, Some((300.0, 400.0)));
996 /// # Ok(())
997 /// # }
998 /// ```
999 pub fn get_node_coords(&self, node: &GateNode) -> Option<(f32, f32)> {
1000 Some((
1001 node.get_coordinate(self.x_parameter_channel_name())?,
1002 node.get_coordinate(self.y_parameter_channel_name())?,
1003 ))
1004 }
1005
1006 /// Clone this gate with a new ID
1007 ///
1008 /// Creates a new gate with the same properties but a different ID.
1009 /// Useful for duplicating gates or creating variations.
1010 ///
1011 /// # Arguments
1012 /// * `new_id` - The new ID for the cloned gate
1013 ///
1014 /// # Returns
1015 /// A new `Gate` instance with the specified ID
1016 ///
1017 /// # Example
1018 /// ```rust
1019 /// use flow_gates::Gate;
1020 ///
1021 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1022 /// let gate1 = Gate::rectangle("gate1", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
1023 /// let gate2 = gate1.clone_with_id("gate2");
1024 /// assert_eq!(gate2.id.as_ref(), "gate2");
1025 /// assert_eq!(gate1.name, gate2.name);
1026 /// # Ok(())
1027 /// # }
1028 /// ```
1029 pub fn clone_with_id(&self, new_id: impl Into<Arc<str>>) -> Self {
1030 Self {
1031 id: new_id.into(),
1032 name: self.name.clone(),
1033 geometry: self.geometry.clone(),
1034 mode: self.mode.clone(),
1035 parameters: self.parameters.clone(),
1036 label_position: self.label_position.clone(),
1037 }
1038 }
1039
1040 /// Create a polygon gate from coordinates
1041 ///
1042 /// Convenience constructor for creating polygon gates directly.
1043 ///
1044 /// # Arguments
1045 /// * `id` - Unique identifier for the gate
1046 /// * `name` - Human-readable name for the gate
1047 /// * `coords` - Vector of (x, y) coordinate tuples
1048 /// * `x_param` - Channel name for the x-axis parameter
1049 /// * `y_param` - Channel name for the y-axis parameter
1050 ///
1051 /// # Returns
1052 /// A new `Gate` with polygon geometry
1053 ///
1054 /// # Errors
1055 /// Returns an error if the coordinates are invalid (less than 3 points, non-finite values)
1056 ///
1057 /// # Example
1058 /// ```rust
1059 /// use flow_gates::Gate;
1060 ///
1061 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1062 /// let coords = vec![
1063 /// (100.0, 200.0),
1064 /// (300.0, 200.0),
1065 /// (300.0, 400.0),
1066 /// (100.0, 400.0),
1067 /// ];
1068 /// let gate = Gate::polygon("poly", "Polygon", coords, "FSC-A", "SSC-A")?;
1069 /// # Ok(())
1070 /// # }
1071 /// ```
1072 pub fn polygon(
1073 id: impl Into<Arc<str>>,
1074 name: impl Into<String>,
1075 coords: Vec<(f32, f32)>,
1076 x_param: impl Into<Arc<str>>,
1077 y_param: impl Into<Arc<str>>,
1078 ) -> Result<Self> {
1079 use crate::geometry::create_polygon_geometry;
1080 let x_param_arc = x_param.into();
1081 let y_param_arc = y_param.into();
1082 let geometry = create_polygon_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1083 Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1084 }
1085
1086 /// Create a rectangle gate from min and max coordinates
1087 ///
1088 /// Convenience constructor for creating rectangle gates directly.
1089 ///
1090 /// # Arguments
1091 /// * `id` - Unique identifier for the gate
1092 /// * `name` - Human-readable name for the gate
1093 /// * `min` - (x, y) coordinates for the minimum corner
1094 /// * `max` - (x, y) coordinates for the maximum corner
1095 /// * `x_param` - Channel name for the x-axis parameter
1096 /// * `y_param` - Channel name for the y-axis parameter
1097 ///
1098 /// # Returns
1099 /// A new `Gate` with rectangle geometry
1100 ///
1101 /// # Errors
1102 /// Returns an error if the coordinates are invalid (min > max, non-finite values)
1103 ///
1104 /// # Example
1105 /// ```rust
1106 /// use flow_gates::Gate;
1107 ///
1108 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1109 /// let gate = Gate::rectangle("rect", "Rectangle", (100.0, 200.0), (500.0, 600.0), "FSC-A", "SSC-A")?;
1110 /// # Ok(())
1111 /// # }
1112 /// ```
1113 pub fn rectangle(
1114 id: impl Into<Arc<str>>,
1115 name: impl Into<String>,
1116 min: (f32, f32),
1117 max: (f32, f32),
1118 x_param: impl Into<Arc<str>>,
1119 y_param: impl Into<Arc<str>>,
1120 ) -> Result<Self> {
1121 use crate::geometry::create_rectangle_geometry;
1122 let x_param_arc = x_param.into();
1123 let y_param_arc = y_param.into();
1124 let coords = vec![min, max];
1125 let geometry =
1126 create_rectangle_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1127 Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1128 }
1129
1130 /// Create an ellipse gate from center, radii, and angle
1131 ///
1132 /// Convenience constructor for creating ellipse gates directly.
1133 ///
1134 /// # Arguments
1135 /// * `id` - Unique identifier for the gate
1136 /// * `name` - Human-readable name for the gate
1137 /// * `center` - (x, y) coordinates for the center point
1138 /// * `radius_x` - Radius along the x-axis
1139 /// * `radius_y` - Radius along the y-axis
1140 /// * `angle` - Rotation angle in radians
1141 /// * `x_param` - Channel name for the x-axis parameter
1142 /// * `y_param` - Channel name for the y-axis parameter
1143 ///
1144 /// # Returns
1145 /// A new `Gate` with ellipse geometry
1146 ///
1147 /// # Errors
1148 /// Returns an error if the coordinates or radii are invalid (non-finite, negative radii)
1149 ///
1150 /// # Example
1151 /// ```rust
1152 /// use flow_gates::Gate;
1153 ///
1154 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1155 /// let gate = Gate::ellipse("ellipse", "Ellipse", (300.0, 400.0), 100.0, 50.0, 0.0, "FSC-A", "SSC-A")?;
1156 /// # Ok(())
1157 /// # }
1158 /// ```
1159 pub fn ellipse(
1160 id: impl Into<Arc<str>>,
1161 name: impl Into<String>,
1162 center: (f32, f32),
1163 radius_x: f32,
1164 radius_y: f32,
1165 angle: f32,
1166 x_param: impl Into<Arc<str>>,
1167 y_param: impl Into<Arc<str>>,
1168 ) -> Result<Self> {
1169 use crate::geometry::create_ellipse_geometry;
1170 let x_param_arc = x_param.into();
1171 let y_param_arc = y_param.into();
1172 let coords = vec![center];
1173 let geometry = create_ellipse_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1174 // Override radii and angle since create_ellipse_geometry may calculate them differently
1175 let geometry = match geometry {
1176 GateGeometry::Ellipse { center: c, .. } => GateGeometry::Ellipse {
1177 center: c,
1178 radius_x,
1179 radius_y,
1180 angle,
1181 },
1182 _ => geometry,
1183 };
1184 Ok(Self::new(id, name, geometry, x_param_arc, y_param_arc))
1185 }
1186}
1187
1188/// Builder for constructing gates with a fluent API
1189///
1190/// The `GateBuilder` provides a convenient way to construct gates step by step,
1191/// allowing you to set geometry, parameters, mode, and other properties before
1192/// finalizing the gate.
1193///
1194/// # Example
1195///
1196/// ```rust
1197/// use flow_gates::{GateBuilder, GateMode};
1198///
1199/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1200/// let gate = GateBuilder::new("my-gate", "My Gate")
1201/// .polygon(vec![(100.0, 200.0), (300.0, 200.0), (300.0, 400.0)], "FSC-A", "SSC-A")?
1202/// .mode(GateMode::Global)
1203/// .build()?;
1204/// # Ok(())
1205/// # }
1206/// ```
1207#[derive(Debug, Clone)]
1208pub struct GateBuilder {
1209 id: Arc<str>,
1210 name: String,
1211 geometry: Option<GateGeometry>,
1212 x_param: Option<Arc<str>>,
1213 y_param: Option<Arc<str>>,
1214 mode: GateMode,
1215 label_position: Option<LabelPosition>,
1216}
1217
1218impl GateBuilder {
1219 /// Create a new gate builder
1220 ///
1221 /// # Arguments
1222 /// * `id` - Unique identifier for the gate
1223 /// * `name` - Human-readable name for the gate
1224 pub fn new(id: impl Into<Arc<str>>, name: impl Into<String>) -> Self {
1225 Self {
1226 id: id.into(),
1227 name: name.into(),
1228 geometry: None,
1229 x_param: None,
1230 y_param: None,
1231 mode: GateMode::Global,
1232 label_position: None,
1233 }
1234 }
1235
1236 /// Set the geometry to a polygon
1237 ///
1238 /// This also sets the parameters from the geometry creation.
1239 ///
1240 /// # Arguments
1241 /// * `coords` - Vector of (x, y) coordinate tuples
1242 /// * `x_param` - Channel name for the x-axis parameter
1243 /// * `y_param` - Channel name for the y-axis parameter
1244 pub fn polygon(
1245 mut self,
1246 coords: Vec<(f32, f32)>,
1247 x_param: impl Into<Arc<str>>,
1248 y_param: impl Into<Arc<str>>,
1249 ) -> Result<Self> {
1250 use crate::geometry::create_polygon_geometry;
1251 let x_param_arc = x_param.into();
1252 let y_param_arc = y_param.into();
1253 let geometry = create_polygon_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1254 self.geometry = Some(geometry);
1255 self.x_param = Some(x_param_arc);
1256 self.y_param = Some(y_param_arc);
1257 Ok(self)
1258 }
1259
1260 /// Set the geometry to a rectangle
1261 ///
1262 /// This also sets the parameters from the geometry creation.
1263 ///
1264 /// # Arguments
1265 /// * `min` - (x, y) coordinates for the minimum corner
1266 /// * `max` - (x, y) coordinates for the maximum corner
1267 /// * `x_param` - Channel name for the x-axis parameter
1268 /// * `y_param` - Channel name for the y-axis parameter
1269 pub fn rectangle(
1270 mut self,
1271 min: (f32, f32),
1272 max: (f32, f32),
1273 x_param: impl Into<Arc<str>>,
1274 y_param: impl Into<Arc<str>>,
1275 ) -> Result<Self> {
1276 use crate::geometry::create_rectangle_geometry;
1277 let x_param_arc = x_param.into();
1278 let y_param_arc = y_param.into();
1279 let coords = vec![min, max];
1280 let geometry =
1281 create_rectangle_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1282 self.geometry = Some(geometry);
1283 self.x_param = Some(x_param_arc);
1284 self.y_param = Some(y_param_arc);
1285 Ok(self)
1286 }
1287
1288 /// Set the geometry to an ellipse
1289 ///
1290 /// This also sets the parameters from the geometry creation.
1291 ///
1292 /// # Arguments
1293 /// * `center` - (x, y) coordinates for the center point
1294 /// * `radius_x` - Radius along the x-axis
1295 /// * `radius_y` - Radius along the y-axis
1296 /// * `angle` - Rotation angle in radians
1297 /// * `x_param` - Channel name for the x-axis parameter
1298 /// * `y_param` - Channel name for the y-axis parameter
1299 pub fn ellipse(
1300 mut self,
1301 center: (f32, f32),
1302 radius_x: f32,
1303 radius_y: f32,
1304 angle: f32,
1305 x_param: impl Into<Arc<str>>,
1306 y_param: impl Into<Arc<str>>,
1307 ) -> Result<Self> {
1308 use crate::geometry::create_ellipse_geometry;
1309 let x_param_arc = x_param.into();
1310 let y_param_arc = y_param.into();
1311 let coords = vec![center];
1312 let geometry = create_ellipse_geometry(coords, x_param_arc.as_ref(), y_param_arc.as_ref())?;
1313 // Override radii and angle
1314 let geometry = match geometry {
1315 GateGeometry::Ellipse { center: c, .. } => GateGeometry::Ellipse {
1316 center: c,
1317 radius_x,
1318 radius_y,
1319 angle,
1320 },
1321 _ => geometry,
1322 };
1323 self.geometry = Some(geometry);
1324 self.x_param = Some(x_param_arc);
1325 self.y_param = Some(y_param_arc);
1326 Ok(self)
1327 }
1328
1329 /// Set the parameters (channels) this gate operates on
1330 ///
1331 /// # Arguments
1332 /// * `x` - Channel name for the x-axis parameter
1333 /// * `y` - Channel name for the y-axis parameter
1334 pub fn parameters(mut self, x: impl Into<Arc<str>>, y: impl Into<Arc<str>>) -> Self {
1335 self.x_param = Some(x.into());
1336 self.y_param = Some(y.into());
1337 self
1338 }
1339
1340 /// Set the gate mode (scope)
1341 ///
1342 /// # Arguments
1343 /// * `mode` - The gate mode (Global, FileSpecific, or FileGroup)
1344 pub fn mode(mut self, mode: GateMode) -> Self {
1345 self.mode = mode;
1346 self
1347 }
1348
1349 /// Set the label position
1350 ///
1351 /// # Arguments
1352 /// * `position` - The label position as an offset from the first node
1353 pub fn label_position(mut self, position: LabelPosition) -> Self {
1354 self.label_position = Some(position);
1355 self
1356 }
1357
1358 /// Build the gate from the builder
1359 ///
1360 /// # Returns
1361 /// A new `Gate` instance
1362 ///
1363 /// # Errors
1364 /// Returns an error if:
1365 /// - Geometry is not set
1366 /// - Parameters are not set
1367 /// - Builder is in an invalid state
1368 pub fn build(self) -> Result<Gate> {
1369 let geometry = self.geometry.ok_or_else(|| {
1370 GateError::invalid_builder_state("geometry", "Geometry must be set before building")
1371 })?;
1372 let x_param = self.x_param.ok_or_else(|| {
1373 GateError::invalid_builder_state("x_param", "X parameter must be set before building")
1374 })?;
1375 let y_param = self.y_param.ok_or_else(|| {
1376 GateError::invalid_builder_state("y_param", "Y parameter must be set before building")
1377 })?;
1378
1379 Ok(Gate {
1380 id: self.id,
1381 name: self.name,
1382 geometry,
1383 mode: self.mode,
1384 parameters: (x_param, y_param),
1385 label_position: self.label_position,
1386 })
1387 }
1388}
1389
1390// Custom serde helpers for Arc<str> types
1391mod arc_str_serde {
1392 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1393 use std::sync::Arc;
1394
1395 pub fn serialize<S>(arc: &Arc<str>, serializer: S) -> Result<S::Ok, S::Error>
1396 where
1397 S: Serializer,
1398 {
1399 arc.as_ref().serialize(serializer)
1400 }
1401
1402 pub fn deserialize<'de, D>(deserializer: D) -> Result<Arc<str>, D::Error>
1403 where
1404 D: Deserializer<'de>,
1405 {
1406 let s = String::deserialize(deserializer)?;
1407 Ok(Arc::from(s.as_str()))
1408 }
1409}
1410
1411mod arc_str_vec {
1412 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1413 use std::sync::Arc;
1414
1415 pub fn serialize<S>(vec: &Vec<Arc<str>>, serializer: S) -> Result<S::Ok, S::Error>
1416 where
1417 S: Serializer,
1418 {
1419 let strings: Vec<&str> = vec.iter().map(|arc| arc.as_ref()).collect();
1420 strings.serialize(serializer)
1421 }
1422
1423 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Arc<str>>, D::Error>
1424 where
1425 D: Deserializer<'de>,
1426 {
1427 let vec = Vec::<String>::deserialize(deserializer)?;
1428 Ok(vec.into_iter().map(|s| Arc::from(s.as_str())).collect())
1429 }
1430}
1431
1432mod arc_str_pair {
1433 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1434 use std::sync::Arc;
1435
1436 pub fn serialize<S>(pair: &(Arc<str>, Arc<str>), serializer: S) -> Result<S::Ok, S::Error>
1437 where
1438 S: Serializer,
1439 {
1440 (pair.0.as_ref(), pair.1.as_ref()).serialize(serializer)
1441 }
1442
1443 pub fn deserialize<'de, D>(deserializer: D) -> Result<(Arc<str>, Arc<str>), D::Error>
1444 where
1445 D: Deserializer<'de>,
1446 {
1447 let (s1, s2) = <(String, String)>::deserialize(deserializer)?;
1448 Ok((Arc::from(s1.as_str()), Arc::from(s2.as_str())))
1449 }
1450}
1451
1452mod arc_str_hashmap {
1453 use serde::{Deserialize, Deserializer, Serialize, Serializer};
1454 use std::collections::HashMap;
1455 use std::sync::Arc;
1456
1457 pub fn serialize<S>(map: &HashMap<Arc<str>, f32>, serializer: S) -> Result<S::Ok, S::Error>
1458 where
1459 S: Serializer,
1460 {
1461 let string_map: HashMap<&str, f32> = map.iter().map(|(k, v)| (k.as_ref(), *v)).collect();
1462 string_map.serialize(serializer)
1463 }
1464
1465 pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<Arc<str>, f32>, D::Error>
1466 where
1467 D: Deserializer<'de>,
1468 {
1469 let map = HashMap::<String, f32>::deserialize(deserializer)?;
1470 Ok(map
1471 .into_iter()
1472 .map(|(k, v)| (Arc::from(k.as_str()), v))
1473 .collect())
1474 }
1475}