use crate::models::{
resource_id::{ResourceId, ResourceIdentifier, ResourceName},
AccountReference, ContainerReference, DatabaseReference, ItemReference, ResourceType,
StoredProcedureReference, TriggerReference, UdfReference,
};
use std::borrow::Cow;
#[derive(Clone, Debug)]
pub struct CosmosResourceReference {
resource_type: ResourceType,
account: AccountReference,
database: Option<DatabaseReference>,
container: Option<ContainerReference>,
id: Option<ResourceIdentifier>,
is_feed: bool,
}
impl std::fmt::Display for CosmosResourceReference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}('{}')", self.resource_type, self.request_path())
}
}
impl CosmosResourceReference {
pub fn resource_type(&self) -> ResourceType {
self.resource_type
}
pub fn account(&self) -> &AccountReference {
&self.account
}
pub fn container(&self) -> Option<&ContainerReference> {
self.container.as_ref()
}
pub fn with_name(mut self, name: Cow<'static, str>) -> Self {
self.id = Some(ResourceIdentifier::by_name(ResourceName::new(name)));
self.is_feed = false;
self
}
pub fn with_rid(mut self, rid: Cow<'static, str>) -> Self {
self.id = Some(ResourceIdentifier::by_rid(ResourceId::new(rid)));
self.is_feed = false;
self
}
pub fn with_resource_type(mut self, resource_type: ResourceType) -> Self {
self.resource_type = resource_type;
self
}
pub fn into_feed_reference(mut self) -> Self {
self.id = None;
self.is_feed = true;
self
}
pub fn link_for_signing(&self) -> String {
if self.is_feed {
self.parent_signing_link()
} else {
self.resolved_resource_link()
}
}
pub fn request_path(&self) -> String {
if self.is_feed {
let parent = self.parent_signing_link();
let segment = self.resource_type.path_segment();
if parent.is_empty() {
format!("/{}", segment)
} else {
format!("{}/{}", parent, segment)
}
} else {
self.resolved_resource_link()
}
}
fn resolved_resource_link(&self) -> String {
match self.resource_type {
ResourceType::DatabaseAccount => {
String::new()
}
ResourceType::Database => {
self.db_link()
}
ResourceType::DocumentCollection => {
self.container_link()
}
ResourceType::Document
| ResourceType::StoredProcedure
| ResourceType::Trigger
| ResourceType::UserDefinedFunction
| ResourceType::PartitionKeyRange => {
let container_path = self.container_link();
let segment = self.resource_type.path_segment();
if let Some(ref id) = self.id {
let id_str = Self::identifier_str(id);
format!("{}/{}/{}", container_path, segment, id_str)
} else {
format!("{}/{}", container_path, segment)
}
}
ResourceType::Offer => {
if let Some(ref id) = self.id {
let id_str = Self::identifier_str(id);
format!("/offers/{}", id_str)
} else {
"/offers".to_string()
}
}
}
}
fn parent_signing_link(&self) -> String {
match self.resource_type {
ResourceType::DatabaseAccount => String::new(),
ResourceType::Database | ResourceType::Offer => {
String::new()
}
ResourceType::DocumentCollection => {
self.db_link()
}
ResourceType::Document
| ResourceType::StoredProcedure
| ResourceType::Trigger
| ResourceType::UserDefinedFunction
| ResourceType::PartitionKeyRange => {
self.container_link()
}
}
}
fn db_link(&self) -> String {
if let Some(ref db) = self.database {
if let Some(name) = db.name() {
return format!("/dbs/{}", name);
}
if let Some(rid) = db.rid() {
return format!("/dbs/{}", rid);
}
}
if let Some(ref id) = self.id {
let id_str = Self::identifier_str(id);
return format!("/dbs/{}", id_str);
}
String::new()
}
fn container_link(&self) -> String {
if let Some(ref container) = self.container {
return container.name_based_path();
}
if let Some(ref id) = self.id {
let db = self.db_link();
let id_str = Self::identifier_str(id);
return format!("{}/colls/{}", db, id_str);
}
self.db_link()
}
fn identifier_str(id: &ResourceIdentifier) -> &str {
if let Some(name) = id.name() {
name
} else {
id.rid().unwrap_or_default()
}
}
}
impl From<AccountReference> for CosmosResourceReference {
fn from(account: AccountReference) -> Self {
Self {
resource_type: ResourceType::DatabaseAccount,
account,
database: None,
container: None,
id: None,
is_feed: false,
}
}
}
impl From<DatabaseReference> for CosmosResourceReference {
fn from(database: DatabaseReference) -> Self {
let account = database.account().clone();
Self {
resource_type: ResourceType::Database,
account,
database: Some(database),
container: None,
id: None,
is_feed: false,
}
}
}
impl From<ContainerReference> for CosmosResourceReference {
fn from(container: ContainerReference) -> Self {
let account = container.account().clone();
Self {
resource_type: ResourceType::DocumentCollection,
account,
database: None,
container: Some(container),
id: None,
is_feed: false,
}
}
}
impl From<ItemReference> for CosmosResourceReference {
fn from(item: ItemReference) -> Self {
let account = item.account().clone();
let container = item.container().clone();
let id = if let Some(name) = item.name() {
Some(ResourceIdentifier::by_name(ResourceName::new(
name.to_owned(),
)))
} else {
item.rid()
.map(|rid| ResourceIdentifier::by_rid(ResourceId::new(rid.to_owned())))
};
Self {
resource_type: ResourceType::Document,
account,
database: None,
container: Some(container),
id,
is_feed: false,
}
}
}
impl From<StoredProcedureReference> for CosmosResourceReference {
fn from(sp: StoredProcedureReference) -> Self {
let account = sp.account().clone();
let container = sp.container().clone();
let id = if let Some(name) = sp.name() {
Some(ResourceIdentifier::by_name(ResourceName::new(
name.to_owned(),
)))
} else {
sp.rid()
.map(|rid| ResourceIdentifier::by_rid(ResourceId::new(rid.to_owned())))
};
Self {
resource_type: ResourceType::StoredProcedure,
account,
database: None,
container: Some(container),
id,
is_feed: false,
}
}
}
impl From<TriggerReference> for CosmosResourceReference {
fn from(trigger: TriggerReference) -> Self {
let account = trigger.account().clone();
let container = trigger.container().clone();
let id = if let Some(name) = trigger.name() {
Some(ResourceIdentifier::by_name(ResourceName::new(
name.to_owned(),
)))
} else {
trigger
.rid()
.map(|rid| ResourceIdentifier::by_rid(ResourceId::new(rid.to_owned())))
};
Self {
resource_type: ResourceType::Trigger,
account,
database: None,
container: Some(container),
id,
is_feed: false,
}
}
}
impl From<UdfReference> for CosmosResourceReference {
fn from(udf: UdfReference) -> Self {
let account = udf.account().clone();
let container = udf.container().clone();
let id = if let Some(name) = udf.name() {
Some(ResourceIdentifier::by_name(ResourceName::new(
name.to_owned(),
)))
} else {
udf.rid()
.map(|rid| ResourceIdentifier::by_rid(ResourceId::new(rid.to_owned())))
};
Self {
resource_type: ResourceType::UserDefinedFunction,
account,
database: None,
container: Some(container),
id,
is_feed: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{ContainerProperties, PartitionKey, PartitionKeyDefinition};
use url::Url;
fn test_account() -> AccountReference {
AccountReference::with_master_key(
Url::parse("https://test.documents.azure.com:443/").unwrap(),
"test-key",
)
}
fn test_partition_key_definition() -> PartitionKeyDefinition {
serde_json::from_str(r#"{"paths":["/pk"]}"#).unwrap()
}
fn test_container_props() -> ContainerProperties {
ContainerProperties {
id: "testcontainer".into(),
partition_key: test_partition_key_definition(),
system_properties: Default::default(),
}
}
fn test_container() -> ContainerReference {
ContainerReference::new(
test_account(),
"testdb",
"testdb_rid",
"testcontainer",
"testcontainer_rid",
&test_container_props(),
)
}
#[test]
fn from_account_reference() {
let account = test_account();
let r: CosmosResourceReference = account.into();
assert_eq!(r.resource_type(), ResourceType::DatabaseAccount);
assert!(r.container().is_none());
assert_eq!(r.link_for_signing(), "");
assert_eq!(r.request_path(), "");
}
#[test]
fn from_database_reference() {
let db = DatabaseReference::from_name(test_account(), "mydb");
let r: CosmosResourceReference = db.into();
assert_eq!(r.resource_type(), ResourceType::Database);
assert!(r.container().is_none());
assert_eq!(r.link_for_signing(), "/dbs/mydb");
}
#[test]
fn database_feed_reference() {
let account = test_account();
let r: CosmosResourceReference = CosmosResourceReference::from(account)
.with_resource_type(ResourceType::Database)
.into_feed_reference();
assert_eq!(r.resource_type(), ResourceType::Database);
assert_eq!(r.link_for_signing(), "");
assert_eq!(r.request_path(), "/dbs");
}
#[test]
fn container_feed_reference() {
let db = DatabaseReference::from_name(test_account(), "mydb");
let r: CosmosResourceReference = CosmosResourceReference::from(db)
.with_resource_type(ResourceType::DocumentCollection)
.into_feed_reference();
assert_eq!(r.link_for_signing(), "/dbs/mydb");
assert_eq!(r.request_path(), "/dbs/mydb/colls");
}
#[test]
fn from_container_reference() {
let r: CosmosResourceReference = test_container().into();
assert_eq!(r.resource_type(), ResourceType::DocumentCollection);
assert!(r.container().is_some());
assert_eq!(r.link_for_signing(), "/dbs/testdb/colls/testcontainer");
}
#[test]
fn item_feed_reference() {
let container = test_container();
let r: CosmosResourceReference = CosmosResourceReference::from(container)
.with_resource_type(ResourceType::Document)
.into_feed_reference();
assert_eq!(r.link_for_signing(), "/dbs/testdb/colls/testcontainer");
assert_eq!(r.request_path(), "/dbs/testdb/colls/testcontainer/docs");
}
#[test]
fn from_item_reference() {
let item = ItemReference::from_name(&test_container(), PartitionKey::from("pk1"), "doc1");
let r: CosmosResourceReference = item.into();
assert_eq!(r.resource_type(), ResourceType::Document);
assert!(r.container().is_some());
assert_eq!(
r.link_for_signing(),
"/dbs/testdb/colls/testcontainer/docs/doc1"
);
}
#[test]
fn read_container_by_name_reference() {
let db = DatabaseReference::from_name(test_account(), "mydb");
let r = CosmosResourceReference::from(db)
.with_resource_type(ResourceType::DocumentCollection)
.with_name("mycontainer".into());
assert_eq!(r.resource_type(), ResourceType::DocumentCollection);
assert_eq!(r.link_for_signing(), "/dbs/mydb/colls/mycontainer");
}
#[test]
fn from_stored_procedure_reference() {
let sp = StoredProcedureReference::from_name(&test_container(), "mysproc");
let r: CosmosResourceReference = sp.into();
assert_eq!(r.resource_type(), ResourceType::StoredProcedure);
assert_eq!(
r.link_for_signing(),
"/dbs/testdb/colls/testcontainer/sprocs/mysproc"
);
}
#[test]
fn from_trigger_reference() {
let trigger = TriggerReference::from_name(&test_container(), "mytrigger");
let r: CosmosResourceReference = trigger.into();
assert_eq!(r.resource_type(), ResourceType::Trigger);
assert_eq!(
r.link_for_signing(),
"/dbs/testdb/colls/testcontainer/triggers/mytrigger"
);
}
#[test]
fn from_udf_reference() {
let udf = UdfReference::from_name(&test_container(), "myudf");
let r: CosmosResourceReference = udf.into();
assert_eq!(r.resource_type(), ResourceType::UserDefinedFunction);
assert_eq!(
r.link_for_signing(),
"/dbs/testdb/colls/testcontainer/udfs/myudf"
);
}
}