Skip to content

Commit a57c28d

Browse files
Aaron Elinejohn-h-kastner-aws
andauthored
Adding read/write json methods to entity (#924)
Signed-off-by: Aaron Eline <[email protected]> Co-authored-by: John Kastner <[email protected]>
1 parent 10e1b27 commit a57c28d

File tree

6 files changed

+596
-1
lines changed

6 files changed

+596
-1
lines changed

cedar-policy-core/src/ast/entity.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
use crate::ast::*;
18+
use crate::entities::{err::EntitiesError, json::err::JsonSerializationError, EntityJson};
1819
use crate::evaluator::{EvaluationError, RestrictedEvaluator};
1920
use crate::extensions::Extensions;
2021
use crate::parser::err::ParseErrors;
@@ -445,6 +446,27 @@ impl Entity {
445446
ancestors,
446447
)
447448
}
449+
450+
/// Write the entity to a json document
451+
pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
452+
let ejson = EntityJson::from_entity(self)?;
453+
serde_json::to_writer_pretty(f, &ejson).map_err(JsonSerializationError::from)?;
454+
Ok(())
455+
}
456+
457+
/// write the entity to a json value
458+
pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
459+
let ejson = EntityJson::from_entity(self)?;
460+
let v = serde_json::to_value(ejson).map_err(JsonSerializationError::from)?;
461+
Ok(v)
462+
}
463+
464+
/// write the entity to a json string
465+
pub fn to_json_string(&self) -> Result<String, EntitiesError> {
466+
let ejson = EntityJson::from_entity(self)?;
467+
let string = serde_json::to_string(&ejson).map_err(JsonSerializationError::from)?;
468+
Ok(string)
469+
}
448470
}
449471

