Skip to main content

openapi_trustfall_adapter/
adapter_impl.rs

1use 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    /// Get the defined schema for the adapter
34    pub fn schema() -> &'static Schema {
35        SCHEMA.get_or_init(|| Schema::parse(Self::SCHEMA_TEXT).expect("not a valid schema"))
36    }
37
38    /// Create a new OpenAPI default adapter
39    pub fn new() -> Self {
40        Default::default()
41    }
42
43    /// New instance with selected files to used
44    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    /// Set the files that are to be used
57    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    /// Sets the directory that files are to be found
65    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        // Vertex::Tags(self.openapi.tags.clone())
110    }
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                // super::entrypoints::path(path, resolve_info, &self.openapi)
135                Box::new(std::iter::once(self.path(path)))
136            }
137            // "Paths" => super::entrypoints::paths(resolve_info, &self.openapi),
138            "Paths" => self.paths(),
139            // "Tags" => super::entrypoints::tags(resolve_info, &self.openapi),
140            // "Tags" => Box::new(std::iter::once(self.tags())),
141            "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}