mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 11:07:11 +03:00
Replace lazy_static
with stdlib equivalents. (#758)
### What The `lazy_static` macro is poorly maintained, fairly bloated, and has been mostly superseded by [`OnceLock`](https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html) in the stdlib. ### How 1. I turned a couple of `static ref` values into `const`, sometimes by creating `const fn` equivalents to other functions. 2. I inlined static behavior to construct a JSON pointer into some tests, where we don't care too much about losing a few milliseconds. 3. For the rest, I replaced `lazy_static` with a `static OnceLock` and a call to `OnceLock::get_or_init`. V3_GIT_ORIGIN_REV_ID: 18e4150a5fb24fe71f6ed77fe6178b7942405aa3
This commit is contained in:
parent
82c0c65bd0
commit
5d619a540f
6
v3/Cargo.lock
generated
6
v3/Cargo.lock
generated
@ -2170,7 +2170,6 @@ dependencies = [
|
||||
"jsonwebkey",
|
||||
"jsonwebtoken",
|
||||
"lang-graphql",
|
||||
"lazy_static",
|
||||
"mockito",
|
||||
"open-dds",
|
||||
"openssl",
|
||||
@ -2191,7 +2190,6 @@ dependencies = [
|
||||
"axum",
|
||||
"hasura-authn-core",
|
||||
"lang-graphql",
|
||||
"lazy_static",
|
||||
"mockito",
|
||||
"open-dds",
|
||||
"rand",
|
||||
@ -2598,7 +2596,6 @@ dependencies = [
|
||||
"http",
|
||||
"human_bytes",
|
||||
"indexmap 2.2.6",
|
||||
"lazy_static",
|
||||
"lexical-core",
|
||||
"nonempty",
|
||||
"postcard",
|
||||
@ -2783,7 +2780,6 @@ dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"insta",
|
||||
"lang-graphql",
|
||||
"lazy_static",
|
||||
"ndc-models",
|
||||
"nonempty",
|
||||
"open-dds",
|
||||
@ -3062,7 +3058,6 @@ dependencies = [
|
||||
"goldenfile",
|
||||
"indexmap 2.2.6",
|
||||
"jsonschema-tidying",
|
||||
"lazy_static",
|
||||
"ndc-models",
|
||||
"opendds-derive",
|
||||
"pretty_assertions",
|
||||
@ -3082,7 +3077,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"darling",
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
@ -82,7 +82,6 @@ json_value_merge = "2"
|
||||
jsonptr = "0.4"
|
||||
jsonwebkey = "0.3"
|
||||
jsonwebtoken = "8"
|
||||
lazy_static = "1"
|
||||
lexical-core = "0.8"
|
||||
mockito = { version = "1", default-features = false }
|
||||
nonempty = "0.10"
|
||||
|
@ -179,7 +179,7 @@ pub async fn resolve_session<'a, B>(
|
||||
Ok(h) => SessionVariableValue::new(h),
|
||||
};
|
||||
|
||||
if session_variable == SESSION_VARIABLE_ROLE.to_owned() {
|
||||
if session_variable == SESSION_VARIABLE_ROLE {
|
||||
role = Some(Role::new(&variable_value.0));
|
||||
} else {
|
||||
// TODO: Handle the duplicate case?
|
||||
|
@ -17,7 +17,6 @@ axum = { workspace = true }
|
||||
cookie = { workspace = true }
|
||||
jsonptr = { workspace = true }
|
||||
jsonwebtoken = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
schemars = { workspace = true, features = ["url"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
@ -148,7 +148,7 @@ mod tests {
|
||||
) -> anyhow::Result<String> {
|
||||
let claims: Claims = get_claims(
|
||||
&serde_json::to_value(hasura_claims)?,
|
||||
&DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER,
|
||||
jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]).as_str(),
|
||||
)?;
|
||||
let jwt_header = jwt::Header {
|
||||
alg,
|
||||
@ -180,27 +180,25 @@ mod tests {
|
||||
async fn test_unsuccessful_role_emulation() -> anyhow::Result<()> {
|
||||
let encoded_claims = get_encoded_claims(Algorithm::HS256, &get_default_hasura_claims())?;
|
||||
|
||||
let jwt_secret_config_json = json!(
|
||||
{
|
||||
"key": {
|
||||
"fixed": {
|
||||
let jwt_secret_config_json = json!({
|
||||
"key": {
|
||||
"fixed": {
|
||||
"algorithm": "HS256",
|
||||
"key": {
|
||||
"value": "token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization"
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
"value": "token"
|
||||
},
|
||||
},
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization",
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let jwt_config: JWTConfig = serde_json::from_value(jwt_secret_config_json)?;
|
||||
|
||||
@ -279,27 +277,25 @@ mod tests {
|
||||
);
|
||||
let encoded_claims = get_encoded_claims(Algorithm::HS256, &hasura_claims)?;
|
||||
|
||||
let jwt_secret_config_json = json!(
|
||||
{
|
||||
"key": {
|
||||
"fixed": {
|
||||
let jwt_secret_config_json = json!({
|
||||
"key": {
|
||||
"fixed": {
|
||||
"algorithm": "HS256",
|
||||
"key": {
|
||||
"value": "token"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization"
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
"value": "token",
|
||||
},
|
||||
}
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization",
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let jwt_config: JWTConfig = serde_json::from_value(jwt_secret_config_json)?;
|
||||
|
||||
|
@ -8,7 +8,6 @@ use hasura_authn_core::{Role, SessionVariable, SessionVariableValue};
|
||||
use jsonptr::Pointer;
|
||||
use jsonwebtoken::{self as jwt, decode, DecodingKey, Validation};
|
||||
use jwt::decode_header;
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::header::{AUTHORIZATION, COOKIE};
|
||||
use reqwest::StatusCode;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
@ -24,14 +23,7 @@ use url::Url;
|
||||
|
||||
/// Name of the key, which is by default used to lookup the Hasura claims
|
||||
/// in the claims obtained after decoding the JWT.
|
||||
const DEFAULT_HASURA_CLAIMS_NAMESPACE: &str = "claims.jwt.hasura.io";
|
||||
|
||||
lazy_static! {
|
||||
/// Make top level JSON pointer of the `DEFAULT_HASURA_CLAIMS_NAMESPACE`
|
||||
/// by escaping the `/` by `~1`.
|
||||
pub(crate) static ref DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER: String =
|
||||
"/".to_owned() + &DEFAULT_HASURA_CLAIMS_NAMESPACE.replace('/', "~1");
|
||||
}
|
||||
pub(crate) const DEFAULT_HASURA_CLAIMS_NAMESPACE: &str = "claims.jwt.hasura.io";
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
@ -361,7 +353,7 @@ impl JWTConfig {
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -723,7 +715,7 @@ mod tests {
|
||||
let hasura_claims = get_default_hasura_claims();
|
||||
let claims: Claims = get_claims(
|
||||
&serde_json::to_value(hasura_claims)?,
|
||||
&DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER,
|
||||
jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]).as_str(),
|
||||
)?;
|
||||
let jwt_header = jwt::Header {
|
||||
alg,
|
||||
@ -851,7 +843,7 @@ mod tests {
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -886,7 +878,7 @@ mod tests {
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "StringifiedJson",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -898,7 +890,7 @@ mod tests {
|
||||
let stringified_hasura_claims = serde_json::to_string(&hasura_claims)?;
|
||||
let claims = get_claims(
|
||||
&serde_json::to_value(stringified_hasura_claims)?,
|
||||
&DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER,
|
||||
jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]).as_str(),
|
||||
)?;
|
||||
let alg = jwt::Algorithm::HS256;
|
||||
let jwt_header = jwt::Header {
|
||||
@ -1105,7 +1097,7 @@ mod tests {
|
||||
|
||||
let claims: Claims = get_claims(
|
||||
&serde_json::to_value(&hasura_claims)?,
|
||||
&DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER,
|
||||
jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]).as_str(),
|
||||
)?;
|
||||
|
||||
let mut jwt_header = jwt::Header::new(jwt::Algorithm::ES256);
|
||||
@ -1119,21 +1111,20 @@ mod tests {
|
||||
|
||||
let jwk_url = url + "/jwk";
|
||||
|
||||
let jwt_config_json = json!(
|
||||
{
|
||||
"key": {
|
||||
"jwkFromUrl": jwk_url
|
||||
let jwt_config_json = json!({
|
||||
"key": {
|
||||
"jwkFromUrl": jwk_url,
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization",
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
},
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization"
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
let jwt_config: JWTConfig = serde_json::from_value(jwt_config_json)?;
|
||||
@ -1334,23 +1325,21 @@ mod tests {
|
||||
let hasura_claims = get_default_hasura_claims();
|
||||
let claims: Claims = get_claims(
|
||||
&serde_json::to_value(hasura_claims.clone())?,
|
||||
&DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER,
|
||||
jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]).as_str(),
|
||||
)?;
|
||||
let encoded_claims = encode(&jwt_header, &claims, &encoding_key)?;
|
||||
let jwt_secret_config_json = json!(
|
||||
{
|
||||
"key": jwt_key_config,
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization"
|
||||
let jwt_secret_config_json = json!({
|
||||
"key": jwt_key_config,
|
||||
"tokenLocation": {
|
||||
"type": "BearerAuthorization"
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": jsonptr::Pointer::new([DEFAULT_HASURA_CLAIMS_NAMESPACE]),
|
||||
},
|
||||
"claimsConfig": {
|
||||
"namespace": {
|
||||
"claimsFormat": "Json",
|
||||
"location": *DEFAULT_HASURA_CLAIMS_NAMESPACE_POINTER
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
let jwt_config: JWTConfig = serde_json::from_value(jwt_secret_config_json)?;
|
||||
let http_client = reqwest::Client::new();
|
||||
let decoded_claims =
|
||||
|
@ -14,8 +14,7 @@ open-dds = { path = "../../open-dds" }
|
||||
tracing-util = { path = "../../utils/tracing-util" }
|
||||
|
||||
axum = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
schemars = { workspace = true, features = ["smol_str", "url"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
@ -1,15 +1,13 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use auth_base::{Identity, Role, RoleAuthorization, SessionVariable, SessionVariableValue};
|
||||
use axum::{
|
||||
http::{HeaderMap, HeaderName, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::{header::ToStrError, Url};
|
||||
use serde::{de::Error as SerdeDeError, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
@ -78,29 +76,6 @@ impl IntoResponse for Error {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Ignore the following list of request headers, sent by the client
|
||||
/// when making a GET request to the auth hook. Note that, in the case
|
||||
/// the auth hook mode is `POST`, this is *not* applicable i.e. all the
|
||||
/// headers sent by the client are forwarded to the auth hook.
|
||||
static ref COMMON_CLIENT_HEADERS_TO_IGNORE: HashSet<String> = HashSet::from([
|
||||
"Content-Length",
|
||||
"Content-MD5",
|
||||
"User-Agent",
|
||||
"Host",
|
||||
"Origin",
|
||||
"Referer",
|
||||
"Accept",
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Accept-Datetime",
|
||||
"Cache-Control",
|
||||
"Connection",
|
||||
"DNT",
|
||||
"Content-Type",
|
||||
].map(str::to_lowercase));
|
||||
}
|
||||
|
||||
fn serialize_url<S>(url: &Url, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@ -171,7 +146,7 @@ async fn make_auth_hook_request(
|
||||
AuthHookMethod::Get => {
|
||||
let mut auth_hook_headers = tracing_util::get_trace_headers();
|
||||
for (header_name, header_value) in client_headers {
|
||||
if !COMMON_CLIENT_HEADERS_TO_IGNORE.contains(header_name.as_str()) {
|
||||
if !ignore_header(header_name.as_str()) {
|
||||
auth_hook_headers.insert(header_name, header_value.clone());
|
||||
}
|
||||
}
|
||||
@ -306,6 +281,41 @@ pub async fn authenticate_request(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Ignore the following list of request headers, sent by the client when making a GET request to
|
||||
/// the auth hook.
|
||||
///
|
||||
/// Note that, in the case the auth hook mode is `POST`, this is *not* applicable, i.e. all the
|
||||
/// headers sent by the client are forwarded to the auth hook.
|
||||
const COMMON_CLIENT_HEADERS_TO_IGNORE: [&str; 14] = [
|
||||
"Accept",
|
||||
"Accept-Datetime",
|
||||
"Accept-Encoding",
|
||||
"Accept-Language",
|
||||
"Cache-Control",
|
||||
"Connection",
|
||||
"Content-Length",
|
||||
"Content-MD5",
|
||||
"Content-Type",
|
||||
"DNT",
|
||||
"Host",
|
||||
"Origin",
|
||||
"Referer",
|
||||
"User-Agent",
|
||||
];
|
||||
|
||||
/// Decides whether to ignore the given header sent by the client when making a GET request to
|
||||
/// the auth hook.
|
||||
///
|
||||
/// The header is compared to a static list of headers (above).
|
||||
///
|
||||
/// Note that, in the case the auth hook mode is `POST`, this is *not* applicable, i.e. all the
|
||||
/// headers sent by the client are forwarded to the auth hook.
|
||||
fn ignore_header(header: &str) -> bool {
|
||||
static CELL: OnceLock<HashSet<String>> = OnceLock::new();
|
||||
CELL.get_or_init(|| HashSet::from(COMMON_CLIENT_HEADERS_TO_IGNORE.map(str::to_lowercase)))
|
||||
.contains(header)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
@ -37,7 +37,6 @@ serde_json = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
smol_str = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
@ -3,6 +3,8 @@ This module provides functions to generate introspection result as GraphQL schem
|
||||
for each namespace from the schema.
|
||||
*/
|
||||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -21,24 +23,6 @@ pub enum Error {
|
||||
SerializeJson(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
|
||||
static ref INTROSPECTION_REQUEST: Result<crate::http::Request, String> = {
|
||||
Ok(
|
||||
crate::http::Request {
|
||||
operation_name: None,
|
||||
query: {
|
||||
let query_str = include_str!("introspection_query.graphql");
|
||||
crate::parser::Parser::new(query_str)
|
||||
.parse_executable_document()
|
||||
.map_err(|e| e.to_string())?
|
||||
},
|
||||
variables: HashMap::new(),
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Generate GraphQL schema for a given namespace
|
||||
pub fn build_namespace_schema<
|
||||
S: crate::schema::SchemaContext,
|
||||
@ -47,12 +31,9 @@ pub fn build_namespace_schema<
|
||||
namespaced_getter: &NSGet,
|
||||
schema: &crate::schema::Schema<S>,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
let request = match &(*INTROSPECTION_REQUEST) {
|
||||
Ok(req) => req,
|
||||
Err(e) => Err(Error::ParseIntrospectionQuery((*e).clone()))?,
|
||||
};
|
||||
let nr = crate::validation::normalize_request(namespaced_getter, schema, request)
|
||||
.map_err(|e| Error::NormalizeIntrospectionQuery(e.to_string()))?;
|
||||
let nr =
|
||||
crate::validation::normalize_request(namespaced_getter, schema, introspection_request())
|
||||
.map_err(|e| Error::NormalizeIntrospectionQuery(e.to_string()))?;
|
||||
let mut result = HashMap::new();
|
||||
for (_alias, field) in &nr.selection_set.fields {
|
||||
let field_call = field.field_call().map_err(|_| Error::FieldCallNotFound)?;
|
||||
@ -74,3 +55,17 @@ pub fn build_namespace_schema<
|
||||
}
|
||||
Ok(serde_json::to_value(result)?)
|
||||
}
|
||||
|
||||
fn introspection_request() -> &'static crate::http::Request {
|
||||
static CELL: OnceLock<crate::http::Request> = OnceLock::new();
|
||||
CELL.get_or_init(|| crate::http::Request {
|
||||
operation_name: None,
|
||||
query: {
|
||||
let query_str = include_str!("introspection_query.graphql");
|
||||
crate::parser::Parser::new(query_str)
|
||||
.parse_executable_document()
|
||||
.unwrap()
|
||||
},
|
||||
variables: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ open-dds = { path = "../open-dds" }
|
||||
|
||||
derive_more = { workspace = true }
|
||||
indexmap = { workspace = true, features = ["serde"] }
|
||||
lazy_static = { workspace = true }
|
||||
ndc-models = { workspace = true }
|
||||
nonempty = { workspace = true }
|
||||
ref-cast = { workspace = true }
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! This is where we will resolve graphql configuration
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::helpers::types::mk_name;
|
||||
use crate::types::error::{Error, GraphqlConfigError};
|
||||
@ -10,51 +11,6 @@ use open_dds::graphql_config::{self, OrderByDirection};
|
||||
use open_dds::types::GraphQlFieldName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref FALLBACK_GRAPHQL_CONFIG: graphql_config::GraphqlConfig = graphql_config::GraphqlConfig::V1(graphql_config::GraphqlConfigV1 {
|
||||
query: graphql_config::QueryGraphqlConfig {
|
||||
root_operation_type_name: "Query".to_string(),
|
||||
arguments_input: Some (graphql_config::ArgumentsInputGraphqlConfig {
|
||||
field_name: "args".to_string(),
|
||||
}),
|
||||
limit_input: Some(graphql_config::LimitInputGraphqlConfig {
|
||||
field_name: "limit".to_string(),
|
||||
}),
|
||||
offset_input: Some(graphql_config::OffsetInputGraphqlConfig {
|
||||
field_name: "offset".to_string(),
|
||||
}),
|
||||
filter_input: Some(graphql_config::FilterInputGraphqlConfig {
|
||||
field_name: "where".to_string(),
|
||||
operator_names: graphql_config::FilterInputOperatorNames {
|
||||
and: "_and".to_string(),
|
||||
or: "_or".to_string(),
|
||||
not: "_not".to_string(),
|
||||
is_null: "_is_null".to_string(),
|
||||
},
|
||||
}),
|
||||
order_by_input: Some(graphql_config::OrderByInputGraphqlConfig {
|
||||
field_name:"order_by".to_string(),
|
||||
enum_direction_values: graphql_config::OrderByDirectionValues {
|
||||
asc: "Asc".to_string(),
|
||||
desc: "Desc".to_string(),
|
||||
},
|
||||
enum_type_names: vec![graphql_config::OrderByEnumTypeName{
|
||||
type_name: "order_by".to_string(),
|
||||
directions: vec![graphql_config::OrderByDirection::Asc, graphql_config::OrderByDirection::Desc],
|
||||
}] }),
|
||||
aggregate: Some(graphql_config::AggregateGraphqlConfig {
|
||||
filter_input_field_name: GraphQlFieldName("filter_input".to_string()),
|
||||
count_field_name: GraphQlFieldName("_count".to_string()),
|
||||
count_distinct_field_name: GraphQlFieldName("_count_distinct".to_string()),
|
||||
})
|
||||
},
|
||||
mutation: graphql_config::MutationGraphqlConfig{
|
||||
root_operation_type_name: "Mutation".to_string(),
|
||||
},
|
||||
apollo_federation: None,
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct GraphqlConfig {
|
||||
// The graphql configuration that needs to be applied to each model, depending on it's conditions
|
||||
@ -79,7 +35,7 @@ pub struct GraphqlConfig {
|
||||
/// * if the flag is not set, use the fallback object
|
||||
pub fn resolve(
|
||||
graphql_configs: &Vec<QualifiedObject<graphql_config::GraphqlConfig>>,
|
||||
flags: &open_dds::flags::Flags,
|
||||
flags: open_dds::flags::Flags,
|
||||
) -> Result<GraphqlConfig, Error> {
|
||||
if graphql_configs.is_empty() {
|
||||
if flags.require_graphql_config {
|
||||
@ -87,8 +43,7 @@ pub fn resolve(
|
||||
graphql_config_error: GraphqlConfigError::MissingGraphqlConfig,
|
||||
});
|
||||
}
|
||||
let graphql_config = resolve_graphql_config(&FALLBACK_GRAPHQL_CONFIG)?;
|
||||
Ok(graphql_config)
|
||||
resolve_graphql_config(fallback_graphql_config())
|
||||
} else {
|
||||
match graphql_configs.as_slice() {
|
||||
// There should only be one graphql config in supergraph
|
||||
@ -305,3 +260,55 @@ pub fn resolve_graphql_config(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_graphql_config() -> &'static graphql_config::GraphqlConfig {
|
||||
static CELL: OnceLock<graphql_config::GraphqlConfig> = OnceLock::new();
|
||||
CELL.get_or_init(|| {
|
||||
graphql_config::GraphqlConfig::V1(graphql_config::GraphqlConfigV1 {
|
||||
query: graphql_config::QueryGraphqlConfig {
|
||||
root_operation_type_name: "Query".to_string(),
|
||||
arguments_input: Some(graphql_config::ArgumentsInputGraphqlConfig {
|
||||
field_name: "args".to_string(),
|
||||
}),
|
||||
limit_input: Some(graphql_config::LimitInputGraphqlConfig {
|
||||
field_name: "limit".to_string(),
|
||||
}),
|
||||
offset_input: Some(graphql_config::OffsetInputGraphqlConfig {
|
||||
field_name: "offset".to_string(),
|
||||
}),
|
||||
filter_input: Some(graphql_config::FilterInputGraphqlConfig {
|
||||
field_name: "where".to_string(),
|
||||
operator_names: graphql_config::FilterInputOperatorNames {
|
||||
and: "_and".to_string(),
|
||||
or: "_or".to_string(),
|
||||
not: "_not".to_string(),
|
||||
is_null: "_is_null".to_string(),
|
||||
},
|
||||
}),
|
||||
order_by_input: Some(graphql_config::OrderByInputGraphqlConfig {
|
||||
field_name: "order_by".to_string(),
|
||||
enum_direction_values: graphql_config::OrderByDirectionValues {
|
||||
asc: "Asc".to_string(),
|
||||
desc: "Desc".to_string(),
|
||||
},
|
||||
enum_type_names: vec![graphql_config::OrderByEnumTypeName {
|
||||
type_name: "order_by".to_string(),
|
||||
directions: vec![
|
||||
graphql_config::OrderByDirection::Asc,
|
||||
graphql_config::OrderByDirection::Desc,
|
||||
],
|
||||
}],
|
||||
}),
|
||||
aggregate: Some(graphql_config::AggregateGraphqlConfig {
|
||||
filter_input_field_name: GraphQlFieldName("filter_input".to_string()),
|
||||
count_field_name: GraphQlFieldName("_count".to_string()),
|
||||
count_distinct_field_name: GraphQlFieldName("_count_distinct".to_string()),
|
||||
}),
|
||||
},
|
||||
mutation: graphql_config::MutationGraphqlConfig {
|
||||
root_operation_type_name: "Mutation".to_string(),
|
||||
},
|
||||
apollo_federation: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ pub fn resolve(
|
||||
// The graphql config represents the shape of the Hasura features in the graphql schema,
|
||||
// and which features should be enabled or disabled. We check this structure is valid.
|
||||
let graphql_config =
|
||||
graphql_config::resolve(&metadata_accessor.graphql_config, &metadata_accessor.flags)?;
|
||||
graphql_config::resolve(&metadata_accessor.graphql_config, metadata_accessor.flags)?;
|
||||
|
||||
// Fetch and check schema information for all our data connectors
|
||||
let data_connectors = data_connectors::resolve(&metadata_accessor)?;
|
||||
|
@ -13,7 +13,6 @@ opendds-derive = { path = "../utils/opendds-derive" }
|
||||
|
||||
derive_more = { workspace = true }
|
||||
indexmap = { workspace = true, features = ["serde"] }
|
||||
lazy_static = { workspace = true }
|
||||
ndc-models = { workspace = true }
|
||||
ref-cast = { workspace = true }
|
||||
schemars = { workspace = true, features = ["smol_str", "preserve_order"] }
|
||||
|
@ -19,9 +19,7 @@ impl<T> QualifiedObject<T> {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref DEFAULT_FLAGS: flags::Flags = flags::Flags::default();
|
||||
}
|
||||
const DEFAULT_FLAGS: flags::Flags = flags::Flags::new();
|
||||
|
||||
pub struct MetadataAccessor {
|
||||
pub data_connectors: Vec<QualifiedObject<data_connector::DataConnectorLinkV1>>,
|
||||
@ -199,7 +197,7 @@ impl MetadataAccessor {
|
||||
relationships: vec![],
|
||||
commands: vec![],
|
||||
command_permissions: vec![],
|
||||
flags: flags.unwrap_or_else(|| DEFAULT_FLAGS.clone()),
|
||||
flags: flags.unwrap_or(DEFAULT_FLAGS),
|
||||
graphql_config: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Clone, Debug, Default, PartialEq, opendds_derive::OpenDd)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, opendds_derive::OpenDd)]
|
||||
#[opendd(json_schema(rename = "OpenDdFlags"))]
|
||||
pub struct Flags {
|
||||
#[opendd(default, rename = "require_graphql_config")]
|
||||
@ -9,9 +9,20 @@ pub struct Flags {
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
// This is separated from `Default` so we can use a `const` implementation.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
require_graphql_config: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_json() -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"require_graphql_config": false
|
||||
})
|
||||
serde_json::to_value(Self::new()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Flags {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
/// Module that defines the Hasura session variables.
|
||||
use core::fmt;
|
||||
//! Module that defines the Hasura session variables.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -11,23 +11,20 @@ use serde::{Deserialize, Serialize};
|
||||
/// "x-hasura-role".
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, JsonSchema, Serialize, Deserialize)]
|
||||
#[schemars(rename = "OpenDdSessionVariable")]
|
||||
pub struct SessionVariable(String);
|
||||
pub struct SessionVariable(Cow<'static, str>);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SESSION_VARIABLE_ROLE: SessionVariable =
|
||||
SessionVariable("x-hasura-role".to_string());
|
||||
}
|
||||
pub const SESSION_VARIABLE_ROLE: SessionVariable = SessionVariable(Cow::Borrowed("x-hasura-role"));
|
||||
|
||||
impl FromStr for SessionVariable {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(SessionVariable(s.trim().to_lowercase()))
|
||||
Ok(SessionVariable(s.trim().to_lowercase().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SessionVariable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl std::fmt::Display for SessionVariable {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
@ -57,8 +54,8 @@ mod tests {
|
||||
fn serialize_and_deserialize_session_variable() {
|
||||
let mut session_variables = HashMap::new();
|
||||
session_variables.insert(
|
||||
SessionVariable("test-role".to_string()),
|
||||
SessionVariableValue("test-role".to_string()),
|
||||
SessionVariable("test-role".into()),
|
||||
SessionVariableValue("test-role".into()),
|
||||
);
|
||||
let json_str = serde_json::to_string(&session_variables).unwrap();
|
||||
|
||||
|
@ -10,7 +10,6 @@ bench = false
|
||||
[dependencies]
|
||||
convert_case = { workspace = true }
|
||||
darling = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
|
@ -1,14 +1,11 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use darling::{FromAttributes, FromField, FromMeta};
|
||||
use syn::{self, DeriveInput};
|
||||
|
||||
use crate::MacroResult;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// Characters that we reject when constructing titles from schema names.
|
||||
static ref INVALID_NAME_CHARACTER: regex::Regex = regex::Regex::new("[^0-9A-Za-z_]").unwrap();
|
||||
}
|
||||
|
||||
/// JSON schema attributes
|
||||
#[derive(Default, FromMeta)]
|
||||
#[darling(default)]
|
||||
@ -82,6 +79,10 @@ pub struct JsonSchemaMetadata {
|
||||
|
||||
impl<'a> Container<'a> {
|
||||
pub fn from_derive_input(input: &'a DeriveInput) -> MacroResult<Self> {
|
||||
static INVALID_NAME_CHARACTER: OnceLock<regex::Regex> = OnceLock::new();
|
||||
let invalid_name_character =
|
||||
INVALID_NAME_CHARACTER.get_or_init(|| regex::Regex::new("[^0-9A-Za-z_]").unwrap());
|
||||
|
||||
let (doc_title, doc_description) =
|
||||
crate::helpers::get_title_and_desc_from_doc(&input.attrs);
|
||||
let (json_schema_opts, data) = match &input.data {
|
||||
@ -117,7 +118,7 @@ impl<'a> Container<'a> {
|
||||
let schema_id = format!("https://hasura.io/jsonschemas/metadata/{id}");
|
||||
let schema_name = json_schema_opts
|
||||
.rename
|
||||
.unwrap_or_else(|| INVALID_NAME_CHARACTER.replace_all(&id, "_").to_string());
|
||||
.unwrap_or_else(|| invalid_name_character.replace_all(&id, "_").to_string());
|
||||
let schema_title = json_schema_opts
|
||||
.title
|
||||
.or(doc_title)
|
||||
|
Loading…
Reference in New Issue
Block a user