simple_ldap/
lib.rs

1//! # simple-ldap
2//!
3//! This is a high-level LDAP client library created by wrapping the rust LDAP3 clinet.
4//! This provides high-level functions that helps to interact with LDAP.
5//!
6//!
7//! ## Features
8//!
9//! - All the usual LDAP operations
10//! - Search result [deserialization](#deserialization)
11//! - Connection pooling
12//! - Streaming search with native rust [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html)s
13//!
14//!
15//! ## Usage
16//!
17//! Adding `simple_ldap` as a dependency to your project:
18//!
19//! ```commandline
20//! cargo add simple-ldap
21//! ```
22//!
23//! Most functionalities are defined on the [`LdapClient`] type. Have a look at the docs.
24//!
25//!
26//! ### Example
27//!
28//! Examples of individual operations are scattered throughout the docs, but here's the basic usage:
29//!
30//! ```no_run
31//! use simple_ldap::{
32//!     LdapClient, LdapConfig, SimpleDN,
33//!     filter::EqFilter,
34//!     ldap3::Scope
35//! };
36//! use url::Url;
37//! use serde::Deserialize;
38//!
39//! // A type for deserializing the search result into.
40//! #[derive(Debug, Deserialize)]
41//! struct User {
42//!     // // A convenience type for Distinguished Names.
43//!     pub dn: SimpleDN,
44//!     pub uid: String,
45//!     pub cn: String,
46//!     pub sn: String,
47//! }
48//!
49//!
50//! #[tokio::main]
51//! async fn main(){
52//!     let ldap_config = LdapConfig {
53//!         bind_dn: String::from("cn=manager"),
54//!         bind_password: String::from("password"),
55//!         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
56//!         dn_attribute: None,
57//!         connection_settings: None
58//!     };
59//!     let mut client = LdapClient::new(ldap_config).await.unwrap();
60//!     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
61//!     let user: User = client
62//!         .search(
63//!         "ou=people,dc=example,dc=com",
64//!         Scope::OneLevel,
65//!         &name_filter,
66//!         vec!["dn", "cn", "sn", "uid"],
67//!     ).await.unwrap();
68//! }
69//! ```
70//!
71//!
72//! ### Deserialization
73//!
74//! Search results are deserialized into user provided types using [`serde`](https://serde.rs/).
75//! Define a type that reflects the expected results of your search, and derive `Deserialize` for it. For example:
76//!
77//! ```
78//! use serde::Deserialize;
79//! use serde_with::serde_as;
80//! use serde_with::OneOrMany;
81//!
82//! use simple_ldap::SimpleDN;
83//!
84//! // A type for deserializing the search result into.
85//! #[serde_as] // serde_with for multiple values
86//! #[derive(Debug, Deserialize)]
87//! struct User {
88//!     // DN is always returned, whether you ask it or not.
89//!     // You could deserialize it as a plain String, but using
90//!     // SimpleDN gives you type-safety.
91//!     pub dn: SimpleDN,
92//!     pub cn: String,
93//!     // LDAP and Rust naming conventions differ.
94//!     // You can make up for the difference by using serde's renaming annotations.
95//!     #[serde(rename = "mayNotExist")]
96//!     pub may_not_exist: Option<String>,
97//!     #[serde_as(as = "OneOrMany<_>")] // serde_with for multiple values
98//!     pub multivalued_attribute: Vec<String>
99//! }
100//! ```
101//!
102//! Take care to actually request for all the attribute fields in the search.
103//! Otherwise they won't be returned, and the deserialization will fail (unless you used an `Option`).
104//!
105//!
106//! #### String attributes
107//!
108//! Most attributes are returned as strings. You can deserialize them into just Strings, but also into
109//! anything else that can supports deserialization from a string. E.g. perhaps the string represents a
110//! timestamp, and you can deserialize it directly into [`chrono::DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html).
111//!
112//!
113//! #### Binary attributes
114//!
115//! Some attributes may be binary encoded. (Active Directory especially has a bad habit of using these.)
116//! You can just capture the bytes directly into a `Vec<u8>`, but you can also use a type that knows how to
117//! deserialize from bytes. E.g. [`uuid::Uuid`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html)
118//!
119//!
120//! #### Multi-valued attributes
121//!
122//! Multi-valued attributes should be marked as #[serde_as(as = "OneOrMany<_>")] using `serde_with`. Currently, there is a limitation when handing
123//! binary attributes. This will be fixed in the future. As a workaround, you can use `search_multi_valued` or `Record::to_multi_valued_record_`.
124//! To use those method all the attributes should be multi-valued.
125//!
126//!
127//! ## Compile time features
128//!
129//! * `tls-native` - (Enabled by default) Enables TLS support using the systems native implementation.
130//! * `tls-rustls` - Enables TLS support using `rustls`. **Conflicts with `tls-native` so you need to disable default features to use this.**
131//! * `pool` - Enable connection pooling
132//!
133
134use std::{
135    collections::{HashMap, HashSet},
136    fmt, iter,
137};
138
139use filter::{AndFilter, EqFilter, Filter, OrFilter};
140use futures::{Stream, StreamExt, executor::block_on, stream};
141use ldap3::{
142    Ldap, LdapConnAsync, LdapConnSettings, LdapError, LdapResult, Mod, Scope, SearchEntry,
143    SearchStream, StreamState,
144    adapters::{Adapter, EntriesOnly, PagedResults},
145};
146use serde::{Deserialize, Serialize};
147use serde_value::Value;
148use thiserror::Error;
149use tracing::{Level, debug, error, info, instrument, warn};
150use url::Url;
151
152pub mod filter;
153#[cfg(feature = "pool")]
154pub mod pool;
155pub mod simple_dn;
156// Export the main type of the module right here in the root.
157pub use simple_dn::SimpleDN;
158
159// Would likely be better if we could avoid re-exporting this.
160// I suspect it's only used in some configs?
161pub extern crate ldap3;
162
163const LDAP_ENTRY_DN: &str = "entryDN";
164const NO_SUCH_RECORD: u32 = 32;
165
166/// Configuration and authentication for LDAP connection
167#[derive(derive_more::Debug, Clone)]
168pub struct LdapConfig {
169    pub ldap_url: Url,
170    /// DistinguishedName, aka the "username" to use for the connection.
171    // Perhaps we don't want to use SimpleDN here, as it would make it impossible to bind to weird DNs.
172    pub bind_dn: String,
173    #[debug(skip)] // We don't want to print passwords.
174    pub bind_password: String,
175    pub dn_attribute: Option<String>,
176    /// Low level configuration for the connection.
177    /// You can probably skip it.
178    #[debug(skip)] // Debug omitted, because it just doesn't implement it.
179    pub connection_settings: Option<LdapConnSettings>,
180}
181
182///
183/// High-level LDAP client wrapper ontop of ldap3 crate. This wrapper provides a high-level interface to perform LDAP operations
184/// including authentication, search, update, delete
185///
186#[derive(Debug, Clone)]
187pub struct LdapClient {
188    /// The internal connection handle.
189    ldap: Ldap,
190    dn_attr: Option<String>,
191}
192
193impl LdapClient {
194    ///
195    /// Creates a new asynchronous LDAP client.s
196    /// It's capable of running multiple operations concurrently.
197    ///
198    /// # Bind
199    ///
200    /// This performs a simple bind on the connection so need to worry about that.
201    ///
202    pub async fn new(config: LdapConfig) -> Result<Self, Error> {
203        debug!("Creating new connection");
204
205        // With or without connection settings
206        let (conn, mut ldap) = match config.connection_settings {
207            None => LdapConnAsync::from_url(&config.ldap_url).await,
208            Some(settings) => {
209                LdapConnAsync::from_url_with_settings(settings, &config.ldap_url).await
210            }
211        }
212        .map_err(|ldap_err| {
213            Error::Connection(
214                String::from("Failed to initialize LDAP connection."),
215                ldap_err,
216            )
217        })?;
218
219        ldap3::drive!(conn);
220
221        ldap.simple_bind(&config.bind_dn, &config.bind_password)
222            .await
223            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?
224            .success()
225            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?;
226
227        Ok(Self {
228            dn_attr: config.dn_attribute,
229            ldap,
230        })
231    }
232}
233
234impl LdapClient {
235    /// Returns the ldap3 client
236    #[deprecated = "This abstraction leakage will be removed in a future release.
237                    Use the provided methods instead. If something's missing, open an issue in github."]
238    pub fn get_inner(&self) -> Ldap {
239        self.ldap.clone()
240    }
241
242    /// End the LDAP connection.
243    ///
244    /// **Caution advised!**
245    ///
246    /// This will close the connection for all clones of this client as well,
247    /// including open streams. So make sure that you're really good to close.
248    ///
249    /// Closing an LDAP connection with an unbind is *a curtesy.*
250    /// It's fine to skip it, and because of the async hurdless outlined above,
251    /// I would perhaps even recommend it.
252    // Consuming self to prevent accidental use after unbind.
253    // This also conveniently prevents calling this with pooled clients, as the
254    // wrapper `Object` prohibiths moving.
255    pub async fn unbind(mut self) -> Result<(), Error> {
256        match self.ldap.unbind().await {
257            Ok(_) => Ok(()),
258            Err(error) => Err(Error::Close(String::from("Failed to unbind"), error)),
259        }
260    }
261
262    ///
263    /// The user is authenticated by searching for the user in the LDAP server.
264    /// The search is performed using the provided filter. The filter should be a filter that matches a single user.
265    ///
266    /// # Arguments
267    ///
268    /// * `base` - The base DN to search for the user
269    /// * `uid` - The uid of the user
270    /// * `password` - The password of the user
271    /// * `filter` - The filter to search for the user
272    ///
273    ///
274    /// # Returns
275    ///
276    /// * `Result<(), Error>` - Returns an error if the authentication fails
277    ///
278    ///
279    /// # Example
280    ///
281    /// ```no_run
282    /// use simple_ldap::{
283    ///     LdapClient, LdapConfig,
284    ///     filter::EqFilter
285    /// };
286    /// use url::Url;
287    ///
288    /// #[tokio::main]
289    /// async fn main(){
290    ///     let ldap_config = LdapConfig {
291    ///         bind_dn: String::from("cn=manager"),
292    ///         bind_password: String::from("password"),
293    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
294    ///         dn_attribute: None,
295    ///         connection_settings: None
296    ///     };
297    ///
298    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
299    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
300    ///
301    ///     let result = client.authenticate("", "Sam", "password", Box::new(name_filter)).await;
302    /// }
303    /// ```
304    pub async fn authenticate(
305        &mut self,
306        base: &str,
307        uid: &str,
308        password: &str,
309        filter: Box<dyn Filter>,
310    ) -> Result<(), Error> {
311        let attr_dn = self.dn_attr.as_deref().unwrap_or(LDAP_ENTRY_DN);
312
313        let rs = self
314            .ldap
315            .search(base, Scope::OneLevel, filter.filter().as_str(), [attr_dn])
316            .await
317            .map_err(|e| Error::Query("Unable to query user for authentication".into(), e))?;
318
319        let (data, _rs) = rs
320            .success()
321            .map_err(|e| Error::Query("Could not find user for authentication".into(), e))?;
322
323        if data.is_empty() {
324            return Err(Error::NotFound(format!("No record found {uid:?}")));
325        }
326        if data.len() > 1 {
327            return Err(Error::MultipleResults(format!(
328                "Found multiple records for uid {uid:?}"
329            )));
330        }
331
332        let record = data.first().unwrap().to_owned();
333        let record = SearchEntry::construct(record);
334        let result: HashMap<&str, String> = record
335            .attrs
336            .iter()
337            .filter(|(_, value)| !value.is_empty())
338            .map(|(arrta, value)| (arrta.as_str(), value.first().unwrap().clone()))
339            .collect();
340
341        let entry_dn = result.get(attr_dn).ok_or_else(|| {
342            Error::AuthenticationFailed(format!("Unable to retrieve DN of user {uid}"))
343        })?;
344
345        self.ldap
346            .simple_bind(entry_dn, password)
347            .await
348            .map_err(|_| Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}")))
349            .and_then(|r| {
350                r.success().map_err(|_| {
351                    Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}"))
352                })
353            })
354            .and(Ok(()))
355    }
356
357    async fn search_inner<'a, F, A, S>(
358        &mut self,
359        base: &str,
360        scope: Scope,
361        filter: &F,
362        attributes: A,
363    ) -> Result<SearchEntry, Error>
364    where
365        F: Filter,
366        A: AsRef<[S]> + Send + Sync + 'a,
367        S: AsRef<str> + Send + Sync + 'a,
368    {
369        let search = self
370            .ldap
371            .search(base, scope, filter.filter().as_str(), attributes)
372            .await;
373        if let Err(error) = search {
374            return Err(Error::Query(
375                format!("Error searching for record: {error:?}"),
376                error,
377            ));
378        }
379        let result = search.unwrap().success();
380        if let Err(error) = result {
381            return Err(Error::Query(
382                format!("Error searching for record: {error:?}"),
383                error,
384            ));
385        }
386
387        let records = result.unwrap().0;
388
389        if records.len() > 1 {
390            return Err(Error::MultipleResults(String::from(
391                "Found multiple records for the search criteria",
392            )));
393        }
394
395        if records.is_empty() {
396            return Err(Error::NotFound(String::from(
397                "No records found for the search criteria",
398            )));
399        }
400
401        let record = records.first().unwrap();
402
403        Ok(SearchEntry::construct(record.to_owned()))
404    }
405
406    ///
407    /// Search a single value from the LDAP server. The search is performed using the provided filter.
408    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
409    /// This operation will treat all the attributes as single-valued, silently ignoring the possible extra
410    /// values.
411    ///
412    ///
413    /// # Arguments
414    ///
415    /// * `base` - The base DN to search for the user
416    /// * `scope` - The scope of the search
417    /// * `filter` - The filter to search for the user
418    /// * `attributes` - The attributes to return from the search
419    ///
420    ///
421    /// # Returns
422    ///
423    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
424    ///
425    ///
426    /// # Example
427    ///
428    /// ```no_run
429    /// use simple_ldap::{
430    ///     LdapClient, LdapConfig,
431    ///     filter::EqFilter,
432    ///     ldap3::Scope
433    /// };
434    /// use url::Url;
435    /// use serde::Deserialize;
436    ///
437    ///
438    /// #[derive(Debug, Deserialize)]
439    /// struct User {
440    ///     uid: String,
441    ///     cn: String,
442    ///     sn: String,
443    /// }
444    ///
445    /// #[tokio::main]
446    /// async fn main(){
447    ///     let ldap_config = LdapConfig {
448    ///         bind_dn: String::from("cn=manager"),
449    ///         bind_password: String::from("password"),
450    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
451    ///         dn_attribute: None,
452    ///         connection_settings: None
453    ///     };
454    ///
455    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
456    ///
457    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
458    ///     let user_result: User = client
459    ///         .search(
460    ///         "ou=people,dc=example,dc=com",
461    ///         Scope::OneLevel,
462    ///         &name_filter,
463    ///         vec!["cn", "sn", "uid"],
464    ///     ).await
465    ///     .unwrap();
466    /// }
467    /// ```
468    ///
469    pub async fn search<'a, F, A, S, T>(
470        &mut self,
471        base: &str,
472        scope: Scope,
473        filter: &F,
474        attributes: A,
475    ) -> Result<T, Error>
476    where
477        F: Filter,
478        A: AsRef<[S]> + Send + Sync + 'a,
479        S: AsRef<str> + Send + Sync + 'a,
480        T: for<'de> serde::Deserialize<'de>,
481    {
482        let search_entry = self.search_inner(base, scope, filter, attributes).await?;
483        to_value(search_entry)
484    }
485
486    ///
487    /// Search a single value from the LDAP server. The search is performed using the provided filter.
488    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
489    /// This operatrion is useful when records has multi-valued attributes.
490    ///
491    /// # Arguments
492    ///
493    /// * `base` - The base DN to search for the user
494    /// * `scope` - The scope of the search
495    /// * `filter` - The filter to search for the user
496    /// * `attributes` - The attributes to return from the search
497    ///
498    ///
499    /// # Returns
500    ///
501    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
502    ///
503    ///
504    /// # Example
505    ///
506    /// ```no_run
507    /// use simple_ldap::{
508    ///     LdapClient, LdapConfig,
509    ///     filter::EqFilter,
510    ///     ldap3::Scope
511    /// };
512    /// use url::Url;
513    /// use serde::Deserialize;
514    ///
515    ///
516    /// #[derive(Debug, Deserialize)]
517    /// struct TestMultiValued {
518    ///    key1: Vec<String>,
519    ///    key2: Vec<String>,
520    /// }
521    ///
522    /// #[tokio::main]
523    /// async fn main(){
524    ///     let ldap_config = LdapConfig {
525    ///         bind_dn: String::from("cn=manager"),
526    ///         bind_password: String::from("password"),
527    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
528    ///         dn_attribute: None,
529    ///         connection_settings: None
530    ///     };
531    ///
532    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
533    ///
534    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
535    ///     let user_result = client.search_multi_valued::<TestMultiValued>(
536    ///         "",
537    ///         Scope::OneLevel,
538    ///         &name_filter,
539    ///         &vec!["cn", "sn", "uid"]
540    ///     ).await;
541    /// }
542    /// ```
543    ///
544    pub async fn search_multi_valued<T: for<'a> serde::Deserialize<'a>>(
545        &mut self,
546        base: &str,
547        scope: Scope,
548        filter: &impl Filter,
549        attributes: &Vec<&str>,
550    ) -> Result<T, Error> {
551        let search_entry = self.search_inner(base, scope, filter, attributes).await?;
552        to_multi_value(search_entry)
553    }
554
555    ///
556    /// This method is used to search multiple records from the LDAP server. The search is performed using the provided filter.
557    /// Method will return a Stream. The stream will lazily fetch the results, resulting in a smaller
558    /// memory footprint.
559    ///
560    /// You might also want to take a look at [`streaming_search_paged()`].
561    ///
562    ///
563    /// # Arguments
564    ///
565    /// * `base` - The base DN to search for the user
566    /// * `scope` - The scope of the search
567    /// * `filter` - The filter to search for the user
568    /// * `attributes` - The attributes to return from the search
569    ///
570    ///
571    /// # Returns
572    ///
573    /// A stream that can be used to iterate through the search results.
574    ///
575    ///
576    /// ## Blocking drop caveat
577    ///
578    /// Dropping this stream may issue blocking network requests to cancel the search.
579    /// Running the stream to it's end will minimize the chances of this happening.
580    /// You should take this into account if latency is critical to your application.
581    ///
582    /// We're waiting for [`AsyncDrop`](https://github.com/rust-lang/rust/issues/126482) for implementing this properly.
583    ///
584    ///
585    /// # Example
586    ///
587    /// ```no_run
588    /// use simple_ldap::{
589    ///     LdapClient, LdapConfig,
590    ///     filter::EqFilter,
591    ///     ldap3::Scope
592    /// };
593    /// use url::Url;
594    /// use serde::Deserialize;
595    /// use futures::StreamExt;
596    ///
597    ///
598    /// #[derive(Deserialize, Debug)]
599    /// struct User {
600    ///     uid: String,
601    ///     cn: String,
602    ///     sn: String,
603    /// }
604    ///
605    /// #[tokio::main]
606    /// async fn main(){
607    ///     let ldap_config = LdapConfig {
608    ///         bind_dn: String::from("cn=manager"),
609    ///         bind_password: String::from("password"),
610    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
611    ///         dn_attribute: None,
612    ///         connection_settings: None
613    ///     };
614    ///
615    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
616    ///
617    ///     let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
618    ///     let attributes = vec!["cn", "sn", "uid"];
619    ///
620    ///     let stream = client.streaming_search(
621    ///         "",
622    ///         Scope::OneLevel,
623    ///         &name_filter,
624    ///         attributes
625    ///     ).await.unwrap();
626    ///
627    ///     // The returned stream is not Unpin, so you may need to pin it to use certain operations,
628    ///     // such as next() below.
629    ///     let mut pinned_steam = Box::pin(stream);
630    ///
631    ///     while let Some(result) = pinned_steam.next().await {
632    ///         match result {
633    ///             Ok(element) => {
634    ///                 let user: User = element.to_record().unwrap();
635    ///                 println!("User: {:?}", user);
636    ///             }
637    ///             Err(err) => {
638    ///                 println!("Error: {:?}", err);
639    ///             }
640    ///         }
641    ///     }
642    /// }
643    /// ```
644    ///
645    pub async fn streaming_search<'a, F, A, S>(
646        // This self reference  lifetime has some nuance behind it.
647        //
648        // In principle it could just be a value, but then you wouldn't be able to call this
649        // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
650        //
651        // The lifetime is needed to guarantee that the client is not returned to the pool before
652        // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
653        // just makes a copy. So this lifetime is here just to enforce correct pool usage.
654        &'a mut self,
655        base: &str,
656        scope: Scope,
657        filter: &F,
658        attributes: A,
659    ) -> Result<impl Stream<Item = Result<Record, Error>> + use<'a, F, A, S>, Error>
660    where
661        F: Filter,
662        A: AsRef<[S]> + Send + Sync + 'a,
663        S: AsRef<str> + Send + Sync + 'a,
664    {
665        let search_stream = self
666            .ldap
667            .streaming_search(base, scope, filter.filter().as_str(), attributes)
668            .await
669            .map_err(|ldap_error| {
670                Error::Query(
671                    format!("Error searching for record: {ldap_error:?}"),
672                    ldap_error,
673                )
674            })?;
675
676        to_native_stream(search_stream)
677    }
678
679    ///
680    /// This method is used to search multiple records from the LDAP server and results will be paginated.
681    /// Method will return a Stream. The stream will lazily fetch batches of results resulting in a smaller
682    /// memory footprint.
683    ///
684    /// This is the recommended search method, especially if you don't know that the result set is going to be small.
685    ///
686    ///
687    /// # Arguments
688    ///
689    /// * `base` - The base DN to search for the user
690    /// * `scope` - The scope of the search
691    /// * `filter` - The filter to search for the user
692    /// * `page_size` - The maximum number of records in a page
693    /// * `attributes` - The attributes to return from the search
694    ///
695    ///
696    /// # Returns
697    ///
698    /// A stream that can be used to iterate through the search results.
699    ///
700    ///
701    /// ## Blocking drop caveat
702    ///
703    /// Dropping this stream may issue blocking network requests to cancel the search.
704    /// Running the stream to it's end will minimize the chances of this happening.
705    /// You should take this into account if latency is critical to your application.
706    ///
707    /// We're waiting for [`AsyncDrop`](https://github.com/rust-lang/rust/issues/126482) for implementing this properly.
708    ///
709    ///
710    ///
711    /// # Example
712    ///
713    /// ```no_run
714    /// use simple_ldap::{
715    ///     LdapClient, LdapConfig,
716    ///     filter::EqFilter,
717    ///     ldap3::Scope
718    /// };
719    /// use url::Url;
720    /// use serde::Deserialize;
721    /// use futures::{StreamExt, TryStreamExt};
722    ///
723    ///
724    /// #[derive(Deserialize, Debug)]
725    /// struct User {
726    ///     uid: String,
727    ///     cn: String,
728    ///     sn: String,
729    /// }
730    ///
731    /// #[tokio::main]
732    /// async fn main(){
733    ///     let ldap_config = LdapConfig {
734    ///         bind_dn: String::from("cn=manager"),
735    ///         bind_password: String::from("password"),
736    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
737    ///         dn_attribute: None,
738    ///         connection_settings: None
739    ///     };
740    ///
741    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
742    ///
743    ///     let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
744    ///     let attributes = vec!["cn", "sn", "uid"];
745    ///
746    ///     let stream = client.streaming_search_paged(
747    ///         "",
748    ///         Scope::OneLevel,
749    ///         &name_filter,
750    ///         attributes,
751    ///         200 // The pagesize
752    ///     ).await.unwrap();
753    ///
754    ///     // Map the search results to User type.
755    ///     stream.and_then(async |record| record.to_record())
756    ///          // Do something with the Users concurrently.
757    ///         .try_for_each(async |user: User| {
758    ///             println!("User: {:?}", user);
759    ///             Ok(())
760    ///         })
761    ///         .await
762    ///         .unwrap();
763    /// }
764    /// ```
765    ///
766    pub async fn streaming_search_paged<'a, F, A, S>(
767        // This self reference  lifetime has some nuance behind it.
768        //
769        // In principle it could just be a value, but then you wouldn't be able to call this
770        // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
771        //
772        // The lifetime is needed to guarantee that the client is not returned to the pool before
773        // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
774        // just makes copy. So this lifetime is here just to enforce correct pool usage.
775        &'a mut self,
776        base: &str,
777        scope: Scope,
778        filter: &F,
779        attributes: A,
780        page_size: i32,
781    ) -> Result<impl Stream<Item = Result<Record, Error>> + use<'a, F, A, S>, Error>
782    where
783        F: Filter,
784        // PagedResults requires Clone and Debug too.
785        A: AsRef<[S]> + Send + Sync + Clone + fmt::Debug + 'a,
786        S: AsRef<str> + Send + Sync + Clone + fmt::Debug + 'a,
787    {
788        let adapters: Vec<Box<dyn Adapter<'a, S, A>>> = vec![
789            Box::new(EntriesOnly::new()),
790            Box::new(PagedResults::new(page_size)),
791        ];
792        let search_stream = self
793            .ldap
794            .streaming_search_with(adapters, base, scope, filter.filter().as_str(), attributes)
795            .await
796            .map_err(|ldap_error| {
797                Error::Query(
798                    format!("Error searching for record: {ldap_error:?}"),
799                    ldap_error,
800                )
801            })?;
802
803        to_native_stream(search_stream)
804    }
805
806    ///
807    /// Create a new record in the LDAP server. The record will be created in the provided base DN.
808    ///
809    /// # Arguments
810    ///
811    /// * `uid` - The uid of the record
812    /// * `base` - The base DN to create the record
813    /// * `data` - The attributes of the record
814    ///
815    ///
816    /// # Returns
817    ///
818    /// * `Result<(), Error>` - Returns an error if the record creation fails
819    ///
820    ///
821    /// # Example
822    ///
823    /// ```no_run
824    /// use simple_ldap::{LdapClient, LdapConfig};
825    /// use url::Url;
826    /// use std::collections::HashSet;
827    ///
828    /// #[tokio::main]
829    /// async fn main(){
830    ///     let ldap_config = LdapConfig {
831    ///         bind_dn: String::from("cn=manager"),
832    ///         bind_password: String::from("password"),
833    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
834    ///         dn_attribute: None,
835    ///         connection_settings: None
836    ///     };
837    ///
838    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
839    ///
840    ///     let data = vec![
841    ///         ( "objectClass",HashSet::from(["organizationalPerson", "inetorgperson", "top", "person"]),),
842    ///         ("uid",HashSet::from(["bd9b91ec-7a69-4166-bf67-cc7e553b2fd9"]),),
843    ///         ("cn", HashSet::from(["Kasun"])),
844    ///         ("sn", HashSet::from(["Ranasingh"])),
845    ///     ];
846    ///
847    ///     let result = client.create("bd9b91ec-7a69-4166-bf67-cc7e553b2fd9", "ou=people,dc=example,dc=com", data).await;
848    /// }
849    /// ```
850    ///
851    pub async fn create(
852        &mut self,
853        uid: &str,
854        base: &str,
855        data: Vec<(&str, HashSet<&str>)>,
856    ) -> Result<(), Error> {
857        let dn = format!("uid={uid},{base}");
858        let save = self.ldap.add(dn.as_str(), data).await;
859        if let Err(err) = save {
860            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
861        }
862        let save = save.unwrap().success();
863
864        if let Err(err) = save {
865            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
866        }
867        let res = save.unwrap();
868        debug!("Sucessfully created record result: {:?}", res);
869        Ok(())
870    }
871
872    ///
873    /// Update a record in the LDAP server. The record will be updated in the provided base DN.
874    ///
875    /// # Arguments
876    ///
877    /// * `uid` - The uid of the record
878    /// * `base` - The base DN to update the record
879    /// * `data` - The attributes of the record
880    /// * `new_uid` - The new uid of the record. If the new uid is provided, the uid of the record will be updated.
881    ///
882    ///
883    /// # Returns
884    ///
885    /// * `Result<(), Error>` - Returns an error if the record update fails
886    ///
887    ///
888    /// # Example
889    ///
890    /// ```no_run
891    /// use simple_ldap::{
892    ///     LdapClient, LdapConfig,
893    ///     ldap3::Mod
894    /// };
895    /// use url::Url;
896    /// use std::collections::HashSet;
897    ///
898    /// #[tokio::main]
899    /// async fn main(){
900    ///     let ldap_config = LdapConfig {
901    ///         bind_dn: String::from("cn=manager"),
902    ///         bind_password: String::from("password"),
903    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
904    ///         dn_attribute: None,
905    ///         connection_settings: None
906    ///     };
907    ///
908    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
909    ///
910    ///     let data = vec![
911    ///         Mod::Replace("cn", HashSet::from(["Jhon_Update"])),
912    ///         Mod::Replace("sn", HashSet::from(["Eliet_Update"])),
913    ///     ];
914    ///
915    ///     let result = client.update(
916    ///         "e219fbc0-6df5-4bc3-a6ee-986843bb157e",
917    ///         "ou=people,dc=example,dc=com",
918    ///         data,
919    ///         None
920    ///     ).await;
921    /// }
922    /// ```
923    ///
924    pub async fn update(
925        &mut self,
926        uid: &str,
927        base: &str,
928        data: Vec<Mod<&str>>,
929        new_uid: Option<&str>,
930    ) -> Result<(), Error> {
931        let dn = format!("uid={uid},{base}");
932
933        let res = self.ldap.modify(dn.as_str(), data).await;
934        if let Err(err) = res {
935            return Err(Error::Update(
936                format!("Error updating record: {err:?}"),
937                err,
938            ));
939        }
940
941        let res = res.unwrap().success();
942        if let Err(err) = res {
943            match err {
944                LdapError::LdapResult { result } => {
945                    if result.rc == NO_SUCH_RECORD {
946                        return Err(Error::NotFound(format!(
947                            "No records found for the uid: {uid:?}"
948                        )));
949                    }
950                }
951                _ => {
952                    return Err(Error::Update(
953                        format!("Error updating record: {err:?}"),
954                        err,
955                    ));
956                }
957            }
958        }
959
960        if new_uid.is_none() {
961            return Ok(());
962        }
963
964        let new_uid = new_uid.unwrap();
965        if !uid.eq_ignore_ascii_case(new_uid) {
966            let new_dn = format!("uid={new_uid}");
967            let dn_update = self
968                .ldap
969                .modifydn(dn.as_str(), new_dn.as_str(), true, None)
970                .await;
971            if let Err(err) = dn_update {
972                error!("Failed to update dn for record {:?} error {:?}", uid, err);
973                return Err(Error::Update(
974                    format!("Failed to update dn for record {uid:?}"),
975                    err,
976                ));
977            }
978
979            let dn_update = dn_update.unwrap().success();
980            if let Err(err) = dn_update {
981                error!("Failed to update dn for record {:?} error {:?}", uid, err);
982                return Err(Error::Update(
983                    format!("Failed to update dn for record {uid:?}"),
984                    err,
985                ));
986            }
987
988            let res = dn_update.unwrap();
989            debug!("Sucessfully updated dn result: {:?}", res);
990        }
991
992        Ok(())
993    }
994
995    ///
996    /// Delete a record in the LDAP server. The record will be deleted in the provided base DN.
997    ///
998    /// # Arguments
999    ///
1000    /// * `uid` - The uid of the record
1001    /// * `base` - The base DN to delete the record
1002    ///
1003    ///
1004    /// # Returns
1005    ///
1006    /// * `Result<(), Error>` - Returns an error if the record delete fails
1007    ///
1008    ///
1009    /// # Example
1010    ///
1011    /// ```no_run
1012    /// use simple_ldap::{LdapClient, LdapConfig};
1013    /// use url::Url;
1014    ///
1015    /// #[tokio::main]
1016    /// async fn main(){
1017    ///     let ldap_config = LdapConfig {
1018    ///         bind_dn: String::from("cn=manager"),
1019    ///         bind_password: String::from("password"),
1020    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1021    ///         dn_attribute: None,
1022    ///         connection_settings: None
1023    ///     };
1024    ///
1025    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1026    ///
1027    ///     let result = client.delete("e219fbc0-6df5-4bc3-a6ee-986843bb157e", "ou=people,dc=example,dc=com").await;
1028    /// }
1029    /// ```
1030    pub async fn delete(&mut self, uid: &str, base: &str) -> Result<(), Error> {
1031        let dn = format!("uid={uid},{base}");
1032        let delete = self.ldap.delete(dn.as_str()).await;
1033
1034        if let Err(err) = delete {
1035            return Err(Error::Delete(
1036                format!("Error deleting record: {err:?}"),
1037                err,
1038            ));
1039        }
1040        let delete = delete.unwrap().success();
1041        if let Err(err) = delete {
1042            match err {
1043                LdapError::LdapResult { result } => {
1044                    if result.rc == NO_SUCH_RECORD {
1045                        return Err(Error::NotFound(format!(
1046                            "No records found for the uid: {uid:?}"
1047                        )));
1048                    }
1049                }
1050                _ => {
1051                    return Err(Error::Delete(
1052                        format!("Error deleting record: {err:?}"),
1053                        err,
1054                    ));
1055                }
1056            }
1057        }
1058        debug!("Sucessfully deleted record result: {:?}", uid);
1059        Ok(())
1060    }
1061
1062    ///
1063    /// Create a new group in the LDAP server. The group will be created in the provided base DN.
1064    ///
1065    /// # Arguments
1066    ///
1067    /// * `group_name` - The name of the group
1068    /// * `group_ou` - The ou of the group
1069    /// * `description` - The description of the group
1070    ///
1071    /// # Returns
1072    ///
1073    /// * `Result<(), Error>` - Returns an error if the group creation fails
1074    ///
1075    ///
1076    /// # Example
1077    ///
1078    /// ```no_run
1079    /// use simple_ldap::{LdapClient, LdapConfig};
1080    /// use url::Url;
1081    ///
1082    /// #[tokio::main]
1083    /// async fn main(){
1084    ///     let ldap_config = LdapConfig {
1085    ///         bind_dn: String::from("cn=manager"),
1086    ///         bind_password: String::from("password"),
1087    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1088    ///         dn_attribute: None,
1089    ///         connection_settings: None
1090    ///     };
1091    ///
1092    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1093    ///
1094    ///     let result = client.create_group("test_group", "ou=groups,dc=example,dc=com", "test group").await;
1095    /// }
1096    /// ```
1097    pub async fn create_group(
1098        &mut self,
1099        group_name: &str,
1100        group_ou: &str,
1101        description: &str,
1102    ) -> Result<(), Error> {
1103        let dn = format!("cn={group_name},{group_ou}");
1104
1105        let data = vec![
1106            ("objectClass", HashSet::from(["top", "groupOfNames"])),
1107            ("cn", HashSet::from([group_name])),
1108            ("ou", HashSet::from([group_ou])),
1109            ("description", HashSet::from([description])),
1110        ];
1111        let save = self.ldap.add(dn.as_str(), data).await;
1112        if let Err(err) = save {
1113            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
1114        }
1115        let save = save.unwrap().success();
1116
1117        if let Err(err) = save {
1118            return Err(Error::Create(format!("Error creating group: {err:?}"), err));
1119        }
1120        let res = save.unwrap();
1121        debug!("Sucessfully created group result: {:?}", res);
1122        Ok(())
1123    }
1124
1125    ///
1126    /// Add users to a group in the LDAP server. The group will be updated in the provided base DN.
1127    ///
1128    /// # Arguments
1129    ///
1130    /// * `users` - The list of users to add to the group
1131    /// * `group_dn` - The dn of the group
1132    ///
1133    ///
1134    /// # Returns
1135    ///
1136    /// * `Result<(), Error>` - Returns an error if failed to add users to the group
1137    ///
1138    ///
1139    /// # Example
1140    ///
1141    /// ```no_run
1142    /// use simple_ldap::{LdapClient, LdapConfig};
1143    /// use url::Url;
1144    ///
1145    /// #[tokio::main]
1146    /// async fn main(){
1147    ///     let ldap_config = LdapConfig {
1148    ///         bind_dn: String::from("cn=manager"),
1149    ///         bind_password: String::from("password"),
1150    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1151    ///         dn_attribute: None,
1152    ///         connection_settings: None
1153    ///     };
1154    ///
1155    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1156    ///
1157    ///     let result = client.add_users_to_group(
1158    ///         vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"],
1159    ///         "cn=test_group,ou=groups,dc=example,dc=com").await;
1160    /// }
1161    /// ```
1162    pub async fn add_users_to_group(
1163        &mut self,
1164        users: Vec<&str>,
1165        group_dn: &str,
1166    ) -> Result<(), Error> {
1167        let mut mods = Vec::new();
1168        let users = users.iter().copied().collect::<HashSet<&str>>();
1169        mods.push(Mod::Replace("member", users));
1170        let res = self.ldap.modify(group_dn, mods).await;
1171        if let Err(err) = res {
1172            return Err(Error::Update(
1173                format!("Error updating record: {err:?}"),
1174                err,
1175            ));
1176        }
1177
1178        let res = res.unwrap().success();
1179        if let Err(err) = res {
1180            match err {
1181                LdapError::LdapResult { result } => {
1182                    if result.rc == NO_SUCH_RECORD {
1183                        return Err(Error::NotFound(format!(
1184                            "No records found for the uid: {group_dn:?}"
1185                        )));
1186                    }
1187                }
1188                _ => {
1189                    return Err(Error::Update(
1190                        format!("Error updating record: {err:?}"),
1191                        err,
1192                    ));
1193                }
1194            }
1195        }
1196        Ok(())
1197    }
1198
1199    ///
1200    /// Get users of a group in the LDAP server. The group will be searched in the provided base DN.
1201    ///
1202    /// # Arguments
1203    ///
1204    /// * `group_dn` - The dn of the group
1205    /// * `base_dn` - The base dn to search for the users
1206    /// * `scope` - The scope of the search
1207    /// * `attributes` - The attributes to return from the search
1208    ///
1209    ///
1210    /// # Returns
1211    ///
1212    /// * `Result<Vec<T>, Error>` - Returns a vector of structs of type T
1213    ///
1214    ///
1215    /// # Example
1216    ///
1217    /// ```no_run
1218    /// use simple_ldap::{
1219    ///     LdapClient, LdapConfig,
1220    ///     ldap3::Scope
1221    /// };
1222    /// use url::Url;
1223    /// use serde::Deserialize;
1224    ///
1225    /// #[derive(Debug, Deserialize)]
1226    /// struct User {
1227    ///     uid: String,
1228    ///     cn: String,
1229    ///     sn: String,
1230    /// }
1231    ///
1232    /// #[tokio::main]
1233    /// async fn main(){
1234    ///     let ldap_config = LdapConfig {
1235    ///         bind_dn: String::from("cn=manager"),
1236    ///         bind_password: String::from("password"),
1237    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1238    ///         dn_attribute: None,
1239    ///         connection_settings: None
1240    ///     };
1241    ///
1242    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1243    ///
1244    ///     let members: Vec<User> = client.get_members(
1245    ///         "cn=test_group,ou=groups,dc=example,dc=com",
1246    ///         "ou=people,dc=example,dc=com",
1247    ///         Scope::OneLevel,
1248    ///         vec!["cn", "sn", "uid"]
1249    ///     ).await
1250    ///     .unwrap();
1251    /// }
1252    /// ```
1253    ///
1254    pub async fn get_members<'a, A, S, T>(
1255        &mut self,
1256        group_dn: &str,
1257        base_dn: &str,
1258        scope: Scope,
1259        attributes: A,
1260    ) -> Result<Vec<T>, Error>
1261    where
1262        A: AsRef<[S]> + Send + Sync + 'a,
1263        S: AsRef<str> + Send + Sync + 'a,
1264        T: for<'de> serde::Deserialize<'de>,
1265    {
1266        let search = self
1267            .ldap
1268            .search(
1269                group_dn,
1270                Scope::Base,
1271                "(objectClass=groupOfNames)",
1272                vec!["member"],
1273            )
1274            .await;
1275
1276        if let Err(error) = search {
1277            return Err(Error::Query(
1278                format!("Error searching for record: {error:?}"),
1279                error,
1280            ));
1281        }
1282        let result = search.unwrap().success();
1283        if let Err(error) = result {
1284            return Err(Error::Query(
1285                format!("Error searching for record: {error:?}"),
1286                error,
1287            ));
1288        }
1289
1290        let records = result.unwrap().0;
1291
1292        if records.len() > 1 {
1293            return Err(Error::MultipleResults(String::from(
1294                "Found multiple records for the search criteria",
1295            )));
1296        }
1297
1298        if records.is_empty() {
1299            return Err(Error::NotFound(String::from(
1300                "No records found for the search criteria",
1301            )));
1302        }
1303
1304        let record = records.first().unwrap();
1305
1306        let mut or_filter = OrFilter::default();
1307
1308        let search_entry = SearchEntry::construct(record.to_owned());
1309        search_entry
1310            .attrs
1311            .into_iter()
1312            .filter(|(_, value)| !value.is_empty())
1313            .map(|(arrta, value)| (arrta.to_owned(), value.to_owned()))
1314            .filter(|(attra, _)| attra.eq("member"))
1315            .flat_map(|(_, value)| value)
1316            .map(|val| {
1317                val.split(',').collect::<Vec<&str>>()[0]
1318                    .split('=')
1319                    .map(|split| split.to_string())
1320                    .collect::<Vec<String>>()
1321            })
1322            .map(|uid| EqFilter::from(uid[0].to_string(), uid[1].to_string()))
1323            .for_each(|eq| or_filter.add(Box::new(eq)));
1324
1325        let result = self
1326            .streaming_search(base_dn, scope, &or_filter, attributes)
1327            .await;
1328
1329        let mut members = Vec::new();
1330        match result {
1331            Ok(result) => {
1332                let mut stream = Box::pin(result);
1333                while let Some(member) = stream.next().await {
1334                    match member {
1335                        Ok(member) => {
1336                            let user: T = member.to_record().unwrap();
1337                            members.push(user);
1338                        }
1339                        Err(err) => {
1340                            // TODO: Exit with an error instead?
1341                            error!("Error getting member error {:?}", err);
1342                        }
1343                    }
1344                }
1345                return Ok(members);
1346            }
1347            Err(err) => {
1348                // TODO: Exit with an error instead?
1349                error!("Error getting members {:?} error {:?}", group_dn, err);
1350            }
1351        }
1352
1353        Ok(members)
1354    }
1355
1356    ///
1357    /// Remove users from a group in the LDAP server. The group will be updated in the provided base DN.
1358    /// This method will remove all the users provided from the group.
1359    ///
1360    ///
1361    /// # Arguments
1362    ///
1363    /// * `group_dn` - The dn of the group
1364    /// * `users` - The list of users to remove from the group
1365    ///
1366    ///
1367    /// # Returns
1368    ///
1369    /// * `Result<(), Error>` - Returns an error if failed to remove users from the group
1370    ///
1371    ///
1372    /// # Example
1373    ///
1374    /// ```no_run
1375    /// use simple_ldap::{LdapClient, LdapConfig};
1376    /// use url::Url;
1377    /// use std::collections::HashSet;
1378    ///
1379    /// #[tokio::main]
1380    /// async fn main(){
1381    ///     let ldap_config = LdapConfig {
1382    ///         bind_dn: String::from("cn=manager"),
1383    ///         bind_password: String::from("password"),
1384    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1385    ///         dn_attribute: None,
1386    ///         connection_settings: None
1387    ///     };
1388    ///
1389    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1390    ///
1391    ///     let result = client.remove_users_from_group("cn=test_group,ou=groups,dc=example,dc=com",
1392    ///     vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"]).await;
1393    /// }
1394    /// ```
1395    pub async fn remove_users_from_group(
1396        &mut self,
1397        group_dn: &str,
1398        users: Vec<&str>,
1399    ) -> Result<(), Error> {
1400        let mut mods = Vec::new();
1401        let users = users.iter().copied().collect::<HashSet<&str>>();
1402        mods.push(Mod::Delete("member", users));
1403        let res = self.ldap.modify(group_dn, mods).await;
1404        if let Err(err) = res {
1405            return Err(Error::Update(
1406                format!("Error removing users from group:{group_dn:?}: {err:?}"),
1407                err,
1408            ));
1409        }
1410
1411        let res = res.unwrap().success();
1412        if let Err(err) = res {
1413            match err {
1414                LdapError::LdapResult { result } => {
1415                    if result.rc == NO_SUCH_RECORD {
1416                        return Err(Error::NotFound(format!(
1417                            "No records found for the uid: {group_dn:?}"
1418                        )));
1419                    }
1420                }
1421                _ => {
1422                    return Err(Error::Update(
1423                        format!("Error removing users from group:{group_dn:?}: {err:?}"),
1424                        err,
1425                    ));
1426                }
1427            }
1428        }
1429        Ok(())
1430    }
1431
1432    ///
1433    /// Get the groups associated with a user in the LDAP server. The user will be searched in the provided base DN.
1434    ///
1435    /// # Arguments
1436    ///
1437    /// * `group_ou` - The ou to search for the groups
1438    /// * `user_dn` - The dn of the user
1439    ///
1440    /// # Returns
1441    ///
1442    /// * `Result<Vec<String>, Error>` - Returns a vector of group names
1443    ///
1444    ///
1445    /// # Example
1446    ///
1447    /// ```no_run
1448    /// use simple_ldap::{LdapClient, LdapConfig};
1449    /// use url::Url;
1450    ///
1451    /// #[tokio::main]
1452    /// async fn main(){
1453    ///     let ldap_config = LdapConfig {
1454    ///         bind_dn: String::from("cn=manager"),
1455    ///         bind_password: String::from("password"),
1456    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1457    ///         dn_attribute: None,
1458    ///         connection_settings: None
1459    ///     };
1460    ///
1461    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1462    ///
1463    ///     let result = client.get_associtated_groups("ou=groups,dc=example,dc=com",
1464    ///     "uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com").await;
1465    /// }
1466    /// ```
1467    pub async fn get_associtated_groups(
1468        &mut self,
1469        group_ou: &str,
1470        user_dn: &str,
1471    ) -> Result<Vec<String>, Error> {
1472        let group_filter = Box::new(EqFilter::from(
1473            "objectClass".to_string(),
1474            "groupOfNames".to_string(),
1475        ));
1476
1477        let user_filter = Box::new(EqFilter::from("member".to_string(), user_dn.to_string()));
1478        let mut filter = AndFilter::default();
1479        filter.add(group_filter);
1480        filter.add(user_filter);
1481
1482        let search = self
1483            .ldap
1484            .search(
1485                group_ou,
1486                Scope::Subtree,
1487                filter.filter().as_str(),
1488                vec!["cn"],
1489            )
1490            .await;
1491
1492        if let Err(error) = search {
1493            return Err(Error::Query(
1494                format!("Error searching for record: {error:?}"),
1495                error,
1496            ));
1497        }
1498        let result = search.unwrap().success();
1499        if let Err(error) = result {
1500            return Err(Error::Query(
1501                format!("Error searching for record: {error:?}"),
1502                error,
1503            ));
1504        }
1505
1506        let records = result.unwrap().0;
1507
1508        if records.is_empty() {
1509            return Err(Error::NotFound(String::from(
1510                "User does not belong to any groups",
1511            )));
1512        }
1513
1514        let record = records
1515            .iter()
1516            .map(|record| SearchEntry::construct(record.to_owned()))
1517            .map(|se| se.attrs)
1518            .flat_map(|att| {
1519                att.get("cn")
1520                    .unwrap()
1521                    .iter()
1522                    .map(|x| x.to_owned())
1523                    .collect::<Vec<String>>()
1524            })
1525            .collect::<Vec<String>>();
1526
1527        Ok(record)
1528    }
1529}
1530
1531/// A proxy type for deriving `Serialize` for `ldap3::SearchEntry`.
1532/// https://serde.rs/remote-derive.html
1533#[derive(Serialize)]
1534#[serde(remote = "ldap3::SearchEntry")]
1535struct Ldap3SearchEntry {
1536    /// Entry DN.
1537    pub dn: String,
1538    /// Attributes.
1539    /// Flattening to ease up the serialization step.
1540    #[serde(flatten)]
1541    pub attrs: HashMap<String, Vec<String>>,
1542    /// Binary-valued attributes.
1543    /// Flattening to ease up the serialization step.
1544    #[serde(flatten)]
1545    pub bin_attrs: HashMap<String, Vec<Vec<u8>>>,
1546}
1547
1548/// This is needed for invoking the deserialize impl directly.
1549/// https://serde.rs/remote-derive.html#invoking-the-remote-impl-directly
1550#[derive(Serialize)]
1551#[serde(transparent)]
1552struct SerializeWrapper(#[serde(with = "Ldap3SearchEntry")] ldap3::SearchEntry);
1553
1554// Allowing users to debug serialization issues from the logs.
1555#[instrument(level = Level::DEBUG)]
1556fn to_signle_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1557    let string_attributes = search_entry
1558        .attrs
1559        .into_iter()
1560        .filter(|(_, value)| !value.is_empty())
1561        .map(|(arrta, value)| {
1562            if value.len() > 1 {
1563                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1564            }
1565            (Value::String(arrta), map_to_single_value(value.first()))
1566        });
1567
1568    let binary_attributes = search_entry
1569        .bin_attrs
1570        .into_iter()
1571        // I wonder if it's possible to have empties here..?
1572        .filter(|(_, value)| !value.is_empty())
1573        .map(|(arrta, value)| {
1574            if value.len() > 1 {
1575                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1576            }
1577            (
1578                Value::String(arrta),
1579                map_to_single_value_bin(value.first().cloned()),
1580            )
1581        });
1582
1583    // DN is always returned.
1584    // Adding it to the serialized fields as well.
1585    let dn_iter = iter::once(search_entry.dn)
1586        .map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
1587
1588    let all_fields = string_attributes
1589        .chain(binary_attributes)
1590        .chain(dn_iter)
1591        .collect();
1592
1593    let value = serde_value::Value::Map(all_fields);
1594
1595    T::deserialize(value)
1596        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1597}
1598
1599#[instrument(level = Level::DEBUG)]
1600fn to_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1601    let string_attributes = search_entry
1602        .attrs
1603        .into_iter()
1604        .filter(|(_, value)| !value.is_empty())
1605        .map(|(arrta, value)| {
1606            if value.len() == 1 {
1607                return (Value::String(arrta), map_to_single_value(value.first()));
1608            }
1609            (Value::String(arrta), map_to_multi_value(value))
1610        });
1611
1612    let binary_attributes = search_entry
1613        .bin_attrs
1614        .into_iter()
1615        // I wonder if it's possible to have empties here..?
1616        .filter(|(_, value)| !value.is_empty())
1617        .map(|(arrta, value)| {
1618            if value.len() > 1 {
1619                //#TODO: This is a bit of a hack to get multi-valued attributes to work for non binary values. SHOULD fix this.
1620                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1621            }
1622            (
1623                Value::String(arrta),
1624                map_to_single_value_bin(value.first().cloned()),
1625            )
1626            // if value.len() == 1 {
1627            //     return (
1628            //         Value::String(arrta),
1629            //         map_to_single_value_bin(value.first().cloned()),
1630            //     );
1631            // }
1632            // (Value::String(arrta), map_to_multi_value_bin(value))
1633        });
1634
1635    // DN is always returned.
1636    // Adding it to the serialized fields as well.
1637    let dn_iter = iter::once(search_entry.dn)
1638        .map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
1639
1640    let all_fields = string_attributes
1641        .chain(binary_attributes)
1642        .chain(dn_iter)
1643        .collect();
1644
1645    let value = serde_value::Value::Map(all_fields);
1646
1647    T::deserialize(value)
1648        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1649}
1650
1651fn map_to_multi_value(attra_value: Vec<String>) -> serde_value::Value {
1652    serde_value::Value::Seq(
1653        attra_value
1654            .iter()
1655            .map(|value| serde_value::Value::String(value.to_string()))
1656            .collect(),
1657    )
1658}
1659
1660fn map_to_multi_value_bin(attra_values: Vec<Vec<u8>>) -> serde_value::Value {
1661    let value_bytes = attra_values
1662        .iter()
1663        .map(|value| {
1664            value
1665                .iter()
1666                .map(|byte| Value::U8(*byte))
1667                .collect::<Vec<Value>>()
1668        })
1669        .map(serde_value::Value::Seq)
1670        .collect::<Vec<Value>>();
1671
1672    serde_value::Value::Seq(value_bytes)
1673}
1674
1675// Allowing users to debug serialization issues from the logs.
1676#[instrument(level = Level::DEBUG)]
1677fn to_multi_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1678    let value = serde_value::to_value(SerializeWrapper(search_entry)).map_err(|err| {
1679        Error::Mapping(format!("Error converting search result to object, {err:?}"))
1680    })?;
1681
1682    T::deserialize(value)
1683        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1684}
1685
1686fn map_to_single_value(attra_value: Option<&String>) -> serde_value::Value {
1687    match attra_value {
1688        Some(value) => serde_value::Value::String(value.to_string()),
1689        None => serde_value::Value::Option(Option::None),
1690    }
1691}
1692
1693fn map_to_single_value_bin(attra_values: Option<Vec<u8>>) -> serde_value::Value {
1694    match attra_values {
1695        Some(bytes) => {
1696            let value_bytes = bytes.into_iter().map(Value::U8).collect();
1697
1698            serde_value::Value::Seq(value_bytes)
1699        }
1700        None => serde_value::Value::Option(Option::None),
1701    }
1702}
1703
1704/// This wrapper exists solely for the purpose of runnig some cleanup in `drop()`.
1705///
1706/// This should be refactored to implement `AsyncDrop` when it gets stabilized:
1707/// https://github.com/rust-lang/rust/issues/126482
1708struct StreamDropWrapper<'a, S, A>
1709where
1710    S: AsRef<str> + Send + Sync + 'a,
1711    A: AsRef<[S]> + Send + Sync + 'a,
1712{
1713    pub search_stream: SearchStream<'a, S, A>,
1714}
1715
1716impl<'a, S, A> Drop for StreamDropWrapper<'a, S, A>
1717where
1718    S: AsRef<str> + Send + Sync + 'a,
1719    A: AsRef<[S]> + Send + Sync + 'a,
1720{
1721    fn drop(&mut self) {
1722        // Making this blocking call in drop is suboptimal.
1723        // We should use async-drop, when it's stabilized:
1724        // https://github.com/rust-lang/rust/issues/126482
1725        block_on(self.cleanup());
1726    }
1727}
1728
1729impl<'a, S, A> StreamDropWrapper<'a, S, A>
1730where
1731    S: AsRef<str> + Send + Sync + 'a,
1732    A: AsRef<[S]> + Send + Sync + 'a,
1733{
1734    ///
1735    /// Cleanup the stream. This method should be called when dropping the stream.
1736    ///
1737    /// This method will cleanup the stream and close the connection.
1738    ///
1739    ///
1740    /// # Errors
1741    ///
1742    /// No errors are returned, as this is meant to be called from `drop()`.
1743    /// Traces are emitted though.
1744    ///
1745    #[instrument(level = Level::TRACE, skip_all)]
1746    async fn cleanup(&mut self) -> () {
1747        // Calling this might not be strictly necessary,
1748        // but it's probably expected so let's just do it.
1749        // I don't think this does any networkig most of the time.
1750        let finish_result = self.search_stream.finish().await;
1751
1752        match finish_result.success() {
1753            Ok(_) => (), // All good.
1754            // This is returned if the stream is cancelled in the middle.
1755            // Which is fine for us.
1756            // https://ldap.com/ldap-result-code-reference-client-side-result-codes/#rc-userCanceled
1757            Err(LdapError::LdapResult {
1758                result: LdapResult { rc: 88, .. },
1759            }) => (),
1760            Err(finish_err) => error!("The stream finished with an error: {finish_err}"),
1761        }
1762
1763        match self.search_stream.state() {
1764            // Stream processed to the end, no need to cancel the operation.
1765            // This should be the common case.
1766            StreamState::Done | StreamState::Closed => (),
1767            StreamState::Error => {
1768                error!(
1769                    "Stream is in Error state. Not trying to cancel it as it could do more harm than good."
1770                );
1771            }
1772            StreamState::Fresh | StreamState::Active => {
1773                info!("Stream is still open. Issuing cancellation to the server.");
1774                let msgid = self.search_stream.ldap_handle().last_id();
1775                let result = self.search_stream.ldap_handle().abandon(msgid).await;
1776
1777                match result {
1778                    Ok(_) => (),
1779                    Err(err) => {
1780                        error!("Error abandoning search result: {:?}", err);
1781                        ()
1782                    }
1783                }
1784            }
1785        }
1786    }
1787}
1788
1789/// A helper to create native rust streams out of `ldap3::SearchStream`s.
1790fn to_native_stream<'a, S, A>(
1791    ldap3_stream: SearchStream<'a, S, A>,
1792) -> Result<impl Stream<Item = Result<Record, Error>> + use<'a, S, A>, Error>
1793where
1794    S: AsRef<str> + Send + Sync + 'a,
1795    A: AsRef<[S]> + Send + Sync + 'a,
1796{
1797    // This will handle stream cleanup.
1798    let stream_wrapper = StreamDropWrapper {
1799        search_stream: ldap3_stream,
1800    };
1801
1802    // Produce the steam itself by unfolding.
1803    let stream = stream::try_unfold(stream_wrapper, async |mut search| {
1804        match search.search_stream.next().await {
1805            // In the middle of the stream. Produce the next result.
1806            Ok(Some(result_entry)) => Ok(Some((
1807                Record {
1808                    search_entry: SearchEntry::construct(result_entry),
1809                },
1810                search,
1811            ))),
1812            // Stream is done.
1813            Ok(None) => Ok(None),
1814            Err(ldap_error) => Err(Error::Query(
1815                format!("Error getting next record: {ldap_error:?}"),
1816                ldap_error,
1817            )),
1818        }
1819    });
1820
1821    Ok(stream)
1822}
1823
1824/// The Record struct is used to map the search result to a struct.
1825/// The Record struct has a method to_record which will map the search result to a struct.
1826/// The Record struct has a method to_multi_valued_record which will map the search result to a struct with multi valued attributes.
1827//
1828// It would be nice to hide this record type from the public API and just expose already
1829// deserialized user types.
1830pub struct Record {
1831    search_entry: SearchEntry,
1832}
1833
1834impl Record {
1835    ///
1836    /// Create a new Record object with single valued attributes.
1837    /// This is essentially parsing the response records into usable types.
1838    //
1839    // This is kind of missnomer, as we aren't creating records here.
1840    // Perhaps something like "deserialize" would fit better?
1841    pub fn to_record<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1842        to_value(self.search_entry)
1843    }
1844
1845    #[deprecated(
1846        since = "6.0.0",
1847        note = "Use to_record instead. This method is deprecated and will be removed in future versions."
1848    )]
1849    pub fn to_multi_valued_record_<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1850        to_multi_value(self.search_entry)
1851    }
1852}
1853
1854pub enum StreamResult<T> {
1855    Record(T),
1856    Done,
1857    Finished,
1858}
1859
1860///
1861/// The error type for the LDAP client
1862///
1863#[derive(Debug, Error)]
1864pub enum Error {
1865    /// Error occured when performing a LDAP query
1866    #[error("{0}")]
1867    Query(String, #[source] LdapError),
1868    /// No records found for the search criteria
1869    #[error("{0}")]
1870    NotFound(String),
1871    /// Multiple records found for the search criteria
1872    #[error("{0}")]
1873    MultipleResults(String),
1874    /// Authenticating a user failed.
1875    #[error("{0}")]
1876    AuthenticationFailed(String),
1877    /// Error occured when creating a record
1878    #[error("{0}")]
1879    Create(String, #[source] LdapError),
1880    /// Error occured when updating a record
1881    #[error("{0}")]
1882    Update(String, #[source] LdapError),
1883    /// Error occured when deleting a record
1884    #[error("{0}")]
1885    Delete(String, #[source] LdapError),
1886    /// Error occured when mapping the search result to a struct
1887    #[error("{0}")]
1888    Mapping(String),
1889    /// Error occurred while attempting to create an LDAP connection
1890    #[error("{0}")]
1891    Connection(String, #[source] LdapError),
1892    /// Error occurred while attempting to close an LDAP connection.
1893    /// Includes unbind issues.
1894    #[error("{0}")]
1895    Close(String, #[source] LdapError),
1896    /// Error occurred while abandoning the search result
1897    #[error("{0}")]
1898    Abandon(String, #[source] LdapError),
1899}
1900
1901#[cfg(test)]
1902mod tests {
1903    //! Local tests that don't need to connect to a server.
1904
1905    use super::*;
1906    use anyhow::anyhow;
1907    use serde::Deserialize;
1908    use serde_with::OneOrMany;
1909    use serde_with::serde_as;
1910    use uuid::Uuid;
1911
1912    #[test]
1913    fn create_multi_value_test() {
1914        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1915        map.insert(
1916            "key1".to_string(),
1917            vec!["value1".to_string(), "value2".to_string()],
1918        );
1919        map.insert(
1920            "key2".to_string(),
1921            vec!["value3".to_string(), "value4".to_string()],
1922        );
1923
1924        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1925        let entry = SearchEntry {
1926            dn: dn.to_string(),
1927            attrs: map,
1928            bin_attrs: HashMap::new(),
1929        };
1930
1931        let test = to_multi_value::<TestMultiValued>(entry);
1932
1933        let test = test.unwrap();
1934        assert_eq!(test.key1, vec!["value1".to_string(), "value2".to_string()]);
1935        assert_eq!(test.key2, vec!["value3".to_string(), "value4".to_string()]);
1936        assert_eq!(test.dn, dn);
1937    }
1938
1939    #[test]
1940    fn create_single_value_test() {
1941        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1942        map.insert("key1".to_string(), vec!["value1".to_string()]);
1943        map.insert("key2".to_string(), vec!["value2".to_string()]);
1944        map.insert("key4".to_string(), vec!["value4".to_string()]);
1945
1946        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1947
1948        let entry = SearchEntry {
1949            dn: dn.to_string(),
1950            attrs: map,
1951            bin_attrs: HashMap::new(),
1952        };
1953
1954        let test = to_signle_value::<TestSingleValued>(entry);
1955
1956        let test = test.unwrap();
1957        assert_eq!(test.key1, "value1".to_string());
1958        assert_eq!(test.key2, "value2".to_string());
1959        assert!(test.key3.is_none());
1960        assert_eq!(test.key4.unwrap(), "value4".to_string());
1961        assert_eq!(test.dn, dn);
1962    }
1963
1964    #[test]
1965    fn create_to_value_string_test() {
1966        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1967        map.insert("key1".to_string(), vec!["value1".to_string()]);
1968        map.insert("key2".to_string(), vec!["value2".to_string()]);
1969        map.insert("key4".to_string(), vec!["value4".to_string()]);
1970        map.insert(
1971            "key5".to_string(),
1972            vec!["value5".to_string(), "value6".to_string()],
1973        );
1974
1975        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1976
1977        let entry = SearchEntry {
1978            dn: dn.to_string(),
1979            attrs: map,
1980            bin_attrs: HashMap::new(),
1981        };
1982
1983        let test = to_value::<TestValued>(entry);
1984
1985        let test = test.unwrap();
1986        assert_eq!(test.key1, "value1".to_string());
1987        assert!(test.key3.is_none());
1988        let key4 = test.key4;
1989        assert_eq!(key4[0], "value4".to_string());
1990        let key5 = test.key5;
1991        assert_eq!(key5[0], "value5".to_string());
1992        assert_eq!(key5[1], "value6".to_string());
1993
1994        assert_eq!(test.dn, dn);
1995    }
1996
1997    #[test]
1998    fn binary_single_to_value_test() -> anyhow::Result<()> {
1999        #[derive(Deserialize)]
2000        struct TestMultivalueBinary {
2001            pub uuids: Uuid,
2002            pub key1: String,
2003        }
2004
2005        let (bytes, correct_string_representation) = get_binary_uuid();
2006
2007        let entry = SearchEntry {
2008            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2009            attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
2010            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
2011        };
2012
2013        let test = to_value::<TestMultivalueBinary>(entry).unwrap();
2014
2015        let string_uuid = test.uuids.hyphenated().to_string();
2016        assert_eq!(string_uuid, correct_string_representation);
2017        Ok(())
2018    }
2019
2020    // #[test] // This test is not working, because the OneOrMany trait is not implemented for Uuid. Will fix this later.
2021    fn binary_multi_to_value_test() -> anyhow::Result<()> {
2022        #[serde_as]
2023        #[derive(Deserialize)]
2024        struct TestMultivalueBinary {
2025            #[serde_as(as = "OneOrMany<_>")]
2026            pub uuids: Vec<Uuid>,
2027            pub key1: String,
2028        }
2029
2030        let (bytes, correct_string_representation) = get_binary_uuid();
2031
2032        let entry = SearchEntry {
2033            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2034            attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
2035            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
2036        };
2037
2038        let test = to_value::<TestMultivalueBinary>(entry).unwrap();
2039
2040        match test.uuids.as_slice() {
2041            [one] => {
2042                let string_uuid = one.hyphenated().to_string();
2043                assert_eq!(string_uuid, correct_string_representation);
2044                Ok(())
2045            }
2046            [..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
2047        }
2048    }
2049
2050    #[derive(Debug, Deserialize)]
2051    struct TestMultiValued {
2052        dn: String,
2053        key1: Vec<String>,
2054        key2: Vec<String>,
2055    }
2056
2057    #[derive(Debug, Deserialize)]
2058    struct TestSingleValued {
2059        dn: String,
2060        key1: String,
2061        key2: String,
2062        key3: Option<String>,
2063        key4: Option<String>,
2064    }
2065
2066    #[serde_as]
2067    #[derive(Debug, Deserialize)]
2068    struct TestValued {
2069        dn: String,
2070        key1: String,
2071        key3: Option<String>,
2072        #[serde_as(as = "OneOrMany<_>")]
2073        key4: Vec<String>,
2074        #[serde_as(as = "OneOrMany<_>")]
2075        key5: Vec<String>,
2076    }
2077    /// Get the binary and hyphenated string representations of an UUID for testing.
2078    fn get_binary_uuid() -> (Vec<u8>, String) {
2079        // Exaple grabbed from uuid docs:
2080        // https://docs.rs/uuid/latest/uuid/struct.Uuid.html#method.from_bytes
2081        let bytes = vec![
2082            0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
2083            0xd7, 0xd8,
2084        ];
2085
2086        let correct_string_representation = String::from("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8");
2087
2088        (bytes, correct_string_representation)
2089    }
2090
2091    #[test]
2092    fn deserialize_binary_multi_value_test() -> anyhow::Result<()> {
2093        #[derive(Deserialize)]
2094        struct TestMultivalueBinary {
2095            pub uuids: Vec<Uuid>,
2096        }
2097
2098        let (bytes, correct_string_representation) = get_binary_uuid();
2099
2100        let entry = SearchEntry {
2101            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2102            attrs: HashMap::new(),
2103            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
2104        };
2105
2106        let record = Record {
2107            search_entry: entry,
2108        };
2109
2110        let deserialized: TestMultivalueBinary = record.to_multi_valued_record_()?;
2111
2112        match deserialized.uuids.as_slice() {
2113            [one] => {
2114                let string_uuid = one.hyphenated().to_string();
2115                assert_eq!(string_uuid, correct_string_representation);
2116                Ok(())
2117            }
2118            [..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
2119        }
2120    }
2121
2122    #[test]
2123    fn deserialize_binary_single_value_test() -> anyhow::Result<()> {
2124        #[derive(Deserialize)]
2125        struct TestSingleValueBinary {
2126            pub uuid: Uuid,
2127        }
2128
2129        let (bytes, correct_string_representation) = get_binary_uuid();
2130
2131        let entry = SearchEntry {
2132            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2133            attrs: HashMap::new(),
2134            bin_attrs: HashMap::from([(String::from("uuid"), vec![bytes])]),
2135        };
2136
2137        let record = Record {
2138            search_entry: entry,
2139        };
2140
2141        let deserialized: TestSingleValueBinary = record.to_record()?;
2142
2143        let string_uuid = deserialized.uuid.hyphenated().to_string();
2144        assert_eq!(string_uuid, correct_string_representation);
2145
2146        Ok(())
2147    }
2148}
2149
2150// Add readme examples to doctests:
2151// https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#include-items-only-when-collecting-doctests
2152#[doc = include_str!("../README.md")]
2153#[cfg(doctest)]
2154pub struct ReadmeDoctests;