diff --git a/src/CedarDotNet/CedarFunctions.cs b/src/CedarDotNet/CedarFunctions.cs
index de417cd..c02de67 100644
--- a/src/CedarDotNet/CedarFunctions.cs
+++ b/src/CedarDotNet/CedarFunctions.cs
@@ -1,5 +1,7 @@
using CedarDotNet.Interop;
using CedarDotNet.Models;
+using System.Runtime.InteropServices;
+using System.Text.Json;
namespace CedarDotNet;
@@ -86,6 +88,30 @@ public static IAuthorizationAnswer IsAuthorized(
inputTypeInfo: CedarJsonSerializerContext.Default.AuthorizationCall,
outputTypeInfo: CedarJsonSerializerContext.Default.IAuthorizationAnswer);
+ ///
+ /// Parses a policy set into its constituent policies.
+ ///
+ /// The policy set text.
+ /// The answer.
+ public static IPolicySetTextToPartsAnswer PolicySetTextToParts(
+ string policySetText)
+ {
+ var result = CedarFfi.PolicySetTextToParts(policySetText);
+
+ try
+ {
+ var resultJson = Marshal.PtrToStringUTF8(result)!;
+
+ return JsonSerializer.Deserialize(
+ json: resultJson,
+ jsonTypeInfo: CedarJsonSerializerContext.Default.IPolicySetTextToPartsAnswer)!;
+ }
+ finally
+ {
+ CedarFfi.FreeString(result);
+ }
+ }
+
///
/// Gets the language version.
///
diff --git a/src/CedarDotNet/Interop/CedarFfi.cs b/src/CedarDotNet/Interop/CedarFfi.cs
index 5bc9410..5901778 100644
--- a/src/CedarDotNet/Interop/CedarFfi.cs
+++ b/src/CedarDotNet/Interop/CedarFfi.cs
@@ -28,6 +28,9 @@ internal static partial class CedarFfi
[LibraryImport(CedarNativeLibrary.Name, EntryPoint = "validate", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr Validate(string call);
+ [LibraryImport(CedarNativeLibrary.Name, EntryPoint = "policy_set_text_to_parts", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr PolicySetTextToParts(string call);
+
[LibraryImport(CedarNativeLibrary.Name, EntryPoint = "get_lang_version", StringMarshalling = StringMarshalling.Utf8)]
public static partial IntPtr GetLangVersion();
diff --git a/src/CedarDotNet/Models/CedarJsonSerializerContext.cs b/src/CedarDotNet/Models/CedarJsonSerializerContext.cs
index 947fa99..6fba56d 100644
--- a/src/CedarDotNet/Models/CedarJsonSerializerContext.cs
+++ b/src/CedarDotNet/Models/CedarJsonSerializerContext.cs
@@ -10,6 +10,7 @@ namespace CedarDotNet.Models;
[JsonSerializable(typeof(IAuthorizationAnswer))]
[JsonSerializable(typeof(ICheckParseAnswer))]
[JsonSerializable(typeof(IFormattingAnswer))]
+[JsonSerializable(typeof(IPolicySetTextToPartsAnswer))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
internal sealed partial class CedarJsonSerializerContext
: JsonSerializerContext;
\ No newline at end of file
diff --git a/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs
new file mode 100644
index 0000000..7c352db
--- /dev/null
+++ b/src/CedarDotNet/Models/PolicySetTextToPartsAnswer.cs
@@ -0,0 +1,40 @@
+using System.Text.Json.Serialization;
+
+namespace CedarDotNet.Models;
+
+///
+/// The marker interface for 'policy set text to parts' answer.
+///
+[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
+[JsonDerivedType(typeof(PolicySetTextToPartsAnswerSuccess), "success")]
+[JsonDerivedType(typeof(PolicySetTextToPartsAnswerFailure), "failure")]
+public interface IPolicySetTextToPartsAnswer;
+
+///
+/// A successful 'policy set text to parts' answer.
+///
+public sealed record class PolicySetTextToPartsAnswerSuccess
+ : IPolicySetTextToPartsAnswer
+{
+ ///
+ /// The policies.
+ ///
+ [JsonPropertyName("policies")]
+ public required IReadOnlyCollection Policies { get; init; }
+
+ ///
+ /// The policy templates.
+ ///
+ [JsonPropertyName("policy_templates")]
+ public required IReadOnlyCollection PolicyTemplates { get; init; }
+}
+
+///
+/// A failed 'policy set text to parts' answer.
+///
+public sealed record class PolicySetTextToPartsAnswerFailure
+ : IPolicySetTextToPartsAnswer
+{
+ [JsonPropertyName("errors")]
+ public required IReadOnlyCollection Errors { get; init; }
+}
\ No newline at end of file
diff --git a/src/CedarDotNetFfi/Cargo.lock b/src/CedarDotNetFfi/Cargo.lock
index d2fe441..d206143 100644
--- a/src/CedarDotNetFfi/Cargo.lock
+++ b/src/CedarDotNetFfi/Cargo.lock
@@ -123,12 +123,11 @@ dependencies = [
[[package]]
name = "cedar-policy"
-version = "4.4.0"
-source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6"
+version = "4.5.0"
+source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97"
dependencies = [
"cedar-policy-core",
"cedar-policy-formatter",
- "cedar-policy-validator",
"itertools",
"lalrpop-util",
"lazy_static",
@@ -145,8 +144,8 @@ dependencies = [
[[package]]
name = "cedar-policy-core"
-version = "4.4.0"
-source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6"
+version = "4.5.0"
+source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97"
dependencies = [
"chrono",
"educe",
@@ -166,12 +165,13 @@ dependencies = [
"smol_str",
"stacker",
"thiserror",
+ "unicode-security",
]
[[package]]
name = "cedar-policy-formatter"
-version = "4.4.0"
-source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6"
+version = "4.5.0"
+source = "git+https://github.com/cedar-policy/cedar?branch=main#55249a83fb1157d6aeba41838916b880d6794f97"
dependencies = [
"cedar-policy-core",
"itertools",
@@ -183,29 +183,6 @@ dependencies = [
"smol_str",
]
-[[package]]
-name = "cedar-policy-validator"
-version = "4.4.0"
-source = "git+https://github.com/cedar-policy/cedar?branch=main#d95df22e17434b4c0cf85d4751ab0660637c24c6"
-dependencies = [
- "cedar-policy-core",
- "educe",
- "itertools",
- "lalrpop",
- "lalrpop-util",
- "lazy_static",
- "miette",
- "nonempty",
- "ref-cast",
- "serde",
- "serde_json",
- "serde_with",
- "smol_str",
- "stacker",
- "thiserror",
- "unicode-security",
-]
-
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -311,6 +288,12 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
[[package]]
name = "educe"
version = "0.6.0"
@@ -501,9 +484,9 @@ dependencies = [
[[package]]
name = "lalrpop"
-version = "0.22.1"
+version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7047a26de42016abf8f181b46b398aef0b77ad46711df41847f6ed869a2a1d5b"
+checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
dependencies = [
"ascii-canvas",
"bit-set",
@@ -523,9 +506,9 @@ dependencies = [
[[package]]
name = "lalrpop-util"
-version = "0.22.1"
+version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d05b3fe34b8bd562c338db725dfa9beb9451a48f65f129ccb9538b48d2c93b"
+checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
dependencies = [
"regex-automata",
"rustversion",
@@ -630,9 +613,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nonempty"
-version = "0.10.0"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "303e8749c804ccd6ca3b428de7fe0d86cb86bc7606bc15291f100fd487960bb8"
+checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6"
dependencies = [
"serde",
]
@@ -853,6 +836,30 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -887,9 +894,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.140"
+version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [
"indexmap 2.9.0",
"itoa",
@@ -900,15 +907,17 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.12.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
+checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.9.0",
+ "schemars 0.9.0",
+ "schemars 1.0.4",
"serde",
"serde_derive",
"serde_json",
@@ -918,9 +927,9 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.12.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
+checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
dependencies = [
"darling",
"proc-macro2",
diff --git a/src/CedarDotNetFfi/Cargo.toml b/src/CedarDotNetFfi/Cargo.toml
index 58fe2d1..d935d28 100644
--- a/src/CedarDotNetFfi/Cargo.toml
+++ b/src/CedarDotNetFfi/Cargo.toml
@@ -7,10 +7,10 @@ edition = "2024"
crate-type=["cdylib"]
[dependencies]
-serde_json = "1.0.140"
+serde_json = "1.0.141"
[dependencies.cedar-policy]
-version = "4.4.0"
+version = "4.5.0"
git = "https://github.com/cedar-policy/cedar"
branch = "main"
features = ["partial-eval"]
diff --git a/src/CedarDotNetFfi/src/lib.rs b/src/CedarDotNetFfi/src/lib.rs
index 56e0a08..1199803 100644
--- a/src/CedarDotNetFfi/src/lib.rs
+++ b/src/CedarDotNetFfi/src/lib.rs
@@ -17,7 +17,8 @@ use cedar_policy::ffi::{
is_authorized_partial_json_str,
validate_json_str,
get_lang_version as internal_get_lang_version,
- get_sdk_version as internal_get_sdk_version
+ get_sdk_version as internal_get_sdk_version,
+ policy_set_text_to_parts as internal_policy_set_text_to_parts
};
#[unsafe(no_mangle)]
@@ -153,4 +154,15 @@ pub fn load_policy_set(text: *const c_char) -> *const c_char {
let result_json_str = serde_json::to_string(&arr).unwrap();
CString::new(result_json_str.to_string()).unwrap().into_raw()
+}
+
+#[unsafe(no_mangle)]
+pub fn policy_set_text_to_parts(text: *const c_char) -> *const c_char {
+ let text_str = unsafe { CStr::from_ptr(text).to_str().unwrap() };
+
+ let parts = internal_policy_set_text_to_parts(text_str);
+
+ let result_json_str = serde_json::to_string(&parts).expect("Could not serialize parts to JSON");
+
+ CString::new(result_json_str).unwrap().into_raw()
}
\ No newline at end of file
diff --git a/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj b/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj
index 423ade6..a4cc99d 100644
--- a/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj
+++ b/tests/CedarDotNet.UnitTests/CedarDotNet.UnitTests.csproj
@@ -13,10 +13,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs b/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs
new file mode 100644
index 0000000..b242c67
--- /dev/null
+++ b/tests/CedarDotNet.UnitTests/CedarFunctionsTests.PolicySetTextToParts.cs
@@ -0,0 +1,46 @@
+using CedarDotNet.Models;
+
+namespace CedarDotNet.UnitTests;
+
+public partial class CedarFunctionTests
+{
+ [Fact]
+ public void PolicySetTextToParts_ValidPolicySet_ReturnsPoliciesAndTemplates()
+ {
+ // Arrange
+ var policySetText = """
+ permit(principal, action, resource)
+ when { principal has "Email" && principal.Email == "a@a.com" };
+
+ permit(principal in UserGroup::"DeathRowRecords", action == Action::"pop", resource);
+
+ permit(principal in ?principal, action, resource);
+ """;
+
+ // Act
+ var result = CedarFunctions.PolicySetTextToParts(policySetText);
+
+ // Assert
+ Assert.IsType(result);
+
+ var success = (PolicySetTextToPartsAnswerSuccess)result;
+
+ Assert.Equal(2, success.Policies.Count);
+ Assert.Single(success.PolicyTemplates);
+ }
+
+ [Fact]
+ public void PolicySetTextToParts_InvalidPolicySet_ReturnsFailure()
+ {
+ // Arrange
+ var policySetText = """
+ invalid
+ """;
+
+ // Act
+ var result = CedarFunctions.PolicySetTextToParts(policySetText);
+
+ // Assert
+ Assert.IsType(result);
+ }
+}
diff --git a/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs b/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs
index 8a8e851..fb096c3 100644
--- a/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs
+++ b/tests/CedarDotNet.UnitTests/IntegrationTests/Dtos/TestJsonSerializedContext.cs
@@ -6,6 +6,6 @@ namespace CedarDotNet.UnitTests.IntegrationTests.Dtos;
[JsonSerializable(typeof(TestScenarioDto))]
[JsonSerializable(typeof(IReadOnlyCollection))]
[JsonSourceGenerationOptions(
- PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,UseStringEnumConverter = true)]
+ PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower, UseStringEnumConverter = true)]
internal sealed partial class TestJsonSerializedContext
: JsonSerializerContext;
\ No newline at end of file