ploidy_core/ir/views/
operation.rs

1use std::marker::PhantomData;
2
3use petgraph::{
4    graph::NodeIndex,
5    visit::{Bfs, EdgeFiltered, EdgeRef, VisitMap, Visitable},
6};
7
8use crate::{
9    ir::{
10        graph::{IrGraph, IrGraphG, IrGraphNode},
11        types::{
12            IrOperation, IrParameter, IrParameterInfo, IrParameterStyle, IrRequest, IrResponse,
13        },
14    },
15    parse::{Method, path::PathSegment},
16};
17
18use super::{inline::InlineIrTypeView, ir::IrTypeView};
19
20/// A graph-aware view of an [`IrOperation`].
21#[derive(Debug)]
22pub struct IrOperationView<'a> {
23    graph: &'a IrGraph<'a>,
24    op: &'a IrOperation<'a>,
25}
26
27impl<'a> IrOperationView<'a> {
28    pub(in crate::ir) fn new(graph: &'a IrGraph<'a>, op: &'a IrOperation<'a>) -> Self {
29        Self { graph, op }
30    }
31
32    #[inline]
33    pub fn resource(&self) -> &'a str {
34        self.op.resource
35    }
36
37    #[inline]
38    pub fn id(&self) -> &'a str {
39        self.op.id
40    }
41
42    #[inline]
43    pub fn method(&self) -> Method {
44        self.op.method
45    }
46
47    #[inline]
48    pub fn path(&self) -> IrOperationViewPath<'_, 'a> {
49        IrOperationViewPath(self)
50    }
51
52    #[inline]
53    pub fn description(&self) -> Option<&'a str> {
54        self.op.description
55    }
56
57    /// Returns an iterator over this operation's query parameters.
58    #[inline]
59    pub fn query(&self) -> impl Iterator<Item = IrParameterView<'a, IrQueryParameter>> + '_ {
60        self.op.params.iter().filter_map(move |param| match param {
61            IrParameter::Query(info) => Some(IrParameterView::new(self.graph, info)),
62            _ => None,
63        })
64    }
65
66    /// Returns a view of the request body, if present.
67    #[inline]
68    pub fn request(&self) -> Option<IrRequestView<'a>> {
69        self.op.request.as_ref().map(|ty| match ty {
70            IrRequest::Json(ty) => {
71                let node = IrGraphNode::from_ref(self.graph.spec, ty.as_ref());
72                IrRequestView::Json(IrTypeView::new(self.graph, self.graph.indices[&node]))
73            }
74            IrRequest::Multipart => IrRequestView::Multipart,
75        })
76    }
77
78    /// Returns a view of the response body, if present.
79    #[inline]
80    pub fn response(&self) -> Option<IrResponseView<'a>> {
81        self.op.response.as_ref().map(|ty| match ty {
82            IrResponse::Json(ty) => {
83                let node = IrGraphNode::from_ref(self.graph.spec, ty.as_ref());
84                IrResponseView::Json(IrTypeView::new(self.graph, self.graph.indices[&node]))
85            }
86        })
87    }
88
89    /// Returns an iterator over all the inline types that are
90    /// contained within this operation's referenced types.
91    #[inline]
92    pub fn inlines(&self) -> impl Iterator<Item = InlineIrTypeView<'a>> {
93        // Exclude edges that reference other schemas.
94        let filtered = EdgeFiltered::from_fn(&self.graph.g, |r| {
95            !matches!(self.graph.g[r.target()], IrGraphNode::Schema(_))
96        });
97        let mut bfs = self.bfs();
98        std::iter::from_fn(move || bfs.next(&filtered)).filter_map(|index| {
99            match self.graph.g[index] {
100                IrGraphNode::Inline(ty) => Some(InlineIrTypeView::new(self.graph, index, ty)),
101                _ => None,
102            }
103        })
104    }
105
106    fn bfs(&self) -> Bfs<NodeIndex, <IrGraphG<'a> as Visitable>::Map> {
107        // `Bfs::new()` starts with just one root on the stack,
108        // but operations aren't roots; they reference types that are roots,
109        // so we construct our own visitor with all those types on the stack.
110        let stack = self
111            .op
112            .types()
113            .map(|ty| IrGraphNode::from_ref(self.graph.spec, ty.as_ref()))
114            .map(|node| self.graph.indices[&node])
115            .collect();
116        let mut discovered = self.graph.g.visit_map();
117        for &index in &stack {
118            discovered.visit(index);
119        }
120        Bfs { stack, discovered }
121    }
122}
123
124/// A graph-aware view of operation's path template and parameters.
125#[derive(Clone, Copy, Debug)]
126pub struct IrOperationViewPath<'view, 'a>(&'view IrOperationView<'a>);
127
128impl<'view, 'a> IrOperationViewPath<'view, 'a> {
129    #[inline]
130    pub fn segments(self) -> std::slice::Iter<'view, PathSegment<'a>> {
131        self.0.op.path.iter()
132    }
133
134    /// Returns an iterator over this operation's path parameters.
135    #[inline]
136    pub fn params(self) -> impl Iterator<Item = IrParameterView<'a, IrPathParameter>> + 'view {
137        self.0
138            .op
139            .params
140            .iter()
141            .filter_map(move |param| match param {
142                IrParameter::Path(info) => Some(IrParameterView::new(self.0.graph, info)),
143                _ => None,
144            })
145    }
146}
147
148/// A graph-aware view of an operation parameter.
149#[derive(Debug)]
150pub struct IrParameterView<'a, T> {
151    graph: &'a IrGraph<'a>,
152    info: &'a IrParameterInfo<'a>,
153    phantom: PhantomData<T>,
154}
155
156impl<'a, T> IrParameterView<'a, T> {
157    pub(in crate::ir) fn new(graph: &'a IrGraph<'a>, info: &'a IrParameterInfo<'a>) -> Self {
158        Self {
159            graph,
160            info,
161            phantom: PhantomData,
162        }
163    }
164
165    #[inline]
166    pub fn name(&self) -> &'a str {
167        self.info.name
168    }
169
170    #[inline]
171    pub fn ty(&self) -> IrTypeView<'a> {
172        let graph = self.graph;
173        let node = IrGraphNode::from_ref(graph.spec, self.info.ty.as_ref());
174        IrTypeView::new(graph, graph.indices[&node])
175    }
176
177    #[inline]
178    pub fn required(&self) -> bool {
179        self.info.required
180    }
181
182    #[inline]
183    pub fn style(&self) -> Option<IrParameterStyle> {
184        self.info.style
185    }
186}
187
188/// A marker type for a path parameter.
189#[derive(Clone, Copy, Debug)]
190pub enum IrPathParameter {}
191
192/// A marker type for a query parameter.
193#[derive(Clone, Copy, Debug)]
194pub enum IrQueryParameter {}
195
196/// A graph-aware view of an operation's request body.
197#[derive(Debug)]
198pub enum IrRequestView<'a> {
199    Json(IrTypeView<'a>),
200    Multipart,
201}
202
203/// A graph-aware view of an operation's response body.
204#[derive(Debug)]
205pub enum IrResponseView<'a> {
206    Json(IrTypeView<'a>),
207}