From 6ee3376ff3517d8865e096fe1845325ca3018ecf Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Sun, 27 Sep 2026 11:41:44 +0200 Subject: [PATCH] add support for extracting null, map, array, set terms to python --- Cargo.lock | 54 +++++++++++++----------------------------------- biscuit_test.py | 18 ++++++++++++++++ docs/datalog.rst | 9 +++----- src/lib.rs | 43 +++++++++++++++++++++++++++++--------- 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e583fb..2f3c730 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -468,12 +468,6 @@ dependencies = [ "cc", ] -[[package]] -name = "indoc" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" - [[package]] name = "itertools" version = "0.10.5" @@ -516,15 +510,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -558,9 +543,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -705,38 +690,33 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da310086b068fbdcefbba30aeb3721d5bb9af8db4987d6735b2183ca567229" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ - "cfg-if", "chrono", - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27165889bd793000a098bb966adc4300c312497ea25cf7a690a9f0ac5aa5fc1" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ - "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05280526e1dbf6b420062f3ef228b78c0c54ba94e157f5cb724a609d0f2faabc" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -744,9 +724,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3ce5686aa4d3f63359a5100c62a127c9f15e8398e5fdeb5deef1fed5cd5f44" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -756,9 +736,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.24.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4cf6faa0cbfb0ed08e89beb8103ae9724eb4750e3a78084ba4017cbe94f3855" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", @@ -1012,9 +992,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "thiserror" @@ -1079,12 +1059,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unindent" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" - [[package]] name = "version_check" version = "0.9.5" diff --git a/biscuit_test.py b/biscuit_test.py index ac308fa..9ec6d06 100644 --- a/biscuit_test.py +++ b/biscuit_test.py @@ -11,6 +11,24 @@ def test_fact(): assert fact.name == "fact" assert fact.terms == [1, True, "", "Test", b'\xaa\xbb\xcc', datetime(2023, 4, 29, 1, 0, 0, tzinfo = timezone.utc)] +def test_fact_collection_terms(): + assert Fact('fact({1, 2, 3})').terms == [{1, 2, 3}] + assert Fact('fact([1, 2, 3])').terms == [[1, 2, 3]] + assert Fact('fact({"a": 1, "b": 2})').terms == [{"a": 1, "b": 2}] + assert Fact('fact({1: "x", 2: "y"})').terms == [{1: "x", 2: "y"}] + assert Fact('fact(null)').terms == [None] + assert Fact('fact([1, {2, 3}, {"k": [4, 5]}, null])').terms == [[1, {2, 3}, {"k": [4, 5]}, None]] + +def test_query_collection_terms(): + # collection terms also convert cleanly when returned from an authorizer query + builder = AuthorizerBuilder() + builder.add_fact(Fact('data({1, 2, 3}, [4, 5], {"k": 6}, null)')) + authorizer = builder.build_unauthenticated() + + facts = authorizer.query(Rule("res($s, $a, $m, $n) <- data($s, $a, $m, $n)")) + assert len(facts) == 1 + assert facts[0].terms == [{1, 2, 3}, [4, 5], {"k": 6}, None] + def test_biscuit_builder(): kp = KeyPair() pubkey = PublicKey("ed25519/acdd6d5b53bfee478bf689f8e012fe7988bf755e3d7c5152947abc149bc20189") diff --git a/docs/datalog.rst b/docs/datalog.rst index b4178fc..65b12e1 100644 --- a/docs/datalog.rst +++ b/docs/datalog.rst @@ -130,11 +130,8 @@ Terms of a fact can be extracted to python values.o >>> fact.terms ['abc', 123, b'\xaa', datetime.datetime(2023, 6, 9, 0, 0, tzinfo=datetime.timezone.utc), True] -.. warning:: - Extracting sets is not supported yet. ->>> Fact("fact({123})").terms -Traceback (most recent call last): - ... -pyo3_runtime.PanicException: not yet implemented +>>> fact = Fact("""fact({123}, [123], {"a": 123}, null)""") +>>> fact.terms +[{123}, [123], {'a': 123}, None] diff --git a/src/lib.rs b/src/lib.rs index 0b9dd59..ab08894 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use ::biscuit_auth::RootKeyProvider; use ::biscuit_auth::ThirdPartyBlock; use ::biscuit_auth::ThirdPartyRequest; use ::biscuit_auth::UnverifiedBiscuit; -use chrono::DateTime; use chrono::Duration; use chrono::TimeZone; use chrono::Utc; @@ -1326,19 +1325,43 @@ fn inner_term_to_py(t: &builder::Term, py: Python<'_>) -> PyResult> { } builder::Term::Bytes(bs) => bs.clone().into_py_any(py), builder::Term::Bool(b) => (*b).into_py_any(py), - _ => Err(DataLogError::new_err("Invalid term value".to_string())), + builder::Term::Null => py.None().into_py_any(py), + builder::Term::Set(vs) => { + let set = PySet::empty(py)?; + for v in vs { + set.add(inner_term_to_py(v, py)?)?; + } + set.into_py_any(py) + } + builder::Term::Array(vs) => { + let list = PyList::empty(py); + for v in vs { + list.append(inner_term_to_py(v, py)?)?; + } + list.into_py_any(py) + } + builder::Term::Map(m) => { + let dict = PyDict::new(py); + for (k, v) in m { + let key = match k { + MapKey::Integer(i) => (*i).into_py_any(py)?, + MapKey::Str(s) => s.into_py_any(py)?, + MapKey::Parameter(_) => { + return Err(DataLogError::new_err("Invalid map key".to_string())) + } + }; + dict.set_item(key, inner_term_to_py(v, py)?)?; + } + dict.into_py_any(py) + } + builder::Term::Parameter(_) | builder::Term::Variable(_) => { + Err(DataLogError::new_err("Invalid term value".to_string())) + } } } fn term_to_py(t: &builder::Term) -> PyResult> { - Python::attach(|py| match t { - builder::Term::Parameter(_) => Err(DataLogError::new_err("Invalid term value".to_string())), - builder::Term::Variable(_) => Err(DataLogError::new_err("Invalid term value".to_string())), - builder::Term::Set(_vs) => todo!(), - builder::Term::Array(_vs) => todo!(), - builder::Term::Map(_vs) => todo!(), - term => inner_term_to_py(term, py), - }) + Python::attach(|py| inner_term_to_py(t, py)) } /// Wrapper for a non-naïve python date