use async_trait::async_trait;
use itertools::Itertools;
use ldap3::{
LdapError, LdapResult, ResultEntry, Scope, SearchStream,
adapters::{Adapter, SoloMarker},
controls::{Control, MakeCritical, RawControl},
};
use std::{fmt::Debug, mem};
use thiserror::Error;
use tracing::debug;
use crate::sort::{
SERVER_SIDE_SORT_REQUEST_OID, SERVER_SIDE_SORT_RESPONSE_OID,
control::{self, ServerSideSortResponse, SortResult},
};
#[derive(Debug, Clone)]
pub(crate) struct ServerSideSort {
sorts: Vec<SortBy>,
}
#[derive(Debug, Error)]
#[error("Attributes {} occur more than once in the sort list.",
attributes.iter().format(", ")
)]
pub struct DuplicateSortAttributes {
attributes: Vec<String>,
}
impl ServerSideSort {
pub fn new(sorts: Vec<SortBy>) -> Result<Self, DuplicateSortAttributes> {
let duplicates = sorts
.iter()
.map(|SortBy { attribute, .. }| attribute)
.duplicates()
.collect_vec();
if !duplicates.is_empty() {
let attributes = duplicates.into_iter().map(ToOwned::to_owned).collect();
Err(DuplicateSortAttributes { attributes })
}
else {
Ok(ServerSideSort { sorts })
}
}
}
#[derive(Debug, Clone)]
pub struct SortBy {
pub attribute: String,
pub reverse: bool,
}
impl SoloMarker for ServerSideSort {}
#[async_trait]
impl<'a, S, A> Adapter<'a, S, A> for ServerSideSort
where
S: AsRef<str> + Clone + Debug + Send + Sync + 'a,
A: AsRef<[S]> + Clone + Debug + Send + Sync + 'a,
{
async fn start(
&mut self,
stream: &mut SearchStream<'a, S, A>,
base: &str,
scope: Scope,
filter: &str,
attrs: A,
) -> ldap3::result::Result<()> {
let stream_ldap = stream.ldap_handle();
let sort_control_already_defined = stream_ldap.controls.as_ref().is_some_and(|vec| {
vec.iter()
.any(|control| control.ctype == SERVER_SIDE_SORT_REQUEST_OID)
});
if sort_control_already_defined {
return Err(LdapError::AdapterInit(String::from(
"found Server Side Sort control in op set already",
)));
}
let sorts = mem::take(&mut self.sorts);
let new_control = control::ServerSideSortRequest {
sort_key_list: sorts.into_iter().map_into().collect(),
} .critical();
stream_ldap
.controls
.get_or_insert_default()
.push(new_control.into());
stream.start(base, scope, filter, attrs).await
}
async fn next(
&mut self,
stream: &mut SearchStream<'a, S, A>,
) -> ldap3::result::Result<Option<ResultEntry>> {
match stream.next().await? {
Some(result_entry) => {
let sss_control = stream.res.as_ref().and_then(
|LdapResult {
ctrls: controls, ..
}| get_response_control(controls.as_slice()),
);
match sss_control {
Some(ServerSideSortResponse {
sort_result: SortResult::Success,
..
}) => {
Ok(Some(result_entry))
}
Some(ServerSideSortResponse { sort_result, .. }) => {
panic!(
"Server side sort result was {sort_result:?}. This should never be the case in this branch as the control was set to critical and so should have caused an error earlier."
)
}
None => {
debug!("No server side sort response control.");
Ok(Some(result_entry))
}
}
}
None => Ok(None),
}
}
async fn finish(&mut self, stream: &mut SearchStream<'a, S, A>) -> LdapResult {
let result = stream.finish().await;
let sss_control = get_response_control(result.ctrls.as_slice());
match sss_control {
None => debug!("No Server Side Sort control in the final result"),
Some(control) => debug!("The final Server Side Sort control: {control:?}"),
};
result
}
}
fn get_response_control(controls: &[Control]) -> Option<ServerSideSortResponse> {
controls
.iter()
.map(|Control(_, raw)| raw)
.find(|raw| raw.ctype == SERVER_SIDE_SORT_RESPONSE_OID)
.map(RawControl::parse)
}