nmd_core/resource/
resource_reference.rs

1use getset::{Getters, Setters};
2use once_cell::sync::Lazy;
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7use crate::{resource::remote_resource::RemoteResource, utility::file_utility};
8
9static OF_INTERNAL_RESOURCE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(.*)?#(.*)").unwrap());
10
11const VALUE_SEPARATOR: &str = "-";
12const SPACE_REPLACER: char = '-';
13
14
15#[derive(Error, Debug)]
16pub enum ResourceReferenceError {
17    #[error("invalid URL reference")]
18    InvalidUrlReference,
19
20    #[error("invalid URL reference")]
21    InvalidAssetReference,
22
23    #[error("invalid URL reference")]
24    InvalidInternalReference,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum ResourceReferenceType {
29    Url,
30    Asset,
31    Internal
32}
33
34
35/// # Reference
36/// 
37/// A reference to a resource. A resource can be an internal or external resource.
38/// An internal resource is an heading, image or text with an ID of a dossier document.
39/// An external resource is a URL or dossier (asset) file.
40/// 
41/// An internal resource is composed by "document name" (where there is the resource) and the resource ID.
42/// 
43/// An external resource is interpreted "as it is"
44#[derive(Debug, Clone, Getters, Setters, Serialize, Deserialize)]
45pub struct ResourceReference {
46
47    #[getset(get = "pub", set = "pub")]
48    value: String,
49
50    ref_type: ResourceReferenceType
51}
52
53impl ResourceReference {
54    pub fn new(value: &str, ref_type: ResourceReferenceType) -> Self {
55        Self {
56            value: String::from(value),
57            ref_type
58        }
59    }
60
61    pub fn of_url(raw: &str) -> Result<Self, ResourceReferenceError> {
62
63        if !RemoteResource::is_valid_remote_resource(raw) {
64            return Err(ResourceReferenceError::InvalidUrlReference)
65        }
66
67        Ok(Self::new(raw, ResourceReferenceType::Url))
68    }
69
70    pub fn of_asset(raw: &str) -> Result<Self, ResourceReferenceError> {
71        if !file_utility::is_file_path(raw) {
72            return Err(ResourceReferenceError::InvalidAssetReference)
73        }
74
75        Ok(Self::new(raw, ResourceReferenceType::Asset))
76    }
77
78    /// Reference from raw internal string.
79    /// 
80    /// Raw string must be in the format: <document-name>#id 
81    /// 
82    /// <document-name> can be omitted. 
83    pub fn of_internal(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {
84
85        let raw = raw.to_lowercase();
86
87        let caps = OF_INTERNAL_RESOURCE_REGEX.captures(&raw);
88
89        if caps.is_none() {
90            return Err(ResourceReferenceError::InvalidInternalReference)
91        }
92
93        let caps = caps.unwrap();
94
95        let document_name = caps.get(1);
96        let value = caps.get(2);
97
98        if value.is_none() {
99            return Err(ResourceReferenceError::InvalidInternalReference)
100        }
101        let value = value.unwrap().as_str();
102
103        if let Some(document_name) = document_name {
104
105            let document_name = document_name.as_str().trim();
106
107            if !document_name.is_empty() {
108
109                return Ok(Self::new(&format!("{}{}{}", document_name, VALUE_SEPARATOR, value), ResourceReferenceType::Internal))
110            }
111        }
112
113        Ok(Self::new(&format!("{}{}{}", document_name_if_missed.unwrap().to_string(), VALUE_SEPARATOR, value), ResourceReferenceType::Internal))
114        
115    }
116
117    pub fn of_internal_from_without_sharp(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {
118
119        let raw_with_sharp = format!("#{}", raw);
120
121        Self::of_internal(&raw_with_sharp, document_name_if_missed)
122    }
123
124    /// Create new based on string. Argument can be in the following forms:
125    /// 
126    /// - document_name#id
127    /// - #id
128    /// - url
129    /// - url#id
130    /// - asset 
131    pub fn of(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {
132
133        if RemoteResource::is_valid_remote_resource(raw) {
134            return Self::of_url(raw)
135        }
136
137        if raw.contains("#") {
138            return Self::of_internal(raw, document_name_if_missed)
139
140        } else {        // asset
141            return Self::of_asset(raw)
142        }
143    }
144
145    pub fn build(&self) -> String {
146
147        match self.ref_type {
148            ResourceReferenceType::Url | ResourceReferenceType::Asset => String::from(&self.value),
149            ResourceReferenceType::Internal => format!("#{}", Self::parse_str(&self.value))
150        }
151    }
152
153    pub fn build_without_internal_sharp(&self) -> String {
154
155        match self.ref_type {
156            ResourceReferenceType::Url | ResourceReferenceType::Asset => String::from(&self.value),
157            ResourceReferenceType::Internal => format!("{}", Self::parse_str(&self.value))
158        }
159
160    }
161
162    fn parse_str(s: &str) -> String {
163
164        s.chars().map(|c| {
165
166            if c.is_alphanumeric() {
167                return c;
168            }
169
170            if c == ' ' {
171                return SPACE_REPLACER;
172            }
173
174            '-'
175        }).collect()
176    }
177}