cedar_policy/api/
deprecated_schema_compat.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Defines functions for parsing schema using a format (mostly) compatible with
18//! version 2.5.0 of this library.
19
20// Note: We'll likely want to remove these functions in the future if we can get
21// everyone on to the updated schema format. If we ever get there, we should
22// keep some of the tests in this file. Many of the tests check error behavior
23// and actually test the behavior of standard schema parsing in addition to the
24// functions defined.
25
26use cedar_policy_core::extensions::Extensions;
27use cedar_policy_core::validator::SchemaError;
28
29use super::{Schema, SchemaFragment};
30
31impl SchemaFragment {
32    /// Create a [`SchemaFragment`] from a JSON value in shape of the deprecated
33    /// JSON schema format used for versions 2.5.0 of this library.  Most
34    /// callers should use [`SchemaFragment::from_json_value`] instead.
35    #[deprecated(
36        since = "4.5.0",
37        note = "use `SchemaFragment::from_json_value` instead"
38    )]
39    pub fn from_deprecated_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
40        let lossless =
41            cedar_policy_core::validator::json_schema::Fragment::from_deprecated_json_value(json)?;
42        Ok(Self {
43            value: lossless.clone().try_into()?,
44            lossless,
45        })
46    }
47
48    /// Create a [`SchemaFragment`] from a string containing JSON in the
49    /// deprecated JSON schema format used for versions 2.5.0 of this library.
50    /// Most callers should use [`SchemaFragment::from_json_str`] instead.
51    #[deprecated(since = "4.5.0", note = "use `SchemaFragment::from_json_str` instead")]
52    pub fn from_deprecated_json_str(src: &str) -> Result<Self, SchemaError> {
53        let lossless =
54            cedar_policy_core::validator::json_schema::Fragment::from_deprecated_json_str(src)?;
55        Ok(Self {
56            value: lossless.clone().try_into()?,
57            lossless,
58        })
59    }
60
61    /// Create a [`SchemaFragment`] directly from a JSON file containing JSON in
62    /// the deprecated JSON schema format used for versions 2.5.0 of this
63    /// library. Most callers should use [`SchemaFragment::from_json_file`]
64    /// instead.
65    #[deprecated(since = "4.5.0", note = "use `SchemaFragment::from_json_file` instead")]
66    pub fn from_deprecated_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
67        let lossless =
68            cedar_policy_core::validator::json_schema::Fragment::from_deprecated_json_file(file)?;
69        Ok(Self {
70            value: lossless.clone().try_into()?,
71            lossless,
72        })
73    }
74}
75
76impl Schema {
77    /// Create a [`Schema`] from a string containing JSON in the
78    /// deprecated JSON schema format used for versions 2.5.0 of this library.
79    /// Most callers should use [`Schema::from_json_str`] instead.
80    #[deprecated(since = "4.5.0", note = "use `Schema::from_json_str` instead")]
81    pub fn from_deprecated_json_value(json: serde_json::Value) -> Result<Self, SchemaError> {
82        Ok(Self(
83            cedar_policy_core::validator::ValidatorSchema::from_deprecated_json_value(
84                json,
85                Extensions::all_available(),
86            )?,
87        ))
88    }
89
90    /// Create a [`Schema`] from a JSON value in shape of the deprecated
91    /// JSON schema format used for versions 2.5.0 of this library.  Most
92    /// callers should use [`Schema::from_json_value`] instead.
93    #[deprecated(since = "4.5.0", note = "use `Schema::from_json_value` instead")]
94    pub fn from_deprecated_json_str(json: &str) -> Result<Self, SchemaError> {
95        Ok(Self(
96            cedar_policy_core::validator::ValidatorSchema::from_deprecated_json_str(
97                json,
98                Extensions::all_available(),
99            )?,
100        ))
101    }
102
103    /// Create a [`Schema`] directly from a JSON file containing JSON in
104    /// the deprecated JSON schema format used for versions 2.5.0 of this
105    /// library. Most callers should use [`Schema::from_json_file`] instead.
106    #[deprecated(since = "4.5.0", note = "use `Schema::from_json_file` instead")]
107    pub fn from_deprecated_json_file(file: impl std::io::Read) -> Result<Self, SchemaError> {
108        Ok(Self(
109            cedar_policy_core::validator::ValidatorSchema::from_deprecated_json_file(
110                file,
111                Extensions::all_available(),
112            )?,
113        ))
114    }
115}
116
117#[cfg(test)]
118mod test_utils {
119    use cedar_policy_core::test_utils::{
120        expect_err, ExpectedErrorMessage, ExpectedErrorMessageBuilder,
121    };
122    use miette::Report;
123    use serde_json::json;
124
125    use crate::Schema;
126
127    fn schema_with_entity_attribute(attr_ty: serde_json::Value) -> serde_json::Value {
128        json!({
129            "ns": {
130                "commonTypes": {"ty": {"type": "Long"}},
131                "entityTypes": {
132                    "User": {
133                        "shape": {
134                            "type": "Record",
135                            "attributes": {
136                                "foo": attr_ty,
137                            },
138                        },
139                    }
140                },
141                "actions": {},
142            }
143        })
144    }
145
146    fn schema_with_context_attribute(attr_ty: serde_json::Value) -> serde_json::Value {
147        json!({
148            "ns": {
149                "commonTypes": {"ty": {"type": "Long"}},
150                "entityTypes": {
151                    "User": {}
152                },
153                "actions": {
154                    "Act": {
155                        "appliesTo": {
156                            "principalTypes": [],
157                            "resourceTypes": [],
158                            "context": {
159                                "type": "Record",
160                                "attributes": {
161                                    "foo": attr_ty,
162                                },
163                            }
164                        }
165                    }
166                },
167            }
168        })
169    }
170
171    fn schema_with_common_type(ty: serde_json::Value) -> serde_json::Value {
172        json!({
173            "ns": {
174                "commonTypes": {
175                    "ty": {"type": "Long"},
176                    "ty2": ty,
177                },
178                "entityTypes": {
179                    "User": {}
180                },
181                "actions": {},
182            }
183        })
184    }
185
186    #[track_caller]
187    #[allow(deprecated)]
188    pub(crate) fn assert_type_json_ok_deprecated_and_err_standard(
189        ty: serde_json::Value,
190        err: &str,
191    ) {
192        let in_entity_attr = schema_with_entity_attribute(ty.clone());
193        Schema::from_deprecated_json_value(in_entity_attr.clone()).unwrap();
194        expect_err(
195            "",
196            &Report::new(Schema::from_json_value(in_entity_attr).unwrap_err()),
197            &ExpectedErrorMessageBuilder::error(err).build(),
198        );
199        let in_context = schema_with_context_attribute(ty.clone());
200        Schema::from_deprecated_json_value(in_context.clone()).unwrap();
201        expect_err(
202            "",
203            &Report::new(Schema::from_json_value(in_context).unwrap_err()),
204            &ExpectedErrorMessageBuilder::error(err).build(),
205        );
206        let in_common = schema_with_common_type(ty);
207        Schema::from_deprecated_json_value(in_common.clone()).unwrap();
208        expect_err(
209            "",
210            &Report::new(Schema::from_json_value(in_common).unwrap_err()),
211            &ExpectedErrorMessageBuilder::error(err).build(),
212        );
213    }
214
215    #[track_caller]
216    #[allow(deprecated)]
217    pub(crate) fn assert_type_json_ok_deprecated_and_standard(ty: serde_json::Value) {
218        let in_entity_attr = schema_with_entity_attribute(ty.clone());
219        Schema::from_deprecated_json_value(in_entity_attr.clone()).unwrap();
220        Schema::from_json_value(in_entity_attr).unwrap();
221        let in_context = schema_with_context_attribute(ty.clone());
222        Schema::from_deprecated_json_value(in_context.clone()).unwrap();
223        Schema::from_json_value(in_context).unwrap();
224        let in_common = schema_with_common_type(ty);
225        Schema::from_deprecated_json_value(in_common.clone()).unwrap();
226        Schema::from_json_value(in_common).unwrap();
227    }
228
229    #[track_caller]
230    #[allow(deprecated)]
231    pub(crate) fn assert_type_json_err_deprecated_and_standard(
232        ty: serde_json::Value,
233        current_err: &ExpectedErrorMessage<'_>,
234        deprecated_err: &ExpectedErrorMessage<'_>,
235    ) {
236        let in_entity_attr = schema_with_entity_attribute(ty.clone());
237        assert_schema_json_err_deprecated_and_standard(in_entity_attr, current_err, deprecated_err);
238        let in_context = schema_with_context_attribute(ty.clone());
239        assert_schema_json_err_deprecated_and_standard(in_context, current_err, deprecated_err);
240        let in_common = schema_with_common_type(ty);
241        assert_schema_json_err_deprecated_and_standard(in_common, current_err, deprecated_err);
242    }
243
244    #[track_caller]
245    #[allow(deprecated)]
246    pub(crate) fn assert_schema_json_err_deprecated_and_standard(
247        schema: serde_json::Value,
248        current_err: &ExpectedErrorMessage<'_>,
249        deprecated_err: &ExpectedErrorMessage<'_>,
250    ) {
251        expect_err(
252            "",
253            &Report::new(Schema::from_json_value(schema.clone()).unwrap_err()),
254            current_err,
255        );
256        expect_err(
257            "",
258            &Report::new(Schema::from_deprecated_json_value(schema).unwrap_err()),
259            deprecated_err,
260        );
261    }
262}
263
264/// These tests assert that unknown fields are allowed in specific locations in
265/// the compatibility mode, but not allowed in the standard schema parsing mode.
266#[cfg(test)]
267mod extra_fields_allowed {
268    use super::test_utils::*;
269    use serde_json::json;
270
271    #[test]
272    fn in_long() {
273        assert_type_json_ok_deprecated_and_err_standard(
274            json!({
275                "type": "Long",
276                "bogus": "bogus",
277            }),
278            "unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`",
279        );
280        assert_type_json_ok_deprecated_and_err_standard(
281            json!({
282                "type": "Long",
283                "name": "my_long",
284            }),
285            "unknown field `name`, there are no fields",
286        );
287        assert_type_json_ok_deprecated_and_err_standard(
288            json!({
289                "type": "Long",
290                "element": "bogus"
291            }),
292            "invalid type: string \"bogus\", expected builtin type or reference to type defined in commonTypes",
293        );
294        assert_type_json_ok_deprecated_and_err_standard(
295            json!({
296                "type": "Long",
297                "attributes": "bogus",
298            }),
299            "invalid type: string \"bogus\", expected a map",
300        );
301        assert_type_json_ok_deprecated_and_err_standard(
302            json!({
303                "type": "Long",
304                "additionalAttributes": "bogus",
305            }),
306            "invalid type: string \"bogus\", expected a boolean",
307        );
308        assert_type_json_ok_deprecated_and_standard(json!({
309            "type": "Long",
310            "annotations": {},
311        }));
312    }
313
314    #[test]
315    fn in_bool() {
316        assert_type_json_ok_deprecated_and_err_standard(
317            json!({
318                "type": "Boolean",
319                "bogus": "bogus",
320            }),
321            "unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`",
322        );
323        assert_type_json_ok_deprecated_and_err_standard(
324            json!({
325                "type": "Boolean",
326                "name": "my_long",
327            }),
328            "unknown field `name`, there are no fields",
329        );
330        assert_type_json_ok_deprecated_and_err_standard(
331            json!({
332                "type": "Boolean",
333                "element": "bogus",
334            }),
335            "invalid type: string \"bogus\", expected builtin type or reference to type defined in commonTypes",
336        );
337        assert_type_json_ok_deprecated_and_err_standard(
338            json!({
339                "type": "Boolean",
340                "attributes": "bogus",
341            }),
342            "invalid type: string \"bogus\", expected a map",
343        );
344        assert_type_json_ok_deprecated_and_err_standard(
345            json!({
346                "type": "Boolean",
347                "additionalAttributes": "bogus",
348            }),
349            "invalid type: string \"bogus\", expected a boolean",
350        );
351        assert_type_json_ok_deprecated_and_standard(json!({
352            "type": "Boolean",
353            "annotations": {},
354        }));
355    }
356
357    #[test]
358    fn in_string() {
359        assert_type_json_ok_deprecated_and_err_standard(
360            json!({
361                "type": "String",
362                "bogus": "bogus",
363            }),
364            "unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`",
365        );
366        assert_type_json_ok_deprecated_and_err_standard(
367            json!({
368                "type": "String",
369                "name": "bogus",
370            }),
371            "unknown field `name`, there are no fields",
372        );
373        assert_type_json_ok_deprecated_and_err_standard(
374            json!({
375                "type": "String",
376                "element": {"type": "Long"}
377            }),
378            "unknown field `element`, there are no fields",
379        );
380        assert_type_json_ok_deprecated_and_err_standard(
381            json!({
382                "type": "String",
383                "attributes": {},
384            }),
385            "unknown field `attributes`, there are no fields",
386        );
387        assert_type_json_ok_deprecated_and_err_standard(
388            json!({
389                "type": "String",
390                "additionalAttributes": false,
391            }),
392            "unknown field `additionalAttributes`, there are no fields",
393        );
394        assert_type_json_ok_deprecated_and_standard(json!({
395            "type": "String",
396            "annotations": {},
397        }));
398    }
399
400    #[test]
401    fn in_common() {
402        assert_type_json_ok_deprecated_and_err_standard(
403            json!({
404                "type": "ty",
405                "bogus": "bogus",
406            }),
407            "unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`",
408        );
409        assert_type_json_ok_deprecated_and_err_standard(
410            json!({
411                "type": "ty",
412                "name": "my_long",
413            }),
414            "unknown field `name`, there are no fields",
415        );
416        assert_type_json_ok_deprecated_and_err_standard(
417            json!({
418                "type": "ty",
419                "element": 10,
420            }),
421            "invalid type: integer `10`, expected builtin type or reference to type defined in commonTypes",
422        );
423        assert_type_json_ok_deprecated_and_err_standard(
424            json!({
425                "type": "ty",
426                "attributes": ["bogus"],
427            }),
428            "invalid type: sequence, expected a map",
429        );
430        assert_type_json_ok_deprecated_and_err_standard(
431            json!({
432                "type": "ty",
433                "additionalAttributes": {"bogus": "bogus"},
434            }),
435            "invalid type: map, expected a boolean",
436        );
437        assert_type_json_ok_deprecated_and_standard(json!({
438            "type": "ty",
439            "annotations": {},
440        }));
441    }
442
443    #[test]
444    fn in_set_elem() {
445        assert_type_json_ok_deprecated_and_err_standard(
446            json!({
447                "type": "Set",
448                "element": {"type": "Long", "name": "my_long"},
449            }),
450            "unknown field `name`, there are no fields",
451        );
452    }
453
454    #[test]
455    fn in_record_attr() {
456        assert_type_json_ok_deprecated_and_err_standard(
457            json!({
458                "type": "Record",
459                "attributes": { "a": {"type": "Long", "name": "foo"} },
460            }),
461            "unknown field `name`, there are no fields",
462        );
463    }
464}
465
466/// These tests check the behavior of extra fields that should still be forbidden
467#[cfg(test)]
468mod extra_fields_forbidden {
469    use super::test_utils::*;
470    use cedar_policy_core::test_utils::ExpectedErrorMessageBuilder;
471    use serde_json::json;
472
473    #[test]
474    fn in_set() {
475        assert_type_json_err_deprecated_and_standard(
476            json!({
477                "type": "Set",
478                "element": {"type": "Long"},
479                "bogus": "bogus",
480            }),
481            &ExpectedErrorMessageBuilder::error("unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
482                .build(),
483            &ExpectedErrorMessageBuilder::error("failed to resolve type: Set")
484                .help("neither `ns::Set` nor `Set` refers to anything that has been declared as a common type")
485                .exactly_one_underline("Set")
486                .build(),
487        );
488        assert_type_json_err_deprecated_and_standard(
489            json!({
490                "type": "Set",
491                "element": {"type": "Long"},
492                "name": "my_long",
493            }),
494            &ExpectedErrorMessageBuilder::error("unknown field `name`, expected `element`")
495                .build(),
496            &ExpectedErrorMessageBuilder::error("failed to resolve type: Set")
497                .help("neither `ns::Set` nor `Set` refers to anything that has been declared as a common type")
498                .exactly_one_underline("Set")
499                .build(),
500        );
501
502        assert_type_json_err_deprecated_and_standard(
503            json!({
504                "type": "Set",
505                "element": {"type": "Long"},
506                "attributes": {},
507            }),
508            &ExpectedErrorMessageBuilder::error("unknown field `attributes`, expected `element`")
509                .build(),
510            &ExpectedErrorMessageBuilder::error("failed to resolve type: Set")
511                .help("neither `ns::Set` nor `Set` refers to anything that has been declared as a common type")
512                .exactly_one_underline("Set")
513                .build(),
514        );
515        assert_type_json_err_deprecated_and_standard(
516            json!({
517                "type": "Set",
518                "element": {"type": "Long"},
519                "additionalAttributes": false,
520            }),
521            &ExpectedErrorMessageBuilder::error("unknown field `additionalAttributes`, expected `element`")
522                .build(),
523            &ExpectedErrorMessageBuilder::error("failed to resolve type: Set")
524                .help("neither `ns::Set` nor `Set` refers to anything that has been declared as a common type")
525                .exactly_one_underline("Set")
526                .build(),
527        );
528    }
529
530    #[test]
531    fn in_entity() {
532        assert_type_json_err_deprecated_and_standard(
533            json!({
534                "type": "Entity",
535                "name": "User",
536                "bogus": "bogus",
537            }),
538            &ExpectedErrorMessageBuilder::error("unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
539                .build(),
540            &ExpectedErrorMessageBuilder::error("failed to resolve type: Entity")
541                .help("neither `ns::Entity` nor `Entity` refers to anything that has been declared as a common type")
542                .exactly_one_underline("Entity")
543                .build(),
544        );
545        assert_type_json_err_deprecated_and_standard(
546            json!({
547                "type": "Entity",
548                "name": "User",
549                "element": {"type": "Long"},
550            }),
551            &ExpectedErrorMessageBuilder::error("unknown field `element`, expected `name`")
552                .build(),
553            &ExpectedErrorMessageBuilder::error("failed to resolve type: Entity")
554                .help("neither `ns::Entity` nor `Entity` refers to anything that has been declared as a common type")
555                .exactly_one_underline("Entity")
556                .build(),
557        );
558        assert_type_json_err_deprecated_and_standard(
559            json!({
560                "type": "Entity",
561                "name": "User",
562                "attributes": {},
563            }),
564            &ExpectedErrorMessageBuilder::error("unknown field `attributes`, expected `name`")
565                .build(),
566            &ExpectedErrorMessageBuilder::error("failed to resolve type: Entity")
567                .help("neither `ns::Entity` nor `Entity` refers to anything that has been declared as a common type")
568                .exactly_one_underline("Entity")
569                .build(),
570        );
571        assert_type_json_err_deprecated_and_standard(
572            json!({
573                "type": "Entity",
574                "name": "User",
575                "additionalAttributes": false,
576            }),
577            &ExpectedErrorMessageBuilder::error( "unknown field `additionalAttributes`, expected `name`",)
578                .build(),
579            &ExpectedErrorMessageBuilder::error( "failed to resolve type: Entity",)
580                .help("neither `ns::Entity` nor `Entity` refers to anything that has been declared as a common type")
581                .exactly_one_underline("Entity")
582                .build(),
583        );
584    }
585
586    #[test]
587    fn in_extension() {
588        assert_type_json_err_deprecated_and_standard(
589            json!({
590                "type": "Extension",
591                "name": "ip",
592                "bogus": "bogus"
593            }),
594            &ExpectedErrorMessageBuilder::error("unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
595                .build(),
596            &ExpectedErrorMessageBuilder::error("failed to resolve type: Extension")
597                .help("neither `ns::Extension` nor `Extension` refers to anything that has been declared as a common type")
598                .exactly_one_underline("Extension")
599                .build(),
600        );
601        assert_type_json_err_deprecated_and_standard(
602            json!({
603                "type": "Extension",
604                "name": "ip",
605                "element": {"type": "Long"},
606            }),
607            &ExpectedErrorMessageBuilder::error("unknown field `element`, expected `name`")
608                .build(),
609            &ExpectedErrorMessageBuilder::error("failed to resolve type: Extension")
610                .help("neither `ns::Extension` nor `Extension` refers to anything that has been declared as a common type")
611                .exactly_one_underline("Extension")
612                .build(),
613        );
614        assert_type_json_err_deprecated_and_standard(
615            json!({
616                "type": "Extension",
617                "name": "ip",
618                "attributes": {},
619            }),
620            &ExpectedErrorMessageBuilder::error("unknown field `attributes`, expected `name`")
621                .build(),
622            &ExpectedErrorMessageBuilder::error("failed to resolve type: Extension")
623                .help("neither `ns::Extension` nor `Extension` refers to anything that has been declared as a common type")
624                .exactly_one_underline("Extension")
625                .build(),
626        );
627        assert_type_json_err_deprecated_and_standard(
628            json!({
629                "type": "Extension",
630                "name": "ip",
631                "additionalAttributes": false,
632            }),
633            &ExpectedErrorMessageBuilder::error( "unknown field `additionalAttributes`, expected `name`",)
634                .build(),
635            &ExpectedErrorMessageBuilder::error("failed to resolve type: Extension")
636                .help("neither `ns::Extension` nor `Extension` refers to anything that has been declared as a common type")
637                .exactly_one_underline("Extension")
638                .build(),
639        );
640    }
641
642    #[test]
643    fn in_record() {
644        assert_type_json_err_deprecated_and_standard(
645            json!({
646                "type": "Record",
647                "attributes": {},
648                "bogus": "bogus"
649            }),
650            &ExpectedErrorMessageBuilder::error("unknown field `bogus`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
651                .build(),
652            &ExpectedErrorMessageBuilder::error("failed to resolve type: Record")
653                .help("neither `ns::Record` nor `Record` refers to anything that has been declared as a common type")
654                .exactly_one_underline("Record")
655                .build(),
656        );
657        assert_type_json_err_deprecated_and_standard(
658            json!({
659                "type": "Record",
660                "attributes": {},
661                "element": {"type": "Long"},
662            }),
663            &ExpectedErrorMessageBuilder::error( "unknown field `element`, expected `attributes` or `additionalAttributes`",)
664                .build(),
665            &ExpectedErrorMessageBuilder::error( "failed to resolve type: Record",)
666                .help("neither `ns::Record` nor `Record` refers to anything that has been declared as a common type")
667                .exactly_one_underline("Record")
668                .build(),
669        );
670        assert_type_json_err_deprecated_and_standard(
671            json!({
672                "type": "Record",
673                "attributes": {},
674                "name": "ip",
675            }),
676            &ExpectedErrorMessageBuilder::error( "unknown field `name`, expected `attributes` or `additionalAttributes`",)
677                 .build(),
678            &ExpectedErrorMessageBuilder::error( "failed to resolve type: Record",)
679                .help("neither `ns::Record` nor `Record` refers to anything that has been declared as a common type")
680                .exactly_one_underline("Record")
681                .build(),
682        );
683    }
684
685    #[test]
686    fn in_namespace() {
687        assert_schema_json_err_deprecated_and_standard(
688            json!({
689                "ns": {
690                    "entityTypes": {},
691                    "actions": {},
692                    "commonTypes": {},
693                    "foo": {},
694                }
695            }),
696            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations`").build(),
697            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected one of `commonTypes`, `entityTypes`, `actions`").build(),
698        );
699    }
700
701    #[test]
702    fn in_entity_type() {
703        assert_schema_json_err_deprecated_and_standard(
704            json!({
705                "ns": {
706                    "entityTypes": {
707                        "User": {
708                            "foo": {},
709                        }
710                    },
711                    "actions": {},
712                }
713            }),
714            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected one of `memberOfTypes`, `shape`, `tags`, `enum`, `annotations`").build(),
715            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected `memberOfTypes` or `shape`").build(),
716        );
717    }
718
719    #[test]
720    fn in_action() {
721        assert_schema_json_err_deprecated_and_standard(
722            json!({
723                "ns": {
724                    "entityTypes": {
725                    },
726                    "actions": {
727                        "act": {
728                            "foo": {}
729                        },
730                    },
731                }
732            }),
733            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected one of `attributes`, `appliesTo`, `memberOf`, `annotations`").build(),
734            &ExpectedErrorMessageBuilder::error("unknown field `foo`, expected one of `attributes`, `appliesTo`, `memberOf`").build(),
735        );
736    }
737
738    #[test]
739    fn in_applies_to() {
740        assert_schema_json_err_deprecated_and_standard(
741            json!({
742                "ns": {
743                    "entityTypes": { },
744                    "actions": {
745                        "act": {
746                            "appliesTo": {
747                                "principalTypes": [],
748                                "resourceTypes": [],
749                                "foo": {},
750                            }
751                        },
752                    },
753                }
754            }),
755            &ExpectedErrorMessageBuilder::error(
756                "unknown field `foo`, expected one of `resourceTypes`, `principalTypes`, `context`",
757            )
758            .build(),
759            &ExpectedErrorMessageBuilder::error(
760                "unknown field `foo`, expected one of `resourceTypes`, `principalTypes`, `context`",
761            )
762            .build(),
763        );
764    }
765}
766
767/// Unspecified `appliesTo` is still reported as an error
768#[cfg(test)]
769mod unspecified_not_allowed {
770    use cedar_policy_core::test_utils::ExpectedErrorMessageBuilder;
771    use serde_json::json;
772
773    use super::test_utils::*;
774
775    #[test]
776    fn missing_principal() {
777        assert_schema_json_err_deprecated_and_standard(
778            json!({
779                "ns": {
780                    "entityTypes": { },
781                    "actions": {
782                        "act": {
783                            "appliesTo": {
784                                "resourceTypes": [],
785                            }
786                        },
787                    },
788                }
789            }),
790            &ExpectedErrorMessageBuilder::error("missing field `principalTypes`").build(),
791            &ExpectedErrorMessageBuilder::error("missing field `principalTypes`").build(),
792        );
793    }
794
795    #[test]
796    fn missing_resource() {
797        assert_schema_json_err_deprecated_and_standard(
798            json!({
799                "ns": {
800                    "entityTypes": { },
801                    "actions": {
802                        "act": {
803                            "appliesTo": {
804                                "principalTypes": [],
805                            }
806                        },
807                    },
808                }
809            }),
810            &ExpectedErrorMessageBuilder::error("missing field `resourceTypes`").build(),
811            &ExpectedErrorMessageBuilder::error("missing field `resourceTypes`").build(),
812        );
813    }
814
815    #[test]
816    fn missing_both() {
817        assert_schema_json_err_deprecated_and_standard(
818            json!({
819                "ns": {
820                    "entityTypes": { },
821                    "actions": {
822                        "act": {
823                            "appliesTo": { }
824                        },
825                    },
826                }
827            }),
828            &ExpectedErrorMessageBuilder::error("missing field `resourceTypes`").build(),
829            &ExpectedErrorMessageBuilder::error("missing field `resourceTypes`").build(),
830        );
831    }
832
833    #[test]
834    fn null_values() {
835        assert_schema_json_err_deprecated_and_standard(
836            json!({
837                "ns": {
838                    "entityTypes": { },
839                    "actions": {
840                        "act": {
841                            "appliesTo": {
842                                "principalTypes": [],
843                                "resourceTypes": null
844                            }
845                        },
846                    },
847                }
848            }),
849            &ExpectedErrorMessageBuilder::error("invalid type: null, expected a sequence").build(),
850            &ExpectedErrorMessageBuilder::error("missing field `resourceTypes`").build(),
851        );
852        assert_schema_json_err_deprecated_and_standard(
853            json!({
854                "ns": {
855                    "entityTypes": { },
856                    "actions": {
857                        "act": {
858                            "appliesTo": {
859                                "principalTypes": null,
860                                "resourceTypes": [],
861                            }
862                        },
863                    },
864                }
865            }),
866            &ExpectedErrorMessageBuilder::error("invalid type: null, expected a sequence").build(),
867            &ExpectedErrorMessageBuilder::error("missing field `principalTypes`").build(),
868        );
869    }
870}
871
872#[cfg(test)]
873mod invalid_names_detected {
874    use cedar_policy_core::test_utils::ExpectedErrorMessageBuilder;
875    use serde_json::json;
876
877    use super::test_utils::*;
878
879    #[test]
880    fn in_namespace() {
881        assert_schema_json_err_deprecated_and_standard(
882            json!({
883                " space": {
884                    "entityTypes": { },
885                    "actions": { },
886                }
887            }),
888            &ExpectedErrorMessageBuilder::error("invalid namespace ` space`: `Name` needs to be normalized (e.g., whitespace removed):  space").build(),
889            &ExpectedErrorMessageBuilder::error("invalid namespace ` space`: `Name` needs to be normalized (e.g., whitespace removed):  space").build(),
890        );
891    }
892
893    #[test]
894    fn in_common_type() {
895        assert_schema_json_err_deprecated_and_standard(
896            json!({
897                "ns": {
898                    "commonTypes": {
899                        " space": {"type": "Long"}
900                    },
901                    "entityTypes": { },
902                    "actions": { },
903                }
904            }),
905            &ExpectedErrorMessageBuilder::error("invalid id ` space`: `Id` needs to be normalized (e.g., whitespace removed):  space").build(),
906            &ExpectedErrorMessageBuilder::error("invalid id ` space`: `Id` needs to be normalized (e.g., whitespace removed):  space").build(),
907        );
908    }
909
910    #[test]
911    fn in_common_type_ref() {
912        assert_schema_json_err_deprecated_and_standard(
913            json!({
914                "ns": {
915                    "entityTypes": {
916                        "User": {
917                            "shape": {
918                                "type": "Record",
919                                "attributes": {
920                                    "foo": {"type": " space"}
921                                }
922                            }
923                        }
924                    },
925                    "actions": { },
926                }
927            }),
928            &ExpectedErrorMessageBuilder::error("invalid common type ` space`: `internal name` needs to be normalized (e.g., whitespace removed):  space").build(),
929            &ExpectedErrorMessageBuilder::error("invalid common type ` space`: `internal name` needs to be normalized (e.g., whitespace removed):  space").build(),
930        );
931    }
932
933    #[test]
934    fn reserved_common_type() {
935        assert_schema_json_err_deprecated_and_standard(
936            json!({
937                "ns": {
938                    "commonTypes": {
939                        "Long": {"type": "Long"}
940                    },
941                    "entityTypes": { },
942                    "actions": { },
943                }
944            }),
945            &ExpectedErrorMessageBuilder::error(
946                "this is reserved and cannot be the basename of a common-type declaration: Long",
947            )
948            .build(),
949            &ExpectedErrorMessageBuilder::error(
950                "this is reserved and cannot be the basename of a common-type declaration: Long",
951            )
952            .build(),
953        );
954    }
955
956    #[test]
957    fn in_entity_type() {
958        assert_schema_json_err_deprecated_and_standard(
959            json!({
960                "ns": {
961                    "entityTypes": {
962                        " space": { }
963                    },
964                    "actions": { },
965                }
966            }),
967            &ExpectedErrorMessageBuilder::error("invalid id ` space`: `Id` needs to be normalized (e.g., whitespace removed):  space").build(),
968            &ExpectedErrorMessageBuilder::error("invalid id ` space`: `Id` needs to be normalized (e.g., whitespace removed):  space").build(),
969        );
970    }
971
972    #[test]
973    fn in_member_of_types() {
974        assert_schema_json_err_deprecated_and_standard(
975            json!({
976                "ns": {
977                    "entityTypes": {
978                        "User": {
979                            "memberOfTypes": [" User"]
980                        }
981                    },
982                    "actions": { },
983                }
984            }),
985            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
986            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
987        );
988    }
989
990    #[test]
991    fn in_entity_type_ref() {
992        assert_schema_json_err_deprecated_and_standard(
993            json!({
994                "ns": {
995                    "entityTypes": {
996                        "User": {
997                            "shape": {
998                                "type": "Record",
999                                "attributes": {
1000                                    "foo": {"type": "Entity", "name": " space"}
1001                                }
1002                            }
1003                        }
1004                    },
1005                    "actions": { },
1006                }
1007            }),
1008            &ExpectedErrorMessageBuilder::error("invalid entity type ` space`: `internal name` needs to be normalized (e.g., whitespace removed):  space").build(),
1009            &ExpectedErrorMessageBuilder::error("invalid entity type ` space`: `internal name` needs to be normalized (e.g., whitespace removed):  space").build(),
1010        );
1011    }
1012
1013    #[test]
1014    fn in_extension_type() {
1015        assert_schema_json_err_deprecated_and_standard(
1016            json!({
1017                "ns": {
1018                    "entityTypes": {
1019                        "User": {
1020                            "shape": {
1021                                "type": "Record",
1022                                "attributes": {
1023                                    "foo": {"type": "Extension", "name": " ip"}
1024                                }
1025                            }
1026                        }
1027                    },
1028                    "actions": { },
1029                }
1030            }),
1031            &ExpectedErrorMessageBuilder::error("invalid extension type ` ip`: `Unreserved Id` needs to be normalized (e.g., whitespace removed):  ip").build(),
1032            &ExpectedErrorMessageBuilder::error("invalid extension type ` ip`: `Unreserved Id` needs to be normalized (e.g., whitespace removed):  ip").build(),
1033        );
1034    }
1035
1036    #[test]
1037    fn in_applies_to_principals() {
1038        assert_schema_json_err_deprecated_and_standard(
1039            json!({
1040                "ns": {
1041                    "entityTypes": { },
1042                    "actions": {
1043                        "act": {
1044                            "appliesTo": {
1045                                "principalTypes": [" User"],
1046                                "resourceTypes": [],
1047                            }
1048                        }
1049                    },
1050                }
1051            }),
1052            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
1053            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
1054        );
1055    }
1056
1057    #[test]
1058    fn in_applies_to_resources() {
1059        assert_schema_json_err_deprecated_and_standard(
1060            json!({
1061                "ns": {
1062                    "entityTypes": { },
1063                    "actions": {
1064                        "act": {
1065                            "appliesTo": {
1066                                "principalTypes": [],
1067                                "resourceTypes": [" User"],
1068                            }
1069                        }
1070                    },
1071                }
1072            }),
1073            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
1074            &ExpectedErrorMessageBuilder::error("invalid name ` User`: `internal name` needs to be normalized (e.g., whitespace removed):  User").build(),
1075        );
1076    }
1077
1078    #[test]
1079    fn in_member_of() {
1080        assert_schema_json_err_deprecated_and_standard(
1081            json!({
1082                "ns": {
1083                    "entityTypes": { },
1084                    "actions": {
1085                        "other": {},
1086                        "act": {
1087                            "memberOf": [{"type": " Action", "id": "other"}],
1088                        }
1089                    },
1090                }
1091            }),
1092            &ExpectedErrorMessageBuilder::error("invalid name ` Action`: `internal name` needs to be normalized (e.g., whitespace removed):  Action").build(),
1093            &ExpectedErrorMessageBuilder::error("invalid name ` Action`: `internal name` needs to be normalized (e.g., whitespace removed):  Action").build(),
1094        );
1095    }
1096}
1097
1098#[cfg(test)]
1099mod from_str_parse_err {
1100
1101    use miette::Report;
1102
1103    use crate::{Schema, SchemaFragment};
1104    use cedar_policy_core::test_utils::{expect_err, ExpectedErrorMessageBuilder};
1105
1106    #[test]
1107    #[allow(deprecated)]
1108    fn from_cedar_schema_str_err() {
1109        let src = "entity User;";
1110        expect_err(
1111            src,
1112            &Report::new(Schema::from_deprecated_json_str(src).unwrap_err()),
1113            &ExpectedErrorMessageBuilder::error("expected value at line 1 column 1").help("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?").build(),
1114        );
1115        expect_err(
1116            src,
1117            &Report::new(SchemaFragment::from_deprecated_json_str(src).unwrap_err()),
1118            &ExpectedErrorMessageBuilder::error("expected value at line 1 column 1").help("this API was expecting a schema in the JSON format; did you mean to use a different function, which expects the Cedar schema format?").build(),
1119        );
1120    }
1121}