Skip to content

Commit

Permalink
Add guards for create/update mutation
Browse files Browse the repository at this point in the history
* Add field/entity guards for create/update mutations

* Add corrensponding tests for guards
  • Loading branch information
YiNNx committed Nov 23, 2023
1 parent 9c43f72 commit 462ea23
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 5 deletions.
152 changes: 152 additions & 0 deletions examples/sqlite/tests/guard_mutation_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
use std::collections::BTreeMap;

use async_graphql::{dynamic::*, Response};
use sea_orm::{Database, DatabaseConnection};
use seaography::{Builder, BuilderContext, FnGuard, GuardsConfig};
use seaography_sqlite_example::entities::*;

lazy_static::lazy_static! {
static ref CONTEXT : BuilderContext = {
let context = BuilderContext::default();
let mut entity_guards: BTreeMap<String, FnGuard> = BTreeMap::new();
entity_guards.insert("FilmCategory".into(), Box::new(|_ctx| {
seaography::GuardAction::Block(None)
}));
let mut field_guards: BTreeMap<String, FnGuard> = BTreeMap::new();
field_guards.insert("Language.name".into(), Box::new(|_ctx| {
seaography::GuardAction::Block(None)
}));
BuilderContext {
guards: GuardsConfig {
entity_guards,
field_guards,
},
..context
}
};
}

pub fn schema(
database: DatabaseConnection,
depth: Option<usize>,
complexity: Option<usize>,
) -> Result<Schema, SchemaError> {
let mut builder = Builder::new(&CONTEXT, database.clone());
seaography::register_entities!(
builder,
[
actor,
address,
category,
city,
country,
customer,
film,
film_actor,
film_category,
film_text,
inventory,
language,
payment,
rental,
staff,
store,
]
);
let schema = builder.schema_builder();
let schema = if let Some(depth) = depth {
schema.limit_depth(depth)
} else {
schema
};
let schema = if let Some(complexity) = complexity {
schema.limit_complexity(complexity)
} else {
schema
};
schema.data(database).finish()
}

pub async fn get_schema() -> Schema {
let database = Database::connect("sqlite://sakila.db").await.unwrap();
let schema = schema(database, None, None).unwrap();

schema
}

pub fn assert_eq(a: Response, b: &str) {
assert_eq!(
a.data.into_json().unwrap(),
serde_json::from_str::<serde_json::Value>(b).unwrap()
)
}

#[tokio::test]
async fn entity_guard_mutation() {
let schema = get_schema().await;

assert_eq(
schema
.execute(
r#"
mutation LanguageUpdate {
languageUpdate(
data: { lastUpdate: "2030-01-01 11:11:11 UTC" }
filter: { languageId: { eq: 6 } }
) {
languageId
}
}
"#,
)
.await,
r#"
{
"languageUpdate": [
{
"languageId": 6
}
]
}
"#,
);

let response = schema
.execute(
r#"
mutation FilmCategoryUpdate {
filmCategoryUpdate(
data: { filmId: 1, categoryId: 1, lastUpdate: "2030-01-01 11:11:11 UTC" }
) {
filmId
}
}
"#,
)
.await;

assert_eq!(response.errors.len(), 1);

assert_eq!(response.errors[0].message, "Entity guard triggered.");
}

#[tokio::test]
async fn field_guard_mutation() {
let schema = get_schema().await;

let response = schema
.execute(
r#"
mutation LanguageUpdate {
languageUpdate(data: { name: "Cantonese" }, filter: { languageId: { eq: 6 } }) {
languageId
}
}
"#,
)
.await;

assert_eq!(response.errors.len(), 1);

assert_eq!(response.errors[0].message, "Field guard triggered.");
}
51 changes: 48 additions & 3 deletions src/mutation/entity_create_batch_mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use sea_orm::{

use crate::{
prepare_active_model, BuilderContext, EntityInputBuilder, EntityObjectBuilder,
EntityQueryFieldBuilder,
EntityQueryFieldBuilder, GuardAction,
};

/// The configuration structure of EntityCreateBatchMutationBuilder
Expand Down Expand Up @@ -64,29 +64,74 @@ impl EntityCreateBatchMutationBuilder {

let context = self.context;

let object_name: String = entity_object_builder.type_name::<T>();
let guard = self.context.guards.entity_guards.get(&object_name);
let field_guards = &self.context.guards.field_guards;

Field::new(
self.type_name::<T>(),
TypeRef::named_nn_list_nn(entity_object_builder.basic_type_name::<T>()),
move |ctx| {
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};

if let GuardAction::Block(reason) = guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Entity guard triggered."),
),
};
}

let db = ctx.data::<DatabaseConnection>()?;
let transaction = db.begin().await?;