450472
impl PartialEq for Entity {

cedar-policy-core/src/entities/json/entities.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use super::{
2222
use crate::ast::{
2323
BorrowedRestrictedExpr, Entity, EntityType, EntityUID, PartialValue, RestrictedExpr,
2424
};
25+
use crate::entities::conformance::EntitySchemaConformanceChecker;
2526
use crate::entities::{
2627
conformance::err::{EntitySchemaConformanceError, UnexpectedEntityTypeError},
2728
schematype_of_partialvalue, Entities, EntitiesError, GetSchemaTypeError, TCComputation,
@@ -31,8 +32,8 @@ use crate::jsonvalue::JsonValueWithNoDuplicateKeys;
3132
use serde::{Deserialize, Serialize};
3233
use serde_with::serde_as;
3334
use smol_str::SmolStr;
34-
use std::collections::HashMap;
3535
use std::sync::Arc;
36+
use std::{collections::HashMap, io::Read};
3637

3738
#[cfg(feature = "wasm")]
3839
extern crate tsify;
@@ -78,6 +79,7 @@ pub struct EntityJsonParser<'e, 's, S: Schema = NoEntitiesSchema> {
7879
}
7980

8081
/// Schema information about a single entity can take one of these forms:
82+
#[derive(Debug)]
8183
enum EntitySchemaInfo<E: EntityTypeDescription> {
8284
/// There is no schema, i.e. we're not doing schema-based parsing
8385
NoSchema,
@@ -212,6 +214,39 @@ impl<'e, 's, S: Schema> EntityJsonParser<'e, 's, S> {
212214
Ok(entities.into_iter())
213215
}
214216

217+
/// Parse a single entity from an in-memory JSON value
218+
pub fn single_from_json_value(
219+
&self,
220+
value: serde_json::Value,
221+
) -> Result<Entity, EntitiesError> {
222+
let ejson = serde_json::from_value(value).map_err(JsonDeserializationError::from)?;
223+
self.single_from_ejson(ejson)
224+
}
225+
226+
/// Parse a single entity from a JSON string
227+
pub fn single_from_json_str(&self, src: impl AsRef<str>) -> Result<Entity, EntitiesError> {
228+
let ejson = serde_json::from_str(src.as_ref()).map_err(JsonDeserializationError::from)?;
229+
self.single_from_ejson(ejson)
230+
}
231+
232+
/// Parse a single entity from a JSON reader
233+
pub fn single_from_json_file(&self, r: impl Read) -> Result<Entity, EntitiesError> {
234+
let ejson = serde_json::from_reader(r).map_err(JsonDeserializationError::from)?;
235+
self.single_from_ejson(ejson)
236+
}
237+
238+
fn single_from_ejson(&self, ejson: EntityJson) -> Result<Entity, EntitiesError> {
239+
let entity = self.parse_ejson(ejson)?;
240+
match self.schema {
241+
None => Ok(entity),
242+
Some(schema) => {
243+
let checker = EntitySchemaConformanceChecker::new(schema, self.extensions);
244+
checker.validate_entity(&entity)?;
245+
Ok(entity)
246+
}
247+
}
248+
}
249+
215250
/// Internal function that creates an [`Entities`] from a stream of [`EntityJson`].
216251
///
217252
/// If the `EntityJsonParser` has a `schema`, this also adds `Action`

cedar-policy-validator/src/coreschema.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ impl<'a> entities::Schema for CoreSchema<'a> {
8282
}
8383

8484
/// Struct which carries enough information that it can impl Core's `EntityTypeDescription`
85+
#[derive(Debug)]
8586
pub struct EntityTypeDescription {
8687
/// Core `EntityType` this is describing
8788
core_type: ast::EntityType,

cedar-policy/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- JSON representation for Policy Sets, along with methods like
1212
`::from_json_value/file/str` and `::to_json` for `PolicySet`. (#783,
1313
resolving #549)
14+
- Added methods for reading and writing individual `Entity`s as JSON
15+
(resolving #807)
1416

1517
### Changed
1618

cedar-policy/src/api.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ use miette::Diagnostic;
4949
use ref_cast::RefCast;
5050
use smol_str::SmolStr;
5151
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
52+
use std::io::Read;
5253
use std::str::FromStr;
5354

5455
/// Extended functionality for `Entities` struct
@@ -260,6 +261,81 @@ impl Entity {
260261
ancestors.into_iter().map(EntityUid::new).collect(),
261262
)
262263
}
264+
265+
/// Parse an entity from an in-memory JSON value
266+
/// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
267+
pub fn from_json_value(
268+
value: serde_json::Value,
269+
schema: Option<&Schema>,
270+
) -> Result<Self, EntitiesError> {
271+
let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
272+
let eparser = cedar_policy_core::entities::EntityJsonParser::new(
273+
schema.as_ref(),
274+
Extensions::all_available(),
275+
cedar_policy_core::entities::TCComputation::ComputeNow,
276+
);
277+
eparser.single_from_json_value(value).map(Self)
278+
}
279+
280+
/// Parse an entity from a JSON string
281+
/// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
282+
pub fn from_json_str(
283+
src: impl AsRef<str>,
284+
schema: Option<&Schema>,
285+
) -> Result<Self, EntitiesError> {
286+
let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
287+
let eparser = cedar_policy_core::entities::EntityJsonParser::new(
288+
schema.as_ref(),
289+
Extensions::all_available(),
290+
cedar_policy_core::entities::TCComputation::ComputeNow,
291+
);
292+
eparser.single_from_json_str(src).map(Self)
293+
}
294+
295+
/// Parse an entity from a JSON reader
296+
/// If a schema is provided, it is handled identically to [`Entities::from_json_str`]
297+
pub fn from_json_file(f: impl Read, schema: Option<&Schema>) -> Result<Self, EntitiesError> {
298+
let schema = schema.map(|s| cedar_policy_validator::CoreSchema::new(&s.0));
299+
let eparser = cedar_policy_core::entities::EntityJsonParser::new(
300+
schema.as_ref(),
301+
Extensions::all_available(),
302+
cedar_policy_core::entities::TCComputation::ComputeNow,
303+
);
304+
eparser.single_from_json_file(f).map(Self)
305+
}
306+
307+
/// Dump an `Entity` object into an entity JSON file.
308+
///
309+
/// The resulting JSON will be suitable for parsing in via
310+
/// `from_json_*`, and will be parse-able even with no [`Schema`].
311+
///
312+
/// To read an `Entity` object from JSON , use
313+
/// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
314+
pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
315+
self.0.write_to_json(f)
316+
}
317+
318+
/// Dump an `Entity` object into an in-memory JSON object.
319+
///
320+
/// The resulting JSON will be suitable for parsing in via
321+
/// `from_json_*`, and will be parse-able even with no `Schema`.
322+
///
323+
/// To read an `Entity` object from JSON , use
324+
/// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
325+
pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
326+
self.0.to_json_value()
327+
}
328+
329+
/// Dump an `Entity` object into a JSON string.
330+
///
331+
/// The resulting JSON will be suitable for parsing in via
332+
/// `from_json_*`, and will be parse-able even with no `Schema`.
333+
///
334+
/// To read an `Entity` object from JSON , use
335+
/// [`Self::from_json_file`], [`Self::from_json_value`], or [`Self::from_json_str`].
336+
pub fn to_json_string(&self) -> Result<String, EntitiesError> {
337+
self.0.to_json_string()
338+
}
263339
}
264340

265341
impl std::fmt::Display for Entity {

0 commit comments

Comments
 (0)