use super::*;
pub(crate) fn is_pure_introspection_request(request: &mut Request) -> bool {
if request.introspection_mode == async_graphql::IntrospectionMode::Disabled {
return false;
}
let operation_name = request.operation_name.clone();
let Ok(document) = request.parsed_query() else {
return false;
};
let mut operations = document.operations.iter();
let operation = if let Some(requested) = operation_name.as_deref() {
operations
.find(|(name, _)| name.map(|name| name.as_str()) == Some(requested))
.map(|(_, operation)| operation)
} else {
let first = operations.next().map(|(_, operation)| operation);
if operations.next().is_some() {
None
} else {
first
}
};
let Some(operation) = operation else {
return false;
};
if operation.node.ty != async_graphql::parser::types::OperationType::Query {
return false;
}
fn selection_is_introspection_only(
selection: &async_graphql::parser::types::SelectionSet,
document: &async_graphql::parser::types::ExecutableDocument,
visiting: &mut BTreeSet<String>,
completed: &mut HashMap<String, bool>,
remaining_selections: &mut usize,
depth: usize,
) -> bool {
if depth > REQUEST_ANALYSIS_MAX_DEPTH
|| selection.items.is_empty()
|| selection.items.len() > *remaining_selections
{
return false;
}
*remaining_selections -= selection.items.len();
selection.items.iter().all(|item| match &item.node {
async_graphql::parser::types::Selection::Field(field) => matches!(
field.node.name.node.as_str(),
"__schema" | "__type" | "__typename"
),
async_graphql::parser::types::Selection::InlineFragment(fragment) => {
selection_is_introspection_only(
&fragment.node.selection_set.node,
document,
visiting,
completed,
remaining_selections,
depth + 1,
)
}
async_graphql::parser::types::Selection::FragmentSpread(spread) => {
let name = spread.node.fragment_name.node.to_string();
if let Some(valid) = completed.get(&name) {
return *valid;
}
if !visiting.insert(name.clone()) {
return false;
}
let valid = document
.fragments
.get(&spread.node.fragment_name.node)
.is_some_and(|fragment| {
selection_is_introspection_only(
&fragment.node.selection_set.node,
document,
visiting,
completed,
remaining_selections,
depth + 1,
)
});
visiting.remove(&name);
completed.insert(name, valid);
valid
}
})
}
let mut remaining_selections = REQUEST_ANALYSIS_MAX_SELECTIONS;
selection_is_introspection_only(
&operation.node.selection_set.node,
document,
&mut BTreeSet::new(),
&mut HashMap::new(),
&mut remaining_selections,
0,
)
}
pub fn graphiql_enabled_from_env_vars(
graphiql: Option<&str>,
rust_env: Option<&str>,
env: Option<&str>,
app_env: Option<&str>,
) -> bool {
if let Some(v) = graphiql {
return !matches!(
v.to_ascii_lowercase().as_str(),
"0" | "false" | "off" | "no"
);
}
let prod = rust_env
.or(env)
.or(app_env)
.unwrap_or("")
.to_ascii_lowercase();
!matches!(prod.as_str(), "production" | "prod")
}
pub fn graphiql_enabled_from_env() -> bool {
graphiql_enabled_from_env_vars(
std::env::var("GRAPHIQL").ok().as_deref(),
std::env::var("RUST_ENV").ok().as_deref(),
std::env::var("ENV").ok().as_deref(),
std::env::var("APP_ENV").ok().as_deref(),
)
}
#[cfg(test)]
mod graphiql_env_tests {
use super::graphiql_enabled_from_env_vars;
#[test]
fn production_rust_env_disables_graphiql() {
assert!(!graphiql_enabled_from_env_vars(
None,
Some("production"),
None,
None
));
assert!(!graphiql_enabled_from_env_vars(
None,
Some("prod"),
None,
None
));
assert!(!graphiql_enabled_from_env_vars(
None,
Some("PRODUCTION"),
None,
None
));
}
#[test]
fn non_production_enables_graphiql_by_default() {
assert!(graphiql_enabled_from_env_vars(
None,
Some("development"),
None,
None
));
assert!(graphiql_enabled_from_env_vars(None, None, None, None));
}
#[test]
fn explicit_graphiql_overrides_production() {
assert!(graphiql_enabled_from_env_vars(
Some("1"),
Some("production"),
None,
None
));
assert!(!graphiql_enabled_from_env_vars(
Some("0"),
Some("development"),
None,
None
));
assert!(!graphiql_enabled_from_env_vars(
Some("false"),
None,
None,
None
));
}
#[test]
fn env_and_app_env_aliases() {
assert!(!graphiql_enabled_from_env_vars(
None,
None,
Some("production"),
None
));
assert!(!graphiql_enabled_from_env_vars(
None,
None,
None,
Some("prod")
));
}
}