Skip to content

Commit

Permalink
Update dis_macro and add ref dis injectiond
Browse files Browse the repository at this point in the history
  • Loading branch information
garethj2 committed Feb 23, 2024
1 parent 9d4f989 commit 4de024a
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 16 deletions.
65 changes: 65 additions & 0 deletions src/haystack/encoding/decode_ref_dis_factory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (C) 2020 - 2024, J2 Innovations

use std::borrow::Cow;
use std::sync::RwLock;

/// Enables display values to be injected into decoded Ref objects.
///
/// This is used to handle the case whereby pre-calculated display
/// names for Refs need to be utilized when encoding/decoding haystack data.
///
/// Please note, the return type is a `Cow<str>`. At some point we'll make `Ref` a little
/// more flexible regarding how it holds its display data and hence its usage.
type RefDisFactoryFunc<'a> = Box<dyn Fn(&str, Option<&str>) -> Option<Cow<'a, str>> + Send + Sync>;

/// Holds the Ref factory function used for creating Refs.
static REF_DIS_FACTORY_FUNC: RwLock<Option<RefDisFactoryFunc>> = RwLock::new(None);

/// Get a ref's display name value from any registered factory.
pub fn get_ref_dis_from_factory<'a>(val: &str, dis: Option<&'a str>) -> Option<Cow<'a, str>> {
if let Some(func) = REF_DIS_FACTORY_FUNC.read().unwrap().as_ref() {
func(val, dis)
} else {
dis.map(Cow::Borrowed)
}
}

/// Set the factory function for getting ref display name values.
pub fn set_ref_dis_factory(factory_fn: Option<RefDisFactoryFunc<'static>>) {
*REF_DIS_FACTORY_FUNC.write().unwrap() = factory_fn;
}

#[cfg(test)]
mod test {
use std::{borrow::Cow, sync::Mutex};

use super::{get_ref_dis_from_factory, set_ref_dis_factory};

// Use a mutex to prevent unit tests from interfering with each other. One of the tests
// sets some global data so they can overlap.
static MUTEX: Mutex<()> = Mutex::new(());

#[test]
fn make_ref_returns_ref() {
let _ = MUTEX.lock();

let dis = get_ref_dis_from_factory("a", Some("c"));

assert_eq!(dis, Some(Cow::Borrowed("c")));
}

#[test]
fn make_ref_from_factory_returns_ref() {
let _ = MUTEX.lock();

let factory_fn = Box::new(|_: &str, _: Option<&str>| Some(Cow::Owned(String::from("c"))));

set_ref_dis_factory(Some(factory_fn));

let dis = get_ref_dis_from_factory("a", Some("b"));

assert_eq!(dis, Some(Cow::Borrowed("c")));

set_ref_dis_factory(None);
}
}
41 changes: 32 additions & 9 deletions src/haystack/encoding/json/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//!
//! Implement Hayson decoding
//!
use crate::encoding::decode_ref_dis_factory::get_ref_dis_from_factory;
use crate::haystack::val::{
Column, Coord, Date, DateTime, Dict, Grid, HaystackDict, List, Marker, Na, Number, Ref, Remove,
Str, Symbol, Time, Uri, Value as HVal, XStr,
Expand Down Expand Up @@ -103,7 +104,19 @@ impl<'de> Deserialize<'de> for Ref {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Ref, D::Error> {
let val = deserializer.deserialize_any(HValVisitor)?;
match val {
HVal::Ref(val) => Ok(val),
HVal::Ref(val) => {
let dis = get_ref_dis_from_factory(val.value.as_str(), val.dis.as_deref())
.map(|v| v.to_string());

Ok(if dis == val.dis {
val
} else {
Ref {
value: val.value,
dis,
}
})
}
_ => Err(D::Error::custom("Invalid Hayson Ref")),
}
}
Expand Down Expand Up @@ -377,16 +390,26 @@ fn parse_number(dict: &Dict) -> Result<HVal, JsonErr> {
fn parse_ref(dict: &Dict) -> Result<HVal, JsonErr> {
match dict.get_str("val") {
Some(val) => match dict.get_str("dis") {
Some(dis) => Ok(Ref {
value: val.value.clone(),
dis: Some(dis.value.clone()),
Some(dis) => {
let dis = get_ref_dis_from_factory(val.value.as_str(), Some(dis.as_str()))
.map(|val| val.to_string());

Ok(Ref {
value: val.value.clone(),
dis,
}
.into())
}
.into()),
None => Ok(Ref {
value: val.value.clone(),
dis: None,
None => {
let dis =
get_ref_dis_from_factory(val.value.as_str(), None).map(|val| val.to_string());

Ok(Ref {
value: val.value.clone(),
dis,
}
.into())
}
.into()),
},
None => Err(JsonErr::custom("Missing or invalid 'val'")),
}
Expand Down
2 changes: 2 additions & 0 deletions src/haystack/encoding/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
pub mod json;
#[cfg(feature = "zinc")]
pub mod zinc;

pub mod decode_ref_dis_factory;
6 changes: 5 additions & 1 deletion src/haystack/val/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,11 @@ where

if let Some(val) = dict.get("disMacro") {
return if let Value::Str(val) = val {
dis_macro(&val.value, |val| dict.get(val), get_localized)
dis_macro(
&val.value,
|val| dict.get(val).map(Cow::Borrowed),
get_localized,
)
} else {
decode_str_from_value(val)
};
Expand Down
13 changes: 7 additions & 6 deletions src/haystack/val/dis_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use regex::{Captures, Regex, Replacer};
/// values in a display macro string.
struct DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
where
GetValueFunc: Fn(&str) -> Option<&'a Value>,
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
{
get_value: &'b GetValueFunc,
Expand All @@ -20,7 +20,7 @@ where
impl<'a, 'b, GetValueFunc, GetLocalizedFunc> Replacer
for DisReplacer<'a, 'b, GetValueFunc, GetLocalizedFunc>
where
GetValueFunc: Fn(&str) -> Option<&'a Value>,
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
{
fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) {
Expand All @@ -29,9 +29,9 @@ where
// Replace $tag or ${tag}
if let Some(cap_match) = caps.get(2).or_else(|| caps.get(4)) {
if let Some(value) = (self.get_value)(cap_match.as_str()) {
if let Value::Ref(val) = value {
if let Value::Ref(val) = value.as_ref() {
dst.push_str(val.dis.as_ref().unwrap_or(&val.value));
} else if let Value::Str(val) = value {
} else if let Value::Str(val) = value.as_ref() {
dst.push_str(&val.value);
} else {
dst.push_str(&value.to_string());
Expand Down Expand Up @@ -70,7 +70,7 @@ pub fn dis_macro<'a, GetValueFunc, GetLocalizedFunc>(
get_localized: GetLocalizedFunc,
) -> Cow<'a, str>
where
GetValueFunc: Fn(&str) -> Option<&'a Value>,
GetValueFunc: Fn(&str) -> Option<Cow<'a, Value>>,
GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
{
// Cache the regular expression in memory so it doesn't need to compile on each invocation.
Expand Down Expand Up @@ -103,7 +103,7 @@ mod test {

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

fn dict_cb<'a>(name: &str) -> Option<&'a Value> {
fn dict_cb<'a>(name: &str) -> Option<Cow<'a, Value>> {
DICT.get_or_init(|| {
dict![
"equipRef" => Value::make_ref("ref"),
Expand All @@ -114,6 +114,7 @@ mod test {
]
})
.get(name)
.map(|val| Cow::Borrowed(val))
}

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

0 comments on commit 4de024a

Please sign in to comment.