let entity_input_builder = EntityInputBuilder { context };
let entity_object_builder = EntityObjectBuilder { context };

let mut results: Vec<_> = Vec::new();
for input_object in ctx
for input in ctx
.args
.get(&context.entity_create_batch_mutation.data_field)
.unwrap()
.list()?
.iter()
{
let input_object = &input.object()?;
for (column, _) in input_object.iter() {
let field_guard = field_guards.get(&format!(
"{}.{}",
entity_object_builder.type_name::<T>(),
column
));
let field_guard_flag = if let Some(field_guard) = field_guard {
(*field_guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = field_guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Field guard triggered."),
),
};
}
}

let active_model = prepare_active_model::<T, A>(
&entity_input_builder,
&entity_object_builder,
&(input_object.object()?),
input_object,
)?;
let result = active_model.insert(&transaction).await?;
results.push(result);
Expand Down
48 changes: 47 additions & 1 deletion src/mutation/entity_create_one_mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use sea_orm::{
PrimaryKeyToColumn, PrimaryKeyTrait,
};

use crate::{BuilderContext, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder};
use crate::{
BuilderContext, EntityInputBuilder, EntityObjectBuilder, EntityQueryFieldBuilder, GuardAction,
};

/// The configuration structure of EntityCreateOneMutationBuilder
pub struct EntityCreateOneMutationConfig {
Expand Down Expand Up @@ -62,11 +64,32 @@ impl EntityCreateOneMutationBuilder {

let context = self.context;

let object_name: String = entity_object_builder.type_name::<T>();
let guard = self.context.guards.entity_guards.get(&object_name);
let field_guards = &self.context.guards.field_guards;

Field::new(
self.type_name::<T>(),
TypeRef::named_nn(entity_object_builder.basic_type_name::<T>()),
move |ctx| {
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};

if let GuardAction::Block(reason) = guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Entity guard triggered."),
),
};
}

let entity_input_builder = EntityInputBuilder { context };
let entity_object_builder = EntityObjectBuilder { context };
let db = ctx.data::<DatabaseConnection>()?;
Expand All @@ -76,6 +99,29 @@ impl EntityCreateOneMutationBuilder {
.unwrap();
let input_object = &value_accessor.object()?;

for (column, _) in input_object.iter() {
let field_guard = field_guards.get(&format!(
"{}.{}",
entity_object_builder.type_name::<T>(),
column
));
let field_guard_flag = if let Some(field_guard) = field_guard {
(*field_guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = field_guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Field guard triggered."),
),
};
}
}

let active_model = prepare_active_model::<T, A>(
&entity_input_builder,
&entity_object_builder,
Expand Down
46 changes: 45 additions & 1 deletion src/mutation/entity_update_mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use sea_orm::{

use crate::{
get_filter_conditions, prepare_active_model, BuilderContext, EntityInputBuilder,
EntityObjectBuilder, EntityQueryFieldBuilder, FilterInputBuilder,
EntityObjectBuilder, EntityQueryFieldBuilder, FilterInputBuilder, GuardAction,
};

/// The configuration structure of EntityUpdateMutationBuilder
Expand Down Expand Up @@ -75,11 +75,31 @@ impl EntityUpdateMutationBuilder {

let context = self.context;

let guard = self.context.guards.entity_guards.get(&object_name);
let field_guards = &self.context.guards.field_guards;

Field::new(
self.type_name::<T>(),
TypeRef::named_nn_list_nn(entity_object_builder.basic_type_name::<T>()),
move |ctx| {
FieldFuture::new(async move {
let guard_flag = if let Some(guard) = guard {
(*guard)(&ctx)
} else {
GuardAction::Allow
};

if let GuardAction::Block(reason) = guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Entity guard triggered."),
),
};
}

let db = ctx.data::<DatabaseConnection>()?;
let transaction = db.begin().await?;

Expand All @@ -95,6 +115,30 @@ impl EntityUpdateMutationBuilder {
.get(&context.entity_create_one_mutation.data_field)
.unwrap();
let input_object = &value_accessor.object()?;

for (column, _) in input_object.iter() {
let field_guard = field_guards.get(&format!(
"{}.{}",
entity_object_builder.type_name::<T>(),
column
));
let field_guard_flag = if let Some(field_guard) = field_guard {
(*field_guard)(&ctx)
} else {
GuardAction::Allow
};
if let GuardAction::Block(reason) = field_guard_flag {
return match reason {
Some(reason) => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new(reason),
),
None => Err::<Option<_>, async_graphql::Error>(
async_graphql::Error::new("Field guard triggered."),
),
};
}
}

let active_model = prepare_active_model::<T, A>(
&entity_input_builder,
&entity_object_builder,
Expand Down

0 comments on commit 462ea23

Please sign in to comment.