spdlog-internal 0.2.1

Internal private common code for crate "spdlog-rs"
Documentation
use std::{
    borrow::Cow,
    collections::{hash_map::Entry, HashMap},
    fmt::Debug,
    hash::Hash,
};

use super::{error::TemplateError, BuiltInFormatter, Error, PatternKind, Result};
use crate::impossible;

#[derive(Clone, Debug)]
pub struct PatternRegistry<F> {
    formatters: HashMap<Cow<'static, str>, PatternKind<F>>,
}

impl<F> PatternRegistry<F> {
    #[must_use]
    pub fn with_builtin() -> Self {
        let mut registry = Self {
            formatters: HashMap::new(),
        };

        BuiltInFormatter::iter().for_each(|formatter| registry.register_builtin(formatter));
        registry
    }

    pub fn register_custom(
        &mut self,
        placeholder: impl Into<Cow<'static, str>>,
        factory: F,
    ) -> Result<()> {
        let placeholder = placeholder.into();

        let incoming = PatternKind::Custom {
            placeholder: placeholder.clone(),
            factory,
        };

        match self.formatters.entry(placeholder) {
            Entry::Occupied(entry) => Err(Error::ConflictName {
                existing: entry.get().to_factory_erased(),
                incoming: incoming.to_factory_erased(),
            }),
            Entry::Vacant(entry) => {
                entry.insert(incoming);
                Ok(())
            }
        }
    }

    pub fn find(&self, find_custom: bool, placeholder: impl AsRef<str>) -> Result<&PatternKind<F>> {
        let placeholder = placeholder.as_ref();

        match self.formatters.get(placeholder) {
            Some(found) => match (found, find_custom) {
                (PatternKind::BuiltIn(_), false) => Ok(found),
                (PatternKind::Custom { .. }, true) => Ok(found),
                (PatternKind::BuiltIn(_), true) => {
                    Err(Error::Template(TemplateError::WrongPatternKindReference {
                        is_builtin_as_custom: true,
                        placeholder: placeholder.into(),
                    }))
                }
                (PatternKind::Custom { .. }, false) => {
                    Err(Error::Template(TemplateError::WrongPatternKindReference {
                        is_builtin_as_custom: false,
                        placeholder: placeholder.into(),
                    }))
                }
            },
            None => Err(Error::Template(TemplateError::UnknownPatternReference {
                is_custom: find_custom,
                placeholder: placeholder.into(),
            })),
        }
    }
}

impl<F> PatternRegistry<F> {
    pub(crate) fn register_builtin(&mut self, formatter: BuiltInFormatter) {
        match self
            .formatters
            .entry(Cow::Borrowed(formatter.placeholder()))
        {
            Entry::Occupied(_) => {
                impossible!("formatter={:?}", formatter)
            }
            Entry::Vacant(entry) => {
                entry.insert(PatternKind::BuiltIn(formatter));
            }
        }
    }
}

pub fn check_custom_pattern_names<N, I>(names: I) -> Result<()>
where
    N: AsRef<str> + Eq + PartialEq + Hash,
    I: IntoIterator<Item = N>,
{
    let mut seen_names: HashMap<N, usize> = HashMap::new();
    let mut result = Ok(());

    for name in names {
        if let Some(existing) = BuiltInFormatter::iter().find(|f| f.placeholder() == name.as_ref())
        {
            result = Error::push_err(
                result,
                Error::ConflictName {
                    existing: PatternKind::BuiltIn(existing),
                    incoming: PatternKind::Custom {
                        placeholder: Cow::Owned(name.as_ref().into()),
                        factory: (),
                    },
                },
            );
        }

        if let Some(seen_count) = seen_names.get_mut(&name) {
            *seen_count += 1;
            if *seen_count == 2 {
                let conflict_pattern = PatternKind::Custom {
                    placeholder: Cow::Owned(name.as_ref().into()),
                    factory: (),
                };
                result = Error::push_err(
                    result,
                    Error::ConflictName {
                        existing: conflict_pattern.clone(),
                        incoming: conflict_pattern,
                    },
                );
            }
        } else {
            seen_names.insert(name, 1);
        }
    }

    debug_assert!(seen_names.iter().all(|(_, seen_count)| *seen_count == 1) || result.is_err());

    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn custom_pattern_names_checker() {
        use check_custom_pattern_names as check;

        assert!(check(["a", "b"]).is_ok());
        assert_eq!(
            check(["a", "a"]),
            Err(Error::ConflictName {
                existing: PatternKind::Custom {
                    placeholder: "a".into(),
                    factory: ()
                },
                incoming: PatternKind::Custom {
                    placeholder: "a".into(),
                    factory: ()
                }
            })
        );
        assert_eq!(
            check(["a", "b", "a"]),
            Err(Error::ConflictName {
                existing: PatternKind::Custom {
                    placeholder: "a".into(),
                    factory: ()
                },
                incoming: PatternKind::Custom {
                    placeholder: "a".into(),
                    factory: ()
                }
            })
        );
        assert_eq!(
            check(["date"]),
            Err(Error::ConflictName {
                existing: PatternKind::BuiltIn(BuiltInFormatter::Date),
                incoming: PatternKind::Custom {
                    placeholder: "date".into(),
                    factory: ()
                }
            })
        );
        assert_eq!(
            check(["date", "a", "a"]),
            Err(Error::Multiple(vec![
                Error::ConflictName {
                    existing: PatternKind::BuiltIn(BuiltInFormatter::Date),
                    incoming: PatternKind::Custom {
                        placeholder: "date".into(),
                        factory: ()
                    }
                },
                Error::ConflictName {
                    existing: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    },
                    incoming: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    }
                }
            ]))
        );
        assert_eq!(
            check(["date", "a", "a", "a"]),
            Err(Error::Multiple(vec![
                Error::ConflictName {
                    existing: PatternKind::BuiltIn(BuiltInFormatter::Date),
                    incoming: PatternKind::Custom {
                        placeholder: "date".into(),
                        factory: ()
                    }
                },
                Error::ConflictName {
                    existing: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    },
                    incoming: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    }
                }
            ]))
        );
        assert_eq!(
            check(["b", "date", "a", "b", "a", "a"]),
            Err(Error::Multiple(vec![
                Error::ConflictName {
                    existing: PatternKind::BuiltIn(BuiltInFormatter::Date),
                    incoming: PatternKind::Custom {
                        placeholder: "date".into(),
                        factory: ()
                    }
                },
                Error::ConflictName {
                    existing: PatternKind::Custom {
                        placeholder: "b".into(),
                        factory: ()
                    },
                    incoming: PatternKind::Custom {
                        placeholder: "b".into(),
                        factory: ()
                    }
                },
                Error::ConflictName {
                    existing: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    },
                    incoming: PatternKind::Custom {
                        placeholder: "a".into(),
                        factory: ()
                    }
                }
            ]))
        );
    }
}