use std::ops::Deref;
use std::sync::Arc;
use reqwest_middleware::ClientWithMiddleware as ReqwestClient;
pub use super::generated::apps::AuthenticatedClient as ApplicationOperations;
pub use crate::api::apps::objects::*;
use crate::auth::TokenSource;
#[cfg(feature = "tables")]
use crate::client::ScandalSelection;
use crate::http::HttpClient;
use crate::{
Application as ApplicationHandle, ApplicationRegistration, Error,
Installation as InstallationHandle, InstallationRegistration, List,
UserInstallation as UserInstallationHandle,
};
pub(crate) struct ApplicationService {
pub(crate) api_url: String,
pub(crate) client: ApplicationOperations,
pub(crate) auth: Option<TokenSource>,
pub(crate) auth_url: reqwest::Url,
pub(crate) http: ReqwestClient,
#[cfg(feature = "tables")]
execution: ExecutionSettings,
}
#[derive(Clone, Default)]
pub(crate) struct ExecutionSettings {
#[cfg(feature = "tables")]
pub(crate) scandal: Option<ScandalSelection>,
}
impl ApplicationService {
pub(crate) fn connection(&self, auth: Option<TokenSource>) -> Result<crate::Client, Error> {
let mut builder = crate::Client::builder()
.api_url(&self.api_url)
.auth_url(self.auth_url.as_str())
.http_client(self.http.clone());
if let Some(auth) = auth {
builder = builder.token_source(auth);
}
#[cfg(feature = "tables")]
match &self.execution.scandal {
Some(ScandalSelection::Given(scandal)) => {
builder = builder.scandal(Arc::clone(scandal))
}
Some(ScandalSelection::Remote(endpoint)) => {
builder = builder.scandal_url(endpoint.as_str());
}
#[cfg(feature = "embedded")]
Some(ScandalSelection::Embedded(scandals)) => {
builder = builder.embedded_scandals(scandals.clone());
}
None => {}
}
builder.build()
}
}
#[derive(Clone)]
pub struct ApplicationsClient {
pub(crate) service: Arc<ApplicationService>,
}
#[derive(Clone)]
pub struct InstallationsClient {
pub(crate) service: Arc<ApplicationService>,
pub(crate) connection: crate::Client,
}
pub(crate) fn clients(
base: &str,
auth_url: reqwest::Url,
http: ReqwestClient,
auth: Option<TokenSource>,
_execution: ExecutionSettings,
) -> Result<Arc<ApplicationService>, Error> {
let service = Arc::new(ApplicationService {
api_url: base.to_owned(),
client: ApplicationOperations::from_http(HttpClient::new(
reqwest::Url::parse(base).map_err(Error::invalid)?,
http.clone(),
auth.clone(),
))?,
http,
auth_url,
auth,
#[cfg(feature = "tables")]
execution: _execution,
});
Ok(service)
}
impl Deref for ApplicationsClient {
type Target = ApplicationOperations;
fn deref(&self) -> &Self::Target {
&self.service.client
}
}
impl ApplicationsClient {
pub async fn create(
&self,
project_id: impl AsRef<str>,
request: ApplicationConfiguration,
) -> Result<Application, Error> {
Ok(self
.service
.client
.create_application(project_id.as_ref().to_owned(), request)
.await?)
}
pub fn current(&self) -> ApplicationHandle {
ApplicationHandle::new(self.clone())
}
pub(crate) fn application(&self, id: impl Into<String>) -> ApplicationRegistration {
ApplicationRegistration::new(self.clone(), id.into())
}
}
impl InstallationsClient {
pub fn list(&self) -> List<'_, Installation> {
self.service
.client
.list_installations_list(Default::default())
}
pub fn current(&self) -> InstallationHandle {
InstallationHandle::current(self.clone())
}
pub(crate) fn installation(&self, id: impl Into<String>) -> InstallationRegistration {
InstallationRegistration::new(self.clone(), id.into())
}
pub async fn for_user(&self) -> Result<Option<UserInstallationHandle>, Error> {
let found = self.service.client.get_user_installation().await?;
Ok(found
.installation
.map(|info| UserInstallationHandle::new(self.clone(), info)))
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "tables")]
use std::time::Duration;
#[cfg(feature = "tables")]
use arrow_array::{Int64Array, RecordBatch, RecordBatchIterator};
#[cfg(feature = "tables")]
use arrow_schema::{DataType, Field, Schema};
#[cfg(feature = "tables")]
use wiremock::matchers::{header, method};
#[cfg(feature = "tables")]
use wiremock::{Mock, MockServer, ResponseTemplate};
use super::*;
#[cfg(feature = "tables")]
use crate::IntoArrowStream;
#[cfg(feature = "tables")]
use crate::RemoteScandal;
#[cfg(feature = "tables")]
use crate::auth::TokenSource;
#[cfg(feature = "tables")]
use crate::remote_scandal::tests::{issue_capability, unsigned_jwt};
#[cfg(feature = "tables")]
#[tokio::test]
async fn installations_call_remote_scandal_with_their_own_capability() {
let server = MockServer::start().await;
for caller in ["app", "installation"] {
let capability = unsigned_jwt(caller, Duration::from_secs(900));
issue_capability(&server, caller, &capability, 1, 1).await;
}
let application = crate::Client::builder()
.api_url(server.uri())
.auth_url(server.uri())
.scandal_url(server.uri())
.bearer_token("app")
.build()
.unwrap();
let installation = application
.applications()
.service
.connection(Some(TokenSource::fixed("installation".into())))
.unwrap();
for client in [&application, &installation] {
let tables = client.connection.tables().unwrap();
tables.scandal().list_functions().await.unwrap();
}
}
#[cfg(feature = "tables")]
#[tokio::test]
async fn installations_reuse_the_scandal_with_their_own_identity() {
let server = MockServer::start().await;
let scandal = RemoteScandal::new("https://scandal.example.test".parse().unwrap()).unwrap();
let application = crate::Client::builder()
.api_url(server.uri())
.bearer_token("app")
.scandal(Arc::new(scandal))
.build()
.unwrap();
let installation = application
.applications()
.service
.connection(Some(TokenSource::fixed("installation".into())))
.unwrap();
for (client, token) in [(&application, "app"), (&installation, "installation")] {
Mock::given(method("POST"))
.and(header("authorization", format!("Bearer {token}")))
.respond_with(ResponseTemplate::new(403))
.expect(1)
.mount(&server)
.await;
let schema = Arc::new(Schema::new(vec![Field::new(
"value",
DataType::Int64,
false,
)]));
let rows =
RecordBatch::try_new(schema.clone(), vec![Arc::new(Int64Array::from(vec![1]))])
.unwrap();
let input = Box::new(RecordBatchIterator::new([Ok(rows)], schema));
assert!(
client
.connection
.tables()
.unwrap()
.create("demo", input.into_write_input().unwrap())
.await
.is_err()
);
}
}
}