diff --git a/v3/Cargo.lock b/v3/Cargo.lock index 7480cd31628..6dababd0fe9 100644 --- a/v3/Cargo.lock +++ b/v3/Cargo.lock @@ -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", diff --git a/v3/Cargo.toml b/v3/Cargo.toml index 4a594d5f16e..bcb13f38388 100644 --- a/v3/Cargo.toml +++ b/v3/Cargo.toml @@ -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" diff --git a/v3/crates/auth/hasura-authn-core/src/lib.rs b/v3/crates/auth/hasura-authn-core/src/lib.rs index 1b6f0e1a7a3..3e5a843824c 100644 --- a/v3/crates/auth/hasura-authn-core/src/lib.rs +++ b/v3/crates/auth/hasura-authn-core/src/lib.rs @@ -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? diff --git a/v3/crates/auth/hasura-authn-jwt/Cargo.toml b/v3/crates/auth/hasura-authn-jwt/Cargo.toml index c7716fc8171..65b882b18ef 100644 --- a/v3/crates/auth/hasura-authn-jwt/Cargo.toml +++ b/v3/crates/auth/hasura-authn-jwt/Cargo.toml @@ -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"] } diff --git a/v3/crates/auth/hasura-authn-jwt/src/auth.rs b/v3/crates/auth/hasura-authn-jwt/src/auth.rs index d9afe06dde5..09699221a74 100644 --- a/v3/crates/auth/hasura-authn-jwt/src/auth.rs +++ b/v3/crates/auth/hasura-authn-jwt/src/auth.rs @@ -148,7 +148,7 @@ mod tests { ) -> anyhow::Result { 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)?; diff --git a/v3/crates/auth/hasura-authn-jwt/src/jwt.rs b/v3/crates/auth/hasura-authn-jwt/src/jwt.rs index b4babf3f65e..a4af04db00c 100644 --- a/v3/crates/auth/hasura-authn-jwt/src/jwt.rs +++ b/v3/crates/auth/hasura-authn-jwt/src/jwt.rs @@ -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 = diff --git a/v3/crates/auth/hasura-authn-webhook/Cargo.toml b/v3/crates/auth/hasura-authn-webhook/Cargo.toml index 566657981e5..823f564db2e 100644 --- a/v3/crates/auth/hasura-authn-webhook/Cargo.toml +++ b/v3/crates/auth/hasura-authn-webhook/Cargo.toml @@ -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 } diff --git a/v3/crates/auth/hasura-authn-webhook/src/webhook.rs b/v3/crates/auth/hasura-authn-webhook/src/webhook.rs index 51002d4d0b5..1988ad74fa4 100644 --- a/v3/crates/auth/hasura-authn-webhook/src/webhook.rs +++ b/v3/crates/auth/hasura-authn-webhook/src/webhook.rs @@ -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 = 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(url: &Url, s: S) -> Result 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> = 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; diff --git a/v3/crates/lang-graphql/Cargo.toml b/v3/crates/lang-graphql/Cargo.toml index ff0abd08f7c..903fdcc1f1e 100644 --- a/v3/crates/lang-graphql/Cargo.toml +++ b/v3/crates/lang-graphql/Cargo.toml @@ -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 } diff --git a/v3/crates/lang-graphql/src/generate_graphql_schema.rs b/v3/crates/lang-graphql/src/generate_graphql_schema.rs index cfb764c3f6d..777b76d4a41 100644 --- a/v3/crates/lang-graphql/src/generate_graphql_schema.rs +++ b/v3/crates/lang-graphql/src/generate_graphql_schema.rs @@ -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 = { - 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, ) -> Result { - 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 = 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(), + }) +} diff --git a/v3/crates/metadata-resolve/Cargo.toml b/v3/crates/metadata-resolve/Cargo.toml index b11c36c2d7f..bf1b918f8aa 100644 --- a/v3/crates/metadata-resolve/Cargo.toml +++ b/v3/crates/metadata-resolve/Cargo.toml @@ -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 } diff --git a/v3/crates/metadata-resolve/src/stages/graphql_config/mod.rs b/v3/crates/metadata-resolve/src/stages/graphql_config/mod.rs index f89bdffda26..c163ec8ab3c 100644 --- a/v3/crates/metadata-resolve/src/stages/graphql_config/mod.rs +++ b/v3/crates/metadata-resolve/src/stages/graphql_config/mod.rs @@ -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>, - flags: &open_dds::flags::Flags, + flags: open_dds::flags::Flags, ) -> Result { 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 = 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, + }) + }) +} diff --git a/v3/crates/metadata-resolve/src/stages/mod.rs b/v3/crates/metadata-resolve/src/stages/mod.rs index 2a5b3b4cbc7..ce8b36411a2 100644 --- a/v3/crates/metadata-resolve/src/stages/mod.rs +++ b/v3/crates/metadata-resolve/src/stages/mod.rs @@ -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)?; diff --git a/v3/crates/open-dds/Cargo.toml b/v3/crates/open-dds/Cargo.toml index b6c0cb145ac..404013f05ba 100644 --- a/v3/crates/open-dds/Cargo.toml +++ b/v3/crates/open-dds/Cargo.toml @@ -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"] } diff --git a/v3/crates/open-dds/src/accessor.rs b/v3/crates/open-dds/src/accessor.rs index 3078cd9eada..266d8487c77 100644 --- a/v3/crates/open-dds/src/accessor.rs +++ b/v3/crates/open-dds/src/accessor.rs @@ -19,9 +19,7 @@ impl QualifiedObject { } } -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>, @@ -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![], } } diff --git a/v3/crates/open-dds/src/flags.rs b/v3/crates/open-dds/src/flags.rs index 6c0f16960b6..5d3f7faedbd 100644 --- a/v3/crates/open-dds/src/flags.rs +++ b/v3/crates/open-dds/src/flags.rs @@ -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() } } diff --git a/v3/crates/open-dds/src/session_variables.rs b/v3/crates/open-dds/src/session_variables.rs index a0ace393440..eebab4f1487 100644 --- a/v3/crates/open-dds/src/session_variables.rs +++ b/v3/crates/open-dds/src/session_variables.rs @@ -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 { - 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(); diff --git a/v3/crates/utils/opendds-derive/Cargo.toml b/v3/crates/utils/opendds-derive/Cargo.toml index 5b490663a06..f0e11b7c2d4 100644 --- a/v3/crates/utils/opendds-derive/Cargo.toml +++ b/v3/crates/utils/opendds-derive/Cargo.toml @@ -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 } diff --git a/v3/crates/utils/opendds-derive/src/container.rs b/v3/crates/utils/opendds-derive/src/container.rs index b3ec05e84b7..a91c57b909a 100644 --- a/v3/crates/utils/opendds-derive/src/container.rs +++ b/v3/crates/utils/opendds-derive/src/container.rs @@ -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 { + static INVALID_NAME_CHARACTER: OnceLock = 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)