use std::marker::PhantomData;
use petgraph::{
Direction,
graph::NodeIndex,
visit::{Bfs, EdgeFiltered, EdgeRef, VisitMap, Visitable},
};
use crate::{
ir::{
graph::{CookedGraph, EdgeKind, Traversal, Traverse},
types::{
GraphOperation, GraphParameter, GraphParameterInfo, GraphRequest, GraphResponse,
GraphType, ParameterStyle,
},
},
parse::{Method, path::PathSegment},
};
use super::{Reach, 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| {
matches!(cooked.graph[e.target()], GraphType::Inline(_))
});
let mut bfs = {
let meta = &self.cooked.metadata.operations[self.op];
let stack = meta
.types
.ones()
.map(NodeIndex::new)
.filter(|&index| {
matches!(cooked.graph[index], GraphType::Inline(_))
})
.collect();
let mut discovered = self.cooked.graph.visit_map();
for &index in &stack {
discovered.visit(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 meta = &self.cooked.metadata.operations[self.op];
let mut types = meta.types.clone();
for node in meta.types.ones() {
let meta = &self.cooked.metadata.schemas[node];
types.union_with(&meta.dependencies);
}
types
.into_ones()
.map(NodeIndex::new)
.map(|index| TypeView::new(self.cooked, index))
}
#[inline]
fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'a> {
std::iter::empty()
}
#[inline]
fn traverse<F>(
&self,
reach: Reach,
filter: F,
) -> impl Iterator<Item = TypeView<'a>> + use<'a, F>
where
F: Fn(EdgeKind, &TypeView<'a>) -> Traversal,
{
either!(match reach {
Reach::Dependents => std::iter::empty(),
Reach::Dependencies => {
let cooked = self.cooked;
let meta = &cooked.metadata.operations[self.op];
let traverse = Traverse::at_roots(&cooked.graph, &meta.types, Direction::Outgoing);
traverse
.run(move |kind, index| {
let view = TypeView::new(cooked, index);
filter(kind, &view)
})
.map(|index| TypeView::new(cooked, index))
}
})
}
}
#[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 filtered = EdgeFiltered::from_fn(&cooked.graph, |e| {
matches!(cooked.graph[e.target()], GraphType::Inline(_))
});
let mut bfs = Bfs::new(&cooked.graph, self.info.ty);
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;
let meta = &cooked.metadata.schemas[self.info.ty.index()];
std::iter::once(self.info.ty)
.chain(meta.dependencies.ones().map(NodeIndex::new))
.map(|index| TypeView::new(cooked, index))
}
fn dependents(&self) -> impl Iterator<Item = TypeView<'a>> + use<'view, 'a, T> {
std::iter::empty()
}
fn traverse<F>(
&self,
reach: Reach,
filter: F,
) -> impl Iterator<Item = TypeView<'a>> + use<'view, 'a, T, F>
where
F: Fn(EdgeKind, &TypeView<'a>) -> Traversal,
{
either!(match reach {
Reach::Dependents => std::iter::empty(),
Reach::Dependencies => {
let cooked = self.op.cooked;
let traverse = Traverse::at_root(&cooked.graph, self.info.ty, Direction::Outgoing);
traverse
.run(move |kind, index| {
let view = TypeView::new(cooked, index);
filter(kind, &view)
})
.map(|index| TypeView::new(cooked, 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>),
}