Skip to content

Commit 4de024a

Browse files
committed
Update dis_macro and add ref dis injectiond
1 parent 9d4f989 commit 4de024a

File tree

5 files changed

+111
-16
lines changed

5 files changed

+111
-16
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (C) 2020 - 2024, J2 Innovations
2+
3+
use std::borrow::Cow;
4+
use std::sync::RwLock;
5+
6+
/// Enables display values to be injected into decoded Ref objects.
7+
///
8+
/// This is used to handle the case whereby pre-calculated display
9+
/// names for Refs need to be utilized when encoding/decoding haystack data.
10+
///
11+
/// Please note, the return type is a `Cow<str>`. At some point we'll make `Ref` a little
12+
/// more flexible regarding how it holds its display data and hence its usage.
13+
type RefDisFactoryFunc<'a> = Box<dyn Fn(&str, Option<&str>) -> Option<Cow<'a, str>> + Send + Sync>;
14+
15+
/// Holds the Ref factory function used for creating Refs.
16+
static REF_DIS_FACTORY_FUNC: RwLock<Option<RefDisFactoryFunc>> = RwLock::new(None);
17+
18+
/// Get a ref's display name value from any registered factory.
19+
pub fn get_ref_dis_from_factory<'a>(val: &str, dis: Option<&'a str>) -> Option<Cow<'a, str>> {
20+
if let Some(func) = REF_DIS_FACTORY_FUNC.read().unwrap().as_ref() {
21+
func(val, dis)
22+
} else {
23+
dis.map(Cow::Borrowed)
24+
}
25+
}
26+
27+
/// Set the factory function for getting ref display name values.
28+
pub fn set_ref_dis_factory(factory_fn: Option<RefDisFactoryFunc<'static>>) {
29+
*REF_DIS_FACTORY_FUNC.write().unwrap() = factory_fn;
30+
}
31+
32+
#[cfg(test)]
33+
mod test {
34+
use std::{borrow::Cow, sync::Mutex};
35+
36+
use super::{get_ref_dis_from_factory, set_ref_dis_factory};
37+
38+
// Use a mutex to prevent unit tests from interfering with each other. One of the tests
39+
// sets some global data so they can overlap.
40+
static MUTEX: Mutex<()> = Mutex::new(());
41+
42+
#[test]
43+
fn make_ref_returns_ref() {
44+
let _ = MUTEX.lock();
45+
46+
let dis = get_ref_dis_from_factory("a", Some("c"));
47+
48+
assert_eq!(dis, Some(Cow::Borrowed("c")));
49+
}
50+
51+
#[test]
52+
fn make_ref_from_factory_returns_ref() {
53+
let _ = MUTEX.lock();
54+
55+
let factory_fn = Box::new(|_: &str, _: Option<&str>| Some(Cow::Owned(String::from("c"))));
56+
57+
set_ref_dis_factory(Some(factory_fn));
58+
59+
let dis = get_ref_dis_from_factory("a", Some("b"));
60+
61+
assert_eq!(dis, Some(Cow::Borrowed("c")));
62+
63+
set_ref_dis_factory(None);
64+
}
65+
}

