1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use syn::{Item, ItemTrait};
5
6use crate::config::settings_keys;
7use crate::handlers::HandlerScope;
8use crate::naming::snake_case;
9use crate::provisions::{ImplSite, RootExports};
10use crate::syntax::{has_attribute, line_of};
11use crate::{ConfigKey, ModelError};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Gateway {
15 pub name: String,
16 pub path: Vec<String>,
17 pub adapters: Vec<GatewayAdapter>,
18 pub file: PathBuf,
19 pub line: usize,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct GatewayAdapter {
24 pub ty: Vec<String>,
25 pub is_adapter: bool,
26 pub vendor: Option<String>,
27 pub settings: Vec<ConfigKey>,
28 pub file: PathBuf,
29 pub line: usize,
30}
31
32pub(crate) struct GatewayTrait {
33 path: Vec<String>,
34 file: PathBuf,
35 line: usize,
36}
37
38pub(crate) fn gateway_trait(
39 item: &Item,
40 file: &Path,
41 module_path: &[String],
42) -> Option<GatewayTrait> {
43 let Item::Trait(ItemTrait { attrs, ident, .. }) = item else {
44 return None;
45 };
46 has_attribute(attrs, "gateway").then(|| GatewayTrait {
47 path: [module_path, &[ident.to_string()]].concat(),
48 file: file.to_path_buf(),
49 line: line_of(item),
50 })
51}
52
53pub(crate) fn gateways(
54 traits: Vec<GatewayTrait>,
55 sites: &[ImplSite],
56 syntax: &BTreeMap<PathBuf, syn::File>,
57 exports: &RootExports,
58 module: &str,
59) -> Result<Vec<Gateway>, ModelError> {
60 let resolved: Vec<(Vec<String>, Vec<String>, &ImplSite)> = sites
61 .iter()
62 .map(|site| {
63 let items = syntax.get(&site.file).map_or(&[][..], |file| &file.items);
64 let scope = HandlerScope::new(site.module_path.clone(), items);
65 (scope.callee(&site.trait_path), scope.within(&site.ty), site)
66 })
67 .collect();
68 let adapters: Vec<(&Vec<String>, &ImplSite)> = resolved
69 .iter()
70 .filter(|(trait_path, _, _)| trait_path.last().is_some_and(|name| name == "Adapter"))
71 .map(|(_, ty, site)| (ty, *site))
72 .collect();
73 let mut gateways = Vec::new();
74 for found in traits {
75 let mut implementations = Vec::new();
76 for (_, ty, site) in resolved.iter().filter(|(path, _, _)| *path == found.path) {
77 let adapter = adapters.iter().find(|(adapter, _)| *adapter == ty);
78 let vendor = adapter.and_then(|(_, adapter)| adapter.vendor.clone());
79 let settings = match (adapter, &vendor) {
80 (Some((_, adapter)), Some(vendor)) => {
81 let prefix = format!("{module}_{vendor}").to_ascii_uppercase();
82 settings_keys(syntax, adapter.settings.as_deref(), &prefix)?
83 }
84 _ => Vec::new(),
85 };
86 implementations.push(GatewayAdapter {
87 ty: exports.public(ty),
88 is_adapter: adapter.is_some(),
89 vendor,
90 settings,
91 file: site.file.clone(),
92 line: site.line,
93 });
94 }
95 gateways.push(Gateway {
96 name: snake_case(found.path.last().map_or("", String::as_str)),
97 adapters: implementations,
98 path: exports.public(&found.path),
99 file: found.file,
100 line: found.line,
101 });
102 }
103 Ok(gateways)
104}