conjure_object/resource_identifier/
mod.rs

1// Copyright 2018 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The Conjure `rid` type.
16#![warn(missing_docs, clippy::all)]
17
18use lazy_static::lazy_static;
19use regex::Regex;
20use serde::de::{self, Deserialize, Deserializer, Unexpected};
21use serde::ser::{Serialize, Serializer};
22use std::borrow::Borrow;
23use std::cmp::Ordering;
24use std::error::Error;
25use std::fmt;
26use std::hash::{Hash, Hasher};
27use std::str::FromStr;
28
29#[cfg(test)]
30mod test;
31
32const RID_CLASS: &str = "ri";
33const SEPARATOR: &str = ".";
34
35lazy_static! {
36    static ref PARSE_REGEX: Regex = Regex::new(
37        r"(?x)
38            ^
39            ri
40            \.
41            ([a-z][a-z0-9\-]*) #service
42            \.
43            ((?:[a-z0-9][a-z0-9\-]*)?) #instance
44            \.
45            ([a-z][a-z0-9\-]*) #type
46            \.
47            ([a-zA-Z0-9_\-\.]+) #locator
48            $
49        ",
50    )
51    .unwrap();
52}
53
54/// A common format for wrapping existing unique identifiers to provide additional context.
55///
56/// Resource identifiers contain 4 components, prefixed by a format identifier `ri`, and separated with periods:
57/// `ri.<service>.<instance>.<type>.<locator>`.
58///
59/// * Service: The service or application that namespaces the rest of the identifier. Must conform to the regex pattern
60///     `[a-z][a-z0-9\-]*`.
61/// * Instance: An optionally empty string that represents the specific service cluster, to allow for disambiduation of
62///     artifacts from different service clusters. Must conform to the regex pattern `([a-z0-9][a-z0-9\-]*)?`.
63/// * Type: A service-specific resource type to namespace a group of locators. Must conform to the regex pattern
64///     `[a-z][a-z0-9\-\._]+`.
65/// * Locator: A string used to uniquely locate the specific resource. Must conform to the regex pattern
66///     `[a-zA-Z0-9\-\._]+`.
67#[derive(Clone)]
68pub struct ResourceIdentifier {
69    rid: String,
70    service_end: usize,
71    instance_end: usize,
72    type_end: usize,
73}
74
75impl ResourceIdentifier {
76    /// Creates a resource identifier from a string.
77    ///
78    /// This function behaves identically to `ResourceIdentifier`'s `FromStr` implementation.
79    #[inline]
80    pub fn new(s: &str) -> Result<ResourceIdentifier, ParseError> {
81        s.parse()
82    }
83
84    /// Creates a resource identifier from its individual components.
85    pub fn from_components(
86        service: &str,
87        instance: &str,
88        type_: &str,
89        locator: &str,
90    ) -> Result<ResourceIdentifier, ParseError> {
91        // Make sure there aren't `.`s in componenents other than the locator up front, since that could cause an
92        // invalid input to parse correctly.
93        if service.contains('.') || instance.contains('.') || type_.contains('.') {
94            return Err(ParseError(()));
95        }
96
97        format!("ri.{}.{}.{}.{}", service, instance, type_, locator).parse()
98    }
99
100    /// Returns the service component of the resource identifier.
101    #[inline]
102    pub fn service(&self) -> &str {
103        let start = RID_CLASS.len() + SEPARATOR.len();
104        &self.rid[start..self.service_end]
105    }
106
107    /// Returns the instance component of the resource identifier.
108    #[inline]
109    pub fn instance(&self) -> &str {
110        let start = self.service_end + SEPARATOR.len();
111        &self.rid[start..self.instance_end]
112    }
113
114    /// Returns the type component of the resource identifier.
115    #[inline]
116    pub fn type_(&self) -> &str {
117        let start = self.instance_end + SEPARATOR.len();
118        &self.rid[start..self.type_end]
119    }
120
121    /// Returns the locator component of the resource identifier.
122    #[inline]
123    pub fn locator(&self) -> &str {
124        let start = self.type_end + SEPARATOR.len();
125        &self.rid[start..]
126    }
127
128    /// Returns the string representation of the resource identifier.
129    #[inline]
130    pub fn as_str(&self) -> &str {
131        &self.rid
132    }
133
134    /// Consumes the resource identifier, returning its owned string representation.
135    #[inline]
136    pub fn into_string(self) -> String {
137        self.rid
138    }
139}
140
141impl FromStr for ResourceIdentifier {
142    type Err = ParseError;
143
144    fn from_str(s: &str) -> Result<ResourceIdentifier, ParseError> {
145        let captures = match PARSE_REGEX.captures(s) {
146            Some(captures) => captures,
147            None => return Err(ParseError(())),
148        };
149
150        Ok(ResourceIdentifier {
151            rid: s.to_string(),
152            service_end: captures.get(1).unwrap().end(),
153            instance_end: captures.get(2).unwrap().end(),
154            type_end: captures.get(3).unwrap().end(),
155        })
156    }
157}
158
159impl Serialize for ResourceIdentifier {
160    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
161    where
162        S: Serializer,
163    {
164        self.rid.serialize(s)
165    }
166}
167
168impl<'de> Deserialize<'de> for ResourceIdentifier {
169    fn deserialize<D>(d: D) -> Result<ResourceIdentifier, D::Error>
170    where
171        D: Deserializer<'de>,
172    {
173        // FIXME avoid double-allocating string
174        let s = String::deserialize(d)?;
175        ResourceIdentifier::new(&s)
176            .map_err(|_| de::Error::invalid_value(Unexpected::Str(&s), &"a resource identifier"))
177    }
178}
179
180impl AsRef<str> for ResourceIdentifier {
181    #[inline]
182    fn as_ref(&self) -> &str {
183        &self.rid
184    }
185}
186
187// NOTE: this is *only* OK because our Hash impl skips the other bits of the struct
188impl Borrow<str> for ResourceIdentifier {
189    #[inline]
190    fn borrow(&self) -> &str {
191        &self.rid
192    }
193}
194
195// These are all manually implemented to delegate to the rid field
196impl fmt::Debug for ResourceIdentifier {
197    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
198        fmt::Debug::fmt(&self.rid, fmt)
199    }
200}
201
202impl fmt::Display for ResourceIdentifier {
203    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
204        fmt::Display::fmt(&self.rid, fmt)
205    }
206}
207
208impl PartialEq for ResourceIdentifier {
209    #[inline]
210    fn eq(&self, other: &ResourceIdentifier) -> bool {
211        self.rid == other.rid
212    }
213}
214
215impl Eq for ResourceIdentifier {}
216
217impl PartialOrd for ResourceIdentifier {
218    #[inline]
219    fn partial_cmp(&self, other: &ResourceIdentifier) -> Option<Ordering> {
220        Some(self.cmp(other))
221    }
222
223    #[inline]
224    fn gt(&self, other: &ResourceIdentifier) -> bool {
225        self.rid > other.rid
226    }
227
228    #[inline]
229    fn ge(&self, other: &ResourceIdentifier) -> bool {
230        self.rid >= other.rid
231    }
232
233    #[inline]
234    fn lt(&self, other: &ResourceIdentifier) -> bool {
235        self.rid < other.rid
236    }
237
238    #[inline]
239    fn le(&self, other: &ResourceIdentifier) -> bool {
240        self.rid <= other.rid
241    }
242}
243
244impl Ord for ResourceIdentifier {
245    #[inline]
246    fn cmp(&self, other: &ResourceIdentifier) -> Ordering {
247        self.rid.cmp(&other.rid)
248    }
249}
250
251impl Hash for ResourceIdentifier {
252    #[inline]
253    fn hash<H>(&self, hasher: &mut H)
254    where
255        H: Hasher,
256    {
257        self.rid.hash(hasher)
258    }
259}
260
261/// An error returned from parsing an invalid resource identifier.
262#[derive(Debug)]
263pub struct ParseError(());
264
265impl fmt::Display for ParseError {
266    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
267        fmt.write_str("invalid resource identifier")
268    }
269}
270
271impl Error for ParseError {}