1use 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 #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
60 #[serde(default)]
61 #[serde(skip_serializing_if = "HashMap::is_empty")]
62 #[cfg_attr(feature = "wasm", tsify(type = "Record<string, CedarValueJson>"))]
63 tags: HashMap<SmolStr, JsonValueWithNoDuplicateKeys>,
65}
66
67#[derive(Debug, Clone)]
69pub struct EntityJsonParser<'e, 's, S: Schema = NoEntitiesSchema> {
70 schema: Option<&'s S>,
76
77 extensions: &'e Extensions<'e>,
79
80 tc_computation: TCComputation,
83}
84
85#[derive(Debug)]
87enum EntitySchemaInfo<E: EntityTypeDescription> {
88 NoSchema,
92 NonAction(E),
95}
96
97impl<'e, 's, S: Schema> EntityJsonParser<'e, 's, S> {
98 pub fn new(
118 schema: Option<&'s S>,
119 extensions: &'e Extensions<'e>,
120 tc_computation: TCComputation,
121 ) -> Self {
122 Self {
123 schema,
124 extensions,
125 tc_computation,
126 }
127 }
128
129 pub fn from_json_str(&self, json: &str) -> Result<Entities, EntitiesError> {
134 let ejsons: Vec<EntityJson> =
135 serde_json::from_str(json).map_err(JsonDeserializationError::from)?;
136 self.parse_ejsons(ejsons)
137 }
138
139 pub fn from_json_value(&self, json: serde_json::Value) -> Result<Entities, EntitiesError> {
144 let ejsons: Vec<EntityJson> =
145 serde_json::from_value(json).map_err(JsonDeserializationError::from)?;
146 self.parse_ejsons(ejsons)
147 }
148
149 pub fn from_json_file(&self, json: impl std::io::Read) -> Result<Entities, EntitiesError> {
154 let ejsons: Vec<EntityJson> =
155 serde_json::from_reader(json).map_err(JsonDeserializationError::from)?;
156 self.parse_ejsons(ejsons)
157 }
158
159 pub fn iter_from_json_str(
164 &self,
165 json: &str,
166 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
167 let ejsons: Vec<EntityJson> =
168 serde_json::from_str(json).map_err(JsonDeserializationError::from)?;
169 self.iter_ejson_to_iter_entity(ejsons)
170 }
171
172 pub fn iter_from_json_value(
177 &self,
178 json: serde_json::Value,
179 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
180 let ejsons: Vec<EntityJson> =
181 serde_json::from_value(json).map_err(JsonDeserializationError::from)?;
182 self.iter_ejson_to_iter_entity(ejsons)
183 }
184
185 pub fn iter_from_json_file(
190 &self,
191 json: impl std::io::Read,
192 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
193 let ejsons: Vec<EntityJson> =
194 serde_json::from_reader(json).map_err(JsonDeserializationError::from)?;
195 self.iter_ejson_to_iter_entity(ejsons)
196 }
197
198 fn iter_ejson_to_iter_entity(
202 &self,
203 ejsons: impl IntoIterator<Item = EntityJson>,
204 ) -> Result<impl Iterator<Item = Entity> + '_, EntitiesError> {
205 let mut entities: Vec<Entity> = ejsons
206 .into_iter()
207 .map(|ejson| self.parse_ejson(ejson).map_err(EntitiesError::from))
208 .collect::<Result<_, _>>()?;
209 if let Some(schema) = &self.schema {
210 entities.extend(
211 schema
212 .action_entities()
213 .into_iter()
214 .map(Arc::unwrap_or_clone),
215 );
216 }
217 Ok(entities.into_iter())
218 }
219
220 pub fn single_from_json_value(
222 &self,
223 value: serde_json::Value,
224 ) -> Result<Entity, EntitiesError> {
225 let ejson = serde_json::from_value(value).map_err(JsonDeserializationError::from)?;
226 self.single_from_ejson(ejson)
227 }
228
229 pub fn single_from_json_str(&self, src: impl AsRef<str>) -> Result<Entity, EntitiesError> {
231 let ejson = serde_json::from_str(src.as_ref()).map_err(JsonDeserializationError::from)?;
232 self.single_from_ejson(ejson)
233 }
234
235 pub fn single_from_json_file(&self, r: impl Read) -> Result<Entity, EntitiesError> {
237 let ejson = serde_json::from_reader(r).map_err(JsonDeserializationError::from)?;
238 self.single_from_ejson(ejson)
239 }
240
241 fn single_from_ejson(&self, ejson: EntityJson) -> Result<Entity, EntitiesError> {
242 let entity = self.parse_ejson(ejson)?;
243 match self.schema {
244 None => Ok(entity),
245 Some(schema) => {
246 let checker = EntitySchemaConformanceChecker::new(schema, self.extensions);
247 checker.validate_entity(&entity)?;
248 Ok(entity)
249 }
250 }
251 }
252
253 fn parse_ejsons(
259 &self,
260 ejsons: impl IntoIterator<Item = EntityJson>,
261 ) -> Result<Entities, EntitiesError> {
262 let entities: Vec<Entity> = ejsons
263 .into_iter()
264 .map(|ejson| self.parse_ejson(ejson))
265 .collect::<Result<_, _>>()?;
266 Entities::from_entities(entities, self.schema, self.tc_computation, self.extensions)
267 }
268
269 fn parse_ejson(&self, ejson: EntityJson) -> Result<Entity, JsonDeserializationError> {
274 let uid = ejson
275 .uid
276 .into_euid(|| JsonDeserializationErrorContext::EntityUid)?;
277 let etype = uid.entity_type();
278 let entity_schema_info = match &self.schema {
279 None => EntitySchemaInfo::NoSchema,
280 Some(schema) => {
281 if etype.is_action() {
282 EntitySchemaInfo::NoSchema
284 } else {
285 EntitySchemaInfo::NonAction(schema.entity_type(etype).ok_or_else(|| {
286 let suggested_types = schema
287 .entity_types_with_basename(&etype.name().basename())
288 .collect();
289 JsonDeserializationError::EntitySchemaConformance(
290 UnexpectedEntityTypeError {
291 uid: uid.clone(),
292 suggested_types,
293 }
294 .into(),
295 )
296 })?)
297 }
298 }
299 };
300 let vparser = ValueParser::new(self.extensions);
301 let attrs: HashMap<SmolStr, RestrictedExpr> = ejson
302 .attrs
303 .into_iter()
304 .map(|(k, v)| match &entity_schema_info {
305 EntitySchemaInfo::NoSchema => Ok((
306 k.clone(),
307 vparser.val_into_restricted_expr(v.into(), None, || {
308 JsonDeserializationErrorContext::EntityAttribute {
309 uid: uid.clone(),
310 attr: k.clone(),
311 }
312 })?,
313 )),
314 EntitySchemaInfo::NonAction(desc) => {
315 let rexpr = match desc.attr_type(&k) {
318 None => {
321 if desc.open_attributes() {
322 vparser.val_into_restricted_expr(v.into(), None, || {
323 JsonDeserializationErrorContext::EntityAttribute {
324 uid: uid.clone(),
325 attr: k.clone(),
326 }
327 })?
328 } else {
329 return Err(JsonDeserializationError::EntitySchemaConformance(
330 EntitySchemaConformanceError::unexpected_entity_attr(
331 uid.clone(),
332 k,
333 ),
334 ));
335 }
336 }
337 Some(expected_ty) => vparser.val_into_restricted_expr(
338 v.into(),
339 Some(&expected_ty),
340 || JsonDeserializationErrorContext::EntityAttribute {
341 uid: uid.clone(),
342 attr: k.clone(),
343 },
344 )?,
345 };
346 Ok((k, rexpr))
347 }
348 })
349 .collect::<Result<_, JsonDeserializationError>>()?;
350 let tags: HashMap<SmolStr, RestrictedExpr> = ejson
351 .tags
352 .into_iter()
353 .map(|(k, v)| match &entity_schema_info {
354 EntitySchemaInfo::NoSchema => Ok((
355 k.clone(),
356 vparser.val_into_restricted_expr(v.into(), None, || {
357 JsonDeserializationErrorContext::EntityTag {
358 uid: uid.clone(),
359 tag: k.clone(),
360 }
361 })?,
362 )),
363 EntitySchemaInfo::NonAction(desc) => {
364 let rexpr = match desc.tag_type() {
367 None => {
370 return Err(JsonDeserializationError::EntitySchemaConformance(
371 EntitySchemaConformanceError::unexpected_entity_tag(uid.clone(), k),
372 ));
373 }
374 Some(expected_ty) => vparser.val_into_restricted_expr(
375 v.into(),
376 Some(&expected_ty),
377 || JsonDeserializationErrorContext::EntityTag {
378 uid: uid.clone(),
379 tag: k.clone(),
380 },
381 )?,
382 };
383 Ok((k, rexpr))
384 }
385 })
386 .collect::<Result<_, JsonDeserializationError>>()?;
387 let is_parent_allowed = |parent_euid: &EntityUID| {
388 if etype.is_action() {
392 if parent_euid.is_action() {
393 Ok(())
394 } else {
395 Err(JsonDeserializationError::action_parent_is_not_action(
396 uid.clone(),
397 parent_euid.clone(),
398 ))
399 }
400 } else {
401 Ok(()) }
403 };
404 let parents = ejson
405 .parents
406 .into_iter()
407 .map(|parent| {
408 parent.into_euid(|| JsonDeserializationErrorContext::EntityParents {
409 uid: uid.clone(),
410 })
411 })
412 .map(|res| {
413 res.and_then(|parent_euid| {
414 is_parent_allowed(&parent_euid)?;
415 Ok(parent_euid)
416 })
417 })
418 .collect::<Result<_, JsonDeserializationError>>()?;
419 Ok(Entity::new(uid, attrs, parents, tags, self.extensions)?)
420 }
421}
422
423impl EntityJson {
424 pub fn from_entity(entity: &Entity) -> Result<Self, JsonSerializationError> {
428 let serialize_kpvalue = |(k, pvalue): (&SmolStr, &PartialValue)| -> Result<_, _> {
429 match pvalue {
430 PartialValue::Value(value) => {
431 let cedarvaluejson = CedarValueJson::from_value(value.clone())?;
432 Ok((k.clone(), serde_json::to_value(cedarvaluejson)?.into()))
433 }
434 PartialValue::Residual(expr) => match BorrowedRestrictedExpr::new(expr) {
435 Ok(expr) => {
436 let cedarvaluejson = CedarValueJson::from_expr(expr)?;
437 Ok((k.clone(), serde_json::to_value(cedarvaluejson)?.into()))
438 }
439 Err(_) => Err(JsonSerializationError::residual(expr.clone())),
440 },
441 }
442 };
443 Ok(Self {
444 uid: EntityUidJson::ImplicitEntityEscape(TypeAndId::from(entity.uid())),
446 attrs: entity
447 .attrs()
448 .map(serialize_kpvalue)
449 .collect::<Result<_, JsonSerializationError>>()?,
450 parents: entity
451 .ancestors()
452 .map(|euid| EntityUidJson::ImplicitEntityEscape(TypeAndId::from(euid.clone())))
453 .collect(),
454 tags: entity
455 .tags()
456 .map(serialize_kpvalue)
457 .collect::<Result<_, JsonSerializationError>>()?,
458 })
459 }
460}
461
462#[allow(clippy::panic)]
464#[cfg(test)]
465mod test {
466 use super::*;
467 use cool_asserts::assert_matches;
468
469 #[test]
470 fn reject_duplicates() {
471 let json = serde_json::json!([
472 {
473 "uid" : {
474 "type" : "User",
475 "id" : "alice"
476 },
477 "attrs" : {},
478 "parents": []
479 },
480 {
481 "uid" : {
482 "type" : "User",
483 "id" : "alice"
484 },
485 "attrs" : {},
486 "parents": []
487 }
488 ]);
489 let eparser: EntityJsonParser<'_, '_, NoEntitiesSchema> =
490 EntityJsonParser::new(None, Extensions::all_available(), TCComputation::ComputeNow);
491 let e = eparser.from_json_value(json);
492 let bad_euid: EntityUID = r#"User::"alice""#.parse().unwrap();
493 assert_matches!(e, Err(EntitiesError::Duplicate(euid)) => {
494 assert_eq!(&bad_euid, euid.euid(), r#"Returned euid should be User::"alice""#);
495 });
496 }
497
498 #[test]
499 fn simple() {
500 let test = serde_json::json!({
501 "uid" : { "type" : "A", "id" : "b" },
502 "attrs" : {},
503 "parents" : []
504 });
505 let x: Result<EntityJson, _> = serde_json::from_value(test);
506 x.unwrap();
507 }
508}