use std::{collections::VecDeque, marker::PhantomData};
use petgraph::{
graph::NodeIndex,
visit::{Bfs, EdgeFiltered, EdgeRef, Visitable},
};
use crate::{
ir::{
graph::CookedGraph,
types::{
GraphOperation, GraphParameter, GraphParameterInfo, GraphRequest, GraphResponse,
GraphType, ParameterStyle,
},
},
parse::{Method, path::PathSegment},
};
use super::{View, inline::InlineTypeView, ir::TypeView};
#[derive(Debug)]
pub struct OperationView<'a> {
cooked: &'a CookedGraph<'a>,
op: &'a GraphOperation<'a>,
}
impl<'a> OperationView<'a> {
#[inline]
pub(in crate::ir) fn new(cooked: &'a CookedGraph<'a>, op: &'a GraphOperation<'a>) -> Self {
Self { cooked, op }
}
#[inline]
pub fn id(&self) -> &'a str {
self.op.id
}
#[inline]
pub fn method(&self) -> Method {
self.op.method
}
#[inline]
pub fn path(&self) -> OperationViewPath<'_, 'a> {
OperationViewPath(self)
}
#[inline]
pub fn description(&self) -> Option<&'a str> {
self.op.description
}
#[inline]
pub fn query(&self) -> impl Iterator<Item = ParameterView<'_, 'a, QueryParameter>> {
self.op.params.iter().filter_map(move |param| match param {
GraphParameter::Query(info) => Some(ParameterView::new(self, info)),
_ => None,
})
}
#[inline]
pub fn request(&self) -> Option<RequestView<'a>> {
self.op.request.as_ref().map(|ty| match ty {
GraphRequest::Json(index) => RequestView::Json(TypeView::new(self.cooked, *index)),
GraphRequest::Multipart => RequestView::Multipart,
})
}
#[inline]
pub fn response(&self) -> Option<ResponseView<'a>> {
self.op.response.as_ref().map(|ty| match ty {
GraphResponse::Json(index) => ResponseView::Json(TypeView::new(self.cooked, *index)),
})
}
#[inline]
pub fn resource(&self) -> Option<&'a str> {
self.op.resource
}
}
impl<'a> View<'a> for OperationView<'a> {
#[inline]
fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'a>> + use<'a> {
let cooked = self.cooked;
let filtered = EdgeFiltered::from_fn(&cooked.graph, |e| {
!e.weight().shadow() && matches!(cooked.graph[e.target()], GraphType::Inline(_))
});
let mut bfs = {
let stack: VecDeque<_> = self
.op
.types()
.copied()
.filter(|&index| {
matches!(cooked.graph[index], GraphType::Inline(_))
})
.collect();
let mut discovered = self.cooked.graph.visit_map();
discovered.extend(stack.iter().copied().map(NodeIndex::index));
Bfs { stack, discovered }
};
std::iter::from_fn(move || bfs.next(&filtered)).filter_map(|index| {
match cooked.graph[index] {
GraphType::Inline(ty) => Some(InlineTypeView::new(cooked, index, ty)),
_ => None,
}
})
}
#[inline]
fn used_by(&self) -> impl Iterator<Item = OperationView<'a>> + use<'a> {
std::iter::empty()
}
#[inline]
fn dependencies(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a> {
let cooked = self.cooked;
cooked.metadata.uses[self.op]
.ones()
.map(NodeIndex::new)
.map(move |index| TypeView::new(cooked, index))
}
#[inline]
fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a> {
std::iter::empty()
}
#[inline]
fn hashable(&self) -> bool {
false
}
#[inline]
fn defaultable(&self) -> bool {
false
}
}
#[derive(Clone, Copy, Debug)]
pub struct OperationViewPath<'view, 'a>(&'view OperationView<'a>);
impl<'view, 'a> OperationViewPath<'view, 'a> {
#[inline]
pub fn segments(self) -> std::slice::Iter<'view, PathSegment<'a>> {
self.0.op.path.iter()
}
#[inline]
pub fn params(self) -> impl Iterator<Item = ParameterView<'view, 'a, PathParameter>> {
self.0
.op
.params
.iter()
.filter_map(move |param| match param {
GraphParameter::Path(info) => Some(ParameterView::new(self.0, info)),
_ => None,
})
}
}
#[derive(Debug)]
pub struct ParameterView<'view, 'a, T> {
op: &'view OperationView<'a>,
info: &'a GraphParameterInfo<'a>,
phantom: PhantomData<T>,
}
impl<'view, 'a, T> ParameterView<'view, 'a, T> {
#[inline]
pub(in crate::ir) fn new(
op: &'view OperationView<'a>,
info: &'a GraphParameterInfo<'a>,
) -> Self {
Self {
op,
info,
phantom: PhantomData,
}
}
#[inline]
pub fn name(&self) -> &'a str {
self.info.name
}
#[inline]
pub fn ty(&self) -> TypeView<'a> {
TypeView::new(self.op.cooked, self.info.ty)
}
#[inline]
pub fn required(&self) -> bool {
self.info.required
}
#[inline]
pub fn style(&self) -> Option<ParameterStyle> {
self.info.style
}
}
impl<'view, 'a, T> View<'a> for ParameterView<'view, 'a, T> {
fn inlines(&self) -> impl Iterator<Item = InlineTypeView<'a>> + use<'view, 'a, T> {
let cooked = self.op.cooked;
let start = self.info.ty;
let filtered = EdgeFiltered::from_fn(&cooked.graph, |e| {
!e.weight().shadow() && matches!(cooked.graph[e.target()], GraphType::Inline(_))
});
let mut bfs = {
let stack = match cooked.graph[start] {
GraphType::Inline(_) => std::iter::once(start).collect(),
_ => VecDeque::new(),
};
let mut discovered = cooked.graph.visit_map();
discovered.extend(stack.iter().copied().map(NodeIndex::index));
Bfs { stack, discovered }
};
std::iter::from_fn(move || bfs.next(&filtered)).filter_map(|index| {
match cooked.graph[index] {
GraphType::Inline(ty) => Some(InlineTypeView::new(cooked, index, ty)),
_ => None,
}
})
}
fn used_by(&self) -> impl Iterator<Item = OperationView<'a>> + use<'view, 'a, T> {
std::iter::once(OperationView::new(self.op.cooked, self.op.op))
}
fn dependencies(&self) -> impl Iterator<Item = TypeView<'a>> + use<'view, 'a, T> {
let cooked = self.op.cooked;
cooked
.metadata
.closure
.dependencies_of(self.info.ty)
.map(move |index| TypeView::new(cooked, index))
}
fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'view, 'a, T> {
std::iter::empty()
}
#[inline]
fn hashable(&self) -> bool {
self.op.cooked.metadata.hashable[self.info.ty.index()]
}
#[inline]
fn defaultable(&self) -> bool {
self.op.cooked.metadata.defaultable[self.info.ty.index()]
}
}
#[derive(Clone, Copy, Debug)]
pub enum PathParameter {}
#[derive(Clone, Copy, Debug)]
pub enum QueryParameter {}
#[derive(Debug)]
pub enum RequestView<'a> {
Json(TypeView<'a>),
Multipart,
}
#[derive(Debug)]
pub enum ResponseView<'a> {
Json(TypeView<'a>),
}