Skip to main content

simple_ldap/
lib.rs

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