nmd_core/resource/
resource_reference.rs

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use getset::{Getters, Setters};
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{resource::remote_resource::RemoteResource, utility::file_utility};

static OF_INTERNAL_RESOURCE_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(.*)?#(.*)").unwrap());

const VALUE_SEPARATOR: &str = "-";
const SPACE_REPLACER: char = '-';


#[derive(Error, Debug)]
pub enum ResourceReferenceError {
    #[error("invalid URL reference")]
    InvalidUrlReference,

    #[error("invalid URL reference")]
    InvalidAssetReference,

    #[error("invalid URL reference")]
    InvalidInternalReference,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResourceReferenceType {
    Url,
    Asset,
    Internal
}


/// # Reference
/// 
/// A reference to a resource. A resource can be an internal or external resource.
/// An internal resource is an heading, image or text with an ID of a dossier document.
/// An external resource is a URL or dossier (asset) file.
/// 
/// An internal resource is composed by "document name" (where there is the resource) and the resource ID.
/// 
/// An external resource is interpreted "as it is"
#[derive(Debug, Clone, Getters, Setters, Serialize, Deserialize)]
pub struct ResourceReference {

    #[getset(get = "pub", set = "pub")]
    value: String,

    ref_type: ResourceReferenceType
}

impl ResourceReference {
    pub fn new(value: &str, ref_type: ResourceReferenceType) -> Self {
        Self {
            value: String::from(value),
            ref_type
        }
    }

    pub fn of_url(raw: &str) -> Result<Self, ResourceReferenceError> {

        if !RemoteResource::is_valid_remote_resource(raw) {
            return Err(ResourceReferenceError::InvalidUrlReference)
        }

        Ok(Self::new(raw, ResourceReferenceType::Url))
    }

    pub fn of_asset(raw: &str) -> Result<Self, ResourceReferenceError> {
        if !file_utility::is_file_path(raw) {
            return Err(ResourceReferenceError::InvalidAssetReference)
        }

        Ok(Self::new(raw, ResourceReferenceType::Asset))
    }

    /// Reference from raw internal string.
    /// 
    /// Raw string must be in the format: <document-name>#id 
    /// 
    /// <document-name> can be omitted. 
    pub fn of_internal(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {

        let raw = raw.to_lowercase();

        let caps = OF_INTERNAL_RESOURCE_REGEX.captures(&raw);

        if caps.is_none() {
            return Err(ResourceReferenceError::InvalidInternalReference)
        }

        let caps = caps.unwrap();

        let document_name = caps.get(1);
        let value = caps.get(2);

        if value.is_none() {
            return Err(ResourceReferenceError::InvalidInternalReference)
        }
        let value = value.unwrap().as_str();

        if let Some(document_name) = document_name {

            let document_name = document_name.as_str().trim();

            if !document_name.is_empty() {

                return Ok(Self::new(&format!("{}{}{}", document_name, VALUE_SEPARATOR, value), ResourceReferenceType::Internal))
            }
        }

        Ok(Self::new(&format!("{}{}{}", document_name_if_missed.unwrap().to_string(), VALUE_SEPARATOR, value), ResourceReferenceType::Internal))
        
    }

    pub fn of_internal_from_without_sharp(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {

        let raw_with_sharp = format!("#{}", raw);

        Self::of_internal(&raw_with_sharp, document_name_if_missed)
    }

    /// Create new based on string. Argument can be in the following forms:
    /// 
    /// - document_name#id
    /// - #id
    /// - url
    /// - url#id
    /// - asset 
    pub fn of(raw: &str, document_name_if_missed: Option<&impl ToString>) -> Result<Self, ResourceReferenceError> {

        if RemoteResource::is_valid_remote_resource(raw) {
            return Self::of_url(raw)
        }

        if raw.contains("#") {
            return Self::of_internal(raw, document_name_if_missed)

        } else {        // asset
            return Self::of_asset(raw)
        }
    }

    pub fn build(&self) -> String {

        match self.ref_type {
            ResourceReferenceType::Url | ResourceReferenceType::Asset => String::from(&self.value),
            ResourceReferenceType::Internal => format!("#{}", Self::parse_str(&self.value))
        }
    }

    pub fn build_without_internal_sharp(&self) -> String {

        match self.ref_type {
            ResourceReferenceType::Url | ResourceReferenceType::Asset => String::from(&self.value),
            ResourceReferenceType::Internal => format!("{}", Self::parse_str(&self.value))
        }

    }

    fn parse_str(s: &str) -> String {

        s.chars().map(|c| {

            if c.is_alphanumeric() {
                return c;
            }

            if c == ' ' {
                return SPACE_REPLACER;
            }

            '-'
        }).collect()
    }
}