Argument presets for DataConnectorLink Pt. 2 (#675)

## Description

This PR implements argument presets for `DataConnectorLink`, which can
be used to forward request headers as function/procedure arguments to
NDC. This PR implements the execution part.

**Note**: response header forwarding is not implemented yet.

V3_GIT_ORIGIN_REV_ID: ff69129aee3e3052367ca42acdec3922cbc2cb0c
This commit is contained in:
Anon Ray 2024-06-06 21:33:41 +05:30 committed by hasura-bot
parent d090331be2
commit 8bc5c01961
26 changed files with 428 additions and 28 deletions

1
v3/Cargo.lock generated
View File

@ -725,6 +725,7 @@ dependencies = [
"indexmap 2.2.6",
"ndc-models",
"regex",
"serde",
"serde_json",
"tokio",
]

View File

@ -20,6 +20,7 @@ indexmap = "2"
ndc-models = { git = "https://github.com/hasura/ndc-spec.git", rev = "622c643b4f0b6bbe4601c0f065d6d93a4bd3e9db" }
regex = "1"
serde_json = "1"
serde = "1"
tokio = { version = "1", features = ["full"] }
[lints]

View File

@ -5,6 +5,7 @@ use ndc_models;
use crate::{query::Result, state::AppState};
pub mod login;
pub mod noop_procedure;
pub mod update_actor_name_by_id;
pub mod uppercase_actor_name_by_id;
@ -16,6 +17,7 @@ pub(crate) fn get_procedures() -> Vec<ndc_models::ProcedureInfo> {
vec![
upsert_actor::procedure_info(),
update_actor_name_by_id::procedure_info(),
login::procedure_info(),
noop_procedure::procedure_info(),
// TODO: Looks like the other procedures where never added to the schema?
]
@ -42,6 +44,7 @@ pub(crate) fn execute_procedure(
"uppercase_all_actor_names_return_names_list" => {
uppercase_all_actor_names_return_names_list::execute(state)
}
"login" => login::execute(arguments),
"noop_procedure" => noop_procedure::execute(),
_ => Err((
StatusCode::BAD_REQUEST,

View File

@ -0,0 +1,90 @@
use axum::{http::StatusCode, Json};
use ndc_models;
use std::collections::BTreeMap;
use crate::{
query::Result,
types::login::{LoginResponse, ResponseHeaders},
};
pub(crate) fn procedure_info() -> ndc_models::ProcedureInfo {
ndc_models::ProcedureInfo {
name: "login".into(),
description: Some("Perform a user login".into()),
arguments: BTreeMap::from_iter([
(
"headers".into(),
ndc_models::ArgumentInfo {
description: Some("headers required for authentication".into()),
argument_type: ndc_models::Type::Named {
name: "HeaderMap".into(),
},
},
),
(
"username".into(),
ndc_models::ArgumentInfo {
description: Some("username of the user".into()),
argument_type: ndc_models::Type::Named {
name: "String".into(),
},
},
),
(
"password".into(),
ndc_models::ArgumentInfo {
description: Some("password of the user".into()),
argument_type: ndc_models::Type::Named {
name: "String".into(),
},
},
),
]),
result_type: ndc_models::Type::Named {
name: "login_response".into(),
},
}
}
pub(crate) fn execute(
arguments: &BTreeMap<String, serde_json::Value>,
) -> Result<serde_json::Value> {
let _headers = arguments.get("headers").ok_or((
StatusCode::BAD_REQUEST,
Json(ndc_models::ErrorResponse {
message: "required argument field 'headers' is missing".into(),
details: serde_json::Value::Null,
}),
))?;
let _username = arguments.get("username").ok_or((
StatusCode::BAD_REQUEST,
Json(ndc_models::ErrorResponse {
message: "required argument field 'username' is missing".into(),
details: serde_json::Value::Null,
}),
))?;
let _password = arguments.get("password").ok_or((
StatusCode::BAD_REQUEST,
Json(ndc_models::ErrorResponse {
message: "required argument field 'password' is missing".into(),
details: serde_json::Value::Null,
}),
))?;
let login_response = LoginResponse {
response: true,
headers: ResponseHeaders {
cookie: "Set-Cookie: cookie_name=cookie_val;".to_string(),
session_token: "abcdefghi".to_string(),
},
};
serde_json::to_value(login_response).map_err(|_| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ndc_models::ErrorResponse {
message: "cannot encode response".into(),
details: serde_json::Value::Null,
}),
)
})
}

View File

@ -4,6 +4,7 @@ use std::collections::BTreeMap;
pub mod actor;
pub mod institution;
pub mod location;
pub mod login;
pub mod name_query;
pub mod staff_member;
@ -53,6 +54,21 @@ pub(crate) fn scalar_types() -> BTreeMap<String, ndc_models::ScalarType> {
comparison_operators: BTreeMap::from_iter([]),
},
),
(
"Bool".into(),
ndc_models::ScalarType {
representation: Some(ndc_models::TypeRepresentation::Boolean),
aggregate_functions: BTreeMap::new(),
comparison_operators: BTreeMap::from_iter([(
"eq".into(),
ndc_models::ComparisonOperatorDefinition::Custom {
argument_type: ndc_models::Type::Named {
name: "Bool".into(),
},
},
)]),
},
),
(
"Actor_Name".into(),
ndc_models::ScalarType {
@ -61,6 +77,14 @@ pub(crate) fn scalar_types() -> BTreeMap<String, ndc_models::ScalarType> {
comparison_operators: BTreeMap::new(),
},
),
(
"HeaderMap".into(),
ndc_models::ScalarType {
representation: Some(ndc_models::TypeRepresentation::JSON),
aggregate_functions: BTreeMap::new(),
comparison_operators: BTreeMap::new(),
},
),
])
}
@ -104,5 +128,6 @@ pub(crate) fn object_types() -> BTreeMap<String, ndc_models::ObjectType> {
("institution".into(), institution::definition()),
("location".into(), location::definition()),
("staff_member".into(), staff_member::definition()),
("login_response".into(), login::definition()),
])
}

