cedar_policy_core/entities/json/
entities.rs1use super::{
18 err::{JsonDeserializationError, JsonDeserializationErrorContext, JsonSerializationError},
19 CedarValueJson, EntityTypeDescription, EntityUidJson, NoEntitiesSchema, Schema, TypeAndId,
20 ValueParser,
21};
22use crate::ast::{BorrowedRestrictedExpr, Entity, EntityUID, PartialValue, RestrictedExpr};
23use crate::entities::conformance::EntitySchemaConformanceChecker;
24use crate::entities::{
25 conformance::err::{EntitySchemaConformanceError, UnexpectedEntityTypeError},
26 Entities, EntitiesError, TCComputation,
27};
28use crate::extensions::Extensions;
29use crate::jsonvalue::JsonValueWithNoDuplicateKeys;
30use serde::{Deserialize, Serialize};
31use serde_with::serde_as;
32use smol_str::SmolStr;
33use std::sync::Arc;
34use std::{collections::HashMap, io::Read};
35
36#[cfg(feature = "wasm")]
37extern crate tsify;
38
39#[serde_as]
41#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
42#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
43#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
44pub struct EntityJson {
45 uid: EntityUidJson,
47 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
54 #[cfg_attr(feature = "wasm", tsify(type = "Record<string, CedarValueJson>"))]
55 attrs: HashMap<SmolStr, JsonValueWithNoDuplicateKeys>,
57 parents: Vec<EntityUidJson>,
59}
60
61#[derive(Debug, Clone)]
63pub struct EntityJsonParser<'e, 's, S: Schema = NoEntitiesSchema> {
64 schema: Option<&'s S>,
70
71 extensions: &'e Extensions<'e>,
73
74 tc_computation: TCComputation,
77}
78
79#[derive(Debug)]
81enum EntitySchemaInfo<E: EntityTypeDescription> {
82 NoSchema,
86 NonAction(E),
89}
90
91impl<'e, 's, S: Schema> EntityJsonParser<'e, 's, S> {
92 pub fn new(
112 schema: Option<&'s S>,
113 extensions: &'e Extensions<'e>,
114 tc_computation: TCComputation,
115 ) -> Self {
116 Self {
117 schema,
118 extensions,
119 tc_computation,
120 }
121 }
122
123 pub fn from_json_str(&self, json: &str) -> Result<Entities, EntitiesError> {
128 let ejsons: Vec<EntityJson> =
129 serde_json::from_str(json).map_err(JsonDeserializationError::from)?;
130 self.parse_ejsons(ejsons)
131 }
132
133 pub fn from_json_value(&self, json: serde_json::Value) -> Result<Entities, EntitiesError> {
138 let ejsons: Vec<EntityJson> =
139 serde_json::from_value(json).map_err(JsonDeserializationError::from)?;
140 self.parse_ejsons(ejsons)
141 }
142
143 pub fn from_json_file(&self, json: impl std::io::Read) -> Result<Entities, EntitiesError> {
148 let ejsons: Vec<EntityJson> =
149 serde_json::from_reader(json).map_err(JsonDeserializationError::from)?;
150 self.parse_ejsons(ejsons)
151 }
152
153 pub fn iter_from_json_str(
158 &self,
159 json: &str,
160 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
161 let ejsons: Vec<EntityJson> =
162 serde_json::from_str(json).map_err(JsonDeserializationError::from)?;
163 self.iter_ejson_to_iter_entity(ejsons)
164 }
165
166 pub fn iter_from_json_value(
171 &self,
172 json: serde_json::Value,
173 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
174 let ejsons: Vec<EntityJson> =
175 serde_json::from_value(json).map_err(JsonDeserializationError::from)?;
176 self.iter_ejson_to_iter_entity(ejsons)
177 }
178
179 pub fn iter_from_json_file(
184 &self,
185 json: impl std::io::Read,
186 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
187 let ejsons: Vec<EntityJson> =
188 serde_json::from_reader(json).map_err(JsonDeserializationError::from)?;
189 self.iter_ejson_to_iter_entity(ejsons)
190 }
191
192 fn iter_ejson_to_iter_entity(
196 &self,
197 ejsons: impl IntoIterator<Item = EntityJson>,
198 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
199 let mut entities: Vec<Entity> = ejsons
200 .into_iter()
201 .map(|ejson| self.parse_ejson(ejson).map_err(EntitiesError::from))
202 .collect::<Result<_, _>>()?;
203 if let Some(schema) = &self.schema {
204 entities.extend(
205 schema
206 .action_entities()
207 .into_iter()
208 .map(Arc::unwrap_or_clone),
209 );
210 }
211 Ok(entities.into_iter())
212 }
213
214 pub fn single_from_json_value(
216 &self,
217 value: serde_json::Value,
218 ) -> Result<Entity, EntitiesError> {
219 let ejson = serde_json::from_value(value).map_err(JsonDeserializationError::from)?;
220 self.single_from_ejson(ejson)
221 }
222
223 pub fn single_from_json_str(&self, src: impl AsRef<str>) -> Result<Entity, EntitiesError> {
225 let ejson = serde_json::from_str(src.as_ref()).map_err(JsonDeserializationError::from)?;
226 self.single_from_ejson(ejson)
227 }
228
229 pub fn single_from_json_file(&self, r: impl Read) -> Result<Entity, EntitiesError> {
231 let ejson = serde_json::from_reader(r).map_err(JsonDeserializationError::from)?;
232 self.single_from_ejson(ejson)
233 }
234
235 fn single_from_ejson(&self, ejson: EntityJson) -> Result<Entity, EntitiesError> {
236 let entity = self.parse_ejson(ejson)?;
237 match self.schema {
238 None => Ok(entity),
239 Some(schema) => {
240 let checker = EntitySchemaConformanceChecker::new(schema, self.extensions);
241 checker.validate_entity(&entity)?;
242 Ok(entity)
243 }
244 }
245 }
246
247 fn parse_ejsons(
253 &self,
254 ejsons: impl IntoIterator<Item = EntityJson>,
255 ) -> Result<Entities, EntitiesError> {
256 let entities: Vec<Entity> = ejsons
257 .into_iter()
258 .map(|ejson| self.parse_ejson(ejson))
259 .collect::<Result<_, _>>()?;
260 Entities::from_entities(entities, self.schema, self.tc_computation, self.extensions)
261 }
262
263 fn parse_ejson(&self, ejson: EntityJson) -> Result<Entity, JsonDeserializationError> {
268 let uid = ejson
269 .uid
270 .into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
271 let etype = uid.entity_type();
272 let entity_schema_info = match &self.schema {
273 None => EntitySchemaInfo::NoSchema,
274 Some(schema) => {
275 if etype.is_action() {
276 EntitySchemaInfo::NoSchema
278 } else {
279 EntitySchemaInfo::NonAction(schema.entity_type(etype).ok_or_else(|| {
280 let suggested_types = schema
281 .entity_types_with_basename(&etype.name().basename())
282 .collect();
283 JsonDeserializationError::EntitySchemaConformance(
284 UnexpectedEntityTypeError {
285 uid: uid.clone(),
286 suggested_types,
287 }
288 .into(),
289 )
290 })?)
291 }
292 }
293 };
294 let vparser = ValueParser::new(self.extensions);
295 let attrs: HashMap<SmolStr, RestrictedExpr> = ejson
296 .attrs
297 .into_iter()
298 .map(|(k, v)| match &entity_schema_info {
299 EntitySchemaInfo::NoSchema => Ok((
300 k.clone(),
301 vparser.val_into_restricted_expr(v.into(), None, || {
302 JsonDeserializationErrorContext::EntityAttribute {
303 uid: uid.clone(),
304 attr: k.clone(),
305 }
306 })?,
307 )),
308 EntitySchemaInfo::NonAction(desc) => {
309 let rexpr = match desc.attr_type(&k) {
312 None => {
315 if desc.open_attributes() {
316 vparser.val_into_restricted_expr(v.into(), None, || {
317 JsonDeserializationErrorContext::EntityAttribute {
318 uid: uid.clone(),
319 attr: k.clone(),
320 }
321 })?
322 } else {
323 return Err(JsonDeserializationError::EntitySchemaConformance(
324 EntitySchemaConformanceError::unexpected_entity_attr(
325 uid.clone(),
326 k,
327 ),
328 ));
329 }
330 }
331 Some(expected_ty) => vparser.val_into_restricted_expr(
332 v.into(),
333 Some(&expected_ty),
334 || JsonDeserializationErrorContext::EntityAttribute {
335 uid: uid.clone(),
336 attr: k.clone(),
337 },
338 )?,
339 };
340 Ok((k.clone(), rexpr))
341 }
342 })
343 .collect::<Result<_, JsonDeserializationError>>()?;
344 let is_parent_allowed = |parent_euid: &EntityUID| {
345 if etype.is_action() {
349 if parent_euid.is_action() {
350 Ok(())
351 } else {
352 Err(JsonDeserializationError::action_parent_is_not_action(
353 uid.clone(),
354 parent_euid.clone(),
355 ))
356 }
357 } else {
358 Ok(()) }
360 };
361 let parents = ejson
362 .parents
363 .into_iter()
364 .map(|parent| {
365 parent.into_euid(|| JsonDeserializationErrorContext::EntityParents {
366 uid: uid.clone(),
367 })
368 })
369 .map(|res| {
370 res.and_then(|parent_euid| {
371 is_parent_allowed(&parent_euid)?;
372 Ok(parent_euid)
373 })
374 })
375 .collect::<Result<_, JsonDeserializationError>>()?;
376 Ok(Entity::new(uid, attrs, parents, self.extensions)?)
377 }
378}
379
380impl EntityJson {
381 pub fn from_entity(entity: &Entity) -> Result<Self, JsonSerializationError> {
385 Ok(Self {
386 uid: EntityUidJson::ImplicitEntityEscape(TypeAndId::from(entity.uid())),
388 attrs: entity
389 .attrs()
390 .map(|(k, pvalue)| match pvalue {
391 PartialValue::Value(value) => {
392 let cedarvaluejson = CedarValueJson::from_value(value.clone())?;
393 Ok((k.clone(), serde_json::to_value(cedarvaluejson)?.into()))
394 }
395 PartialValue::Residual(expr) => match BorrowedRestrictedExpr::new(expr) {
396 Ok(expr) => {
397 let cedarvaluejson = CedarValueJson::from_expr(expr)?;
398 Ok((k.clone(), serde_json::to_value(cedarvaluejson)?.into()))
399 }
400 Err(_) => Err(JsonSerializationError::residual(expr.clone())),
401 },
402 })
403 .collect::<Result<_, JsonSerializationError>>()?,
404 parents: entity
405 .ancestors()
406 .map(|euid| EntityUidJson::ImplicitEntityEscape(TypeAndId::from(euid.clone())))
407 .collect(),
408 })
409 }
410}
411
412#[allow(clippy::panic)]
414#[cfg(test)]
415mod test {
416 use super::*;
417 use cool_asserts::assert_matches;
418
419 #[test]
420 fn reject_duplicates() {
421 let json = serde_json::json!([
422 {
423 "uid" : {
424 "type" : "User",
425 "id" : "alice"
426 },
427 "attrs" : {},
428 "parents": []
429 },
430 {
431 "uid" : {
432 "type" : "User",
433 "id" : "alice"
434 },
435 "attrs" : {},
436 "parents": []
437 }
438 ]);
439 let eparser: EntityJsonParser<'_, '_, NoEntitiesSchema> =
440 EntityJsonParser::new(None, Extensions::all_available(), TCComputation::ComputeNow);
441 let e = eparser.from_json_value(json);
442 let bad_euid: EntityUID = r#"User::"alice""#.parse().unwrap();
443 assert_matches!(e, Err(EntitiesError::Duplicate(euid)) => {
444 assert_eq!(&bad_euid, euid.euid(), r#"Returned euid should be User::"alice""#);
445 });
446 }
447
448 #[test]
449 fn simple() {
450 let test = serde_json::json!({
451 "uid" : { "type" : "A", "id" : "b" },
452 "attrs" : {},
453 "parents" : []
454 });
455 let x: Result<EntityJson, _> = serde_json::from_value(test);
456 x.unwrap();
457 }
458}