Skip to main content

oag_core/ir/
grouping.rs

1use super::types::{IrSpec, NormalizedName};
2use crate::config::SplitBy;
3use crate::transform::name_normalizer::normalize_name;
4use indexmap::IndexMap;
5
6/// A group of operations, used for split layout.
7#[derive(Debug, Clone)]
8pub struct OperationGroup {
9    pub name: NormalizedName,
10    pub operation_indices: Vec<usize>,
11}
12
13/// Group operations in the IR spec according to the split strategy.
14pub fn group_operations(ir: &IrSpec, split_by: SplitBy) -> Vec<OperationGroup> {
15    match split_by {
16        SplitBy::Tag => group_by_tag(ir),
17        SplitBy::Operation => group_by_operation(ir),
18        SplitBy::Route => group_by_route(ir),
19    }
20}
21
22/// Group by tag — reuses `IrModule` groupings.
23fn group_by_tag(ir: &IrSpec) -> Vec<OperationGroup> {
24    ir.modules
25        .iter()
26        .map(|m| OperationGroup {
27            name: m.name.clone(),
28            operation_indices: m.operations.clone(),
29        })
30        .collect()
31}
32
33/// Group by operation — one group per operation.
34fn group_by_operation(ir: &IrSpec) -> Vec<OperationGroup> {
35    ir.operations
36        .iter()
37        .enumerate()
38        .map(|(i, op)| OperationGroup {
39            name: op.name.clone(),
40            operation_indices: vec![i],
41        })
42        .collect()
43}
44
45/// Group by route — group operations by their first path segment.
46fn group_by_route(ir: &IrSpec) -> Vec<OperationGroup> {
47    let mut groups: IndexMap<String, Vec<usize>> = IndexMap::new();
48
49    for (i, op) in ir.operations.iter().enumerate() {
50        let prefix = extract_path_prefix(&op.path);
51        groups.entry(prefix).or_default().push(i);
52    }
53
54    groups
55        .into_iter()
56        .map(|(prefix, indices)| OperationGroup {
57            name: normalize_name(&prefix),
58            operation_indices: indices,
59        })
60        .collect()
61}
62
63/// Extract the first meaningful path segment as a group name.
64/// e.g. "/pets/{petId}" → "pets", "/store/inventory" → "store"
65fn extract_path_prefix(path: &str) -> String {
66    let segments: Vec<&str> = path
67        .split('/')
68        .filter(|s| !s.is_empty() && !s.starts_with('{'))
69        .collect();
70
71    segments.first().unwrap_or(&"default").to_string()
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_extract_path_prefix() {
80        assert_eq!(extract_path_prefix("/pets"), "pets");
81        assert_eq!(extract_path_prefix("/pets/{petId}"), "pets");
82        assert_eq!(extract_path_prefix("/store/inventory"), "store");
83        assert_eq!(extract_path_prefix("/chat/completions"), "chat");
84        assert_eq!(extract_path_prefix("/"), "default");
85    }
86}