openapi_trustfall_adapter/
adapter_impl.rs1use std::{
2 path::PathBuf,
3 sync::{Arc, OnceLock},
4};
5
6use trustfall::{
7 provider::{
8 resolve_coercion_using_schema, resolve_property_with, AsVertex, ContextIterator,
9 ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, Typename,
10 VertexIterator,
11 },
12 FieldValue, Schema,
13};
14
15use crate::{errors::OpenAPIAdapterErrors, utils::find_files};
16
17use super::{
18 utils::{merge, Route},
19 vertex::Vertex,
20};
21
22static SCHEMA: OnceLock<Schema> = OnceLock::new();
23
24#[non_exhaustive]
25#[derive(Debug, Default)]
26pub struct OpenApiAdapter {
27 openapi: openapiv3::OpenAPI,
28}
29
30impl OpenApiAdapter {
31 pub const SCHEMA_TEXT: &'static str = include_str!("./schema.graphql");
32
33 pub fn schema() -> &'static Schema {
35 SCHEMA.get_or_init(|| Schema::parse(Self::SCHEMA_TEXT).expect("not a valid schema"))
36 }
37
38 pub fn new() -> Self {
40 Default::default()
41 }
42
43 pub fn new_with_files(files: Vec<PathBuf>) -> Result<Self, OpenAPIAdapterErrors> {
45 let mut adapter = Self::default();
46 adapter.files(files)?;
47 Ok(adapter)
48 }
49
50 pub fn new_with_path(path: PathBuf) -> Result<Self, OpenAPIAdapterErrors> {
51 let mut adapter = Self::default();
52 adapter.set_path(path)?;
53 Ok(adapter)
54 }
55
56 pub fn files(&mut self, files: Vec<PathBuf>) -> Result<(), OpenAPIAdapterErrors> {
58 let merged_content = merge(files)?;
59 self.openapi = serde_yaml::from_str(&merged_content)
60 .map_err(OpenAPIAdapterErrors::FailedToSerializeToOpenAPI)?;
61 Ok(())
62 }
63
64 pub fn set_path(&mut self, path: PathBuf) -> Result<(), OpenAPIAdapterErrors> {
66 if !path.exists() {
67 return Err(OpenAPIAdapterErrors::PathIsNotADirectory(path));
68 }
69 let mut files = find_files(&path, "yaml".as_ref());
70 files.extend(find_files(&path, "yml".as_ref()));
71 if files.is_empty() {
72 return Err(OpenAPIAdapterErrors::FilesNotFound(path));
73 }
74 let merged_content = merge(files)?;
75 self.openapi = serde_yaml::from_str(&merged_content)
76 .map_err(OpenAPIAdapterErrors::FailedToSerializeToOpenAPI)?;
77 Ok(())
78 }
79
80 fn info(&self) -> Vertex {
81 Vertex::Info(self.openapi.info.clone())
82 }
83
84 fn path(&self, path: &str) -> Vertex {
85 let mut route: Route = self
86 .openapi
87 .paths
88 .paths
89 .get(path)
90 .expect("path not found")
91 .clone()
92 .into();
93 route.path = path.to_string();
94 Vertex::Path(route)
95 }
96
97 fn paths<'a>(&self) -> VertexIterator<'a, Vertex> {
98 let iter = self.openapi.paths.clone().into_iter().map(|x| {
99 let mut route: Route = x.1.into();
100 route.path = x.0.clone().to_string();
101 Vertex::Path(route)
102 });
103 Box::new(iter)
104 }
105
106 fn tags<'a>(&self) -> VertexIterator<'a, Vertex> {
107 let iter = self.openapi.tags.clone().into_iter().map(Vertex::Tag);
108 Box::new(iter)
109 }
111}
112
113impl<'a> trustfall::provider::Adapter<'a> for OpenApiAdapter {
114 type Vertex = Vertex;
115
116 fn resolve_starting_vertices(
117 &self,
118 edge_name: &Arc<str>,
119 parameters: &EdgeParameters,
120 _resolve_info: &ResolveInfo,
121 ) -> VertexIterator<'a, Self::Vertex> {
122 match edge_name.as_ref() {
123 "Info" => Box::new(std::iter::once(self.info())),
124 "Path" => {
125 let path: &str = parameters
126 .get("path")
127 .expect(
128 "failed to find parameter 'path' when resolving 'Path' starting vertices",
129 )
130 .as_str()
131 .expect(
132 "unexpected null or other incorrect data type for Trustfall type 'String!'",
133 );
134 Box::new(std::iter::once(self.path(path)))
136 }
137 "Paths" => self.paths(),
139 "Tags" => self.tags(),
142 _ => {
143 unreachable!(
144 "attempted to resolve starting vertices for unexpected edge name: {edge_name}"
145 )
146 }
147 }
148 }
149
150 fn resolve_property<V: AsVertex<Self::Vertex> + 'a>(
151 &self,
152 contexts: ContextIterator<'a, V>,
153 type_name: &Arc<str>,
154 property_name: &Arc<str>,
155 resolve_info: &ResolveInfo,
156 ) -> ContextOutcomeIterator<'a, V, FieldValue> {
157 if property_name.as_ref() == "__typename" {
158 return resolve_property_with(contexts, |vertex| vertex.typename().into());
159 }
160 match type_name.as_ref() {
161 "AmazonApigatewayIntegration" => {
162 super::properties::resolve_amazon_apigateway_integration_property(
163 contexts,
164 property_name.as_ref(),
165 resolve_info,
166 )
167 }
168 "Info" => super::properties::resolve_info_property(
169 contexts,
170 property_name.as_ref(),
171 resolve_info,
172 ),
173 "Operation" => super::properties::resolve_operation_property(
174 contexts,
175 property_name.as_ref(),
176 resolve_info,
177 ),
178 "Path" => super::properties::resolve_path_property(
179 contexts,
180 property_name.as_ref(),
181 resolve_info,
182 ),
183 "Tag" => super::properties::resolve_tag_property(
184 contexts,
185 property_name.as_ref(),
186 resolve_info,
187 ),
188 _ => {
189 unreachable!(
190 "attempted to read property '{property_name}' on unexpected type: {type_name}"
191 )
192 }
193 }
194 }
195
196 fn resolve_neighbors<V: AsVertex<Self::Vertex> + 'a>(
197 &self,
198 contexts: ContextIterator<'a, V>,
199 type_name: &Arc<str>,
200 edge_name: &Arc<str>,
201 parameters: &EdgeParameters,
202 resolve_info: &ResolveEdgeInfo,
203 ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> {
204 match type_name.as_ref() {
205 "Operation" => super::edges::resolve_operation_edge(
206 contexts,
207 edge_name.as_ref(),
208 parameters,
209 resolve_info,
210 ),
211 "Path" => super::edges::resolve_path_edge(
212 contexts,
213 edge_name.as_ref(),
214 parameters,
215 resolve_info,
216 ),
217 _ => {
218 unreachable!(
219 "attempted to resolve edge '{edge_name}' on unexpected type: {type_name}"
220 )
221 }
222 }
223 }
224
225 fn resolve_coercion<V: AsVertex<Self::Vertex> + 'a>(
226 &self,
227 contexts: ContextIterator<'a, V>,
228 _type_name: &Arc<str>,
229 coerce_to_type: &Arc<str>,
230 _resolve_info: &ResolveInfo,
231 ) -> ContextOutcomeIterator<'a, V, bool> {
232 resolve_coercion_using_schema(contexts, Self::schema(), coerce_to_type.as_ref())
233 }
234}