src/haystack/encoding/json/decode.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//!
44
//! Implement Hayson decoding
55
//!
6+
use crate::encoding::decode_ref_dis_factory::get_ref_dis_from_factory;
67
use crate::haystack::val::{
78
Column, Coord, Date, DateTime, Dict, Grid, HaystackDict, List, Marker, Na, Number, Ref, Remove,
89
Str, Symbol, Time, Uri, Value as HVal, XStr,
@@ -103,7 +104,19 @@ impl<'de> Deserialize<'de> for Ref {
103104
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Ref, D::Error> {
104105
let val = deserializer.deserialize_any(HValVisitor)?;
105106
match val {
106-
HVal::Ref(val) => Ok(val),
107+
HVal::Ref(val) => {
108+
let dis = get_ref_dis_from_factory(val.value.as_str(), val.dis.as_deref())
109+
.map(|v| v.to_string());
110+
111+
Ok(if dis == val.dis {
112+
val
113+
} else {
114+
Ref {
115+
value: val.value,
116+
dis,
117+
}
118+
})
119+
}
107120
_ => Err(D::Error::custom("Invalid Hayson Ref")),
108121
}
109122
}
@@ -377,16 +390,26 @@ fn parse_number(dict: &Dict) -> Result<HVal, JsonErr> {
377390
fn parse_ref(dict: &Dict) -> Result<HVal, JsonErr> {
378391
match dict.get_str("val") {
379392
Some(val) => match dict.get_str("dis") {
380-
Some(dis) => Ok(Ref {
381-
value: val.value.clone(),
382-
dis: Some(dis.value.clone()),
393+
Some(dis) => {
394+
let dis = get_ref_dis_from_factory(val.value.as_str(), Some(dis.as_str()))
395+
.map(|val| val.to_string());
396+
397+
Ok(Ref {
398+
value: val.value.clone(),
399+
dis,
400+
}
401+
.into())
383402
}
384-
.into()),
385-
None => Ok(Ref {
386-
value: val.value.clone(),
387-
dis: None,
403+
None => {
404+
let dis =
405+
get_ref_dis_from_factory(val.value.as_str(), None).map(|val| val.to_string());
406+
407+
Ok(Ref {
408+
value: val.value.clone(),
409+
dis,
410+
}
411+
.into())
388412
}
389-
.into()),
390413
},
391414
None => Err(JsonErr::custom("Missing or invalid 'val'")),
392415
}

src/haystack/encoding/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@
77
pub mod json;
88
#[cfg(feature = "zinc")]
99
pub mod zinc;
10+
11+
pub mod decode_ref_dis_factory;

src/haystack/val/dict.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,11 @@ where
386386

387387
if let Some(val) = dict.get("disMacro") {
388388
return if let Value::Str(val) = val {
389-
dis_macro(&val.value, |val| dict.get(val), get_localized)
389+
dis_macro(
390+
&val.value,
391+
|val| dict.get(val).map(Cow::Borrowed),
392+
get_localized,
393+
)
390394
} else {
391395
decode_str_from_value(val)
392396
};

src/haystack/val/dis_macro.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use regex::{Captures, Regex, Replacer};
99
/// values in a display macro string.
1010
struct DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
1111
where
12-
GetValueFunc: Fn(&str) -> Option<&'a Value>,
12+
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
1313
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
1414
{
1515
get_value: &'b GetValueFunc,
@@ -20,7 +20,7 @@ where
2020
impl<'a, 'b, GetValueFunc, GetLocalizedFunc> Replacer
2121
for DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
2222
where
23-
GetValueFunc: Fn(&str) -> Option<&'a Value>,
23+
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
2424
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
2525
{
2626
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
@@ -29,9 +29,9 @@ where
2929
// Replace $tag or ${tag}
3030
if let Some(cap_match) = caps.get(2).or_else(|| caps.get(4)) {
3131
if let Some(value) = (self.get_value)(cap_match.as_str()) {
32-
if let Value::Ref(val) = value {
32+
if let Value::Ref(val) = value.as_ref() {
3333
dst.push_str(val.dis.as_ref().unwrap_or(&val.value));
34-
} else if let Value::Str(val) = value {
34+
} else if let Value::Str(val) = value.as_ref() {
3535
dst.push_str(&val.value);
3636
} else {
3737
dst.push_str(&value.to_string());
@@ -70,7 +70,7 @@ pub fn dis_macro<'a, GetValueFunc, GetLocalizedFunc>(
7070
get_localized: GetLocalizedFunc,
7171
) -> Cow<'a, str>
7272
where
73-
GetValueFunc: Fn(&str) -> Option<&'a Value>,
73+
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
7474
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
7575
{
7676
// Cache the regular expression in memory so it doesn't need to compile on each invocation.
@@ -103,7 +103,7 @@ mod test {
103103

104104
static DICT: OnceLock<Dict> = OnceLock::new();
105105

106-
fn dict_cb<'a>(name: &str) -> Option<&'a Value> {
106+
fn dict_cb<'a>(name: &str) -> Option<Cow<'a, Value>> {
107107
DICT.get_or_init(|| {
108108
dict![
109109
"equipRef" => Value::make_ref("ref"),
@@ -114,6 +114,7 @@ mod test {
114114
]
115115
})
116116
.get(name)
117+
.map(|val| Cow::Borrowed(val))
117118
}
118119

119120
fn i18n_cb<'a>(name: &str) -> Option<Cow<'a, str>> {

0 commit comments

Comments
 (0)