rdf_fusion_functions/scalar/strings/
encode_for_uri.rs

1use crate::scalar::dispatch::dispatch_unary_owned_typed_value;
2use crate::scalar::sparql_op_impl::{
3    ScalarSparqlOpImpl, create_typed_value_sparql_op_impl,
4};
5use crate::scalar::{ScalarSparqlOp, ScalarSparqlOpSignature, SparqlOpArity};
6use rdf_fusion_encoding::typed_value::TypedValueEncoding;
7use rdf_fusion_extensions::functions::BuiltinName;
8use rdf_fusion_extensions::functions::FunctionName;
9use rdf_fusion_model::{SimpleLiteral, ThinError, TypedValue, TypedValueRef};
10
11#[derive(Debug, Hash, PartialEq, Eq)]
12pub struct EncodeForUriSparqlOp;
13
14impl Default for EncodeForUriSparqlOp {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl EncodeForUriSparqlOp {
21    const NAME: FunctionName = FunctionName::Builtin(BuiltinName::EncodeForUri);
22
23    pub fn new() -> Self {
24        Self {}
25    }
26}
27
28impl ScalarSparqlOp for EncodeForUriSparqlOp {
29    fn name(&self) -> &FunctionName {
30        &Self::NAME
31    }
32
33    fn signature(&self) -> ScalarSparqlOpSignature {
34        ScalarSparqlOpSignature::default_with_arity(SparqlOpArity::Fixed(1))
35    }
36
37    fn typed_value_encoding_op(
38        &self,
39    ) -> Option<Box<dyn ScalarSparqlOpImpl<TypedValueEncoding>>> {
40        Some(create_typed_value_sparql_op_impl(|args| {
41            dispatch_unary_owned_typed_value(
42                &args.args[0],
43                |value| {
44                    let string = match value {
45                        TypedValueRef::SimpleLiteral(value) => value.value,
46                        TypedValueRef::LanguageStringLiteral(value) => value.value,
47                        _ => return ThinError::expected(),
48                    };
49
50                    // Based on oxigraph/lib/spareval/src/eval.rs
51                    // Maybe we can use a library in the future?
52                    let mut result = Vec::with_capacity(string.len());
53                    for c in string.bytes() {
54                        match c {
55                            b'A'..=b'Z'
56                            | b'a'..=b'z'
57                            | b'0'..=b'9'
58                            | b'-'
59                            | b'_'
60                            | b'.'
61                            | b'~' => result.push(c),
62                            _ => {
63                                result.push(b'%');
64                                let high = c / 16;
65                                let low = c % 16;
66                                result.push(if high < 10 {
67                                    b'0' + high
68                                } else {
69                                    b'A' + (high - 10)
70                                });
71                                result.push(if low < 10 {
72                                    b'0' + low
73                                } else {
74                                    b'A' + (low - 10)
75                                });
76                            }
77                        }
78                    }
79
80                    let value = String::from_utf8(result)?;
81                    Ok(TypedValue::SimpleLiteral(SimpleLiteral { value }))
82                },
83                ThinError::expected,
84            )
85        }))
86    }
87}