1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("todo")]
ToDo,
}
/// A document id.
///
/// # Limit
///
/// <https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields>
///
/// > - Must be valid UTF-8 characters
/// > - Must be no longer than 1,500 bytes
/// > - Cannot contain a forward slash (/)
/// > - Cannot solely consist of a single period (.) or double periods (..)
/// > - Cannot match the regular expression __.*__
/// > - If you import Datastore entities into a Firestore database, numeric entity IDs are exposed as __id[0-9]+__
///
/// # Examples
///
/// ```rust
/// # fn main() -> anyhow::Result<()> {
/// use firestore_path::DocumentId;
/// use std::str::FromStr;
///
/// let document_id = DocumentId::from_str("chatroom1")?;
/// assert_eq!(document_id.as_ref(), "chatroom1");
/// assert_eq!(document_id.to_string(), "chatroom1");
/// # Ok(())
/// # }
/// ```
///
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct DocumentId(String);
impl std::convert::AsRef<str> for DocumentId {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl std::convert::TryFrom<&str> for DocumentId {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Self::try_from(s.to_string())
}
}
impl std::convert::TryFrom<String> for DocumentId {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
// <https://firebase.google.com/docs/firestore/quotas#collections_documents_and_fields>
if s.len() > 1_500 {
return Err(Error::ToDo);
}
if s.contains('/') {
return Err(Error::ToDo);
}
if s == "." || s == ".." {
return Err(Error::ToDo);
}
if s.starts_with("__") && s.ends_with("__") {
return Err(Error::ToDo);
}
// TODO: Datastore entities
Ok(Self(s))
}
}
impl std::fmt::Display for DocumentId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl std::str::FromStr for DocumentId {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(s)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn test() -> anyhow::Result<()> {
let s = "chatroom1";
let document_id = DocumentId::from_str(s)?;
assert_eq!(document_id.to_string(), s);
assert_eq!(document_id.as_ref(), s);
Ok(())
}
#[test]
fn test_impl_from_str_and_impl_try_from_string() -> anyhow::Result<()> {
for (s, expected) in [
("chatroom1", true),
("x".repeat(1501).as_ref(), false),
("x".repeat(1500).as_ref(), true),
("chat/room1", false),
(".", false),
(".x", true),
("..", false),
("..x", true),
("__x__", false),
("__x", true),
("x__", true),
] {
assert_eq!(DocumentId::from_str(s).is_ok(), expected);
assert_eq!(DocumentId::try_from(s.to_string()).is_ok(), expected);
if expected {
assert_eq!(
DocumentId::from_str(s)?,
DocumentId::try_from(s.to_string())?
);
assert_eq!(DocumentId::from_str(s)?.to_string(), s);
}
}
Ok(())
}
}