Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.cedarpolicy.value.EntityUID;
import com.cedarpolicy.serializer.JsonEUID;
import com.cedarpolicy.value.Value;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

Expand Down Expand Up @@ -87,6 +88,16 @@ private Path resolveIntegrationTestPath(String path) {
}
}

/** The format a policy set or schema file is written in. */
private enum JsonOrCedarFormat {
/** The Cedar (human-readable) format. */
@JsonProperty("cedar")
Cedar,
/** The JSON format. */
@JsonProperty("json")
Json,
}

/**
* Directly corresponds to the structure of the JSON formatted tests files. The fields are
* populated by Jackson when the test files are deserialized.
Expand All @@ -99,6 +110,12 @@ private static class JsonTest {
*/
public String policies;

/**
* Format of the policy set file. Defaults to Cedar, matching the integration test format,
* for files that don't specify it.
*/
public JsonOrCedarFormat policyFormat = JsonOrCedarFormat.Cedar;

/**
* File name of the file containing entities. Path is relative to the integration tests
* root.
Expand All @@ -112,6 +129,12 @@ private static class JsonTest {
*/
public String schema;

/**
* Format of the schema file. Defaults to Cedar, matching the integration test format, for
* files that don't specify it.
*/
public JsonOrCedarFormat schemaFormat = JsonOrCedarFormat.Cedar;

/**
* Whether the given policies are expected to pass the validator with this schema, or not
*/
Expand Down Expand Up @@ -193,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_schema.json",
"tests/example_use_cases/2b.json",
"tests/example_use_cases/2c.json",
"tests/example_use_cases/3a.json",
Expand Down Expand Up @@ -263,8 +287,8 @@ private DynamicContainer loadJsonTests(String jsonFile) throws InternalException
test = OBJECT_MAPPER.reader().readValue(jsonIn, JsonTest.class);
}
Set<Entity> entities = loadEntities(test.entities);
PolicySet policySet = PolicySet.parsePolicies(resolveIntegrationTestPath(test.policies));
Schema schema = loadSchema(test.schema);
PolicySet policySet = loadPolicySet(test.policies, test.policyFormat);
Schema schema = loadSchema(test.schema, test.schemaFormat);

return DynamicContainer.dynamicContainer(
jsonFile,
Expand All @@ -284,12 +308,27 @@ private DynamicContainer loadJsonTests(String jsonFile) throws InternalException
schema)))));
}

/** Load the schema file. */
private Schema loadSchema(String schemaFile) throws IOException {
/**
* 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.
*/
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);
Comment on lines +318 to +319

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this here until #365 is merged. I can also implement this case along with #365

}
return PolicySet.parsePolicies(resolveIntegrationTestPath(policiesFile));
}

/** Load the schema file, in either the Cedar or JSON schema format. */
private Schema loadSchema(String schemaFile, JsonOrCedarFormat format) throws IOException {
try (InputStream schemaStream =
new FileInputStream(resolveIntegrationTestPath(schemaFile).toFile())) {
String schemaText = new String(schemaStream.readAllBytes(), StandardCharsets.UTF_8);
return new Schema(schemaText);
return format == JsonOrCedarFormat.Json
? new Schema(OBJECT_MAPPER.readTree(schemaText))
: new Schema(schemaText);
}
}

Expand Down
8 changes: 6 additions & 2 deletions CedarJavaFFI/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,16 @@ pub fn validate_entities(input: &str) -> serde_json::Result<Answer> {

match CedarEntities::from_json_value(validate_entity_call.entities, Some(&schema)) {
Err(error) => {
// Unwrap only the variants whose own `Display` impl summarizes instead of
// delegating, so the caller keeps the specific inner diagnostic. The rest
// are `#[error(transparent)]` or already interpolate their source, so the
// catch-all preserves their detail and keeps this compiling when upstream
// adds a variant.
let err_message = match error {
EntitiesError::Serialization(err) => err.to_string(),
EntitiesError::Deserialization(err) => err.to_string(),
EntitiesError::Duplicate(err) => err.to_string(),
EntitiesError::TransitiveClosureError(err) => err.to_string(),
EntitiesError::InvalidEntity(err) => err.to_string(),
err => err.to_string(),

@lianah lianah Aug 3, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone adds a new EntitiesError that has it's own Display summarizing the issue without interpolating this would not break cedar-java compilation but it would silently drop the inner detailed error message right? I wonder if a compilation error forcing us to fix this is not preferable to silently dropping the inner error message.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue you have is that right now, your consumers are the ones broken since your own package doesn't lock down to compatible versions in any way and this is why libraries with Enums they plan to update often add a non-exhaustive directive to ensure proper compatibility guarantees are kept. In other words, you're violating core sem-ver principles in Rust where you're actually breaking on minor version updates to your underlying dependencies but don't constrain your dependencies for your consumed libraries to match.

So the ask would be either to:

  1. Add such constraints as either an upper version boundary or precise version locking similar to the cedar-policy packages themselves (using "=VERSION" or <MAJOR.minor for the last minor version you know your release is compatible with.
  2. Make the package flexible enough to accommodate additions that aren't considered "breaking" for the purposes of Rust semver

Otherwise you're just making all of your consumers have to lock or constrain otherwise transitive dependencies that they don't directly use. I'm not saying it isn't an available tool, but it's effectively passing breakages to your consumers for reporting them much like this one which doesn't engender trust if that isn't well-understood at outset.

@john-h-kastner-aws john-h-kastner-aws Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new error message variant shouldn't have made it into the release. We have cargo-semver-checks to guard against this, but it's not perfect.

Probably the best option for now is to roll forward, leaving the new variant in place and patching the Java as propsed here. Adding #[non_exhaustive] is also breaking so unfortunately we can't just patch that in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with the above! In that case, are we aligned on this change to patch Cedar Java?

};
Ok(Answer::fail_bad_request(vec![err_message]))
}
Expand Down
Loading