1mod breaker;
2mod connect;
3mod exchange;
4mod guard;
5#[cfg(feature = "meilisearch")]
6mod meilisearch;
7#[cfg(feature = "remote-contracts")]
8mod remote;
9mod request;
10mod settings;
11mod target;
12mod telemetry;
13#[cfg(feature = "testing")]
14pub mod testing;
15
16use std::sync::Arc;
17
18use http::Method;
19use sofie_core::{Error, Result};
20
21use breaker::Breakers;
22use connect::Connector;
23use guard::Reach;
24#[cfg(feature = "meilisearch")]
25pub use meilisearch::Meilisearch;
26#[cfg(feature = "remote-contracts")]
27pub use remote::{Remote, RemoteApp, RemoteFeed, RemoteLookup};
28pub use request::{Request, Response};
29pub use settings::Vendor;
30use settings::{Allowed, Limits};
31use target::Target;
32pub use telemetry::call;
33
34pub trait Adapter: Sized {
35 const VENDOR: &'static str;
37 type Settings;
38
39 fn new(http: Outgoing, settings: Self::Settings) -> Self;
40}
41
42#[derive(Debug)]
43pub struct NoSettings {}
44
45#[derive(Clone)]
47pub struct Outgoing {
48 shared: Arc<Shared>,
49}
50
51struct Shared {
52 vendor: &'static str,
53 base: Option<Target>,
54 allowed: Allowed,
55 limits: Limits,
56 connector: Connector,
57 vendor_hosts: Breakers,
59 untrusted_hosts: Breakers,
60 operations: Breakers,
61}
62
63#[doc(hidden)]
65pub fn for_vendor(vendor: &'static str, settings: Vendor) -> Result<Outgoing> {
66 Outgoing::build(vendor, settings, Connector::trusting_public_roots())
67}
68
69impl Outgoing {
70 fn build(vendor: &'static str, settings: Vendor, connector: Connector) -> Result<Self> {
71 let (base, allowed, limits) = settings.read(vendor)?;
72 Ok(Self {
73 shared: Arc::new(Shared {
74 vendor,
75 base,
76 allowed,
77 limits,
78 connector,
79 vendor_hosts: Breakers::default(),
80 untrusted_hosts: Breakers::default(),
81 operations: Breakers::default(),
82 }),
83 })
84 }
85
86 #[must_use]
87 pub fn get(&self, path: &str) -> Request<'_> {
88 self.to_vendor(Method::GET, path)
89 }
90
91 #[must_use]
92 pub fn post(&self, path: &str) -> Request<'_> {
93 self.to_vendor(Method::POST, path)
94 }
95
96 #[must_use]
97 pub fn put(&self, path: &str) -> Request<'_> {
98 self.to_vendor(Method::PUT, path)
99 }
100
101 #[must_use]
102 pub fn patch(&self, path: &str) -> Request<'_> {
103 self.to_vendor(Method::PATCH, path)
104 }
105
106 #[must_use]
107 pub fn delete(&self, path: &str) -> Request<'_> {
108 self.to_vendor(Method::DELETE, path)
109 }
110
111 #[must_use]
114 pub fn get_url(&self, url: &str) -> Request<'_> {
115 self.to_vendor_url(Method::GET, url)
116 }
117
118 #[must_use]
119 pub fn post_url(&self, url: &str) -> Request<'_> {
120 self.to_vendor_url(Method::POST, url)
121 }
122
123 fn to_vendor_url(&self, method: Method, url: &str) -> Request<'_> {
124 let target = Target::parse(url).map_err(|problem| {
125 Error::internal_message(format!(
126 "a URL of {} is refused: {problem}",
127 self.shared.vendor
128 ))
129 });
130 Request::new(self, method, target, Reach::Vendor)
131 }
132
133 #[must_use]
135 pub fn untrusted(&self) -> Untrusted<'_> {
136 Untrusted { outgoing: self }
137 }
138
139 pub fn operation<T>(
140 &self,
141 name: &'static str,
142 work: impl Future<Output = Result<T>>,
143 ) -> impl Future<Output = Result<T>> {
144 telemetry::operation(&self.shared, name, work)
145 }
146
147 fn to_vendor(&self, method: Method, path: &str) -> Request<'_> {
148 let target = match &self.shared.base {
149 Some(base) => base.join(path).map_err(|problem| {
150 Error::internal_message(format!(
151 "the path of a request to {}: {problem}",
152 self.shared.vendor
153 ))
154 }),
155 None => Err(Error::internal_message(format!(
156 "{} has no base URL; set its `BASE_URL` key",
157 self.shared.vendor
158 ))),
159 };
160 Request::new(self, method, target, Reach::Vendor)
161 }
162}
163
164pub struct Untrusted<'a> {
165 outgoing: &'a Outgoing,
166}
167
168impl Untrusted<'_> {
169 #[must_use]
170 pub fn get(&self, url: &str) -> Request<'_> {
171 self.to(Method::GET, url)
172 }
173
174 #[must_use]
175 pub fn post(&self, url: &str) -> Request<'_> {
176 self.to(Method::POST, url)
177 }
178
179 fn to(&self, method: Method, url: &str) -> Request<'_> {
180 let target = Target::parse(url)
181 .map_err(|problem| Error::BadRequest(format!("the URL is refused: {problem}")));
182 Request::new(self.outgoing, method, target, Reach::Untrusted)
183 }
184}