[ENG-643] Support array-valued session variables (#1221)

<!-- The PR description should answer 2 important questions: -->

### What

Allows JSON arrays in session variables.

### How

If the type of a session variable isn't special (custom scalar, string,
or ID), then we parse the header as JSON, and switch into
type-_checking_ mode, returning the JSON wholesale if the type matches.

V3_GIT_ORIGIN_REV_ID: 386558898a188c06b73270a90020a20ff963f5a0
This commit is contained in:
Phil Freeman 2024-10-17 12:13:17 -07:00 committed by hasura-bot
parent a0b998ad7c
commit 16118760fd
37 changed files with 640 additions and 118 deletions

View File

@ -4,6 +4,8 @@
### Added
- Support array values in session variables
### Fixed
### Changed

View File

@ -23,7 +23,7 @@ use std::{
// Session variable and role are defined as part of OpenDD
pub use open_dds::{
permissions::Role,
session_variables::{SessionVariable, SESSION_VARIABLE_ROLE},
session_variables::{SessionVariableName, SessionVariableReference, SESSION_VARIABLE_ROLE},
};
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, JsonSchema)]
@ -37,10 +37,10 @@ impl SessionVariableValue {
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
pub struct SessionVariables(HashMap<SessionVariable, SessionVariableValue>);
pub struct SessionVariables(HashMap<SessionVariableName, SessionVariableValue>);
impl SessionVariables {
pub fn get(&self, session_variable: &SessionVariable) -> Option<&SessionVariableValue> {
pub fn get(&self, session_variable: &SessionVariableName) -> Option<&SessionVariableValue> {
self.0.get(session_variable)
}
}
@ -56,14 +56,14 @@ pub struct Session {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RoleAuthorization {
pub role: Role,
pub session_variables: HashMap<SessionVariable, SessionVariableValue>,
pub session_variables: HashMap<SessionVariableName, SessionVariableValue>,
pub allowed_session_variables_from_request: SessionVariableList,
}
impl RoleAuthorization {
pub fn build_session(
&self,
mut variables: HashMap<SessionVariable, SessionVariableValue>,
mut variables: HashMap<SessionVariableName, SessionVariableValue>,
) -> Session {
let allowed_client_session_variables = match &self.allowed_session_variables_from_request {
SessionVariableList::All => variables,
@ -87,7 +87,7 @@ pub enum SessionVariableList {
// like * in Select * from ...
All,
// An explicit list
Some(HashSet<SessionVariable>),
Some(HashSet<SessionVariableName>),
}
// Privileges of the current user
@ -183,7 +183,7 @@ pub fn authorize_identity(
let mut role = None;
// traverse through the headers and collect role and session variables
for (header_name, header_value) in headers {
if let Ok(session_variable) = SessionVariable::from_str(header_name.as_str()) {
if let Ok(session_variable) = SessionVariableName::from_str(header_name.as_str()) {
let variable_value = match header_value.to_str() {
Err(e) => Err(SessionError::InvalidHeaderValue {
header_name: header_name.to_string(),
@ -220,16 +220,16 @@ mod tests {
let mut authenticated_session_variables = HashMap::new();
authenticated_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom").unwrap(),
SessionVariableName::from_str("x-hasura-custom").unwrap(),
SessionVariableValue::new("test"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableName::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableValue::new("claim-value"),
);
let role_authorization = RoleAuthorization {
@ -243,7 +243,7 @@ mod tests {
let mut expected_session_variables = client_session_variables.clone();
expected_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
pa::assert_eq!(
@ -262,23 +262,23 @@ mod tests {
let mut authenticated_session_variables = HashMap::new();
authenticated_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom").unwrap(),
SessionVariableName::from_str("x-hasura-custom").unwrap(),
SessionVariableValue::new("test"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableName::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableValue::new("claim-value"),
);
let mut allowed_sesion_variables_from_request = HashSet::new();
allowed_sesion_variables_from_request
.insert(SessionVariable::from_str("x-hasura-custom").unwrap());
.insert(SessionVariableName::from_str("x-hasura-custom").unwrap());
allowed_sesion_variables_from_request
.insert(SessionVariable::from_str("x-hasura-custom-author-id").unwrap());
.insert(SessionVariableName::from_str("x-hasura-custom-author-id").unwrap());
let role_authorization = RoleAuthorization {
role: Role::new("test-role"),
session_variables: authenticated_session_variables,
@ -291,10 +291,10 @@ mod tests {
let mut expected_session_variables = client_session_variables;
expected_session_variables
.remove(&SessionVariable::from_str("x-hasura-custom-claim").unwrap());
.remove(&SessionVariableName::from_str("x-hasura-custom-claim").unwrap());
expected_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
pa::assert_eq!(
@ -313,16 +313,16 @@ mod tests {
let mut authenticated_session_variables = HashMap::new();
authenticated_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom").unwrap(),
SessionVariableName::from_str("x-hasura-custom").unwrap(),
SessionVariableValue::new("test"),
);
client_session_variables.insert(
SessionVariable::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableName::from_str("x-hasura-custom-claim").unwrap(),
SessionVariableValue::new("claim-value"),
);
@ -337,7 +337,7 @@ mod tests {
let mut expected_session_variables = HashMap::new();
expected_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);

View File

@ -110,10 +110,11 @@ pub async fn authenticate_request(
mod tests {
use std::str::FromStr;
use auth_base::{RoleAuthorization, SessionVariable, SessionVariableValue};
use auth_base::{RoleAuthorization, SessionVariableValue};
use jsonwebtoken as jwt;
use jsonwebtoken::Algorithm;
use jwt::{encode, EncodingKey};
use open_dds::session_variables::SessionVariableName;
use reqwest::header::AUTHORIZATION;
use serde_json::json;
use tokio;
@ -166,7 +167,7 @@ mod tests {
fn get_default_hasura_claims() -> HasuraClaims {
let mut hasura_custom_claims = HashMap::new();
hasura_custom_claims.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue("1".to_string()),
);
HasuraClaims {
@ -223,7 +224,7 @@ mod tests {
let mut role_authorization_session_variables = HashMap::new();
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
expected_allowed_roles.insert(
@ -272,7 +273,7 @@ mod tests {
async fn test_successful_role_emulation() -> anyhow::Result<()> {
let mut hasura_claims = get_default_hasura_claims();
hasura_claims.custom_claims.insert(
SessionVariable::from_str("x-hasura-role").unwrap(),
SessionVariableName::from_str("x-hasura-role").unwrap(),
SessionVariableValue::new("admin"),
);
let encoded_claims = get_encoded_claims(Algorithm::HS256, &hasura_claims)?;

View File

@ -4,10 +4,11 @@ use std::time::Duration;
use axum::http::{HeaderMap, HeaderValue};
use axum::response::IntoResponse;
use cookie::{self, Cookie};
use hasura_authn_core::{Role, SessionVariable, SessionVariableValue};
use hasura_authn_core::{Role, SessionVariableValue};
use jsonptr::Pointer;
use jsonwebtoken::{self as jwt, decode, DecodingKey, Validation};
use jwt::decode_header;
use open_dds::session_variables::SessionVariableName;
use reqwest::header::{AUTHORIZATION, COOKIE};
use reqwest::StatusCode;
use schemars::gen::SchemaGenerator;
@ -253,7 +254,7 @@ pub struct JWTClaimsMap {
/// A dictionary of the custom claims, where the key is the name of the claim and the value
/// is the JSON pointer to lookup the custom claims within the decoded JWT.
pub custom_claims:
Option<HashMap<SessionVariable, JWTClaimsMappingEntry<SessionVariableValue>>>,
Option<HashMap<SessionVariableName, JWTClaimsMappingEntry<SessionVariableValue>>>,
}
#[derive(Serialize, Deserialize, PartialEq, Clone, JsonSchema, Debug)]
@ -390,7 +391,7 @@ pub struct HasuraClaims {
/// as per the user's defined permissions.
/// For example, things like `x-hasura-user-id` can go here.
#[serde(flatten)]
pub custom_claims: HashMap<SessionVariable, SessionVariableValue>,
pub custom_claims: HashMap<SessionVariableName, SessionVariableValue>,
}
#[derive(Debug, Serialize, Deserialize)]
@ -733,7 +734,7 @@ mod tests {
fn get_default_hasura_claims() -> HasuraClaims {
let mut hasura_custom_claims = HashMap::new();
hasura_custom_claims.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue("1".to_string()),
);
HasuraClaims {

View File

@ -1,4 +1,4 @@
use hasura_authn_core::{Role, SessionVariable, SessionVariableValue};
use hasura_authn_core::{Role, SessionVariableName, SessionVariableValue};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::json;
@ -15,7 +15,7 @@ pub struct NoAuthConfig {
pub role: Role,
/// static session variables to use whilst running the engine
#[schemars(title = "SessionVariables")]
pub session_variables: HashMap<SessionVariable, SessionVariableValue>,
pub session_variables: HashMap<SessionVariableName, SessionVariableValue>,
}
impl NoAuthConfig {

View File

@ -3,7 +3,7 @@ use std::str::FromStr;
use std::sync::OnceLock;
use std::time::Duration;
use auth_base::{Identity, Role, RoleAuthorization, SessionVariable, SessionVariableValue};
use auth_base::{Identity, Role, RoleAuthorization, SessionVariableName, SessionVariableValue};
use axum::{
http::{HeaderMap, HeaderName, StatusCode},
response::IntoResponse,
@ -206,7 +206,7 @@ async fn make_auth_hook_request(
response.json().await.map_err(InternalError::ReqwestError)?;
let mut session_variables = HashMap::new();
for (k, v) in &auth_hook_response {
match SessionVariable::from_str(k) {
match SessionVariableName::from_str(k) {
Ok(session_variable) => {
session_variables
.insert(session_variable, SessionVariableValue(v.to_string()));
@ -366,11 +366,11 @@ mod tests {
let mut expected_allowed_roles = HashMap::new();
let mut role_authorization_session_variables = HashMap::new();
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-role").unwrap(),
SessionVariableName::from_str("x-hasura-role").unwrap(),
SessionVariableValue::new("test-role"),
);
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableName::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableValue::new("1"),
);
expected_allowed_roles.insert(
@ -437,11 +437,11 @@ mod tests {
let mut expected_allowed_roles = HashMap::new();
let mut role_authorization_session_variables = HashMap::new();
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-role").unwrap(),
SessionVariableName::from_str("x-hasura-role").unwrap(),
SessionVariableValue::new("test-role"),
);
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableName::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableValue::new("1"),
);
expected_allowed_roles.insert(
@ -509,15 +509,15 @@ mod tests {
let mut expected_allowed_roles = HashMap::new();
let mut role_authorization_session_variables = HashMap::new();
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-role").unwrap(),
SessionVariableName::from_str("x-hasura-role").unwrap(),
SessionVariableValue::new("test-role"),
);
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableName::from_str("x-hasura-test-role-id").unwrap(),
SessionVariableValue::new("1"),
);
role_authorization_session_variables.insert(
SessionVariable::from_str("status").unwrap(),
SessionVariableName::from_str("status").unwrap(),
SessionVariableValue::new("true"),
);
expected_allowed_roles.insert(
@ -637,11 +637,11 @@ mod tests {
let mut expected_allowed_roles = HashMap::new();
let mut role_authorization_session_variables = HashMap::new();
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-role").unwrap(),
SessionVariableName::from_str("x-hasura-role").unwrap(),
SessionVariableValue::new("user"),
);
role_authorization_session_variables.insert(
SessionVariable::from_str("x-hasura-user-id").unwrap(),
SessionVariableName::from_str("x-hasura-user-id").unwrap(),
SessionVariableValue::new("1"),
);
expected_allowed_roles.insert(

View File

@ -7,7 +7,7 @@ use hasura_authn_core::{Identity, Role, Session, SessionError, SessionVariableVa
use lang_graphql::ast::common as ast;
use lang_graphql::{http::RawRequest, schema::Schema};
use metadata_resolve::{data_connectors::NdcVersion, LifecyclePluginConfigs};
use open_dds::session_variables::{SessionVariable, SESSION_VARIABLE_ROLE};
use open_dds::session_variables::{SessionVariableName, SESSION_VARIABLE_ROLE};
use pretty_assertions::assert_eq;
use serde_json as json;
use sql::execute::SqlRequest;
@ -41,7 +41,7 @@ pub fn setup(test_dir: &Path) -> GoldenTestContext {
}
pub(crate) fn resolve_session(
session_variables: HashMap<SessionVariable, SessionVariableValue>,
session_variables: HashMap<SessionVariableName, SessionVariableValue>,
) -> Result<Session, SessionError> {
//return an arbitrary identity with role emulation enabled
let authorization = Identity::admin(Role::new("admin"));
@ -107,7 +107,7 @@ pub(crate) fn test_introspection_expectation(
let request_headers = reqwest::header::HeaderMap::new();
let session_vars_path = &test_path.join("session_variables.json");
let sessions: Vec<HashMap<SessionVariable, SessionVariableValue>> =
let sessions: Vec<HashMap<SessionVariableName, SessionVariableValue>> =
json::from_str(read_to_string(session_vars_path)?.as_ref())?;
let sessions: Vec<Session> = sessions
.into_iter()
@ -266,7 +266,7 @@ pub fn test_execution_expectation_for_multiple_ndc_versions(
let request_headers = reqwest::header::HeaderMap::new();
let session_vars_path = &test_path.join("session_variables.json");
let sessions: Vec<HashMap<SessionVariable, SessionVariableValue>> =
let sessions: Vec<HashMap<SessionVariableName, SessionVariableValue>> =
json::from_str(read_to_string(session_vars_path)?.as_ref())?;
let sessions: Vec<Session> = sessions
.into_iter()
@ -472,7 +472,7 @@ pub fn test_execute_explain(
let session_variables_raw = r#"{
"x-hasura-role": "admin"
}"#;
let session_variables: HashMap<SessionVariable, SessionVariableValue> =
let session_variables: HashMap<SessionVariableName, SessionVariableValue> =
serde_json::from_str(session_variables_raw)?;
resolve_session(session_variables)
}?;
@ -579,7 +579,7 @@ pub(crate) fn test_sql(test_path_string: &str) -> anyhow::Result<()> {
let session = Arc::new({
let session_vars_path = &test_path.join("session_variables.json");
let session_variables: HashMap<SessionVariable, SessionVariableValue> =
let session_variables: HashMap<SessionVariableName, SessionVariableValue> =
serde_json::from_str(read_to_string(session_vars_path)?.as_ref())?;
resolve_session(session_variables)
}?);

View File

@ -0,0 +1,11 @@
[
{
"data": {
"AuthorMany": [
{
"author_id": 1
}
]
}
}
]

View File

@ -0,0 +1,192 @@
{
"version": "v2",
"flags": {
"json_session_variables": true
},
"subgraphs": [
{
"name": "default",
"objects": [
{
"kind": "BooleanExpressionType",
"version": "v1",
"definition": {
"name": "int_bool_exp",
"operand": {
"scalar": {
"type": "Int",
"comparisonOperators": [
{
"name": "_eq",
"argumentType": "Int!"
},
{
"name": "_in",
"argumentType": "[Int!]"
}
],
"dataConnectorOperatorMapping": [
{
"dataConnectorName": "db",
"dataConnectorScalarType": "int4",
"operatorMapping": {}
}
]
}
},
"logicalOperators": {
"enable": true
},
"isNull": {
"enable": true
},
"graphql": {
"typeName": "Int_Filter"
}
}
},
{
"kind": "ObjectType",
"version": "v1",
"definition": {
"name": "author",
"fields": [
{
"name": "author_id",
"type": "Int!"
},
{
"name": "first_name",
"type": "String!"
},
{
"name": "last_name",
"type": "String!"
}
],
"graphql": {
"typeName": "Author"
},
"dataConnectorTypeMapping": [
{
"dataConnectorName": "db",
"dataConnectorObjectType": "author",
"fieldMapping": {
"author_id": {
"column": {
"name": "id"
}
},
"first_name": {
"column": {
"name": "first_name"
}
},
"last_name": {
"column": {
"name": "last_name"
}
}
}
}
]
}
},
{
"kind": "BooleanExpressionType",
"version": "v1",
"definition": {
"name": "author_bool_exp",
"operand": {
"object": {
"type": "author",
"comparableFields": [
{
"fieldName": "author_id",
"booleanExpressionType": "int_bool_exp"
}
],
"comparableRelationships": []
}
},
"logicalOperators": {
"enable": true
},
"isNull": {
"enable": true
},
"graphql": {
"typeName": "Author_Filter"
}
}
},
{
"kind": "Model",
"version": "v1",
"definition": {
"name": "Authors",
"objectType": "author",
"source": {
"dataConnectorName": "db",
"collection": "author"
},
"graphql": {
"selectUniques": [],
"selectMany": {
"queryRootField": "AuthorMany"
},
"orderByExpressionType": "Author_Order_By"
},
"filterExpressionType": "author_bool_exp",
"orderableFields": [
{
"fieldName": "author_id",
"orderByDirections": {
"enableAll": true
}
}
]
}
},
{
"kind": "TypePermissions",
"version": "v1",
"definition": {
"typeName": "author",
"permissions": [
{
"role": "user",
"output": {
"allowedFields": ["author_id", "first_name", "last_name"]
}
}
]
}
},
{
"kind": "ModelPermissions",
"version": "v1",
"definition": {
"modelName": "Authors",
"permissions": [
{
"role": "user",
"select": {
"filter": {
"fieldComparison": {
"field": "author_id",
"operator": "_in",
"value": {
"sessionVariable": "x-hasura-allowed-author-ids"
}
}
}
}
}
]
}
}
]
}
]
}

View File

@ -0,0 +1,5 @@
query MyQuery {
AuthorMany {
author_id
}
}

View File

@ -0,0 +1,6 @@
[
{
"x-hasura-role": "user",
"x-hasura-allowed-author-ids": "[1]"
}
]

View File

@ -129,6 +129,17 @@ fn test_model_select_many_empty_select() -> anyhow::Result<()> {
)
}
#[test]
fn test_model_select_many_array_session_variable() -> anyhow::Result<()> {
let test_path_string = "execute/models/select_many/array_session_variable";
let common_metadata_path_string = "execute/common_metadata/postgres_connector_schema.json";
common::test_execution_expectation(
test_path_string,
&[common_metadata_path_string],
common::TestOpenDDPipeline::TestNDCResponses,
)
}
#[test]
fn test_model_select_many_field_arguments() -> anyhow::Result<()> {
common::test_execution_expectation_for_multiple_ndc_versions(

View File

@ -0,0 +1,52 @@
---
source: crates/engine/tests/common.rs
expression: query_ir
---
V1(
QueryRequestV1 {
queries: {
Alias(
Identifier(
"AuthorMany",
),
): Model(
ModelSelection {
target: ModelTarget {
subgraph: SubgraphName(
"default",
),
model_name: ModelName(
Identifier(
"Authors",
),
),
arguments: {},
filter: None,
order_by: [],
limit: None,
offset: None,
},
selection: {
Alias(
Identifier(
"author_id",
),
): Field(
ObjectFieldSelection {
target: ObjectFieldTarget {
field_name: FieldName(
Identifier(
"author_id",
),
),
arguments: {},
},
selection: None,
},
),
},
},
),
},
},
)

View File

@ -0,0 +1,52 @@
---
source: crates/engine/tests/common.rs
expression: query_ir
---
V1(
QueryRequestV1 {
queries: {
Alias(
Identifier(
"AuthorMany",
),
): Model(
ModelSelection {
target: ModelTarget {
subgraph: SubgraphName(
"default",
),
model_name: ModelName(
Identifier(
"Authors",
),
),
arguments: {},
filter: None,
order_by: [],
limit: None,
offset: None,
},
selection: {
Alias(
Identifier(
"author_id",
),
): Field(
ObjectFieldSelection {
target: ObjectFieldTarget {
field_name: FieldName(
Identifier(
"author_id",
),
),
arguments: {},
},
selection: None,
},
),
},
},
),
},
},
)

View File

@ -0,0 +1,13 @@
---
source: crates/engine/tests/common.rs
expression: rowsets
---
[
{
"rows": [
{
"author_id": 1
}
]
}
]

View File

@ -0,0 +1,15 @@
---
source: crates/engine/tests/common.rs
expression: rowsets
---
{
"Ok": [
{
"rows": [
{
"author_id": 1
}
]
}
]
}

View File

@ -21,7 +21,7 @@ mod tests {
use hasura_authn_core::{Identity, Role, Session, SessionVariableValue};
use lang_graphql::http::Request;
use lang_graphql::{parser::Parser, validation::normalize_request};
use open_dds::session_variables::{SessionVariable, SESSION_VARIABLE_ROLE};
use open_dds::session_variables::{SessionVariableName, SESSION_VARIABLE_ROLE};
use serde_json as json;
use std::{
collections::HashMap,
@ -160,7 +160,7 @@ mod tests {
// TODO: remove duplication between this function and 'add_session'
fn resolve_session(session_vars_path: PathBuf) -> Session {
let authorization = Identity::admin(Role::new("admin"));
let session_variables: HashMap<SessionVariable, SessionVariableValue> = {
let session_variables: HashMap<SessionVariableName, SessionVariableValue> = {
if session_vars_path.exists() {
json::from_str(fs::read_to_string(session_vars_path).unwrap().as_ref()).unwrap()
} else {

View File

@ -369,8 +369,8 @@ pub(crate) fn map_argument_value_to_ndc_type(
#[cfg(test)]
mod test {
use hasura_authn_core::{
Role, RoleAuthorization, Session, SessionVariable, SessionVariableList,
SessionVariableValue,
Role, RoleAuthorization, Session, SessionVariableList, SessionVariableName,
SessionVariableReference, SessionVariableValue,
};
use indexmap::IndexMap;
use metadata_resolve::http::SerializableHeaderName;
@ -380,7 +380,7 @@ mod test {
use std::str::FromStr;
fn make_test_session(
client_session_variables: HashMap<SessionVariable, SessionVariableValue>,
client_session_variables: HashMap<SessionVariableName, SessionVariableValue>,
) -> Session {
let authenticated_session_variables = HashMap::new();
@ -459,7 +459,10 @@ mod test {
let mut additional = IndexMap::new();
additional.insert(
SerializableHeaderName::new("name".into()).unwrap(),
ValueExpression::SessionVariable(SessionVariable::from_str("x-name").unwrap()),
ValueExpression::SessionVariable(SessionVariableReference {
name: SessionVariableName::from_str("x-name").unwrap(),
passed_as_json: false,
}),
);
let http_headers = HttpHeadersPreset {
forward: vec![],
@ -471,7 +474,7 @@ mod test {
// what session variables do we have?
let mut client_session_variables = HashMap::new();
client_session_variables.insert(
SessionVariable::from_str("x-name").unwrap(),
SessionVariableName::from_str("x-name").unwrap(),
SessionVariableValue::new("Mr Horse"),
);

View File

@ -5,7 +5,7 @@ use open_dds::{
arguments::ArgumentName,
data_connector::{DataConnectorColumnName, DataConnectorName},
relationships::RelationshipName,
session_variables::SessionVariable,
session_variables::SessionVariableName,
types::{CustomTypeName, FieldName},
};
use serde_json as json;
@ -118,7 +118,9 @@ pub enum InternalDeveloperError {
},
#[error("Required session variable not found in the request: {session_variable}")]
MissingSessionVariable { session_variable: SessionVariable },
MissingSessionVariable {
session_variable: SessionVariableName,
},
#[error("Unable to typecast session variable. Expected: {expected:}, but found: {found:}")]
VariableTypeCast { expected: String, found: String },

View File

@ -223,13 +223,13 @@ pub(crate) fn make_argument_from_value_expression(
match val_expr {
metadata_resolve::ValueExpression::Literal(val) => Ok(val.clone()),
metadata_resolve::ValueExpression::SessionVariable(session_var) => {
let value = session_variables.get(session_var).ok_or_else(|| {
let value = session_variables.get(&session_var.name).ok_or_else(|| {
error::InternalDeveloperError::MissingSessionVariable {
session_variable: session_var.clone(),
session_variable: session_var.name.clone(),
}
})?;
typecast_session_variable(value, value_type)
typecast_session_variable(session_var.passed_as_json, value, value_type)
}
}
}
@ -247,14 +247,14 @@ pub(crate) fn make_argument_from_value_expression_or_predicate<'s>(
Ok(Argument::Literal { value: val.clone() })
}
metadata_resolve::ValueExpressionOrPredicate::SessionVariable(session_var) => {
let value = session_variables.get(session_var).ok_or_else(|| {
let value = session_variables.get(&session_var.name).ok_or_else(|| {
error::InternalDeveloperError::MissingSessionVariable {
session_variable: session_var.clone(),
session_variable: session_var.name.clone(),
}
})?;
Ok(Argument::Literal {
value: typecast_session_variable(value, value_type)?,
value: typecast_session_variable(session_var.passed_as_json, value, value_type)?,
})
}
metadata_resolve::ValueExpressionOrPredicate::BooleanExpression(model_predicate) => {
@ -274,6 +274,18 @@ pub(crate) fn make_argument_from_value_expression_or_predicate<'s>(
/// Typecast a stringified session variable into a given type, but as a serde_json::Value
fn typecast_session_variable(
passed_as_json: bool,
session_var_value_wrapped: &SessionVariableValue,
to_type: &QualifiedTypeReference,
) -> Result<serde_json::Value, error::Error> {
if passed_as_json {
typecast_session_variable_v2(session_var_value_wrapped, to_type)
} else {
typecast_session_variable_v1(session_var_value_wrapped, to_type)
}
}
fn typecast_session_variable_v1(
session_var_value_wrapped: &SessionVariableValue,
to_type: &QualifiedTypeReference,
) -> Result<serde_json::Value, error::Error> {
@ -328,3 +340,73 @@ fn typecast_session_variable(
QualifiedBaseType::List(_) => Err(error::InternalDeveloperError::VariableArrayTypeCast)?,
}
}
fn typecast_session_variable_v2(
session_var_value: &SessionVariableValue,
to_type: &QualifiedTypeReference,
) -> Result<serde_json::Value, error::Error> {
let value = serde_json::from_str(&session_var_value.0)?;
typecheck_session_variable(&value, to_type)?;
Ok(value)
}
fn typecheck_session_variable(
value: &serde_json::Value,
to_type: &QualifiedTypeReference,
) -> Result<(), crate::Error> {
match &to_type.underlying_type {
QualifiedBaseType::Named(type_name) => match type_name {
QualifiedTypeName::Inbuilt(primitive) => match primitive {
InbuiltType::Int => {
if !value.is_i64() {
Err(error::InternalDeveloperError::VariableTypeCast {
expected: "int".into(),
found: value.to_string(),
})?;
}
Ok(())
}
InbuiltType::Float => {
if !value.is_f64() {
Err(error::InternalDeveloperError::VariableTypeCast {
expected: "float".into(),
found: value.to_string(),
})?;
}
Ok(())
}
InbuiltType::Boolean => {
if !value.is_boolean() {
Err(error::InternalDeveloperError::VariableTypeCast {
expected: "true or false".into(),
found: value.to_string(),
})?;
}
Ok(())
}
InbuiltType::ID | InbuiltType::String => {
if !value.is_string() {
Err(error::InternalDeveloperError::VariableTypeCast {
expected: "string".into(),
found: value.to_string(),
})?;
}
Ok(())
}
},
QualifiedTypeName::Custom(_) => Ok(()),
},
QualifiedBaseType::List(element_type) => {
let elements = value.as_array().ok_or_else(|| {
error::InternalDeveloperError::VariableTypeCast {
expected: "array".into(),
found: value.to_string(),
}
})?;
for element in elements {
typecheck_session_variable(element, element_type)?;
}
Ok(())
}
}
}

View File

@ -253,6 +253,7 @@ pub fn get_argument_mappings<'a>(
/// type to validate it against to ensure the fields it refers to
/// exist etc
pub(crate) fn resolve_value_expression_for_argument(
flags: &open_dds::flags::Flags,
argument_name: &open_dds::arguments::ArgumentName,
value_expression: &open_dds::permissions::ValueExpressionOrPredicate,
argument_type: &QualifiedTypeReference,
@ -278,7 +279,10 @@ pub(crate) fn resolve_value_expression_for_argument(
match value_expression {
open_dds::permissions::ValueExpressionOrPredicate::SessionVariable(session_variable) => {
Ok::<ValueExpressionOrPredicate, Error>(ValueExpressionOrPredicate::SessionVariable(
session_variable.clone(),
hasura_authn_core::SessionVariableReference {
name: session_variable.clone(),
passed_as_json: flags.json_session_variables,
},
))
}
open_dds::permissions::ValueExpressionOrPredicate::Literal(json_value) => {
@ -365,6 +369,7 @@ pub(crate) fn resolve_value_expression_for_argument(
})?;
let resolved_model_predicate = model_permissions::resolve_model_predicate_with_type(
flags,
bool_exp,
base_type,
object_type_representation,

View File

@ -304,17 +304,7 @@ fn build_preset_map_from_input_object_type_permission(
field_path: new_field_path,
};
let value = (
type_reference.clone(),
match &preset.value {
open_dds::permissions::ValueExpression::Literal(literal) => {
ValueExpressionOrPredicate::Literal(literal.clone())
}
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
ValueExpressionOrPredicate::SessionVariable(session_variable.clone())
}
},
);
let value = (type_reference.clone(), preset.value.clone());
Ok((key, value))
})

View File

@ -38,6 +38,7 @@ fn get_command_source_argument<'a>(
}
pub fn resolve_command_permissions(
flags: &open_dds::flags::Flags,
command: &commands::Command,
permissions: &CommandPermissionsV1,
object_types: &BTreeMap<
@ -97,6 +98,7 @@ pub fn resolve_command_permissions(
match command.arguments.get(&argument_preset.argument) {
Some(argument) => {
let value_expression = resolve_value_expression_for_argument(
flags,
&argument_preset.argument,
&argument_preset.value,
&argument.argument_type,

View File

@ -67,6 +67,7 @@ pub fn resolve(
})?;
if command.permissions.is_empty() {
command.permissions = command_permission::resolve_command_permissions(
&metadata_accessor.flags,
&command.command,
command_permissions,
object_types,

View File

@ -29,12 +29,15 @@ pub fn resolve<'a>(
let qualified_data_connector_name =
Qualified::new(subgraph.clone(), data_connector.name.clone());
let (data_connector_context, connector_issues) =
types::DataConnectorContext::new(data_connector, &configuration.unstable_features)
.map_err(|error| NamedDataConnectorError {
data_connector_name: qualified_data_connector_name.clone(),
error,
})?;
let (data_connector_context, connector_issues) = types::DataConnectorContext::new(
metadata_accessor,
data_connector,
&configuration.unstable_features,
)
.map_err(|error| NamedDataConnectorError {
data_connector_name: qualified_data_connector_name.clone(),
error,
})?;
issues.extend(
connector_issues

View File

@ -12,6 +12,7 @@ use crate::types::subgraph::Qualified;
use indexmap::IndexMap;
use lang_graphql::ast::common::OperationType;
use ndc_models;
use open_dds::accessor::MetadataAccessor;
use open_dds::data_connector::DataConnectorColumnName;
use open_dds::types::DataConnectorArgumentName;
use open_dds::{
@ -63,6 +64,7 @@ pub struct DataConnectorContext<'a> {
impl<'a> DataConnectorContext<'a> {
pub fn new(
metadata_accessor: &MetadataAccessor,
data_connector: &'a data_connector::DataConnectorLinkV1,
unstable_features: &UnstableFeatures,
) -> Result<(Self, Vec<DataConnectorIssue>), DataConnectorError> {
@ -102,7 +104,8 @@ impl<'a> DataConnectorContext<'a> {
.argument_presets
.iter()
.map(|argument_preset| -> Result<_, DataConnectorError> {
let header_presets = HttpHeadersPreset::new(&argument_preset.value.http_headers)?;
let header_presets =
HttpHeadersPreset::new(metadata_accessor, &argument_preset.value.http_headers)?;
Ok(ArgumentPreset {
name: argument_preset.argument.clone(),
value: ArgumentPresetValue {
@ -376,6 +379,7 @@ pub struct HttpHeadersPreset {
impl HttpHeadersPreset {
fn new(
metadata_accessor: &MetadataAccessor,
headers_preset: &open_dds::data_connector::HttpHeadersPreset,
) -> Result<Self, DataConnectorError> {
let forward = headers_preset
@ -389,7 +393,7 @@ impl HttpHeadersPreset {
.iter()
.map(|(header_name, header_val)| {
let key = SerializableHeaderName::new(header_name.to_string()).map_err(to_error)?;
let val = resolve_value_expression(header_val.clone());
let val = resolve_value_expression(metadata_accessor, header_val.clone());
Ok((key, val))
})
.collect::<Result<IndexMap<_, _>, DataConnectorError>>()?;
@ -402,11 +406,15 @@ impl HttpHeadersPreset {
}
fn resolve_value_expression(
metadata_accessor: &MetadataAccessor,
value_expression_input: open_dds::permissions::ValueExpression,
) -> ValueExpression {
match value_expression_input {
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
ValueExpression::SessionVariable(session_variable)
ValueExpression::SessionVariable(hasura_authn_core::SessionVariableReference {
name: session_variable,
passed_as_json: metadata_accessor.flags.json_session_variables,
})
}
open_dds::permissions::ValueExpression::Literal(json_value) => {
ValueExpression::Literal(json_value)
@ -465,7 +473,7 @@ pub struct DataConnectorCapabilities {
#[cfg(test)]
mod tests {
use ndc_models;
use open_dds::data_connector::DataConnectorLinkV1;
use open_dds::{accessor::MetadataAccessor, data_connector::DataConnectorLinkV1, Metadata};
use strum::IntoEnumIterator;
use crate::{
@ -525,9 +533,13 @@ mod tests {
enable_ndc_v02_support: true,
..Default::default()
};
let (context, issues) =
DataConnectorContext::new(&data_connector_with_capabilities, &unstable_features)
.unwrap();
let metadata_accessor = MetadataAccessor::new(Metadata::WithoutNamespaces(vec![]));
let (context, issues) = DataConnectorContext::new(
&metadata_accessor,
&data_connector_with_capabilities,
&unstable_features,
)
.unwrap();
assert_eq!(context.capabilities, explicit_capabilities);
assert_eq!(context.supported_ndc_version, NdcVersion::V01);
assert_eq!(issues.len(), 0, "Issues: {issues:#?}");
@ -566,9 +578,13 @@ mod tests {
enable_ndc_v02_support: true,
..Default::default()
};
let (context, issues) =
DataConnectorContext::new(&data_connector_with_capabilities, &unstable_features)
.unwrap();
let metadata_accessor = MetadataAccessor::new(Metadata::WithoutNamespaces(vec![]));
let (context, issues) = DataConnectorContext::new(
&metadata_accessor,
&data_connector_with_capabilities,
&unstable_features,
)
.unwrap();
assert_eq!(context.capabilities, explicit_capabilities);
assert_eq!(context.supported_ndc_version, NdcVersion::V02);
assert_eq!(issues.len(), 0, "Issues: {issues:#?}");

View File

@ -81,6 +81,7 @@ pub fn resolve(
.and_then(|bool_exp| bool_exp.graphql.as_ref());
let select_permissions = model_permission::resolve_model_select_permissions(
&metadata_accessor.flags,
&model.model,
subgraph,
permissions,

View File

@ -33,6 +33,7 @@ use ref_cast::RefCast;
use std::collections::BTreeMap;
fn resolve_model_predicate_with_model(
flags: &open_dds::flags::Flags,
model_predicate: &open_dds::permissions::ModelPredicate,
model: &models::Model,
subgraph: &SubgraphName,
@ -102,6 +103,7 @@ fn resolve_model_predicate_with_model(
)?;
resolve_model_predicate_with_type(
flags,
model_predicate,
&model.data_type,
object_type_representation,
@ -136,6 +138,7 @@ pub fn get_model_source_argument<'a>(
}
pub fn resolve_model_select_permissions(
flags: &open_dds::flags::Flags,
model: &models::Model,
subgraph: &SubgraphName,
model_permissions: &ModelPermissionsV1,
@ -163,6 +166,7 @@ pub fn resolve_model_select_permissions(
let resolved_predicate = match &select.filter {
NullableModelPredicate::NotNull(model_predicate) => {
resolve_model_predicate_with_model(
flags,
model_predicate,
model,
subgraph,
@ -210,6 +214,7 @@ pub fn resolve_model_select_permissions(
match model.arguments.get(&argument_preset.argument) {
Some(argument) => {
let value_expression = resolve_value_expression_for_argument(
flags,
&argument_preset.argument,
&argument_preset.value,
&argument.argument_type,
@ -269,6 +274,7 @@ pub fn resolve_model_select_permissions(
/// re-add in future. Because this function takes the `data_connector_field_mappings` as an input,
/// many of the errors thrown in `resolve_model_predicate` are pushed out.
pub(crate) fn resolve_model_predicate_with_type(
flags: &open_dds::flags::Flags,
model_predicate: &open_dds::permissions::ModelPredicate,
type_name: &Qualified<CustomTypeName>,
object_type_representation: &object_relationships::ObjectTypeWithRelationships,
@ -385,7 +391,10 @@ pub(crate) fn resolve_model_predicate_with_type(
ValueExpression::Literal(json_value.clone())
}
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
ValueExpression::SessionVariable(session_variable.clone())
ValueExpression::SessionVariable(hasura_authn_core::SessionVariableReference {
name: session_variable.clone(),
passed_as_json: flags.json_session_variables,
})
}
};
@ -641,6 +650,7 @@ pub(crate) fn resolve_model_predicate_with_type(
}?;
let target_model_predicate = resolve_model_predicate_with_type(
flags,
nested_predicate,
&target_model.inner.data_type,
target_object_type,
@ -682,6 +692,7 @@ pub(crate) fn resolve_model_predicate_with_type(
open_dds::permissions::ModelPredicate::Not(predicate) => {
let resolved_predicate = resolve_model_predicate_with_type(
flags,
predicate,
type_name,
object_type_representation,
@ -702,6 +713,7 @@ pub(crate) fn resolve_model_predicate_with_type(
let mut resolved_predicates = Vec::new();
for predicate in predicates {
resolved_predicates.push(resolve_model_predicate_with_type(
flags,
predicate,
type_name,
object_type_representation,
@ -723,6 +735,7 @@ pub(crate) fn resolve_model_predicate_with_type(
let mut resolved_predicates = Vec::new();
for predicate in predicates {
resolved_predicates.push(resolve_model_predicate_with_type(
flags,
predicate,
type_name,
object_type_representation,

View File

@ -12,6 +12,7 @@ use crate::types::subgraph::Qualified;
use crate::helpers::typecheck;
use crate::stages::object_types;
use crate::ValueExpressionOrPredicate;
/// resolve type permissions
pub fn resolve(
@ -54,6 +55,7 @@ pub fn resolve(
output_type_permission,
)?;
object_type.type_input_permissions = resolve_input_type_permission(
&metadata_accessor.flags,
&object_type.object_type,
output_type_permission,
)?;
@ -97,6 +99,7 @@ pub fn resolve_output_type_permission(
}
pub(crate) fn resolve_input_type_permission(
flags: &open_dds::flags::Flags,
object_type_representation: &object_types::ObjectTypeRepresentation,
type_permissions: &TypePermissionsV1,
) -> Result<BTreeMap<Role, TypeInputPermission>, TypeInputPermissionError> {
@ -133,10 +136,23 @@ pub(crate) fn resolve_input_type_permission(
);
}
};
let resolved_value = match &value {
open_dds::permissions::ValueExpression::Literal(literal) => {
ValueExpressionOrPredicate::Literal(literal.clone())
}
open_dds::permissions::ValueExpression::SessionVariable(session_variable) => {
ValueExpressionOrPredicate::SessionVariable(
hasura_authn_core::SessionVariableReference {
name: session_variable.clone(),
passed_as_json: flags.json_session_variables,
},
)
}
};
resolved_field_presets.insert(
field_name.clone(),
FieldPresetInfo {
value: value.clone(),
value: resolved_value,
deprecated: field_definition.deprecated.clone(),
},
);

View File

@ -1,12 +1,12 @@
use std::collections::BTreeMap;
use open_dds::{
permissions::{Role, TypeOutputPermission, ValueExpression},
permissions::{Role, TypeOutputPermission},
types::Deprecated,
};
use crate::stages::object_types;
use crate::Qualified;
use crate::{stages::object_types, ValueExpressionOrPredicate};
use open_dds::types::{CustomTypeName, FieldName};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@ -43,7 +43,7 @@ pub struct TypeInputPermission {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct FieldPresetInfo {
pub value: ValueExpression,
pub value: ValueExpressionOrPredicate,
pub deprecated: Option<Deprecated>,
}

View File

@ -4,12 +4,12 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum ValueExpression {
Literal(serde_json::Value),
SessionVariable(open_dds::session_variables::SessionVariable),
SessionVariable(open_dds::session_variables::SessionVariableReference),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum ValueExpressionOrPredicate {
Literal(serde_json::Value),
SessionVariable(open_dds::session_variables::SessionVariable),
SessionVariable(open_dds::session_variables::SessionVariableReference),
BooleanExpression(Box<model_permissions::ModelPredicate>),
}

View File

@ -829,9 +829,12 @@ input_file: crates/metadata-resolve/tests/passing/boolean_expression_type/partia
nullable: true,
},
value: SessionVariable(
SessionVariable(
"x-hasura-user-id",
),
SessionVariableReference {
name: SessionVariableName(
"x-hasura-user-id",
),
passed_as_json: false,
},
),
deprecated: None,
},

View File

@ -818,9 +818,12 @@ input_file: crates/metadata-resolve/tests/passing/missing_subgraph_when_ignoring
nullable: false,
},
value: SessionVariable(
SessionVariable(
"x-hasura-user-id",
),
SessionVariableReference {
name: SessionVariableName(
"x-hasura-user-id",
),
passed_as_json: false,
},
),
deprecated: None,
},

View File

@ -3254,6 +3254,10 @@
"allow_partial_supergraph": {
"default": false,
"type": "boolean"
},
"json_session_variables": {
"default": false,
"type": "boolean"
}
},
"additionalProperties": false
@ -3294,7 +3298,8 @@
"disallow_scalar_type_names_conflicting_with_inbuilt_types": false,
"propagate_boolean_expression_deprecation_status": false,
"require_unique_command_graphql_names": false,
"allow_partial_supergraph": false
"allow_partial_supergraph": false,
"json_session_variables": false
},
"allOf": [
{
@ -3346,7 +3351,8 @@
"disallow_scalar_type_names_conflicting_with_inbuilt_types": false,
"propagate_boolean_expression_deprecation_status": false,
"require_unique_command_graphql_names": false,
"allow_partial_supergraph": false
"allow_partial_supergraph": false,
"json_session_variables": false
},
"allOf": [
{
@ -3388,7 +3394,8 @@
"disallow_scalar_type_names_conflicting_with_inbuilt_types": false,
"propagate_boolean_expression_deprecation_status": false,
"require_unique_command_graphql_names": false,
"allow_partial_supergraph": false
"allow_partial_supergraph": false,
"json_session_variables": false
},
"allOf": [
{

View File

@ -32,6 +32,9 @@ pub struct Flags {
#[opendd(default, rename = "allow_partial_supergraph")]
pub allow_partial_supergraph: bool,
#[opendd(default, rename = "json_session_variables")]
pub json_session_variables: bool,
}
impl Flags {
@ -46,6 +49,7 @@ impl Flags {
propagate_boolean_expression_deprecation_status: false,
require_unique_command_graphql_names: false,
allow_partial_supergraph: false,
json_session_variables: false,
}
}

View File

@ -8,7 +8,7 @@ use crate::{
commands::CommandName,
models::ModelName,
relationships::RelationshipName,
session_variables::SessionVariable,
session_variables::SessionVariableName,
traits,
types::{CustomTypeName, FieldName, OperatorName},
};
@ -649,7 +649,7 @@ pub enum ValueExpression {
#[schemars(title = "Literal")]
Literal(JsonValue),
#[schemars(title = "SessionVariable")]
SessionVariable(SessionVariable),
SessionVariable(SessionVariableName),
}
#[derive(
@ -663,7 +663,7 @@ pub enum ValueExpressionOrPredicate {
#[schemars(title = "Literal")]
Literal(JsonValue),
#[schemars(title = "SessionVariable")]
SessionVariable(SessionVariable),
SessionVariable(SessionVariableName),
#[schemars(title = "BooleanExpression")]
BooleanExpression(Box<ModelPredicate>),
}

View File

@ -7,23 +7,33 @@ use std::str::FromStr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Used to represent a reference to a session variable,
/// where one is required in the IR
#[derive(Debug, Clone, Hash, PartialEq, Eq, JsonSchema, Serialize, Deserialize)]
#[schemars(rename = "OpenDdSessionVariableReference")]
pub struct SessionVariableReference {
pub name: SessionVariableName,
pub passed_as_json: bool,
}
/// Used to represent the name of a session variable, like
/// "x-hasura-role".
#[derive(Debug, Clone, Hash, PartialEq, Eq, JsonSchema, Serialize, Deserialize)]
#[schemars(rename = "OpenDdSessionVariable")]
pub struct SessionVariable(Cow<'static, str>);
pub struct SessionVariableName(Cow<'static, str>);
pub const SESSION_VARIABLE_ROLE: SessionVariable = SessionVariable(Cow::Borrowed("x-hasura-role"));
pub const SESSION_VARIABLE_ROLE: SessionVariableName =
SessionVariableName(Cow::Borrowed("x-hasura-role"));
impl FromStr for SessionVariable {
impl FromStr for SessionVariableName {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(SessionVariable(s.trim().to_lowercase().into()))
Ok(SessionVariableName(s.trim().to_lowercase().into()))
}
}
impl std::fmt::Display for SessionVariable {
impl std::fmt::Display for SessionVariableName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
@ -54,12 +64,12 @@ mod tests {
fn serialize_and_deserialize_session_variable() {
let mut session_variables = HashMap::new();
session_variables.insert(
SessionVariable("test-role".into()),
SessionVariableName("test-role".into()),
SessionVariableValue("test-role".into()),
);
let json_str = serde_json::to_string(&session_variables).unwrap();
let parsed_from_string: HashMap<SessionVariable, SessionVariableValue> =
let parsed_from_string: HashMap<SessionVariableName, SessionVariableValue> =
serde_json::from_str(json_str.trim()).unwrap();
assert_eq!(parsed_from_string, session_variables);