Repository: cedar-policy/cedar
Pull request #1524 (2025-03-07 14:26:52)

Add getter for Schema to Validator
## Description of changes

Allows callers who have already constructed a `Validator`, to take advantage of the getters on `Schema` (and its `Protobuf` trait implementation).  This is necessary because constructing a `Validator` consumes the `Schema`.

## Issue #, if available

## Checklist for requesting a review

The change in this PR is (choose one, and delete the other options):

- [x] A backwards-compatible change requiring a minor version bump to `cedar-policy` (e.g., addition of a new API).

I confirm that this PR (choose one, and delete the other options):

- [x] Updates the "Unreleased" section of the CHANGELOG with a description of my change (required for major/minor version bumps).

I confirm that [`cedar-spec`](https://github.com/cedar-policy/cedar-spec) (choose one, and delete the other options):

- [x] Does not require updates because my change does not impact the Cedar formal model or DRT infrastructure.

I confirm that [`docs.cedarpolicy.com`](https://docs.cedarpolicy.com/) (choose one, and delete the other options):

- [x] Does not require updates because my change does not impact the Cedar language specification.


github-actions[bot]: 
# Coverage Report
Head Commit: ```58708367c0abc3ec1df097dd3cfa3ed86d63b757```

Base Commit: ```81ef1f5644d9a8be89f523339a1b1058f247560f```

[Download the full coverage report.](https://github.com/cedar-policy/cedar/actions/runs/13723120664/artifacts/2711585655)

## Coverage of Added or Modified Lines of Rust Code
**Required coverage:** 80.00%

**Actual coverage:** 0.00%

**Status:** FAILED :x:

<details>
<summary><b>Details</b></summary>

| File | Status | Covered | Coverage | Missed Lines |
|:-----|:------:|:-------:|---------:|:-------------|
cedar-policy-validator/src/lib.rs | :red_circle: | 0/3 | 0.00% | 108-110
cedar-policy/src/api.rs | :red_circle: | 0/3 | 0.00% | 1322-1324
</details>

## Coverage of All Lines of Rust Code
**Required coverage:** 80.00%

**Actual coverage:** 80.75%

**Status:** PASSED :white_check_mark:

<details>
<summary><b>Details</b></summary>

| Package | Status | Covered | Coverage | Base Coverage |
|:--------|:------:|:-------:|---------:|--------------:|
cedar-policy | :red_circle: | 3059/4395 | 69.60% | 69.65%
cedar-policy-cli | :red_circle: | 539/927 | 58.14% | 58.14%
cedar-policy-core | :green_circle: | 11328/13778 | 82.22% | 82.23%
cedar-policy-formatter | :green_circle: | 914/1043 | 87.63% | 87.63%
cedar-policy-validator | :green_circle: | 8242/9650 | 85.41% | 85.44%
cedar-wasm | :red_circle: | 0/29 | 0.00% | 0.00%
</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index fbb00721..4a2d400b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -99,9 +99,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.96"
+version = "1.0.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
+checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
 
 [[package]]
 name = "arbitrary"
@@ -241,17 +241,11 @@ version = "3.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
 
-[[package]]
-name = "byteorder"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
-
 [[package]]
 name = "bytes"
-version = "1.10.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
 
 [[package]]
 name = "cast"
@@ -295,7 +289,7 @@ dependencies = [
  "serde_json",
  "serde_with",
  "smol_str",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "tsify",
  "wasm-bindgen",
 ]
@@ -316,7 +310,7 @@ dependencies = [
  "serde",
  "serde_json",
  "tempfile",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
 ]
 
 [[package]]
@@ -343,7 +337,7 @@ dependencies = [
  "serde_with",
  "smol_str",
  "stacker",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "tsify",
  "wasm-bindgen",
 ]
@@ -385,7 +379,7 @@ dependencies = [
  "similar-asserts",
  "smol_str",
  "stacker",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "tsify",
  "unicode-security",
  "wasm-bindgen",
@@ -753,9 +747,9 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.14.0"
+version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
 
 [[package]]
 name = "ena"
@@ -805,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
 dependencies = [
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -979,9 +973,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
 name = "hermit-abi"
-version = "0.4.0"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
 
 [[package]]
 name = "hex"
@@ -1072,13 +1066,13 @@ dependencies = [
 
 [[package]]
 name = "is-terminal"
-version = "0.4.15"
+version = "0.4.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
 dependencies = [
  "hermit-abi",
  "libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -1113,9 +1107,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.14"
+version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
 name = "js-sys"
@@ -1188,9 +1182,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.15"
+version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
 
 [[package]]
 name = "lock_api"
@@ -1266,7 +1260,7 @@ dependencies = [
  "terminal_size",
  "textwrap",
  "thiserror 1.0.69",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
@@ -1364,9 +1358,9 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
 
 [[package]]
 name = "oorandom"
-version = "11.1.4"
+version = "11.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
 
 [[package]]
 name = "owo-colors"
@@ -1424,18 +1418,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
 
 [[package]]
 name = "pin-project"
-version = "1.1.9"
+version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d"
+checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.9"
+version = "1.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67"
+checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1490,9 +1484,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.20"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
 dependencies = [
  "zerocopy",
 ]
@@ -1541,14 +1535,14 @@ checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579"
 dependencies = [
  "arrayvec",
  "typed-arena",
- "unicode-width",
+ "unicode-width 0.1.14",
 ]
 
 [[package]]
 name = "prettyplease"
-version = "0.2.29"
+version = "0.2.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
+checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a"
 dependencies = [
  "proc-macro2",
  "syn",
@@ -1556,18 +1550,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-crate"
-version = "3.2.0"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
 dependencies = [
  "toml_edit",
 ]
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.93"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
 dependencies = [
  "unicode-ident",
 ]
@@ -1661,9 +1655,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
 
 [[package]]
 name = "quote"
-version = "1.0.38"
+version = "1.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
 dependencies = [
  "proc-macro2",
 ]
@@ -1729,27 +1723,27 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.9"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
+checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
 dependencies = [
  "bitflags",
 ]
 
 [[package]]
 name = "ref-cast"
-version = "1.0.23"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931"
+checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
 dependencies = [
  "ref-cast-impl",
 ]
 
 [[package]]
 name = "ref-cast-impl"
-version = "1.0.23"
+version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
+checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1853,22 +1847,22 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.44"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
 dependencies = [
  "bitflags",
  "errno",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "rustversion"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
 
 [[package]]
 name = "rusty-fork"
@@ -1884,9 +1878,9 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
 
 [[package]]
 name = "same-file"
@@ -1905,15 +1899,15 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "semver"
-version = "1.0.25"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
 
 [[package]]
 name = "serde"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
 dependencies = [
  "serde_derive",
 ]
@@ -1931,9 +1925,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.218"
+version = "1.0.219"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1953,9 +1947,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.139"
+version = "1.0.140"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
 dependencies = [
  "indexmap 2.7.0",
  "itoa",
@@ -2071,7 +2065,7 @@ dependencies = [
  "cfg-if",
  "libc",
  "psm",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -2115,9 +2109,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
 
 [[package]]
 name = "syn"
-version = "2.0.98"
+version = "2.0.100"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2126,16 +2120,16 @@ dependencies = [
 
 [[package]]
 name = "tempfile"
-version = "3.17.1"
+version = "3.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
+checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
 dependencies = [
  "cfg-if",
  "fastrand",
  "getrandom 0.3.1",
  "once_cell",
  "rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -2150,9 +2144,9 @@ dependencies = [
 
 [[package]]
 name = "terminal_size"
-version = "0.4.1"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9"
+checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
 dependencies = [
  "rustix",
  "windows-sys 0.59.0",
@@ -2166,12 +2160,12 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
 
 [[package]]
 name = "textwrap"
-version = "0.16.1"
+version = "0.16.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
+checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
 dependencies = [
  "unicode-linebreak",
- "unicode-width",
+ "unicode-width 0.2.0",
 ]
 
 [[package]]
@@ -2185,11 +2179,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.11"
+version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
+checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
 dependencies = [
- "thiserror-impl 2.0.11",
+ "thiserror-impl 2.0.12",
 ]
 
 [[package]]
@@ -2205,9 +2199,9 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.11"
+version = "2.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
+checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2222,9 +2216,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
 
 [[package]]
 name = "time"
-version = "0.3.37"
+version = "0.3.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
+checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
 dependencies = [
  "deranged",
  "itoa",
@@ -2237,15 +2231,15 @@ dependencies = [
 
 [[package]]
 name = "time-core"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
 
 [[package]]
 name = "time-macros"
-version = "0.2.19"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
+checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
 dependencies = [
  "num-conv",
  "time-core",
@@ -2263,9 +2257,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.8.1"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
+checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -2338,9 +2332,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
 name = "unicode-linebreak"
@@ -2385,6 +2379,12 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
 
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.6"
@@ -2548,7 +2548,7 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -2668,19 +2668,18 @@ dependencies = [
 
 [[package]]
 name = "zerocopy"
-version = "0.7.35"
+version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
 dependencies = [
- "byteorder",
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.7.35"
+version = "0.8.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/cedar-policy-cli/Cargo.toml b/cedar-policy-cli/Cargo.toml
index 405a93c3..0e9b6ded 100644
--- a/cedar-policy-cli/Cargo.toml
+++ b/cedar-policy-cli/Cargo.toml
@@ -18,7 +18,7 @@ serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 miette = { version = "7.5.0", features = ["fancy"] }
 thiserror = "2.0"
-semver = "1.0.25"
+semver = "1.0.26"
 
 [features]
 default = []
diff --git a/cedar-policy-cli/src/lib.rs b/cedar-policy-cli/src/lib.rs
index 41d8635f..a05acf0c 100644
--- a/cedar-policy-cli/src/lib.rs
+++ b/cedar-policy-cli/src/lib.rs
@@ -955,9 +955,9 @@ fn translate_policy_to_cedar(
     json_src: Option<impl AsRef<Path> + std::marker::Copy>,
 ) -> Result<String> {
     let policy_set = read_json_policy_set(json_src)?;
-    policy_set.to_cedar().ok_or(miette!(
-        "Unable to translate policy set containing template linked policies."
-    ))
+    policy_set.to_cedar().ok_or_else(|| {
+        miette!("Unable to translate policy set containing template linked policies.")
+    })
 }
 
 fn translate_policy_to_json(
diff --git a/cedar-policy-core/Cargo.toml b/cedar-policy-core/Cargo.toml
index 0eb285a5..e721ed52 100644
--- a/cedar-policy-core/Cargo.toml
+++ b/cedar-policy-core/Cargo.toml
@@ -18,7 +18,7 @@ serde_with = { version = "3.12", features = ["json"] }
 serde_json = "1.0"
 lalrpop-util = { version = "0.22.1", features = ["lexer"] }
 lazy_static = "1.4"
-either = "1.14"
+either = "1.15"
 itertools = "0.14"
 ref-cast = "1.0"
 rustc_lexer = "0.1"
diff --git a/cedar-policy-core/src/ast/policy.rs b/cedar-policy-core/src/ast/policy.rs
index 36ab0aa0..00744ee9 100644
--- a/cedar-policy-core/src/ast/policy.rs
+++ b/cedar-policy-core/src/ast/policy.rs
@@ -50,13 +50,13 @@ cfg_tolerant_ast! {
         std::sync::LazyLock::new(|| Arc::new(Annotations::default()));
 
     static DEFAULT_PRINCIPAL_CONSTRAINT: std::sync::LazyLock<PrincipalConstraint> =
-        std::sync::LazyLock::new(|| PrincipalConstraint::any());
+        std::sync::LazyLock::new(PrincipalConstraint::any);
 
     static DEFAULT_RESOURCE_CONSTRAINT: std::sync::LazyLock<ResourceConstraint> =
-        std::sync::LazyLock::new(|| ResourceConstraint::any());
+        std::sync::LazyLock::new(ResourceConstraint::any);
 
     static DEFAULT_ACTION_CONSTRAINT: std::sync::LazyLock<ActionConstraint> =
-        std::sync::LazyLock::new(|| ActionConstraint::any());
+        std::sync::LazyLock::new(ActionConstraint::any);
 
     static DEFAULT_ERROR_EXPR: std::sync::LazyLock<Arc<Expr>> = std::sync::LazyLock::new(|| {
         // Non scope constraint expression of an Error policy should also be an error
@@ -984,9 +984,9 @@ impl TemplateBody {
     /// Get the `Id` of this policy.
     pub fn id(&self) -> &PolicyID {
         match self {
-            TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => &id,
+            TemplateBody::TemplateBody(TemplateBodyImpl { id, .. }) => id,
             #[cfg(feature = "tolerant-ast")]
-            TemplateBody::TemplateBodyError(policy_id, _) => &policy_id,
+            TemplateBody::TemplateBodyError(id, _) => id,
         }
     }
 
@@ -1145,7 +1145,7 @@ impl TemplateBody {
             TemplateBody::TemplateBody(TemplateBodyImpl {
                 non_scope_constraints,
                 ..
-            }) => &non_scope_constraints,
+            }) => non_scope_constraints,
             #[cfg(feature = "tolerant-ast")]
             TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ERROR_EXPR,
         }
@@ -1157,7 +1157,7 @@ impl TemplateBody {
             TemplateBody::TemplateBody(TemplateBodyImpl {
                 non_scope_constraints,
                 ..
-            }) => &non_scope_constraints,
+            }) => non_scope_constraints,
             #[cfg(feature = "tolerant-ast")]
             TemplateBody::TemplateBodyError(_, _) => &DEFAULT_ERROR_EXPR,
         }
diff --git a/cedar-policy-core/src/est.rs b/cedar-policy-core/src/est.rs
index 327cd5e6..6219868e 100644
--- a/cedar-policy-core/src/est.rs
+++ b/cedar-policy-core/src/est.rs
@@ -124,6 +124,14 @@ impl Policy {
             annotations: self.annotations,
         })
     }
+
+    /// Returns true if this policy is a template, i.e., it has at least one slot.
+    pub fn is_template(&self) -> bool {
+        self.principal.has_slot()
+            || self.action.has_slot()
+            || self.resource.has_slot()
+            || self.conditions.iter().any(|c| c.has_slot())
+    }
 }
 
 impl Clause {
@@ -146,6 +154,12 @@ impl Clause {
             Unless(e) => Ok(Unless(e.sub_entity_literals(mapping)?)),
         }
     }
+
+    /// Returns true if this clause has a slot.
+    pub fn has_slot(&self) -> bool {
+        // currently, slots are not allowed in clauses
+        false
+    }
 }
 
 impl TryFrom<cst::Policy> for Policy {
@@ -4599,6 +4613,82 @@ mod test {
             .unwrap()
         );
     }
+
+    #[test]
+    fn is_template_principal_slot() {
+        let template = r#"
+            permit(
+                principal == ?principal,
+                action == Action::"view",
+                resource
+            );
+        "#;
+        let cst = parser::text_to_cst::parse_policy(template)
+            .unwrap()
+            .node
+            .unwrap();
+        let est: Policy = cst.try_into().unwrap();
+        assert!(
+            est.is_template(),
+            "Policy with principal slot not marked as template"
+        );
+    }
+
+    #[test]
+    fn is_template_resource_slot() {
+        let template = r#"
+            permit(
+                principal,
+                action == Action::"view",
+                resource in ?resource
+            );
+        "#;
+        let cst = parser::text_to_cst::parse_policy(template)
+            .unwrap()
+            .node
+            .unwrap();
+        let est: Policy = cst.try_into().unwrap();
+        assert!(
+            est.is_template(),
+            "Policy with resource slot not marked as template"
+        );
+    }
+
+    #[test]
+    fn is_template_static_policy() {
+        let template = r#"
+            permit(
+                principal,
+                action == Action::"view",
+                resource
+            );
+        "#;
+        let cst = parser::text_to_cst::parse_policy(template)
+            .unwrap()
+            .node
+            .unwrap();
+        let est: Policy = cst.try_into().unwrap();
+        assert!(!est.is_template(), "Static policy marked as template");
+    }
+
+    #[test]
+    fn is_template_static_policy_with_condition() {
+        let template = r#"
+            permit(
+                principal,
+                action == Action::"view",
+                resource
+            ) when {
+                principal in resource.owners
+            };
+        "#;
+        let cst = parser::text_to_cst::parse_policy(template)
+            .unwrap()
+            .node
+            .unwrap();
+        let est: Policy = cst.try_into().unwrap();
+        assert!(!est.is_template(), "Static policy marked as template");
+    }
 }
 
 #[cfg(test)]
diff --git a/cedar-policy-core/src/est/policy_set.rs b/cedar-policy-core/src/est/policy_set.rs
index 16abf261..899cd1a7 100644
--- a/cedar-policy-core/src/est/policy_set.rs
+++ b/cedar-policy-core/src/est/policy_set.rs
@@ -19,6 +19,9 @@ use super::PolicySetFromJsonError;
 use crate::ast::{self, EntityUID, PolicyID, SlotId};
 use crate::entities::json::err::JsonDeserializationErrorContext;
 use crate::entities::json::EntityUidJson;
+use crate::parser::cst::Policies;
+use crate::parser::err::ParseErrors;
+use crate::parser::Node;
 use serde::{Deserialize, Serialize};
 use serde_with::serde_as;
 use std::collections::HashMap;
@@ -128,6 +131,45 @@ impl TryFrom<PolicySet> for ast::PolicySet {
     }
 }
 
+impl TryFrom<Node<Option<Policies>>> for PolicySet {
+    type Error = ParseErrors;
+
+    fn try_from(policies: Node<Option<Policies>>) -> Result<Self, Self::Error> {
+        let mut templates = HashMap::new();
+        let mut static_policies = HashMap::new();
+        let mut all_errs: Vec<ParseErrors> = vec![];
+        for (policy_id, policy) in policies.with_generated_policyids()? {
+            match policy.try_as_inner() {
+                Ok(cst) => match Policy::try_from(cst.clone()) {
+                    Ok(est) => {
+                        if est.is_template() {
+                            templates.insert(policy_id, est);
+                        } else {
+                            static_policies.insert(policy_id, est);
+                        }
+                    }
+                    Err(e) => {
+                        all_errs.push(e);
+                    }
+                },
+                Err(e) => {
+                    all_errs.push(e.into());
+                }
+            };
+        }
+        // fail on any error
+        if let Some(errs) = ParseErrors::flatten(all_errs) {
+            Err(errs)
+        } else {
+            Ok(PolicySet {
+                templates,
+                static_policies,
+                template_links: Vec::new(),
+            })
+        }
+    }
+}
+
 #[cfg(test)]
 mod test {
     use serde_json::json;
@@ -298,4 +340,39 @@ mod test {
             "invalid entry: found duplicate key at line 6 column 65"
         );
     }
+
+    #[test]
+    fn try_from_policies_static_only() {
+        let src = r#"
+            permit(principal == User::"alice", action, resource);
+            permit(principal, action == Action::"view", resource);
+        "#;
+        let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
+        let policy_set =
+            PolicySet::try_from(node).expect("Conversion to policy set should succeed");
+        assert_eq!(policy_set.static_policies.len(), 2);
+        assert!(policy_set.templates.is_empty());
+        assert!(policy_set.template_links.is_empty());
+    }
+
+    #[test]
+    fn try_from_policies_static_and_templates() {
+        let src = r#"
+            permit(principal == User::"alice", action, resource);
+            permit(principal == ?principal, action == Action::"view", resource);
+        "#;
+        let node = crate::parser::text_to_cst::parse_policies(src).expect("Policies should parse");
+        let policy_set =
+            PolicySet::try_from(node).expect("Conversion to policy set should succeed");
+        assert_eq!(policy_set.static_policies.len(), 1);
+        assert_eq!(policy_set.templates.len(), 1);
+        assert!(policy_set.template_links.is_empty());
+    }
+
+    #[test]
+    fn try_from_policies_with_parse_error() {
+        let src = r#"principal(p, action, resource);"#;
+        let node = crate::parser::text_to_cst::parse_policies(src).expect("policies should parse");
+        PolicySet::try_from(node).expect_err("Expected parse error to result in err");
+    }
 }
diff --git a/cedar-policy-core/src/est/scope_constraints.rs b/cedar-policy-core/src/est/scope_constraints.rs
index f18a8865..c999fe16 100644
--- a/cedar-policy-core/src/est/scope_constraints.rs
+++ b/cedar-policy-core/src/est/scope_constraints.rs
@@ -272,6 +272,25 @@ impl PrincipalConstraint {
             }
         }
     }
+
+    /// Returns true if this constraint has a slot.
+    pub fn has_slot(&self) -> bool {
+        match self {
+            PrincipalConstraint::All => false,
+            PrincipalConstraint::Eq(EqConstraint::Entity { .. }) => false,
+            PrincipalConstraint::Eq(EqConstraint::Slot { .. }) => true,
+            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Entity { .. }) => false,
+            PrincipalConstraint::In(PrincipalOrResourceInConstraint::Slot { .. }) => true,
+            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
+                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
+                ..
+            }) => false,
+            PrincipalConstraint::Is(PrincipalOrResourceIsConstraint {
+                in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
+                ..
+            }) => true,
+        }
+    }
 }
 
 impl ResourceConstraint {
@@ -376,6 +395,25 @@ impl ResourceConstraint {
             }
         }
     }
+
+    /// Returns true if this constraint has a slot.
+    pub fn has_slot(&self) -> bool {
+        match self {
+            ResourceConstraint::All => false,
+            ResourceConstraint::Eq(EqConstraint::Entity { .. }) => false,
+            ResourceConstraint::In(PrincipalOrResourceInConstraint::Entity { .. }) => false,
+            ResourceConstraint::Eq(EqConstraint::Slot { .. }) => true,
+            ResourceConstraint::In(PrincipalOrResourceInConstraint::Slot { .. }) => true,
+            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
+                in_entity: None | Some(PrincipalOrResourceInConstraint::Entity { .. }),
+                ..
+            }) => false,
+            ResourceConstraint::Is(PrincipalOrResourceIsConstraint {
+                in_entity: Some(PrincipalOrResourceInConstraint::Slot { .. }),
+                ..
+            }) => true,
+        }
+    }
 }
 
 impl ActionConstraint {
@@ -431,6 +469,12 @@ impl ActionConstraint {
             ActionConstraint::ErrorConstraint => Ok(self),
         }
     }
+
+    /// Returns true if this constraint has a slot.
+    pub fn has_slot(&self) -> bool {
+        // currently, slots are not allowed in action constraints
+        false
+    }
 }
 
 impl std::fmt::Display for PrincipalConstraint {
@@ -889,3 +933,96 @@ impl TryFrom<ActionConstraint> for ast::ActionConstraint {
             })
     }
 }
+
+#[cfg(test)]
+mod test {
+    fn parse_policy(template: &str) -> crate::est::Policy {
+        let cst = crate::parser::text_to_cst::parse_policy(template)
+            .unwrap()
+            .node
+            .unwrap();
+        cst.try_into().unwrap()
+    }
+
+    fn principal_has_slot(principal_text: &str) -> bool {
+        let text = format!("permit({principal_text}, action, resource);");
+        parse_policy(&text).principal.has_slot()
+    }
+
+    fn resource_has_slot(resource_text: &str) -> bool {
+        let text = format!("permit(principal, action, {resource_text});");
+        parse_policy(&text).resource.has_slot()
+    }
+
+    #[test]
+    fn has_slot_principal_all() {
+        assert!(!principal_has_slot(r#"principal"#));
+    }
+
+    #[test]
+    fn has_slot_principal_eq_entity() {
+        assert!(!principal_has_slot(r#"principal == User::"alice""#));
+    }
+
+    #[test]
+    fn has_slot_principal_eq_slot() {
+        assert!(principal_has_slot(r#"principal == ?principal"#));
+    }
+
+    #[test]
+    fn has_slot_principal_in_entity() {
+        assert!(!principal_has_slot(r#"principal in Group::"friends""#));
+    }
+
+    #[test]
+    fn has_slot_principal_in_slot() {
+        assert!(principal_has_slot(r#"principal in ?principal"#));
+    }
+
+    #[test]
+    fn has_slot_principal_is_entity() {
+        assert!(!principal_has_slot(r#"principal is User"#));
+    }
+
+    #[test]
+    fn has_slot_principal_is_slot() {
+        assert!(principal_has_slot(r#"principal is User in ?principal"#));
+    }
+
+    #[test]
+    fn has_slot_resource_all() {
+        assert!(!resource_has_slot(r#"resource"#));
+    }
+
+    #[test]
+    fn has_slot_resource_eq_entity() {
+        assert!(!resource_has_slot(
+            r#"resource == Photo::"VacationPhoto94.jpg""#
+        ));
+    }
+
+    #[test]
+    fn has_slot_resource_eq_slot() {
+        assert!(resource_has_slot(r#"resource == ?resource"#));
+    }
+
+    #[test]
+    fn has_slot_resource_in_entity() {
+        assert!(!resource_has_slot(r#"resource in Group::"vacation""#));
+    }
+
+    #[test]
+    fn has_slot_resource_in_slot() {
+        assert!(resource_has_slot(r#"resource in ?resource"#));
+    }
+
+    #[test]
+    fn has_slot_resource_is_entity() {
+        assert!(!resource_has_slot(r#"resource is Photo"#));
+    }
+
+    #[test]
+    fn has_slot_resource_is_slot() {
+        assert!(resource_has_slot(r#"resource is Photo in ?resource"#));
+    }
+}
diff --git a/cedar-policy-validator/src/lib.rs b/cedar-policy-validator/src/lib.rs
index 54f541c0..cbaf7a18 100644
--- a/cedar-policy-validator/src/lib.rs
+++ b/cedar-policy-validator/src/lib.rs
@@ -104,6 +104,11 @@ impl Validator {
         Self { schema }
     }
 
+    /// Get the `ValidatorSchema` this `Validator` is using.
+    pub fn schema(&self) -> &ValidatorSchema {
+        &self.schema
+    }
+
     /// Validate all templates, links, and static policies in a policy set.
     /// Return a `ValidationResult`.
     pub fn validate(&self, policies: &PolicySet, mode: ValidationMode) -> ValidationResult {
diff --git a/cedar-policy/CHANGELOG.md b/cedar-policy/CHANGELOG.md
index 3a477fc3..904e42a5 100644
--- a/cedar-policy/CHANGELOG.md
+++ b/cedar-policy/CHANGELOG.md
@@ -29,6 +29,8 @@ Cedar Language Version: TBD
 - Added `to_cedar` functions for `PolicySet`, `Policy`, and `Template` that
   render the policy in the human-readable Cedar syntax. These functions can be used
   to convert JSON formatted policies into the human-readable syntax.
+- Added `Validator::schema()` to get a reference to the `Schema` even after it has been
+  consumed to construct a `Validator` (#1524)
 
 ## [4.3.3] - 2025-02-25
 
diff --git a/cedar-policy/Cargo.toml b/cedar-policy/Cargo.toml
index 80fefe48..488942ba 100644
--- a/cedar-policy/Cargo.toml
+++ b/cedar-policy/Cargo.toml
@@ -31,7 +31,7 @@ prost = { version = "0.13", optional = true }
 serde-wasm-bindgen = { version = "0.6", optional = true }
 tsify = { version = "0.4.5", optional = true }
 wasm-bindgen = { version = "0.2.97", optional = true }
-semver = "1.0.25"
+semver = "1.0.26"
 lazy_static = "1.5.0"
 
 [build-dependencies]
diff --git a/cedar-policy/src/api.rs b/cedar-policy/src/api.rs
index 9d84e4a0..609796fc 100644
--- a/cedar-policy/src/api.rs
+++ b/cedar-policy/src/api.rs
@@ -1318,6 +1318,11 @@ impl Validator {
         Self(cedar_policy_validator::Validator::new(schema.0))
     }
 
+    /// Get the `Schema` this `Validator` is using.
+    pub fn schema(&self) -> &Schema {
+        RefCast::ref_cast(self.0.schema())
+    }
+
     /// Validate all policies in a policy set, collecting all validation errors
     /// found into the returned `ValidationResult`. Each error is returned together with the
     /// policy id of the policy where the error was found. If a policy id
@@ -2243,13 +2248,13 @@ impl PolicySet {
             // before printing, hoping that the size of policy sets is fairly
             // small.
             .sorted_by_key(|p| AsRef::<str>::as_ref(p.id()))
-            .map(|p| p.to_cedar())
+            .map(Policy::to_cedar)
             .collect::<Option<Vec<_>>>()?;
         let templates = self
             .templates
             .values()
             .sorted_by_key(|t| AsRef::<str>::as_ref(t.id()))
-            .map(|t| t.to_cedar());
+            .map(Template::to_cedar);
 
         Some(policies.into_iter().chain(templates).join("\n\n"))
     }


Test patch:
diff --git a/cedar-testing/Cargo.toml b/cedar-testing/Cargo.toml
index 41cb6790..3e9e9157 100644
--- a/cedar-testing/Cargo.toml
+++ b/cedar-testing/Cargo.toml
@@ -18,6 +18,7 @@ miette = { version = "7.5.0", features = ["fancy"] }
 default = ["ipaddr", "decimal"]
 decimal = ["cedar-policy/decimal"]
 ipaddr = ["cedar-policy/ipaddr"]
+level-validate = ["cedar-policy/level-validate"]
 integration-testing = []
 entity-manifest = ["cedar-policy/entity-manifest"]
 
diff --git a/cedar-testing/src/cedar_test_impl.rs b/cedar-testing/src/cedar_test_impl.rs
index 98dfb60d..27004a07 100644
--- a/cedar-testing/src/cedar_test_impl.rs
+++ b/cedar-testing/src/cedar_test_impl.rs
@@ -174,6 +174,16 @@ pub trait CedarTestImplementation {
         mode: ValidationMode,
     ) -> TestResult<TestValidationResult>;
 
+    #[cfg(feature = "level-validate")]
+    /// Custom validator entry point with level.
+    fn validate_with_level(
+        &self,
+        schema: &ValidatorSchema,
+        policies: &PolicySet,
+        mode: ValidationMode,
+        level: i32,
+    ) -> TestResult<TestValidationResult>;
+
     fn validate_request(
         &self,
         schema: &ValidatorSchema,
@@ -322,6 +332,28 @@ impl CedarTestImplementation for RustEngine {
         };
         TestResult::Success(response)
     }
+
+    #[cfg(feature = "level-validate")]
+    fn validate_with_level(
+        &self,
+        schema: &ValidatorSchema,
+        policies: &PolicySet,
+        mode: ValidationMode,
+        level: i32,
+    ) -> TestResult<TestValidationResult> {
+        let validator = Validator::new(schema.clone());
+        let (result, duration) =
+            time_function(|| validator.validate_with_level(policies, mode, level as u32));
+        let response = TestValidationResult {
+            errors: result
+                .validation_errors()
+                .map(|err| format!("{err:?}"))
+                .collect(),
+            timing_info: HashMap::from([("validate".into(), Micros(duration.as_micros()))]),
+        };
+        TestResult::Success(response)
+    }
+
     fn validate_entities(
         &self,
         schema: &ValidatorSchema,