View File

@ -0,0 +1,43 @@
use ndc_models;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginResponse {
pub headers: ResponseHeaders,
pub response: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ResponseHeaders {
pub cookie: String,
pub session_token: String,
}
pub(crate) fn definition() -> ndc_models::ObjectType {
ndc_models::ObjectType {
description: Some("Response to a login action".into()),
fields: BTreeMap::from_iter([
(
"headers".into(),
ndc_models::ObjectField {
description: Some("Response headers to be forwarded".into()),
r#type: ndc_models::Type::Named {
name: "HeaderMap".into(),
},
arguments: BTreeMap::new(),
},
),
(
"response".into(),
ndc_models::ObjectField {
description: Some("Authentication successful or not".into()),
r#type: ndc_models::Type::Named {
name: "Bool".into(),
},
arguments: BTreeMap::new(),
},
),
]),
}
}

View File

@ -62,6 +62,7 @@ pub fn bench_execute(
variables: None,
};
let request_headers = reqwest::header::HeaderMap::new();
let session = Identity::admin(Role::new("admin"))
.get_role_authorization(None)
.unwrap()
@ -127,12 +128,13 @@ pub fn bench_execute(
BenchmarkId::new("bench_execute", "Generate IR"),
&(&runtime, &schema),
|b, (runtime, schema)| {
b.to_async(*runtime)
.iter(|| async { generate_ir(schema, &session, &normalized_request).unwrap() })
b.to_async(*runtime).iter(|| async {
generate_ir(schema, &session, &request_headers, &normalized_request).unwrap()
})
},
);
let ir = generate_ir(&schema, &session, &normalized_request).unwrap();
let ir = generate_ir(&schema, &session, &request_headers, &normalized_request).unwrap();
// Generate Query Plan
group.bench_with_input(
@ -168,9 +170,16 @@ pub fn bench_execute(
&(&runtime, &schema, raw_request),
|b, (runtime, schema, request)| {
b.to_async(*runtime).iter(|| async {
execute_query_internal(&http_context, schema, &session, request.clone(), None)
.await
.unwrap()
execute_query_internal(
&http_context,
schema,
&session,
&request_headers,
request.clone(),
None,
)
.await
.unwrap()
})
},
);

View File

@ -475,6 +475,7 @@ async fn graphiql() -> Html<&'static str> {
}
async fn handle_request(
headers: axum::http::header::HeaderMap,
State(state): State<Arc<EngineState>>,
Extension(session): Extension<Session>,
Json(request): Json<gql::http::RawRequest>,
@ -490,6 +491,7 @@ async fn handle_request(
&state.http_context,
&state.schema,
&session,
&headers,
request,
None,
))
@ -508,6 +510,7 @@ async fn handle_request(
}
async fn handle_explain_request(
headers: axum::http::header::HeaderMap,
State(state): State<Arc<EngineState>>,
Extension(session): Extension<Session>,
Json(request): Json<gql::http::RawRequest>,
@ -523,6 +526,7 @@ async fn handle_explain_request(
&state.http_context,
&state.schema,
&session,
&headers,
request,
))
},

