launchdarkly_server_sdk_evaluation/contexts/
attribute_reference.rs1use serde::{Deserialize, Serialize, Serializer};
2use std::fmt::Display;
3
4#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize)]
5enum Error {
6 Empty,
7 InvalidEscapeSequence,
8 DoubleOrTrailingSlash,
9}
10
11impl Display for Error {
12 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
13 match self {
14 Error::Empty => write!(f, "Reference cannot be empty"),
15 Error::InvalidEscapeSequence => write!(f, "Reference contains invalid escape sequence"),
16 Error::DoubleOrTrailingSlash => {
17 write!(f, "Reference contains double or trailing slash")
18 }
19 }
20 }
21}
22
23#[derive(Clone, Hash, PartialEq, Eq, Debug)]
69pub struct Reference {
70 variant: Variant,
71 input: String,
72}
73
74#[derive(Clone, Hash, PartialEq, Eq, Debug)]
75enum Variant {
76 PlainName,
78 Pointer(Vec<String>),
80 Error(Error),
82}
83
84impl Serialize for Reference {
85 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86 where
87 S: Serializer,
88 {
89 serializer.serialize_str(&self.input)
90 }
91}
92
93impl<'de> Deserialize<'de> for Reference {
94 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95 where
96 D: serde::Deserializer<'de>,
97 {
98 let s = String::deserialize(deserializer)?;
99 Ok(Reference::new(s))
100 }
101}
102
103impl Reference {
104 pub fn new<S: AsRef<str>>(value: S) -> Self {
110 let value = value.as_ref();
111
112 if value.is_empty() || value == "/" {
113 return Self {
114 variant: Variant::Error(Error::Empty),
115 input: value.to_owned(),
116 };
117 }
118
119 if !value.starts_with('/') {
120 return Self {
121 variant: Variant::PlainName,
122 input: value.to_owned(),
123 };
124 }
125
126 let component_result = value[1..]
127 .split('/')
128 .map(|part| {
129 if part.is_empty() {
130 return Err(Error::DoubleOrTrailingSlash);
131 }
132 Reference::unescape_path(part)
133 })
134 .collect::<Result<Vec<String>, Error>>();
135
136 match component_result {
137 Ok(components) => Self {
138 variant: Variant::Pointer(components),
139 input: value.to_owned(),
140 },
141 Err(e) => Self {
142 variant: Variant::Error(e),
143 input: value.to_owned(),
144 },
145 }
146 }
147
148 pub(crate) fn from_literal_name(name: &str) -> Self {
151 if !name.starts_with('/') {
152 return Self::new(name);
153 }
154 let mut escaped = name.replace('~', "~0").replace('/', "~1");
155 escaped.insert(0, '/');
156 Self::new(escaped)
157 }
158
159 pub fn is_valid(&self) -> bool {
161 !matches!(&self.variant, Variant::Error(_))
162 }
163
164 pub fn error(&self) -> String {
167 match &self.variant {
168 Variant::Error(e) => e.to_string(),
169 _ => "".to_owned(),
170 }
171 }
172
173 pub fn depth(&self) -> usize {
186 match &self.variant {
187 Variant::Pointer(components) => components.len(),
188 Variant::PlainName => 1,
189 _ => 0,
190 }
191 }
192
193 pub fn component(&self, index: usize) -> Option<&str> {
209 match (&self.variant, index) {
210 (Variant::Pointer(components), _) => components.get(index).map(|c| c.as_str()),
211 (Variant::PlainName, 0) => Some(&self.input),
212 _ => None,
213 }
214 }
215
216 pub(crate) fn is_kind(&self) -> bool {
218 matches!((self.depth(), self.component(0)), (1, Some(comp)) if comp == "kind")
219 }
220
221 fn unescape_path(path: &str) -> Result<String, Error> {
222 if !path.contains('~') {
224 return Ok(path.to_string());
225 }
226
227 let mut out = String::new();
228
229 let mut iter = path.chars().peekable();
230 while let Some(c) = iter.next() {
231 if c != '~' {
232 out.push(c);
233 continue;
234 }
235 if iter.peek().is_none() {
236 return Err(Error::InvalidEscapeSequence);
237 }
238
239 let unescaped = match iter.next().unwrap() {
240 '0' => '~',
241 '1' => '/',
242 _ => return Err(Error::InvalidEscapeSequence),
243 };
244 out.push(unescaped);
245 }
246
247 Ok(out)
248 }
249}
250
251impl Default for Reference {
252 fn default() -> Self {
254 Reference::new("")
255 }
256}
257
258impl Display for Reference {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
261 write!(f, "{}", self.input)
262 }
263}
264
265impl<S> From<S> for Reference
266where
267 S: AsRef<str>,
268{
269 fn from(reference: S) -> Self {
270 Reference::new(reference)
271 }
272}
273
274impl From<Reference> for String {
275 fn from(r: Reference) -> Self {
276 r.input
277 }
278}
279
280#[derive(Debug, Deserialize, PartialEq)]
281#[serde(transparent)]
282pub(crate) struct AttributeName(String);
286
287impl AttributeName {
288 #[cfg(test)]
290 pub(crate) fn new(s: String) -> Self {
291 Self(s)
292 }
293}
294
295impl Default for AttributeName {
296 fn default() -> Self {
297 Self("".to_owned())
298 }
299}
300
301impl From<AttributeName> for Reference {
302 fn from(name: AttributeName) -> Self {
317 Reference::from_literal_name(&name.0)
318 }
319}
320
321#[cfg(test)]
322pub(crate) mod proptest_generators {
323 use super::{AttributeName, Reference};
324 use proptest::prelude::*;
325
326 prop_compose! {
337 pub(crate) fn any_valid_ref_string()(s in "([^/].*|(/([^/~]|~[01])+)+)") -> String {
340 s
341 }
342
343 }
344
345 prop_compose! {
346 pub(crate) fn any_valid_plain_name()(s in "([^/].*)") -> String {
347 s
348 }
349 }
350
351 prop_compose! {
352 pub(crate) fn any_attribute_name()(s in any_valid_ref_string()) -> AttributeName {
353 AttributeName::new(s)
354 }
355 }
356
357 prop_compose! {
358 pub(crate) fn any_valid_ref()(s in any_valid_ref_string()) -> Reference {
360 Reference::new(s)
361 }
362 }
363
364 prop_compose! {
365 pub(crate) fn any_ref()(s in any::<String>()) -> Reference {
367 Reference::new(s)
368 }
369 }
370
371 prop_compose! {
372 pub(crate) fn any_valid_ref_transformed_from_attribute_name()(s in any_valid_ref_string()) -> Reference {
373 Reference::from(AttributeName::new(s))
374 }
375 }
376
377 prop_compose! {
378 pub(crate) fn any_ref_transformed_from_attribute_name()(s in any::<String>()) -> Reference {
380 Reference::from(AttributeName::new(s))
381 }
382 }
383
384 prop_compose! {
385 pub(crate) fn any_valid_plain_ref()(s in any_valid_plain_name()) -> Reference {
386 Reference::new(s)
387 }
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::{AttributeName, Error, Reference};
394 use crate::proptest_generators::*;
395 use proptest::prelude::*;
396 use test_case::test_case;
397
398 proptest! {
399 #[test]
400 fn regex_creates_valid_references(reference in any_valid_ref()) {
401 prop_assert!(reference.is_valid());
402 }
403 }
404
405 proptest! {
406 #[test]
409 fn regex_creates_valid_plain_references(reference in any_valid_plain_ref()) {
410 prop_assert!(reference.is_valid());
411 }
412 }
413
414 proptest! {
415 #[test]
416 fn plain_references_have_single_component(reference in any_valid_plain_ref()) {
417 prop_assert_eq!(reference.depth(), 1);
418 }
419 }
420
421 proptest! {
422 #[test]
423 fn attribute_names_are_valid_references(reference in any_valid_ref_transformed_from_attribute_name()) {
424 prop_assert!(reference.is_valid());
425 prop_assert_eq!(reference.depth(), 1);
426 }
427 }
428
429 proptest! {
430 #[test]
431 fn attribute_name_references_have_single_component(reference in any_valid_ref_transformed_from_attribute_name()) {
432 prop_assert_eq!(reference.depth(), 1);
433 let component = reference.component(0);
434 prop_assert!(component.is_some(), "component 0 should exist");
435 }
436 }
437
438 proptest! {
439 #[test]
440 fn raw_returns_input_unmodified(s in any::<String>()) {
441 let a = Reference::new(s.clone());
442 prop_assert_eq!(a.to_string(), s);
443 }
444 }
445
446 #[test]
447 fn default_reference_is_invalid() {
448 assert!(!Reference::default().is_valid());
449 }
450
451 #[test_case("", Error::Empty; "Empty reference")]
452 #[test_case("/", Error::Empty; "Single slash")]
453 #[test_case("//", Error::DoubleOrTrailingSlash; "Double slash")]
454 #[test_case("/a//b", Error::DoubleOrTrailingSlash; "Double slash in middle")]
455 #[test_case("/a/b/", Error::DoubleOrTrailingSlash; "Trailing slash")]
456 #[test_case("/~3", Error::InvalidEscapeSequence; "Tilde must be followed by 0 or 1 only")]
457 #[test_case("/testing~something", Error::InvalidEscapeSequence; "Tilde cannot be alone")]
458 #[test_case("/m~~0", Error::InvalidEscapeSequence; "Extra tilde before valid escape")]
459 #[test_case("/a~", Error::InvalidEscapeSequence; "Tilde cannot be followed by nothing")]
460 fn invalid_references(input: &str, error: Error) {
461 let reference = Reference::new(input);
462 assert!(!reference.is_valid());
463 assert_eq!(error.to_string(), reference.error());
464 }
465
466 #[test_case("key")]
467 #[test_case("kind")]
468 #[test_case("name")]
469 #[test_case("name/with/slashes")]
470 #[test_case("name~0~1with-what-looks-like-escape-sequences")]
471 fn plain_reference_syntax(input: &str) {
472 let reference = Reference::new(input);
473 assert!(reference.is_valid());
474 assert_eq!(input, reference.to_string());
475 assert_eq!(
476 input,
477 reference
478 .component(0)
479 .expect("Failed to get first component")
480 );
481 assert_eq!(1, reference.depth());
482 }
483
484 #[test_case("/key", "key")]
485 #[test_case("/kind", "kind")]
486 #[test_case("/name", "name")]
487 #[test_case("/custom", "custom")]
488 fn pointer_syntax(input: &str, path: &str) {
489 let reference = Reference::new(input);
490 assert!(reference.is_valid());
491 assert_eq!(input, reference.to_string());
492 assert_eq!(
493 path,
494 reference
495 .component(0)
496 .expect("Failed to get first component")
497 );
498 assert_eq!(1, reference.depth())
499 }
500
501 #[test_case("/a/b", 2, 0, "a")]
502 #[test_case("/a/b", 2, 1, "b")]
503 #[test_case("/a~1b/c", 2, 0, "a/b")]
504 #[test_case("/a~1b/c", 2, 1, "c")]
505 #[test_case("/a/10/20/30x", 4, 1, "10")]
506 #[test_case("/a/10/20/30x", 4, 2, "20")]
507 #[test_case("/a/10/20/30x", 4, 3, "30x")]
508 fn handles_subcomponents(input: &str, len: usize, index: usize, expected_name: &str) {
509 let reference = Reference::new(input);
510 assert!(reference.is_valid());
511 assert_eq!(input, reference.input);
512 assert_eq!(len, reference.depth());
513 assert_eq!(expected_name, reference.component(index).unwrap());
514 }
515
516 #[test]
517 fn can_handle_invalid_index_requests() {
518 let reference = Reference::new("/a/b/c");
519 assert!(reference.is_valid());
520 assert!(reference.component(0).is_some());
521 assert!(reference.component(1).is_some());
522 assert!(reference.component(2).is_some());
523 assert!(reference.component(3).is_none());
524 }
525
526 #[test_case("/a/b", "/~1a~1b")]
527 #[test_case("a", "a")]
528 #[test_case("a~1b", "a~1b")]
529 #[test_case("/a~1b", "/~1a~01b")]
530 #[test_case("/a~0b", "/~1a~00b")]
531 #[test_case("", "")]
532 #[test_case("/", "/~1")]
533 fn attribute_name_equality(name: &str, reference: &str) {
534 let as_name = AttributeName::new(name.to_owned());
535 let reference = Reference::new(reference);
536 assert_eq!(Reference::from(as_name), reference);
537 }
538
539 #[test]
540 fn is_kind() {
541 assert!(Reference::new("/kind").is_kind());
542 assert!(Reference::new("kind").is_kind());
543 assert!(Reference::from(AttributeName::new("kind".to_owned())).is_kind());
544
545 assert!(!Reference::from(AttributeName::new("/kind".to_owned())).is_kind());
546 assert!(!Reference::new("foo").is_kind());
547 }
548}