From 4563f02f67252c2a30b041f392719b47592101d8 Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 28 Jul 2026 13:20:57 +0000 Subject: [PATCH 1/6] Adding PolicySet parsePoliciesJson method --- .../cedarpolicy/model/policy/PolicySet.java | 42 +++++++++++++ .../java/com/cedarpolicy/PolicySetTests.java | 51 ++++++++++++++++ CedarJava/src/test/resources/policies.json | 37 ++++++++++++ CedarJava/src/test/resources/template.json | 53 +++++++++++++++++ CedarJavaFFI/src/interface.rs | 59 +++++++++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 CedarJava/src/test/resources/policies.json create mode 100644 CedarJava/src/test/resources/template.json diff --git a/CedarJava/src/main/java/com/cedarpolicy/model/policy/PolicySet.java b/CedarJava/src/main/java/com/cedarpolicy/model/policy/PolicySet.java index 8dd1c98c..371b03d5 100644 --- a/CedarJava/src/main/java/com/cedarpolicy/model/policy/PolicySet.java +++ b/CedarJava/src/main/java/com/cedarpolicy/model/policy/PolicySet.java @@ -151,6 +151,46 @@ public static PolicySet parsePolicies(String policiesString) throws InternalExce return policySet; } + /** + * Parse multiple policies and templates from a JSON file into a PolicySet. + * + *

The file is expected to contain the JSON (EST) representation of a policy set, e.g. + *

{@code
+     * {
+     *   "staticPolicies": { "policy0": { ... } },
+     *   "templates": { "template0": { ... } },
+     *   "templateLinks": [ ... ]
+     * }
+     * }
+ * + * @param filePath the path to the JSON file containing the policies + * @return a PolicySet containing the parsed policies + * @throws InternalException + * @throws IOException + * @throws NullPointerException + */ + public static PolicySet parsePoliciesJson(Path filePath) throws InternalException, IOException { + // Read the file contents into a String + String policiesJsonString = Files.readString(filePath); + return parsePoliciesJson(policiesJsonString); + } + + /** + * Parse a JSON string containing multiple policies and templates into a PolicySet. + * + *

The string is expected to be the JSON (EST) representation of a policy set. See + * {@link #parsePoliciesJson(Path)} for the expected shape. + * + * @param policiesJsonString the JSON string containing the policies + * @return a PolicySet containing the parsed policies + * @throws InternalException + * @throws NullPointerException + */ + public static PolicySet parsePoliciesJson(String policiesJsonString) throws InternalException { + PolicySet policySet = parsePoliciesJsonJni(policiesJsonString); + return policySet; + } + // --- Caching support --- private volatile String cacheId; @@ -219,6 +259,8 @@ public void run() { } private static native PolicySet parsePoliciesJni(String policiesStr) throws InternalException, NullPointerException; + private static native PolicySet parsePoliciesJsonJni(String policiesJsonStr) + throws InternalException, NullPointerException; private static native String policySetToJson(String policySetStr) throws InternalException, NullPointerException; private static native void preparsePolicySetJni(String id, String policiesJson) throws InternalException; private static native void removeCachedPolicySetJni(String id); diff --git a/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java b/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java index 86490c56..9abc46d5 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java @@ -79,6 +79,57 @@ public void parseTemplatesTests() throws InternalException, IOException { assertEquals(1, policySet.templates.size()); } + @Test + public void parsePoliciesJsonTests() throws InternalException, IOException { + PolicySet policySet = PolicySet.parsePoliciesJson(Path.of(TEST_RESOURCES_DIR + "policies.json")); + for (Policy p: policySet.policies) { + assertNotNull(p.policySrc); + } + // Make sure the policy IDs are unique as Policies are made + assertEquals(2, policySet.policies.stream().map(p -> p.policyID).distinct().count()); + assertEquals(2, policySet.policies.size()); + assertEquals(0, policySet.templates.size()); + } + + @Test + public void parsePoliciesJsonStringTests() throws InternalException { + String json = "{\"staticPolicies\":{\"policy0\":{\"effect\":\"permit\"," + + "\"principal\":{\"op\":\"All\"},\"action\":{\"op\":\"All\"}," + + "\"resource\":{\"op\":\"All\"},\"conditions\":[]}}," + + "\"templates\":{},\"templateLinks\":[]}"; + PolicySet policySet = PolicySet.parsePoliciesJson(json); + for (Policy p: policySet.policies) { + assertNotNull(p.policySrc); + } + assertEquals(1, policySet.policies.size()); + assertEquals(0, policySet.templates.size()); + } + + @Test + public void parseTemplatesJsonTests() throws InternalException, IOException { + PolicySet policySet = PolicySet.parsePoliciesJson(Path.of(TEST_RESOURCES_DIR + "template.json")); + for (Policy p: policySet.policies) { + assertNotNull(p.policySrc); + } + assertEquals(2, policySet.policies.size()); + + for (Policy p: policySet.templates) { + assertNotNull(p.policySrc); + } + assertEquals(1, policySet.templates.size()); + } + + @Test + public void parsePoliciesJsonExceptionTests() throws InternalException, IOException { + assertThrows(IOException.class, () -> { + PolicySet.parsePoliciesJson(Path.of("nonExistentFilePath.json")); + }); + // Cedar policy text, not JSON, should fail to parse as JSON + assertThrows(InternalException.class, () -> { + PolicySet.parsePoliciesJson("permit(principal, action, resource);"); + }); + } + @Test public void parsePoliciesExceptionTests() throws InternalException, IOException { assertThrows(IOException.class, () -> { diff --git a/CedarJava/src/test/resources/policies.json b/CedarJava/src/test/resources/policies.json new file mode 100644 index 00000000..98c39d3c --- /dev/null +++ b/CedarJava/src/test/resources/policies.json @@ -0,0 +1,37 @@ +{ + "staticPolicies": { + "Policy #1": { + "effect": "permit", + "principal": { + "op": "in", + "entity": { "type": "UserGroup", "id": "friends" } + }, + "action": { + "op": "==", + "entity": { "type": "Action", "id": "view" } + }, + "resource": { + "op": "==", + "entity": { "type": "Photo", "id": "Husky.jpg" } + }, + "conditions": [] + }, + "Policy #2": { + "effect": "forbid", + "principal": { + "op": "==", + "entity": { "type": "User", "id": "Matt" } + }, + "action": { + "op": "All" + }, + "resource": { + "op": "==", + "entity": { "type": "Photo", "id": "Husky.jpg" } + }, + "conditions": [] + } + }, + "templates": {}, + "templateLinks": [] +} diff --git a/CedarJava/src/test/resources/template.json b/CedarJava/src/test/resources/template.json new file mode 100644 index 00000000..108fba01 --- /dev/null +++ b/CedarJava/src/test/resources/template.json @@ -0,0 +1,53 @@ +{ + "staticPolicies": { + "Policy #1": { + "effect": "permit", + "principal": { + "op": "in", + "entity": { "type": "UserGroup", "id": "friends" } + }, + "action": { + "op": "==", + "entity": { "type": "Action", "id": "view" } + }, + "resource": { + "op": "==", + "entity": { "type": "Photo", "id": "Husky.jpg" } + }, + "conditions": [] + }, + "Policy #2": { + "effect": "forbid", + "principal": { + "op": "==", + "entity": { "type": "User", "id": "Matt" } + }, + "action": { + "op": "All" + }, + "resource": { + "op": "==", + "entity": { "type": "Photo", "id": "Husky.jpg" } + }, + "conditions": [] + } + }, + "templates": { + "Template #1": { + "effect": "permit", + "principal": { + "op": "==", + "slot": "?principal" + }, + "action": { + "op": "All" + }, + "resource": { + "op": "in", + "slot": "?resource" + }, + "conditions": [] + } + }, + "templateLinks": [] +} diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index e072d2bf..f4c7fd3d 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -704,6 +704,65 @@ fn parse_policies_internal<'a>( } } +#[jni_fn("com.cedarpolicy.model.policy.PolicySet")] +pub fn parsePoliciesJsonJni<'a>( + mut env: JNIEnv<'a>, + _: JClass, + policies_json_jstr: JString<'a>, +) -> jvalue { + match parse_policies_json_internal(&mut env, policies_json_jstr) { + Err(e) => jni_failed(&mut env, e.as_ref()), + Ok(policies_set) => policies_set.as_jni(), + } +} + +fn parse_policies_json_internal<'a>( + env: &mut JNIEnv<'a>, + policies_json_jstr: JString<'a>, +) -> Result> { + if policies_json_jstr.is_null() { + raise_npe(env) + } else { + // Parse the JSON string into the Rust PolicySet + let policies_json_jstring = env.get_string(&policies_json_jstr)?; + let policies_json_string = String::from(policies_json_jstring); + let policy_set = PolicySet::from_json_str(&policies_json_string)?; + + // Enumerate over the parsed policies + let mut policies_java_hash_set = Set::new(env)?; + for policy in policy_set.policies() { + let policy_id = format!("{}", policy.id()); + let policy_text = format!("{}", policy); + let java_policy_object = JPolicy::new( + env, + &env.new_string(&policy_text)?, + &env.new_string(&policy_id)?, + )?; + let _ = policies_java_hash_set.add(env, java_policy_object); + } + + let mut templates_java_hash_set = Set::new(env)?; + for template in policy_set.templates() { + let policy_id = format!("{}", template.id()); + let policy_text = format!("{}", template); + let java_policy_object = JPolicy::new( + env, + &env.new_string(&policy_text)?, + &env.new_string(&policy_id)?, + )?; + let _ = templates_java_hash_set.add(env, java_policy_object); + } + + let java_policy_set = create_java_policy_set( + env, + policies_java_hash_set.as_ref(), + templates_java_hash_set.as_ref(), + ); + + Ok(JValueGen::Object(java_policy_set)) + } +} + fn create_java_policy_set<'a>( env: &mut JNIEnv<'a>, policies_java_hash_set: &JObject<'a>, From 46caf7a39ad29dd2457c694c2282da3834791340 Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 28 Jul 2026 18:40:00 +0000 Subject: [PATCH 2/6] Supporting template linked policies extraction in parsePoliciesJson --- .../java/com/cedarpolicy/PolicySetTests.java | 16 ++ CedarJava/src/test/resources/template.json | 11 +- CedarJavaFFI/src/interface.rs | 174 +++++++++++------- CedarJavaFFI/src/objects.rs | 72 ++++++++ 4 files changed, 206 insertions(+), 67 deletions(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java b/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java index 9abc46d5..5d4ed623 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/PolicySetTests.java @@ -19,10 +19,13 @@ import com.cedarpolicy.model.exception.InternalException; import com.cedarpolicy.model.policy.Policy; import com.cedarpolicy.model.policy.PolicySet; +import com.cedarpolicy.model.policy.TemplateLink; +import com.cedarpolicy.value.EntityUID; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Path; +import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -111,12 +114,25 @@ public void parseTemplatesJsonTests() throws InternalException, IOException { for (Policy p: policySet.policies) { assertNotNull(p.policySrc); } + // The two static policies only; the template-linked policy is reported + // via templateLinks rather than as a static policy. assertEquals(2, policySet.policies.size()); for (Policy p: policySet.templates) { assertNotNull(p.policySrc); } assertEquals(1, policySet.templates.size()); + + // The template instantiation is preserved as a TemplateLink + assertEquals(1, policySet.templateLinks.size()); + TemplateLink link = policySet.templateLinks.get(0); + assertEquals("Template #1", link.getTemplateId()); + assertEquals("Link #1", link.getResultPolicyId()); + + Map linkValues = link.getLinkValues(); + assertEquals(2, linkValues.size()); + assertEquals("User::\"Matt\"", linkValues.get("?principal").toString()); + assertEquals("Album::\"Vacation\"", linkValues.get("?resource").toString()); } @Test diff --git a/CedarJava/src/test/resources/template.json b/CedarJava/src/test/resources/template.json index 108fba01..090b315f 100644 --- a/CedarJava/src/test/resources/template.json +++ b/CedarJava/src/test/resources/template.json @@ -49,5 +49,14 @@ "conditions": [] } }, - "templateLinks": [] + "templateLinks": [ + { + "newId": "Link #1", + "templateId": "Template #1", + "values": { + "?principal": { "type": "User", "id": "Matt" }, + "?resource": { "type": "Album", "id": "Vacation" } + } + } + ] } diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index f4c7fd3d..06da9dc0 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -22,7 +22,8 @@ use cedar_policy::ffi::{ }; use cedar_policy::{ ffi::{is_authorized_json_str, validate_json_str}, - Authorizer, Entities as CedarEntities, EntityUid, Policy, PolicySet, Request, Schema, Template, + Authorizer, Entities as CedarEntities, EntityUid, Policy, PolicySet, Request, Schema, SlotId, + Template, }; use cedar_policy_formatter::{policies_str_to_pretty, Config}; use dashmap::DashMap; @@ -34,15 +35,17 @@ use jni::{ use jni_fn::jni_fn; use serde::{Deserialize, Serialize}; use serde_json::{from_str, Value}; +use std::collections::HashMap; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::LazyLock; use std::{error::Error, panic, str::FromStr}; use crate::{ answer::Answer, + jlist::List, jmap::Map, jset::Set, - objects::{JEntityId, JEntityTypeName, JEntityUID, JPolicy, Object}, + objects::{JEntityId, JEntityTypeName, JEntityUID, JLinkValue, JPolicy, JTemplateLink, Object}, utils::raise_npe, }; use crate::{helpers::validate_with_level_json_str, objects::JFormatterConfig}; @@ -669,38 +672,7 @@ fn parse_policies_internal<'a>( let policies_string = String::from(policies_jstring); let policy_set = PolicySet::from_str(&policies_string)?; - // Enumerate over the parsed policies - let mut policies_java_hash_set = Set::new(env)?; - for policy in policy_set.policies() { - let policy_id = format!("{}", policy.id()); - let policy_text = format!("{}", policy); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = policies_java_hash_set.add(env, java_policy_object); - } - - let mut templates_java_hash_set = Set::new(env)?; - for template in policy_set.templates() { - let policy_id = format!("{}", template.id()); - let policy_text = format!("{}", template); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = templates_java_hash_set.add(env, java_policy_object); - } - - let java_policy_set = create_java_policy_set( - env, - policies_java_hash_set.as_ref(), - templates_java_hash_set.as_ref(), - ); - - Ok(JValueGen::Object(java_policy_set)) + policy_set_to_java(env, &policy_set) } } @@ -728,52 +700,122 @@ fn parse_policies_json_internal<'a>( let policies_json_string = String::from(policies_json_jstring); let policy_set = PolicySet::from_json_str(&policies_json_string)?; - // Enumerate over the parsed policies - let mut policies_java_hash_set = Set::new(env)?; - for policy in policy_set.policies() { - let policy_id = format!("{}", policy.id()); - let policy_text = format!("{}", policy); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = policies_java_hash_set.add(env, java_policy_object); - } - - let mut templates_java_hash_set = Set::new(env)?; - for template in policy_set.templates() { - let policy_id = format!("{}", template.id()); - let policy_text = format!("{}", template); - let java_policy_object = JPolicy::new( - env, - &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, - )?; - let _ = templates_java_hash_set.add(env, java_policy_object); - } - - let java_policy_set = create_java_policy_set( - env, - policies_java_hash_set.as_ref(), - templates_java_hash_set.as_ref(), - ); + policy_set_to_java(env, &policy_set) + } +} + +/// Build a Java `LinkValue` from a slot id and the entity UID linked to it. +fn create_java_link_value<'a>( + env: &mut JNIEnv<'a>, + slot_id: &SlotId, + euid: &EntityUid, +) -> Result> { + let slot_jstring = env.new_string(format!("{}", slot_id))?; + let entity_type = JEntityTypeName::try_from(env, euid.type_name())?; + let entity_id = JEntityId::try_from(env, euid.id())?; + let java_euid = JEntityUID::new(env, entity_type, entity_id)?; + + JLinkValue::new(env, slot_jstring, java_euid) +} - Ok(JValueGen::Object(java_policy_set)) +/// Build a Java `TemplateLink` describing a template-linked policy. +fn create_java_template_link<'a>( + env: &mut JNIEnv<'a>, + template_id: &str, + result_policy_id: &str, + link_values: &HashMap, +) -> Result> { + let mut link_values_list: List<'a, JLinkValue<'a>> = List::new(env)?; + // Sort by slot id so the resulting list has a deterministic order + let mut sorted_values: Vec<(&SlotId, &EntityUid)> = link_values.iter().collect(); + sorted_values.sort_by_key(|(slot_id, _)| format!("{}", slot_id)); + for (slot_id, euid) in sorted_values { + let link_value = create_java_link_value(env, slot_id, euid)?; + link_values_list.add(env, link_value)?; } + + let template_id_jstring = env.new_string(template_id)?; + let result_policy_id_jstring = env.new_string(result_policy_id)?; + JTemplateLink::new( + env, + template_id_jstring, + result_policy_id_jstring, + link_values_list, + ) +} + +/// Convert a Rust `PolicySet` into the equivalent Java `PolicySet` object. +/// +/// Static policies land in `policies`, templates in `templates`, and each +/// template-linked policy contributes a `TemplateLink` to `templateLinks`. +/// Note that `PolicySet::policies()` yields both static and template-linked +/// policies, so linked policies are separated out by `template_id()`. +fn policy_set_to_java<'a>(env: &mut JNIEnv<'a>, policy_set: &PolicySet) -> Result> { + let mut policies_java_hash_set = Set::new(env)?; + let mut template_links_java_list: List<'a, JTemplateLink<'a>> = List::new(env)?; + + for policy in policy_set.policies() { + let policy_id = format!("{}", policy.id()); + match (policy.template_id(), policy.template_links()) { + // A template-linked policy: record the link instead of treating it + // as a static policy. + (Some(template_id), Some(link_values)) => { + let java_template_link = create_java_template_link( + env, + &format!("{}", template_id), + &policy_id, + &link_values, + )?; + template_links_java_list.add(env, java_template_link)?; + } + // A static policy. + _ => { + let policy_text = format!("{}", policy); + let java_policy_object = JPolicy::new( + env, + &env.new_string(&policy_text)?, + &env.new_string(&policy_id)?, + )?; + let _ = policies_java_hash_set.add(env, java_policy_object); + } + } + } + + let mut templates_java_hash_set = Set::new(env)?; + for template in policy_set.templates() { + let policy_id = format!("{}", template.id()); + let policy_text = format!("{}", template); + let java_policy_object = JPolicy::new( + env, + &env.new_string(&policy_text)?, + &env.new_string(&policy_id)?, + )?; + let _ = templates_java_hash_set.add(env, java_policy_object); + } + + let java_policy_set = create_java_policy_set( + env, + policies_java_hash_set.as_ref(), + templates_java_hash_set.as_ref(), + template_links_java_list.as_ref(), + ); + + Ok(JValueGen::Object(java_policy_set)) } fn create_java_policy_set<'a>( env: &mut JNIEnv<'a>, policies_java_hash_set: &JObject<'a>, templates_java_hash_set: &JObject<'a>, + template_links_java_list: &JObject<'a>, ) -> JObject<'a> { env.new_object( "com/cedarpolicy/model/policy/PolicySet", - "(Ljava/util/Set;Ljava/util/Set;)V", + "(Ljava/util/Set;Ljava/util/Set;Ljava/util/List;)V", &[ JValueGen::Object(policies_java_hash_set), JValueGen::Object(templates_java_hash_set), + JValueGen::Object(template_links_java_list), ], ) .expect("Failed to create new PolicySet object") diff --git a/CedarJavaFFI/src/objects.rs b/CedarJavaFFI/src/objects.rs index 5aa24ce5..0507e0dd 100644 --- a/CedarJavaFFI/src/objects.rs +++ b/CedarJavaFFI/src/objects.rs @@ -370,6 +370,78 @@ impl<'a> AsRef> for JPolicy<'a> { } } +/// Typed wrapper for LinkValue objects +/// (com.cedarpolicy.model.policy.LinkValue) +pub struct JLinkValue<'a> { + obj: JObject<'a>, +} + +impl<'a> JLinkValue<'a> { + /// Construct a new LinkValue object from a slot name and the entity UID + /// filling that slot + pub fn new(env: &mut JNIEnv<'a>, slot: JString<'a>, value: JEntityUID<'a>) -> Result { + let obj = env.new_object( + "com/cedarpolicy/model/policy/LinkValue", + "(Ljava/lang/String;Lcom/cedarpolicy/value/EntityUID;)V", + &[JValueGen::Object(&slot), JValueGen::Object(value.as_ref())], + )?; + Ok(Self { obj }) + } +} + +impl<'a> Object<'a> for JLinkValue<'a> { + fn cast(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + assert_is_class(env, &obj, "com/cedarpolicy/model/policy/LinkValue")?; + Ok(Self { obj }) + } +} + +impl<'a> AsRef> for JLinkValue<'a> { + fn as_ref(&self) -> &JObject<'a> { + &self.obj + } +} + +/// Typed wrapper for TemplateLink objects +/// (com.cedarpolicy.model.policy.TemplateLink) +pub struct JTemplateLink<'a> { + obj: JObject<'a>, +} + +impl<'a> JTemplateLink<'a> { + /// Construct a new TemplateLink object + pub fn new( + env: &mut JNIEnv<'a>, + template_id: JString<'a>, + result_policy_id: JString<'a>, + link_values: List<'a, JLinkValue<'a>>, + ) -> Result { + let obj = env.new_object( + "com/cedarpolicy/model/policy/TemplateLink", + "(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V", + &[ + JValueGen::Object(&template_id), + JValueGen::Object(&result_policy_id), + JValueGen::Object(link_values.as_ref()), + ], + )?; + Ok(Self { obj }) + } +} + +impl<'a> Object<'a> for JTemplateLink<'a> { + fn cast(env: &mut JNIEnv<'a>, obj: JObject<'a>) -> Result { + assert_is_class(env, &obj, "com/cedarpolicy/model/policy/TemplateLink")?; + Ok(Self { obj }) + } +} + +impl<'a> AsRef> for JTemplateLink<'a> { + fn as_ref(&self) -> &JObject<'a> { + &self.obj + } +} + pub struct JFormatterConfig<'a> { obj: JObject<'a>, formatter_config: Config, From 6f76a4b4cbcd2290fccb2a321171c71cd12eb1bc Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 11 Aug 2026 15:19:33 +0000 Subject: [PATCH 3/6] test: exercise the JSON policy format in the shared integration tests `policyFormat`/`schemaFormat` support landed in https://github.com/cedar-policy/cedar-java/pull/366, but `loadPolicySet` still rejected `policyFormat: json` because there was no Java interface for parsing a policy set from its JSON (EST) representation. Now that `PolicySet.parsePoliciesJson` exists, honor the JSON format and add `tests/example_use_cases/2a_json_policy.json` to the handwritten test list. --- .../com/cedarpolicy/SharedIntegrationTests.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java b/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java index aa2e857b..fc149afb 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java @@ -216,6 +216,7 @@ private static class JsonEntity { "tests/decimal/2.json", "tests/example_use_cases/1a.json", "tests/example_use_cases/2a.json", + "tests/example_use_cases/2a_json_policy.json", "tests/example_use_cases/2a_json_schema.json", "tests/example_use_cases/2b.json", "tests/example_use_cases/2c.json", @@ -308,17 +309,13 @@ private DynamicContainer loadJsonTests(String jsonFile) throws InternalException schema))))); } - /** - * Load the policy set file. Only the Cedar policy format is supported; there is not yet a Java - * interface for parsing a policy set from its JSON (EST) representation. - */ + /** Load the policy set file, in either the Cedar or JSON policy format. */ private PolicySet loadPolicySet(String policiesFile, JsonOrCedarFormat format) throws InternalException, IOException { - if (format == JsonOrCedarFormat.Json) { - throw new UnsupportedOperationException( - "The JSON policy format is not supported by these tests yet: " + policiesFile); - } - return PolicySet.parsePolicies(resolveIntegrationTestPath(policiesFile)); + final Path policiesPath = resolveIntegrationTestPath(policiesFile); + return format == JsonOrCedarFormat.Json + ? PolicySet.parsePoliciesJson(policiesPath) + : PolicySet.parsePolicies(policiesPath); } /** Load the schema file, in either the Cedar or JSON schema format. */ From ccce18ab67d64bbf67254c67d3a91293790b57eb Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 11 Aug 2026 17:23:08 +0000 Subject: [PATCH 4/6] Use PolicyId::as_ref instead of format! in policy_set_to_java Addresses review feedback: `format!("{}", policy.id())` allocates a String just to recover the id text that `AsRef` already exposes. `PolicyId` also implements `AsRef`, so the `let` bindings are annotated `&str` to pick the intended impl. --- CedarJavaFFI/src/interface.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index 06da9dc0..47ea2af0 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -755,15 +755,15 @@ fn policy_set_to_java<'a>(env: &mut JNIEnv<'a>, policy_set: &PolicySet) -> Resul let mut template_links_java_list: List<'a, JTemplateLink<'a>> = List::new(env)?; for policy in policy_set.policies() { - let policy_id = format!("{}", policy.id()); + let policy_id: &str = policy.id().as_ref(); match (policy.template_id(), policy.template_links()) { // A template-linked policy: record the link instead of treating it // as a static policy. (Some(template_id), Some(link_values)) => { let java_template_link = create_java_template_link( env, - &format!("{}", template_id), - &policy_id, + template_id.as_ref(), + policy_id, &link_values, )?; template_links_java_list.add(env, java_template_link)?; @@ -774,7 +774,7 @@ fn policy_set_to_java<'a>(env: &mut JNIEnv<'a>, policy_set: &PolicySet) -> Resul let java_policy_object = JPolicy::new( env, &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, + &env.new_string(policy_id)?, )?; let _ = policies_java_hash_set.add(env, java_policy_object); } @@ -783,12 +783,12 @@ fn policy_set_to_java<'a>(env: &mut JNIEnv<'a>, policy_set: &PolicySet) -> Resul let mut templates_java_hash_set = Set::new(env)?; for template in policy_set.templates() { - let policy_id = format!("{}", template.id()); + let policy_id: &str = template.id().as_ref(); let policy_text = format!("{}", template); let java_policy_object = JPolicy::new( env, &env.new_string(&policy_text)?, - &env.new_string(&policy_id)?, + &env.new_string(policy_id)?, )?; let _ = templates_java_hash_set.add(env, java_policy_object); } From e912f0c7ba0192663d5d0ddff405657084f4c9ed Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 11 Aug 2026 17:32:15 +0000 Subject: [PATCH 5/6] Drop stale note claiming the schema field is unused The note predates commit b53563e (Aug 2023), which wired up executeJsonValidationTest. The schema field has been read since then, feeding both the ValidationRequest and each AuthorizationRequest, and the Java validator interface it waits on exists. --- .../test/java/com/cedarpolicy/SharedIntegrationTests.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java b/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java index fc149afb..4810160f 100644 --- a/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java +++ b/CedarJava/src/test/java/com/cedarpolicy/SharedIntegrationTests.java @@ -123,9 +123,8 @@ private static class JsonTest { public String entities; /** - * File name of the schema file. Path is relative to the integration tests root. Note: This - * field is currently unused by these tests. The tests should be updated to take advantage - * of it once there is a Java interface to the validator. + * File name of the schema file. Path is relative to the integration tests root. Used both + * to validate the policy set and to validate each request. */ public String schema; From 968269e2aa2cbd106b59279c5b1686c66f4f3806 Mon Sep 17 00:00:00 2001 From: Mark Creamer Date: Tue, 11 Aug 2026 18:20:38 +0000 Subject: [PATCH 6/6] fix: clippy lint --- CedarJavaFFI/src/interface.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CedarJavaFFI/src/interface.rs b/CedarJavaFFI/src/interface.rs index 47ea2af0..b03841e6 100644 --- a/CedarJavaFFI/src/interface.rs +++ b/CedarJavaFFI/src/interface.rs @@ -760,12 +760,8 @@ fn policy_set_to_java<'a>(env: &mut JNIEnv<'a>, policy_set: &PolicySet) -> Resul // A template-linked policy: record the link instead of treating it // as a static policy. (Some(template_id), Some(link_values)) => { - let java_template_link = create_java_template_link( - env, - template_id.as_ref(), - policy_id, - &link_values, - )?; + let java_template_link = + create_java_template_link(env, template_id.as_ref(), policy_id, &link_values)?; template_links_java_list.add(env, java_template_link)?; } // A static policy.