View File

@ -89,6 +89,7 @@ pub fn test_execution_expectation_legacy(
let query = fs::read_to_string(request_path)?;
let request_headers = reqwest::header::HeaderMap::new();
let session = {
let session_vars_path = &test_path.join("session_variables.json");
let session_variables: HashMap<SessionVariable, SessionVariableValue> =
@ -104,8 +105,15 @@ pub fn test_execution_expectation_legacy(
// Execute the test
let response =
execute_query(&test_ctx.http_context, &schema, &session, raw_request, None).await;
let response = execute_query(
&test_ctx.http_context,
&schema,
&session,
&request_headers,
raw_request,
None,
)
.await;
let mut expected = test_ctx.mint.new_goldenfile_with_differ(
response_path,
@ -174,6 +182,7 @@ pub(crate) fn test_introspection_expectation(
let query = fs::read_to_string(request_path)?;
let request_headers = reqwest::header::HeaderMap::new();
let session_vars_path = &test_path.join("session_variables.json");
let sessions: Vec<HashMap<SessionVariable, SessionVariableValue>> =
json::from_str(fs::read_to_string(session_vars_path)?.as_ref())?;
@ -200,6 +209,7 @@ pub(crate) fn test_introspection_expectation(
&test_ctx.http_context,
&schema,
session,
&request_headers,
raw_request.clone(),
None,
)
@ -289,6 +299,7 @@ pub fn test_execution_expectation(
Err(_) => None,
};
let request_headers = reqwest::header::HeaderMap::new();
let session_vars_path = &test_path.join("session_variables.json");
let sessions: Vec<HashMap<SessionVariable, SessionVariableValue>> =
json::from_str(fs::read_to_string(session_vars_path)?.as_ref())?;
@ -317,6 +328,7 @@ pub fn test_execution_expectation(
&test_ctx.http_context,
&schema,
session,
&request_headers,
raw_request.clone(),
None,
)
@ -335,6 +347,7 @@ pub fn test_execution_expectation(
&test_ctx.http_context,
&schema,
session,
&request_headers,
raw_request.clone(),
None,
)
@ -403,6 +416,7 @@ pub fn test_execute_explain(
let gds = GDS::new_with_default_flags(open_dds::traits::OpenDd::deserialize(metadata)?)?;
let schema = GDS::build_schema(&gds)?;
let request_headers = reqwest::header::HeaderMap::new();
let session = {
let session_variables_raw = r#"{
"x-hasura-role": "admin"
@ -417,8 +431,14 @@ pub fn test_execute_explain(
query,
variables: None,
};
let raw_response =
execute::execute_explain(&test_ctx.http_context, &schema, &session, raw_request).await;
let raw_response = execute::execute_explain(
&test_ctx.http_context,
&schema,
&session,
&request_headers,
raw_request,
)
.await;
let response = execute::redact_ndc_explain(raw_response);

View File

@ -64,6 +64,28 @@
"type": "custom"
}
}
},
"Bool": {
"representation": {
"type": "boolean"
},
"aggregate_functions": {},
"comparison_operators": {
"eq": {
"type": "custom",
"argument_type": {
"type": "named",
"name": "Bool"
}
}
}
},
"HeaderMap": {
"representation": {
"type": "json"
},
"aggregate_functions": {},
"comparison_operators": {}
}
},
"object_types": {
@ -254,6 +276,25 @@
}
}
}
},
"login_response": {
"description": "Response to a login action",
"fields": {
"headers": {
"description": "Response headers to be forwarded",
"type": {
"type": "named",
"name": "HeaderMap"
}
},
"response": {
"description": "Authentication successful or not",
"type": {
"type": "named",
"name": "Bool"
}
}
}
}
},
"collections": [
@ -666,6 +707,37 @@
}
}
}
},
{
"name": "login",
"description": "Perform a user login",
"arguments": {
"headers": {
"description": "headers required for authentication",
"type": {
"type": "named",
"name": "HeaderMap"
}
},
"password": {
"description": "password of the user",
"type": {
"type": "named",
"name": "String"
}
},
"username": {
"description": "username of the user",
"type": {
"type": "named",
"name": "String"
}
}
},
"result_type": {
"type": "named",
"name": "login_response"
}
}
]
},

View File

