Skip to main content

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