Skip to main content

pg_embedded_setup_unpriv/extensions/
name.rs

1//! Validated `CREATE EXTENSION` names.
2
3use std::fmt;
4
5use color_eyre::eyre::eyre;
6
7use super::extension_error;
8use crate::error::{BootstrapErrorKind, BootstrapResult};
9
10/// A `CREATE EXTENSION` name: one or more of `a-z`, `0-9` and `_`.
11///
12/// # Examples
13///
14/// ```
15/// use pg_embedded_setup_unpriv::extensions::ExtensionName;
16///
17/// let name = ExtensionName::new("vector")?;
18/// assert_eq!(name.as_str(), "vector");
19/// assert!(ExtensionName::new("Vector").is_err());
20/// assert!(ExtensionName::new("vec tor").is_err());
21/// # Ok::<(), pg_embedded_setup_unpriv::BootstrapError>(())
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24pub struct ExtensionName(String);
25
26impl ExtensionName {
27    /// Validates and wraps a name.
28    ///
29    /// # Errors
30    ///
31    /// Returns `ExtensionConfigInvalid` when the name is empty or contains a
32    /// character outside `[a-z0-9_]`.
33    pub fn new(name: impl Into<String>) -> BootstrapResult<Self> {
34        let value = name.into();
35        if value.is_empty() || !value.bytes().all(is_name_byte) {
36            return Err(extension_error(
37                BootstrapErrorKind::ExtensionConfigInvalid,
38                eyre!(
39                    "extension name {value:?} is invalid; names use lower-case letters, digits \
40                     and underscores"
41                ),
42            ));
43        }
44        Ok(Self(value))
45    }
46
47    /// Returns the name as a string slice.
48    #[must_use]
49    pub fn as_str(&self) -> &str { &self.0 }
50}
51
52/// True for the bytes an extension name may contain: lower-case ASCII
53/// letters, digits and underscore.
54const fn is_name_byte(byte: u8) -> bool {
55    byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_'
56}
57
58impl AsRef<str> for ExtensionName {
59    fn as_ref(&self) -> &str { &self.0 }
60}
61
62impl TryFrom<&str> for ExtensionName {
63    type Error = crate::error::BootstrapError;
64
65    fn try_from(value: &str) -> Result<Self, Self::Error> { Self::new(value) }
66}
67
68impl fmt::Display for ExtensionName {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0) }
70}