@ -24,6 +24,7 @@ pub fn bench_generate_ir(c: &mut Criterion) {
group.sample_size(20);
group.sampling_mode(SamplingMode::Flat);
let request_headers = reqwest::header::HeaderMap::new();
let session = Identity::admin(Role::new("admin"))
.get_role_authorization(None)
.unwrap()
@ -61,7 +62,10 @@ pub fn bench_generate_ir(c: &mut Criterion) {
BenchmarkId::new("generate_ir", test_name),
&(&schema, &normalized_request),
|b, (schema, normalized_request)| {
b.iter(|| execute::generate_ir(schema, &session, normalized_request).unwrap())
b.iter(|| {
execute::generate_ir(schema, &session, &request_headers, normalized_request)
.unwrap()
})
},
);
}

View File

@ -21,9 +21,10 @@ pub async fn execute_explain(
http_context: &HttpContext,
schema: &Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
request: RawRequest,
) -> types::ExplainResponse {
super::explain_query_internal(http_context, schema, session, request)
super::explain_query_internal(http_context, schema, session, request_headers, request)
.await
.unwrap_or_else(|e| types::ExplainResponse::error(e.to_graphql_error()))
}

View File

@ -16,11 +16,13 @@ use serde_json as json;
use std::collections::BTreeMap;
use super::arguments;
use super::error::InternalDeveloperError;
use super::selection_set;
use crate::ir::error;
use crate::ir::permissions;
use crate::model_tracking::{count_command, UsagesCounts};
use metadata_resolve;
use metadata_resolve::http::SerializableHeaderMap;
use metadata_resolve::{ConnectorArgumentName, Qualified, QualifiedTypeReference};
use schema::ArgumentNameAndPath;
use schema::ArgumentPresets;
@ -87,6 +89,7 @@ pub(crate) fn generate_command_info<'n, 's>(
result_base_type_kind: TypeKind,
command_source: &'s CommandSourceDetail,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<CommandInfo<'s>, error::Error> {
let mut command_arguments = BTreeMap::new();
@ -100,7 +103,8 @@ pub(crate) fn generate_command_info<'n, 's>(
command_arguments.insert(ndc_arg_name, ndc_val);
}
// fetch argument presets from namespace annotation
// preset arguments from permissions presets (both command permission argument
// presets and input field presets)
if let Some(ArgumentPresets { argument_presets }) =
permissions::get_argument_presets(field_call.info.namespaced)?
{
@ -148,6 +152,48 @@ pub(crate) fn generate_command_info<'n, 's>(
}
}
// preset arguments from `DataConnectorLink` argument presets
for dc_argument_preset in &command_source.data_connector.argument_presets {
let mut headers_argument = reqwest::header::HeaderMap::new();
// add headers from the request to be forwarded
for header_name in &dc_argument_preset.value.http_headers.forward {
if let Some(header_value) = request_headers.get(&header_name.0) {
headers_argument.insert(header_name.0.clone(), header_value.clone());
}
}
// add additional headers from `ValueExpression`
for (header_name, value_expression) in &dc_argument_preset.value.http_headers.additional {
// TODO: have helper functions to create types
let string_type = QualifiedTypeReference {
nullable: false,
underlying_type: metadata_resolve::QualifiedBaseType::Named(
metadata_resolve::QualifiedTypeName::Inbuilt(
open_dds::types::InbuiltType::String,
),
),
};
let value = permissions::make_value_from_value_expression(
value_expression,
&string_type,
session_variables,
usage_counts,
)?;
let header_value =
reqwest::header::HeaderValue::from_str(serde_json::to_string(&value)?.as_str())
.map_err(|_e| {
InternalDeveloperError::UnableToConvertValueExpressionToHeaderValue
})?;
headers_argument.insert(header_name.0.clone(), header_value);
}
command_arguments.insert(
dc_argument_preset.name.to_string(),
serde_json::to_value(SerializableHeaderMap(headers_argument))?,
);
}
// Add the name of the root command
let mut usage_counts = UsagesCounts::new();
count_command(command_name, &mut usage_counts);
@ -159,6 +205,7 @@ pub(crate) fn generate_command_info<'n, 's>(
&command_source.data_connector,
&command_source.type_mappings,
session_variables,
request_headers,
&mut usage_counts,
)?;
@ -183,6 +230,7 @@ pub(crate) fn generate_function_based_command<'n, 's>(
result_base_type_kind: TypeKind,
command_source: &'s CommandSourceDetail,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FunctionBasedCommand<'s>, error::Error> {
let command_info = generate_command_info(
@ -193,6 +241,7 @@ pub(crate) fn generate_function_based_command<'n, 's>(
result_base_type_kind,
command_source,
session_variables,
request_headers,
usage_counts,
)?;
@ -213,6 +262,7 @@ pub(crate) fn generate_procedure_based_command<'n, 's>(
result_base_type_kind: TypeKind,
command_source: &'s CommandSourceDetail,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
) -> Result<ProcedureBasedCommand<'s>, error::Error> {
let mut usage_counts = UsagesCounts::new();
@ -224,6 +274,7 @@ pub(crate) fn generate_procedure_based_command<'n, 's>(
result_base_type_kind,
command_source,
session_variables,
request_headers,
&mut usage_counts,
)?;

View File

@ -150,6 +150,9 @@ pub enum InternalDeveloperError {
argument_name: ArgumentName,
},
#[error("The value expression could not be converted to header value. Error: ")]
UnableToConvertValueExpressionToHeaderValue,
// we'll be adding them shortly, and not advertising the feature until they are complete
// however temporarily emitting the error allows merging the work in chunks
#[error("boolean expressions not implemented")]

View File

@ -58,6 +58,7 @@ pub(crate) fn model_selection_ir<'s>(
offset: Option<u32>,
order_by: Option<ResolvedOrderBy<'s>>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<ModelSelection<'s>, error::Error> {
match permissions_predicate {
@ -98,6 +99,7 @@ pub(crate) fn model_selection_ir<'s>(
&model_source.type_mappings,
field_mappings,
session_variables,
request_headers,
usage_counts,
)?;

View File

@ -17,6 +17,7 @@ use schema::{OutputAnnotation, RootFieldAnnotation};
pub fn generate_ir<'n, 's>(
selection_set: &'s gql::normalized_ast::SelectionSet<'s, GDS>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
) -> Result<IndexMap<ast::Alias, root_field::RootField<'n, 's>>, error::Error> {
let tracer = tracing_util::global_tracer();
tracer.in_span(
@ -70,6 +71,7 @@ pub fn generate_ir<'n, 's>(
*result_base_type_kind,
source,
session_variables,
request_headers,
)?,
})
}

View File

@ -30,6 +30,7 @@ pub mod select_one;
pub fn generate_ir<'n, 's>(
schema: &'s gql::schema::Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
selection_set: &'s gql::normalized_ast::SelectionSet<'s, GDS>,
) -> Result<IndexMap<ast::Alias, root_field::RootField<'n, 's>>, error::Error> {
let type_name = selection_set
@ -62,7 +63,14 @@ pub fn generate_ir<'n, 's>(
name: model_name,
} => {
let ir = generate_model_rootfield_ir(
&type_name, source, data_type, kind, field, field_call, session,
&type_name,
source,
data_type,
kind,
field,
field_call,
session,
request_headers,
model_name,
)?;
Ok(ir)
@ -84,6 +92,7 @@ pub fn generate_ir<'n, 's>(
field,
field_call,
&session.variables,
request_headers,
)?;
Ok(ir)
}
@ -93,6 +102,7 @@ pub fn generate_ir<'n, 's>(
field_call,
typename_mappings,
session,
request_headers,
)?;
Ok(ir)
}
@ -104,6 +114,7 @@ pub fn generate_ir<'n, 's>(
field_call,
typename_mappings,
session,
request_headers,
)?;
Ok(ir)
}
@ -173,6 +184,7 @@ fn generate_model_rootfield_ir<'n, 's>(
field: &'n gql::normalized_ast::Field<'s, GDS>,
field_call: &'s gql::normalized_ast::FieldCall<'s, GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
model_name: &'s metadata_resolve::Qualified<models::ModelName>,
) -> Result<root_field::QueryRootField<'n, 's>, error::Error> {
let source =
@ -191,6 +203,7 @@ fn generate_model_rootfield_ir<'n, 's>(
data_type,
source,
&session.variables,
request_headers,
model_name,
)?,
},
@ -202,6 +215,7 @@ fn generate_model_rootfield_ir<'n, 's>(
data_type,
source,
&session.variables,
request_headers,
model_name,
)?,
},
@ -219,6 +233,7 @@ fn generate_command_rootfield_ir<'n, 's>(
field: &'n gql::normalized_ast::Field<'s, GDS>,
field_call: &'s gql::normalized_ast::FieldCall<'s, GDS>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
) -> Result<root_field::QueryRootField<'n, 's>, error::Error> {
let mut usage_counts = UsagesCounts::new();
let source =
@ -247,6 +262,7 @@ fn generate_command_rootfield_ir<'n, 's>(
*result_base_type_kind,
source,
session_variables,
request_headers,
&mut usage_counts,
)?,
};
@ -258,12 +274,14 @@ fn generate_nodefield_ir<'n, 's>(
field_call: &'n gql::normalized_ast::FieldCall<'s, GDS>,
typename_mappings: &'s HashMap<ast::TypeName, NodeFieldTypeNameMapping>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
) -> Result<root_field::QueryRootField<'n, 's>, error::Error> {
let ir = root_field::QueryRootField::NodeSelect(node_field::relay_node_ir(
field,
field_call,
typename_mappings,
&session.variables,
request_headers,
)?);
Ok(ir)
}
@ -273,6 +291,7 @@ fn generate_entities_ir<'n, 's>(
field_call: &'n gql::normalized_ast::FieldCall<'s, GDS>,
typename_mappings: &'s HashMap<ast::TypeName, EntityFieldTypeNameMapping>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
) -> Result<root_field::QueryRootField<'n, 's>, error::Error> {
let ir = root_field::QueryRootField::ApolloFederation(
root_field::ApolloFederationRootFields::EntitiesSelect(apollo_federation::entities_ir(
@ -280,6 +299,7 @@ fn generate_entities_ir<'n, 's>(
field_call,
typename_mappings,
&session.variables,
request_headers,
)?),
);
Ok(ir)

View File

@ -80,6 +80,7 @@ pub(crate) fn entities_ir<'n, 's>(
field_call: &'n normalized_ast::FieldCall<'s, GDS>,
typename_mappings: &'s HashMap<ast::TypeName, EntityFieldTypeNameMapping>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
) -> Result<Vec<EntitySelect<'n, 's>>, error::Error> {
let representations = field_call
.expected_argument(&lang_graphql::mk_name!("representations"))?
@ -178,6 +179,7 @@ pub(crate) fn entities_ir<'n, 's>(
None, // offset
None, // order_by
session_variables,
request_headers,
// Get all the models/commands that were used as relationships
&mut usage_counts,
)?;

View File

@ -77,6 +77,7 @@ pub(crate) fn relay_node_ir<'n, 's>(
field_call: &'n normalized_ast::FieldCall<'s, GDS>,
typename_mappings: &'s HashMap<ast::TypeName, NodeFieldTypeNameMapping>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
) -> Result<Option<NodeSelect<'n, 's>>, error::Error> {
let id_arg_value = field_call
.expected_argument(&lang_graphql::mk_name!("id"))?
@ -161,6 +162,7 @@ pub(crate) fn relay_node_ir<'n, 's>(
None, // offset
None, // order_by
session_variables,
request_headers,
// Get all the models/commands that were used as relationships
&mut usage_counts,
)?;

View File

@ -47,6 +47,7 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
data_type: &Qualified<open_dds::types::CustomTypeName>,
model_source: &'s metadata_resolve::ModelSource,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
model_name: &'s Qualified<open_dds::models::ModelName>,
) -> Result<ModelSelectMany<'n, 's>, error::Error> {
let mut limit = None;
@ -153,6 +154,7 @@ pub(crate) fn select_many_generate_ir<'n, 's>(
offset,
order_by,
session_variables,
request_headers,
// Get all the models/commands that were used as relationships
&mut usage_counts,
)?;

View File

@ -47,6 +47,7 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
data_type: &Qualified<open_dds::types::CustomTypeName>,
model_source: &'s metadata_resolve::ModelSource,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
model_name: &'s Qualified<open_dds::models::ModelName>,
) -> Result<ModelSelectOne<'n, 's>, error::Error> {
let mut filter_clause_expressions = vec![];
@ -122,6 +123,7 @@ pub(crate) fn select_one_generate_ir<'n, 's>(
None, // offset
None, // order_by
session_variables,
request_headers,
// Get all the models/commands that were used as relationships
&mut usage_counts,
)?;

View File

@ -79,6 +79,7 @@ pub(crate) fn generate_model_relationship_ir<'s>(
source_data_connector: &'s metadata_resolve::DataConnectorLink,
source_type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
// Add the target model being used in the usage counts
@ -177,6 +178,7 @@ pub(crate) fn generate_model_relationship_ir<'s>(
offset,
order_by,
session_variables,
request_headers,
usage_counts,
),
metadata_resolve::RelationshipExecutionCategory::RemoteForEach => {
@ -191,6 +193,7 @@ pub(crate) fn generate_model_relationship_ir<'s>(
offset,
order_by,
session_variables,
request_headers,
usage_counts,
)
}
@ -203,6 +206,7 @@ pub(crate) fn generate_command_relationship_ir<'s>(
source_data_connector: &'s metadata_resolve::DataConnectorLink,
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
count_command(&annotation.command_name, usage_counts);
@ -235,6 +239,7 @@ pub(crate) fn generate_command_relationship_ir<'s>(
type_mappings,
target_source,
session_variables,
request_headers,
usage_counts,
),
metadata_resolve::RelationshipExecutionCategory::RemoteForEach => {
@ -245,6 +250,7 @@ pub(crate) fn generate_command_relationship_ir<'s>(
type_mappings,
target_source,
session_variables,
request_headers,
usage_counts,
)
}
@ -264,6 +270,7 @@ pub(crate) fn build_local_model_relationship<'s>(
offset: Option<u32>,
order_by: Option<ResolvedOrderBy<'s>>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
let relationships_ir = model_selection_ir(
@ -277,6 +284,7 @@ pub(crate) fn build_local_model_relationship<'s>(
offset,
order_by,
session_variables,
request_headers,
usage_counts,
)?;
let rel_info = LocalModelRelationshipInfo {
@ -306,6 +314,7 @@ pub(crate) fn build_local_command_relationship<'s>(
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
target_source: &'s CommandTargetSource,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
let relationships_ir = generate_function_based_command(
@ -317,6 +326,7 @@ pub(crate) fn build_local_command_relationship<'s>(
annotation.target_base_type_kind,
&target_source.details,
session_variables,
request_headers,
usage_counts,
)?;
@ -353,6 +363,7 @@ pub(crate) fn build_remote_relationship<'n, 's>(
offset: Option<u32>,
order_by: Option<ResolvedOrderBy<'s>>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
let mut join_mapping: Vec<(SourceField, TargetField)> = vec![];
@ -392,6 +403,7 @@ pub(crate) fn build_remote_relationship<'n, 's>(
offset,
order_by,
session_variables,
request_headers,
usage_counts,
)?;
@ -435,6 +447,7 @@ pub(crate) fn build_remote_command_relationship<'n, 's>(
type_mappings: &'s BTreeMap<Qualified<CustomTypeName>, metadata_resolve::TypeMapping>,
target_source: &'s CommandTargetSource,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<FieldSelection<'s>, error::Error> {
let mut join_mapping: Vec<(SourceField, ArgumentName)> = vec![];
@ -462,6 +475,7 @@ pub(crate) fn build_remote_command_relationship<'n, 's>(
annotation.target_base_type_kind,
&target_source.details,
session_variables,
request_headers,
usage_counts,
)?;

View File

@ -153,6 +153,7 @@ pub(crate) fn generate_nested_selection<'s>(
metadata_resolve::TypeMapping,
>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<Option<NestedSelection<'s>>, error::Error> {
match &qualified_type_reference.underlying_type {
@ -164,6 +165,7 @@ pub(crate) fn generate_nested_selection<'s>(
data_connector,
type_mappings,
session_variables,
request_headers,
usage_counts,
)?;
Ok(array_selection.map(|a| NestedSelection::Array(Box::new(a))))
@ -189,6 +191,7 @@ pub(crate) fn generate_nested_selection<'s>(
type_mappings,
field_mappings,
session_variables,
request_headers,
usage_counts,
)?;
Ok(Some(NestedSelection::Object(nested_selection)))
@ -213,6 +216,7 @@ pub(crate) fn generate_selection_set_ir<'s>(
>,
field_mappings: &BTreeMap<FieldName, metadata_resolve::FieldMapping>,
session_variables: &SessionVariables,
request_headers: &reqwest::header::HeaderMap,
usage_counts: &mut UsagesCounts,
) -> Result<ResultSelectionSet<'s>, error::Error> {
let mut fields = IndexMap::new();
@ -237,6 +241,7 @@ pub(crate) fn generate_selection_set_ir<'s>(
data_connector,
type_mappings,
session_variables,
request_headers,
usage_counts,
)?;
fields.insert(
@ -293,6 +298,7 @@ pub(crate) fn generate_selection_set_ir<'s>(
data_connector,
type_mappings,
session_variables,
request_headers,
usage_counts,
)?,
);
@ -306,6 +312,7 @@ pub(crate) fn generate_selection_set_ir<'s>(
data_connector,
type_mappings,
session_variables,
request_headers,
usage_counts,
)?,
);

View File

@ -80,12 +80,20 @@ pub async fn execute_query(
http_context: &HttpContext,
schema: &Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
request: RawRequest,
project_id: Option<&ProjectId>,
) -> GraphQLResponse {
execute_query_internal(http_context, schema, session, request, project_id)
.await
.unwrap_or_else(|e| GraphQLResponse(Response::error(e.to_graphql_error())))
execute_query_internal(
http_context,
schema,
session,
request_headers,
request,
project_id,
)
.await
.unwrap_or_else(|e| GraphQLResponse(Response::error(e.to_graphql_error())))
}
#[derive(Error, Debug)]
@ -112,6 +120,7 @@ pub async fn execute_query_internal(
http_context: &HttpContext,
schema: &gql::schema::Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
raw_request: gql::http::RawRequest,
project_id: Option<&ProjectId>,
) -> Result<GraphQLResponse, error::RequestError> {
@ -141,7 +150,7 @@ pub async fn execute_query_internal(
normalize_request(schema, session, query, raw_request)?;
// generate IR
let ir = build_ir(schema, session, &normalized_request)?;
let ir = build_ir(schema, session, request_headers, &normalized_request)?;
// construct a plan to execute the request
let request_plan = build_request_plan(&ir)?;
@ -198,6 +207,7 @@ pub async fn explain_query_internal(
http_context: &HttpContext,
schema: &gql::schema::Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
raw_request: gql::http::RawRequest,
) -> Result<explain::types::ExplainResponse, error::RequestError> {
let tracer = tracing_util::global_tracer();
@ -226,7 +236,7 @@ pub async fn explain_query_internal(
normalize_request(schema, session, query, raw_request)?;
// generate IR
let ir = build_ir(schema, session, &normalized_request)?;
let ir = build_ir(schema, session, request_headers, &normalized_request)?;
// construct a plan to execute the request
let request_plan = build_request_plan(&ir)?;
@ -341,6 +351,7 @@ pub(crate) fn normalize_request<'s>(
pub(crate) fn build_ir<'n, 's>(
schema: &'s gql::schema::Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
normalized_request: &'s Operation<'s, GDS>,
) -> Result<IndexMap<ast::Alias, ir::root_field::RootField<'n, 's>>, ir::error::Error> {
let tracer = tracing_util::global_tracer();
@ -348,7 +359,7 @@ pub(crate) fn build_ir<'n, 's>(
"generate_ir",
"Generate IR for the request",
SpanVisibility::Internal,
|| generate_ir(schema, session, normalized_request),
|| generate_ir(schema, session, request_headers, normalized_request),
)?;
Ok(ir)
}
@ -370,15 +381,21 @@ pub(crate) fn build_request_plan<'n, 's, 'ir>(
pub fn generate_ir<'n, 's>(
schema: &'s gql::schema::Schema<GDS>,
session: &Session,
request_headers: &reqwest::header::HeaderMap,
normalized_request: &'s Operation<'s, GDS>,
) -> Result<IndexMap<ast::Alias, ir::root_field::RootField<'n, 's>>, ir::error::Error> {
let ir = match &normalized_request.ty {
ast::OperationType::Query => {
ir::query_root::generate_ir(schema, session, &normalized_request.selection_set)?
}
ast::OperationType::Mutation => {
ir::mutation_root::generate_ir(&normalized_request.selection_set, &session.variables)?
}
ast::OperationType::Query => ir::query_root::generate_ir(
schema,
session,
request_headers,
&normalized_request.selection_set,
)?,
ast::OperationType::Mutation => ir::mutation_root::generate_ir(
&normalized_request.selection_set,
&session.variables,
request_headers,
)?,
ast::OperationType::Subscription => {
Err(ir::error::InternalEngineError::SubscriptionsNotSupported)?
}
@ -418,6 +435,7 @@ mod tests {
let path = input_file?.path();
assert!(path.is_dir());
let request_headers = reqwest::header::HeaderMap::new();
let test_name = path
.file_name()
.ok_or_else(|| format!("{path:?} is not a normal file or directory"))?;
@ -444,7 +462,7 @@ mod tests {
&request,
)?;
let ir = generate_ir(&schema, &session, &normalized_request)?;
let ir = generate_ir(&schema, &session, &request_headers, &normalized_request)?;
let mut expected = mint.new_goldenfile_with_differ(
expected_path,
Box::new(|file1, file2| {

View File

@ -304,7 +304,8 @@ pub fn validate_ndc_command(
// Check if the arguments are correctly mapped
for (open_dd_argument_name, ndc_argument_name) in &command_source.argument_mappings {
// OpenDD command argument should not be also used in DataConnectorLink.argumentPresets
// Arguments already used in DataConnectorLink.argumentPresets can't be
// used as command arguments
if dc_link_argument_presets.contains(&open_dd_argument_name) {
return Err(
NDCValidationError::CannotUseDataConnectorLinkArgumentPresetInCommand {

View File

@ -11,6 +11,7 @@ mod helpers;
mod stages;
mod types;
pub use helpers::http;
pub use helpers::ndc_validation::NDCValidationError;
pub use helpers::types::{
get_type_representation, mk_name, object_type_exists, unwrap_custom_type_name,