Skip to content

feat: add Schema.fromFragments to combine schema fragments (#313) - #1

Open
Vedthakar wants to merge 1 commit into
mainfrom
feat/313-schema-fragments
Open

Vedthakar wants to merge 1 commit into
mainfrom
feat/313-schema-fragments

Conversation

@Vedthakar

@Vedthakar Vedthakar commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Issue #, if available: Fixes cedar-policy#313

Description of changes:

This solves issue cedar-policy#313: CedarJava can now build one schema from several schema fragments, as Cedar's Rust API does with Schema::from_schema_fragments.

Problem. The issue asks for a way to split a large schema (thousands of actions, many teams) into fragments and use them together as one schema. Schema in CedarJava holds a single JSON or Cedar schema, and nothing in CedarJavaFFI calls Schema::from_schema_fragments. Users had to concatenate schemas themselves, which is error-prone, especially across the two formats.

Why a new API is needed. Every path that sends a schema to Rust (AuthorizationOperation, ValidateOperation, entity validation, and Schema.cache()/preparseSchemaJni) takes one cedar_policy::ffi::Schema, which is a single JSON or Cedar schema.

Change.

  • Schema.fromFragments(List<Schema> fragments) is a new public static method. Each fragment can be in the JSON or Cedar format. It returns an ordinary Schema in the JSON format, like Schema.parse, so the result works with every existing API with no other changes: authorization (uncached and cached through schema.cache()), request validation, policy validation, and entity validation.
  • CedarJavaFFI has a new fromFragmentsJni. The fragments go over JNI as a JSON array using the existing SchemaSerializer: an object for a JSON fragment, a string for a Cedar fragment. Rust parses each one into a SchemaFragment and calls Schema::from_schema_fragments. That means cross-fragment references, duplicate declarations, undeclared types and RFC 70 shadowing are checked exactly as Cedar checks them, and the Rust error comes back as an InternalException. Warnings from Cedar-format fragments are dropped, as in Schema.parse.
  • The fragments are then merged namespace by namespace (entityTypes, actions, commonTypes, annotations) into one JSON schema. Validation does not depend on how definitions are split across fragments, and from_schema_fragments has already rejected duplicate type and action definitions. The only remaining conflict is the same namespace annotation with two different values, which is reported as an error. The same value on both is fine. The merge is hand-written because the validated Rust Schema can't be serialized back to JSON or Cedar text, and every operation takes a single ffi::Schema. A code comment explains this, and a test round-trips every definition's contents.
Input Result
JSON and Cedar fragments, same or different namespaces, referring to each other combined Schema (JSON format)
two fragments declare the same entity type, action or common type InternalException with Cedar's error
a fragment refers to a type no fragment declares InternalException (failed to resolve type: ...)
a fragment fails to parse InternalException
empty list empty schema {}
null list or null fragment NullPointerException

Existing APIs are unchanged.

Alternatives considered.

  • A cache-only design: a preparseSchemaJni variant that stores the from_schema_fragments result under a cache key. It would only help cached authorization. Validation and uncached authorization still send the schema itself, so a fragments-only Schema would not work there.
  • Changing the wire format to a list of schemas: this touches every request type, and cedar_policy::ffi::Schema has no fragments variant, so it would need a change in cedar first.

Since this is a new public API, I'm happy to rename it or change its shape (for example, varargs) if you prefer.

Tests

Rust (CedarJavaFFI/src/interface.rs, schema_test):

  • combine_schema_fragments_mixed_formats: a JSON and a Cedar fragment in the same namespace that refer to each other
  • combine_schema_fragments_matches_from_schema_fragments: the combined schema has the same entity types and actions as Schema::from_schema_fragments (common type, two namespaces, a namespace split across fragments)
  • combine_schema_fragments_round_trips_contents: four fragments in two namespaces with common types, entity and attribute annotations, tags, an action memberOf across fragments and context types. Every definition comes back unchanged in the combined schema, with nothing extra, and the combined schema keeps the cross-fragment action hierarchy
  • combine_schema_fragments_single_and_empty
  • combine_schema_fragments_duplicate_entity_type, _undeclared_type, _conflicting_namespace_annotation (plus the same annotation value, which is allowed), _invalid_input
  • from_fragments_internal_valid, from_fragments_internal_null (JNI path, NPE on null)

JUnit (SchemaTests.FromFragmentsTests): combining JSON and Cedar fragments; policy validation against the combined schema (valid policy and unknown action); authorization with the combined schema, uncached and cached, plus request validation rejecting a principal type the schema doesn't allow; single and empty lists; duplicate declaration; undeclared type; malformed fragment; null inputs.

On main the new JUnit tests don't compile (cannot find symbol: fromFragments). With the Java change and the main native library, 7 of 8 fail with UnsatisfiedLinkError: ... Schema.fromFragmentsJni. The null-input test passes because it throws before the JNI call. All pass with this change.

Verification

  • cargo fmt --all --check (CedarJavaFFI): clean
  • cargo test --all-features (CedarJavaFFI): 124 passed
  • cargo clippy --all-features --all-targets: no new warnings compared with main
  • ./gradlew build javadoc: BUILD SUCCESSFUL, 68,100 tests including the corpus tests. Checkstyle only reports the 3 existing warnings in pbt/IntegrationTests.java. I ran this locally against a native library built with cargo build --release --features partial-eval,tpe for x86_64-unknown-linux-gnu, through CEDAR_JAVA_FFI_LIB, instead of the zigbuild cross-compile.

Author: Ved Thakar (@Vedthakar)

@Vedthakar Vedthakar closed this Oct 16, 2026
@Vedthakar Vedthakar reopened this Oct 16, 2026
…icy#313)

Cedar's Rust API can build one schema from several fragments with
Schema::from_schema_fragments, so teams can each own part of a large
schema. CedarJava had no way to do this.

Schema.fromFragments(List<Schema>) passes the fragments, in either
format, to a new JNI function. The function checks them with
Schema::from_schema_fragments, so conflicts and undeclared types are
reported as Cedar reports them, and returns the combined schema in the
JSON format. The result is an ordinary Schema, so it works for
authorization (cached or not), validation and entity validation without
any other changes.

Signed-off-by: Ved Thakar <111605341+Vedthakar@users.noreply.github.com>
Co-authored-by: Ved Thakar <111605341+Vedthakar@users.noreply.github.com>
@Vedthakar
Vedthakar force-pushed the feat/313-schema-fragments branch from 7edd7b8 to 1b45023 Compare October 16, 2026 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support schema fragments

1 participant