From 1f1287582da15b336ba2d09714003ee4a34d8c22 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sat, 13 Apr 2024 22:12:01 +0800 Subject: [PATCH 01/14] Add MergeRight derive macro. --- Cargo.lock | 10 ++++++++ Cargo.toml | 2 ++ src/config/upstream.rs | 52 +++----------------------------------- tailcall-macros/Cargo.toml | 12 +++++++++ tailcall-macros/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 tailcall-macros/Cargo.toml create mode 100644 tailcall-macros/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d29a0a09e1..a5b9f465e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5140,6 +5140,7 @@ dependencies = [ "serde_yaml", "stripmargin", "strum_macros 0.26.2", + "tailcall-macros", "temp-env", "tempfile", "thiserror", @@ -5208,6 +5209,15 @@ dependencies = [ "worker", ] +[[package]] +name = "tailcall-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "tailcall_query_plan" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3aeca1ee6a..f2507629db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ async-graphql = { workspace = true, features = [ dotenvy = "0.15" convert_case = "0.6.0" rand = "0.8.5" +tailcall-macros = { path = "tailcall-macros" } [dev-dependencies] @@ -195,6 +196,7 @@ members = [ "tailcall-autogen", "tailcall-aws-lambda", "tailcall-cloudflare", + "tailcall-macros", "tailcall-query-plan", ] diff --git a/src/config/upstream.rs b/src/config/upstream.rs index 47e8d9b367..3d37e61aa9 100644 --- a/src/config/upstream.rs +++ b/src/config/upstream.rs @@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize}; use crate::is_default; use crate::merge_right::MergeRight; +use tailcall_macros::MergeRight; -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase", default)] pub struct Batch { pub delay: usize, @@ -20,19 +21,13 @@ impl Default for Batch { } } -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, schemars::JsonSchema, MergeRight)] pub struct Proxy { pub url: String, } -impl MergeRight for Proxy { - fn merge_right(self, other: Self) -> Self { - other - } -} - #[derive( - Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, Default, schemars::JsonSchema, + Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, Default, schemars::JsonSchema, MergeRight )] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase", default)] @@ -174,45 +169,6 @@ impl Upstream { } } -impl MergeRight for Upstream { - // TODO: add unit tests for merge - fn merge_right(mut self, other: Self) -> Self { - self.allowed_headers = self.allowed_headers.merge_right(other.allowed_headers); - self.base_url = self.base_url.merge_right(other.base_url); - self.connect_timeout = self.connect_timeout.merge_right(other.connect_timeout); - self.http_cache = self.http_cache.merge_right(other.http_cache); - self.keep_alive_interval = self - .keep_alive_interval - .merge_right(other.keep_alive_interval); - self.keep_alive_timeout = self - .keep_alive_timeout - .merge_right(other.keep_alive_timeout); - self.keep_alive_while_idle = self - .keep_alive_while_idle - .merge_right(other.keep_alive_while_idle); - self.pool_idle_timeout = self.pool_idle_timeout.merge_right(other.pool_idle_timeout); - self.pool_max_idle_per_host = self - .pool_max_idle_per_host - .merge_right(other.pool_max_idle_per_host); - self.proxy = self.proxy.merge_right(other.proxy); - self.tcp_keep_alive = self.tcp_keep_alive.merge_right(other.tcp_keep_alive); - self.timeout = self.timeout.merge_right(other.timeout); - self.user_agent = self.user_agent.merge_right(other.user_agent); - - if let Some(other) = other.batch { - let mut batch = self.batch.unwrap_or_default(); - batch.max_size = other.max_size; - batch.delay = other.delay; - batch.headers = batch.headers.merge_right(other.headers); - - self.batch = Some(batch); - } - - self.http2_only = self.http2_only.merge_right(other.http2_only); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/tailcall-macros/Cargo.toml b/tailcall-macros/Cargo.toml new file mode 100644 index 0000000000..464b4e219e --- /dev/null +++ b/tailcall-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tailcall-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["derive", "full"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs new file mode 100644 index 0000000000..5f31da58c0 --- /dev/null +++ b/tailcall-macros/src/lib.rs @@ -0,0 +1,52 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields}; + +#[proc_macro_derive(MergeRight)] +pub fn merge_right_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident; + let gen = match input.data { + // Implement for structs + Data::Struct(data) => { + let fields = if let Fields::Named(fields) = data.fields { + fields.named + } else { + // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple structs, unit structs) + unimplemented!() + }; + + let merge_logic = fields.iter().map(|f| { + let name = &f.ident; + quote! { + #name: self.#name.merge_right(other.#name), + } + }); + + quote! { + impl MergeRight for #name { + fn merge_right(self, other: Self) -> Self { + Self { + #(#merge_logic)* + } + } + } + } + }, + // Implement for enums + Data::Enum(_) => quote! { + impl MergeRight for #name { + fn merge_right(self, other: Self) -> Self { + other + } + } + }, + // Optionally handle or disallow unions + Data::Union(_) => unimplemented!(), + }; + + gen.into() +} From 33ffe1a5a5e06c6a83ec4b8169c2b2d65c62fb6c Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sat, 13 Apr 2024 22:22:38 +0800 Subject: [PATCH 02/14] Lint fix. --- src/config/upstream.rs | 17 ++++++++++++++--- tailcall-macros/src/lib.rs | 5 +++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/config/upstream.rs b/src/config/upstream.rs index 3d37e61aa9..3afb85094c 100644 --- a/src/config/upstream.rs +++ b/src/config/upstream.rs @@ -2,12 +2,14 @@ use std::collections::BTreeSet; use derive_setters::Setters; use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; use crate::is_default; use crate::merge_right::MergeRight; -use tailcall_macros::MergeRight; -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, schemars::JsonSchema, MergeRight, +)] #[serde(rename_all = "camelCase", default)] pub struct Batch { pub delay: usize, @@ -27,7 +29,16 @@ pub struct Proxy { } #[derive( - Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Setters, Default, schemars::JsonSchema, MergeRight + Serialize, + Deserialize, + PartialEq, + Eq, + Clone, + Debug, + Setters, + Default, + schemars::JsonSchema, + MergeRight, )] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase", default)] diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 5f31da58c0..df3367989c 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -15,7 +15,8 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { let fields = if let Fields::Named(fields) = data.fields { fields.named } else { - // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple structs, unit structs) + // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple + // structs, unit structs) unimplemented!() }; @@ -35,7 +36,7 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { } } } - }, + } // Implement for enums Data::Enum(_) => quote! { impl MergeRight for #name { From f622109e35ae3060e707c04df759eaf68f67ef6c Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 11:54:48 +0800 Subject: [PATCH 03/14] Use derive MergeRight. --- src/config/config.rs | 94 ++++--------------------------------- src/config/config_module.rs | 34 ++------------ src/config/cors.rs | 4 +- src/config/headers.rs | 22 ++------- src/config/key_values.rs | 13 ----- src/config/server.rs | 55 ++-------------------- src/config/telemetry.rs | 45 ++---------------- src/merge_right.rs | 16 +++++++ src/primitive.rs | 4 ++ src/rest/endpoint_set.rs | 11 ++--- tailcall-macros/src/lib.rs | 11 ++++- 11 files changed, 59 insertions(+), 250 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 65d579f2a4..b0d84ce9aa 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -8,6 +8,8 @@ use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; +use tailcall_macros::MergeRight; + use super::telemetry::Telemetry; use super::{KeyValue, Link, Server, Upstream}; use crate::config::from_document::from_document; @@ -20,7 +22,7 @@ use crate::merge_right::MergeRight; use crate::valid::{Valid, Validator}; #[derive( - Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, + Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, MergeRight )] #[serde(rename_all = "camelCase")] pub struct Config { @@ -115,28 +117,10 @@ impl Config { } } -impl MergeRight for Config { - fn merge_right(self, other: Self) -> Self { - let server = self.server.merge_right(other.server); - let types = merge_types(self.types, other.types); - let unions = merge_unions(self.unions, other.unions); - let schema = self.schema.merge_right(other.schema); - let upstream = self.upstream.merge_right(other.upstream); - let links = merge_links(self.links, other.links); - let telemetry = self.telemetry.merge_right(other.telemetry); - - Self { server, upstream, types, schema, unions, links, telemetry } - } -} - -fn merge_links(self_links: Vec, other_links: Vec) -> Vec { - self_links.merge_right(other_links) -} - /// /// Represents a GraphQL type. /// A type can be an object, interface, enum or scalar. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] pub struct Type { /// /// A map of field name and its definition. @@ -190,22 +174,7 @@ impl Type { } } -impl MergeRight for Type { - fn merge_right(mut self, other: Self) -> Self { - let fields = self.fields.merge_right(other.fields); - self.implements = self.implements.merge_right(other.implements); - if let Some(ref variants) = self.variants { - if let Some(ref other) = other.variants { - self.variants = Some(variants.union(other).cloned().collect()); - } - } else { - self.variants = other.variants; - } - Self { fields, ..self } - } -} - -#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema, MergeRight)] #[serde(deny_unknown_fields)] /// Used to represent an identifier for a type. Typically used via only by the /// configuration generators to provide additional information about the type. @@ -214,7 +183,7 @@ pub struct Tag { pub id: String, } -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema, MergeRight)] /// The @cache operator enables caching for the query, field or type it is /// applied to. #[serde(rename_all = "camelCase")] @@ -225,38 +194,11 @@ pub struct Cache { pub max_age: NonZeroU64, } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default, schemars::JsonSchema)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default, schemars::JsonSchema, MergeRight)] pub struct Protected {} -fn merge_types( - mut self_types: BTreeMap, - other_types: BTreeMap, -) -> BTreeMap { - for (name, mut other_type) in other_types { - if let Some(self_type) = self_types.remove(&name) { - other_type = self_type.merge_right(other_type); - } - - self_types.insert(name, other_type); - } - self_types -} - -fn merge_unions( - mut self_unions: BTreeMap, - other_unions: BTreeMap, -) -> BTreeMap { - for (name, mut other_union) in other_unions { - if let Some(self_union) = self_unions.remove(&name) { - other_union = self_union.merge_right(other_union); - } - self_unions.insert(name, other_union); - } - self_unions -} - #[derive( - Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, + Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, MergeRight )] #[setters(strip_option)] pub struct RootSchema { @@ -267,17 +209,6 @@ pub struct RootSchema { pub subscription: Option, } -impl MergeRight for RootSchema { - // TODO: add unit-tests - fn merge_right(self, other: Self) -> Self { - Self { - query: self.query.merge_right(other.query), - mutation: self.mutation.merge_right(other.mutation), - subscription: self.subscription.merge_right(other.subscription), - } - } -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)] #[serde(deny_unknown_fields)] /// Used to omit a field from public consumption. @@ -483,19 +414,12 @@ pub struct Arg { pub default_value: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, MergeRight)] pub struct Union { pub types: BTreeSet, pub doc: Option, } -impl MergeRight for Union { - fn merge_right(mut self, other: Self) -> Self { - self.types = self.types.merge_right(other.types); - self - } -} - #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] #[serde(deny_unknown_fields)] /// The @http operator indicates that a field or node is backed by a REST API. diff --git a/src/config/config_module.rs b/src/config/config_module.rs index 9c043a5f02..1c545e5092 100644 --- a/src/config/config_module.rs +++ b/src/config/config_module.rs @@ -6,6 +6,7 @@ use derive_setters::Setters; use jsonwebtoken::jwk::JwkSet; use prost_reflect::prost_types::FileDescriptorSet; use rustls_pki_types::{CertificateDer, PrivateKeyDer}; +use tailcall_macros::MergeRight; use crate::blueprint::GrpcMethod; use crate::config::Config; @@ -15,7 +16,7 @@ use crate::scalar; /// A wrapper on top of Config that contains all the resolved extensions and /// computed values. -#[derive(Clone, Debug, Default, Setters)] +#[derive(Clone, Debug, Default, Setters, MergeRight)] pub struct ConfigModule { pub config: Config, pub extensions: Extensions, @@ -39,7 +40,7 @@ impl Deref for Content { /// Extensions are meta-information required before we can generate the /// blueprint. Typically, this information cannot be inferred without performing /// an IO operation, i.e., reading a file, making an HTTP call, etc. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, MergeRight)] pub struct Extensions { /// Contains the file descriptor sets resolved from the links pub grpc_file_descriptors: Vec>, @@ -79,35 +80,6 @@ impl Extensions { } } -impl MergeRight for Extensions { - fn merge_right(mut self, mut other: Self) -> Self { - self.grpc_file_descriptors = self - .grpc_file_descriptors - .merge_right(other.grpc_file_descriptors); - self.script = self.script.merge_right(other.script.take()); - self.cert = self.cert.merge_right(other.cert); - self.keys = if !other.keys.is_empty() { - other.keys - } else { - self.keys - }; - self.endpoint_set = self.endpoint_set.merge_right(other.endpoint_set); - self.htpasswd = self.htpasswd.merge_right(other.htpasswd); - self.jwks = self.jwks.merge_right(other.jwks); - self - } -} - -impl MergeRight for ConfigModule { - fn merge_right(mut self, other: Self) -> Self { - self.config = self.config.merge_right(other.config); - self.extensions = self.extensions.merge_right(other.extensions); - self.input_types = self.input_types.merge_right(other.input_types); - self.output_types = self.output_types.merge_right(other.output_types); - self - } -} - impl Deref for ConfigModule { type Target = Config; fn deref(&self) -> &Self::Target { diff --git a/src/config/cors.rs b/src/config/cors.rs index 3934a719f6..b3465bb722 100644 --- a/src/config/cors.rs +++ b/src/config/cors.rs @@ -1,11 +1,13 @@ use hyper::header; use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; use crate::http::Method; use crate::is_default; +use crate::merge_right::MergeRight; /// Type to configure Cross-Origin Resource Sharing (CORS) for a server. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct Cors { /// Indicates whether the server allows credentials (e.g., cookies, diff --git a/src/config/headers.rs b/src/config/headers.rs index 0dd82f5cd1..dcfca576bd 100644 --- a/src/config/headers.rs +++ b/src/config/headers.rs @@ -1,12 +1,14 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; use crate::config::cors::Cors; use crate::config::KeyValue; use crate::is_default; +use crate::merge_right::MergeRight; -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct Headers { #[serde(default, skip_serializing_if = "is_default")] @@ -47,21 +49,3 @@ impl Headers { self.cors.clone() } } - -pub fn merge_headers(current: Option, other: Option) -> Option { - let mut headers = current.clone(); - - if let Some(other_headers) = other { - if let Some(mut self_headers) = current.clone() { - self_headers.cache_control = other_headers.cache_control.or(self_headers.cache_control); - self_headers.custom.extend(other_headers.custom); - self_headers.cors = other_headers.cors.or(self_headers.cors); - - headers = Some(self_headers); - } else { - headers = Some(other_headers); - } - } - - headers -} diff --git a/src/config/key_values.rs b/src/config/key_values.rs index c36c53fa8a..44f55f6269 100644 --- a/src/config/key_values.rs +++ b/src/config/key_values.rs @@ -26,19 +26,6 @@ pub struct KeyValue { pub value: String, } -pub fn merge_key_value_vecs(current: &[KeyValue], other: &[KeyValue]) -> Vec { - let mut acc: BTreeMap<&String, &String> = - current.iter().map(|kv| (&kv.key, &kv.value)).collect(); - - for kv in other { - acc.insert(&kv.key, &kv.value); - } - - acc.iter() - .map(|(k, v)| KeyValue { key: k.to_string(), value: v.to_string() }) - .collect() -} - impl Serialize for KeyValues { fn serialize(&self, serializer: S) -> Result where diff --git a/src/config/server.rs b/src/config/server.rs index 797e169425..d4370fea2f 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -1,14 +1,14 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; -use super::{merge_headers, merge_key_value_vecs}; use crate::config::headers::Headers; use crate::config::KeyValue; use crate::is_default; use crate::merge_right::MergeRight; -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] /// The `@server` directive, when applied at the schema level, offers a @@ -97,31 +97,19 @@ pub struct Server { pub workers: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct ScriptOptions { pub timeout: Option, } -impl MergeRight for ScriptOptions { - fn merge_right(self, other: Self) -> Self { - ScriptOptions { timeout: self.timeout.merge_right(other.timeout) } - } -} - -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default, schemars::JsonSchema)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default, schemars::JsonSchema, MergeRight)] pub enum HttpVersion { #[default] HTTP1, HTTP2, } -impl MergeRight for HttpVersion { - fn merge_right(self, other: Self) -> Self { - other - } -} - impl Server { pub fn enable_apollo_tracing(&self) -> bool { self.apollo_tracing.unwrap_or(false) @@ -208,41 +196,6 @@ impl Server { } } -impl MergeRight for Server { - fn merge_right(mut self, other: Self) -> Self { - self.apollo_tracing = self.apollo_tracing.merge_right(other.apollo_tracing); - self.headers = merge_headers(self.headers, other.headers); - self.graphiql = self.graphiql.merge_right(other.graphiql); - self.introspection = self.introspection.merge_right(other.introspection); - self.query_validation = self.query_validation.merge_right(other.query_validation); - self.response_validation = self - .response_validation - .merge_right(other.response_validation); - self.batch_requests = self.batch_requests.merge_right(other.batch_requests); - self.global_response_timeout = self - .global_response_timeout - .merge_right(other.global_response_timeout); - self.showcase = self.showcase.merge_right(other.showcase); - self.workers = self.workers.merge_right(other.workers); - self.port = self.port.merge_right(other.port); - self.hostname = self.hostname.merge_right(other.hostname); - self.vars = other.vars.iter().fold(self.vars.to_vec(), |mut acc, kv| { - let position = acc.iter().position(|x| x.key == kv.key); - if let Some(pos) = position { - acc[pos] = kv.clone(); - } else { - acc.push(kv.clone()); - }; - acc - }); - self.vars = merge_key_value_vecs(&self.vars, &other.vars); - self.version = self.version.merge_right(other.version); - self.pipeline_flush = self.pipeline_flush.merge_right(other.pipeline_flush); - self.script = self.script.merge_right(other.script); - self - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/config/telemetry.rs b/src/config/telemetry.rs index 8be111bd7e..d618dd780d 100644 --- a/src/config/telemetry.rs +++ b/src/config/telemetry.rs @@ -1,5 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; +use tailcall_macros::MergeRight; use super::KeyValue; use crate::config::{Apollo, ConfigReaderContext}; @@ -18,7 +19,7 @@ mod defaults { } /// Output the opentelemetry data to the stdout. Mostly used for debug purposes -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct StdoutExporter { /// Output to stdout in pretty human-readable format @@ -26,14 +27,8 @@ pub struct StdoutExporter { pub pretty: bool, } -impl MergeRight for StdoutExporter { - fn merge_right(self, other: Self) -> Self { - Self { pretty: other.pretty || self.pretty } - } -} - /// Output the opentelemetry data to otlp collector -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct OtlpExporter { pub url: String, @@ -41,15 +36,6 @@ pub struct OtlpExporter { pub headers: Vec, } -impl MergeRight for OtlpExporter { - fn merge_right(self, other: Self) -> Self { - let mut headers = self.headers; - headers.extend(other.headers.iter().cloned()); - - Self { url: other.url, headers } - } -} - /// Output format for prometheus data #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)] #[serde(rename_all = "camelCase")] @@ -72,13 +58,7 @@ pub struct PrometheusExporter { pub format: PrometheusFormat, } -impl PrometheusExporter { - fn merge_right(&self, other: Self) -> Self { - other - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub enum TelemetryExporter { Stdout(StdoutExporter), @@ -87,23 +67,6 @@ pub enum TelemetryExporter { Apollo(Apollo), } -impl MergeRight for TelemetryExporter { - fn merge_right(self, other: Self) -> Self { - match (self, other) { - (TelemetryExporter::Stdout(left), TelemetryExporter::Stdout(right)) => { - TelemetryExporter::Stdout(left.merge_right(right)) - } - (TelemetryExporter::Otlp(left), TelemetryExporter::Otlp(right)) => { - TelemetryExporter::Otlp(left.merge_right(right)) - } - (TelemetryExporter::Prometheus(left), TelemetryExporter::Prometheus(right)) => { - TelemetryExporter::Prometheus(left.merge_right(right)) - } - (_, other) => other, - } - } -} - #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] diff --git a/src/merge_right.rs b/src/merge_right.rs index e9e7f9c5ca..27b605ee67 100644 --- a/src/merge_right.rs +++ b/src/merge_right.rs @@ -1,4 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::sync::Arc; +use std::marker::PhantomData; pub trait MergeRight { fn merge_right(self, other: Self) -> Self; @@ -15,6 +17,20 @@ impl MergeRight for Option { } } +impl MergeRight for PhantomData { + fn merge_right(self, other: Self) -> Self { + other + } +} + +impl MergeRight for Arc { + fn merge_right(self, other: Self) -> Self { + let l = Arc::into_inner(self); + let r = Arc::into_inner(other); + Arc::new(l.merge_right(r).unwrap_or_default()) + } +} + impl MergeRight for Vec { fn merge_right(mut self, other: Self) -> Self { self.extend(other); diff --git a/src/primitive.rs b/src/primitive.rs index 2c205009f6..a98a064b85 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroU64; + use crate::merge_right::MergeRight; pub trait Primitive {} @@ -30,6 +32,8 @@ impl Primitive for char {} impl Primitive for String {} +impl Primitive for NonZeroU64 {} + impl MergeRight for A { fn merge_right(self, other: Self) -> Self { other diff --git a/src/rest/endpoint_set.rs b/src/rest/endpoint_set.rs index 1805fe836e..1ba926c83c 100644 --- a/src/rest/endpoint_set.rs +++ b/src/rest/endpoint_set.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use tailcall_macros::MergeRight; + use super::endpoint::Endpoint; use super::partial_request::PartialRequest; use super::Request; @@ -11,7 +13,7 @@ use crate::runtime::TargetRuntime; use crate::valid::Validator; /// Collection of endpoints -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone, Debug, MergeRight)] pub struct EndpointSet { endpoints: Vec, marker: std::marker::PhantomData, @@ -81,13 +83,6 @@ impl EndpointSet { } } -impl MergeRight for EndpointSet { - fn merge_right(mut self, other: Self) -> Self { - self.extend(other); - self - } -} - impl EndpointSet { pub fn matches(&self, request: &Request) -> Option { self.endpoints.iter().find_map(|e| e.matches(request)) diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index df3367989c..cc58f8bccc 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -9,6 +9,7 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let name = input.ident; + let generics = input.generics; let gen = match input.data { // Implement for structs Data::Struct(data) => { @@ -26,9 +27,17 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { #name: self.#name.merge_right(other.#name), } }); + + let generics_lt = generics.lt_token; + let generics_gt = generics.gt_token; + let generics_params = generics.params; + + let generics_del = quote! { + #generics_lt #generics_params #generics_gt + }; quote! { - impl MergeRight for #name { + impl #generics_del MergeRight for #name #generics_del { fn merge_right(self, other: Self) -> Self { Self { #(#merge_logic)* From 00141b24e06e2ab413e58a9e02bb79d298d30b1a Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 12:04:54 +0800 Subject: [PATCH 04/14] Lint fix. --- src/config/config.rs | 35 +++++++++++++++++++++----- src/config/cors.rs | 4 ++- src/config/headers.rs | 4 ++- src/config/key_values.rs | 50 -------------------------------------- src/config/server.rs | 8 ++++-- src/merge_right.rs | 2 +- tailcall-macros/src/lib.rs | 2 +- 7 files changed, 43 insertions(+), 62 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index b0d84ce9aa..a9107e8c45 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -7,7 +7,6 @@ use async_graphql::parser::types::ServiceDocument; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; - use tailcall_macros::MergeRight; use super::telemetry::Telemetry; @@ -22,7 +21,16 @@ use crate::merge_right::MergeRight; use crate::valid::{Valid, Validator}; #[derive( - Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, MergeRight + Serialize, + Deserialize, + Clone, + Debug, + Default, + Setters, + PartialEq, + Eq, + schemars::JsonSchema, + MergeRight, )] #[serde(rename_all = "camelCase")] pub struct Config { @@ -120,7 +128,9 @@ impl Config { /// /// Represents a GraphQL type. /// A type can be an object, interface, enum or scalar. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, +)] pub struct Type { /// /// A map of field name and its definition. @@ -174,7 +184,9 @@ impl Type { } } -#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Clone, Debug, Default, PartialEq, Deserialize, Serialize, Eq, schemars::JsonSchema, MergeRight, +)] #[serde(deny_unknown_fields)] /// Used to represent an identifier for a type. Typically used via only by the /// configuration generators to provide additional information about the type. @@ -194,11 +206,22 @@ pub struct Cache { pub max_age: NonZeroU64, } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default, schemars::JsonSchema, MergeRight)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default, schemars::JsonSchema, MergeRight, +)] pub struct Protected {} #[derive( - Serialize, Deserialize, Clone, Debug, Default, Setters, PartialEq, Eq, schemars::JsonSchema, MergeRight + Serialize, + Deserialize, + Clone, + Debug, + Default, + Setters, + PartialEq, + Eq, + schemars::JsonSchema, + MergeRight, )] #[setters(strip_option)] pub struct RootSchema { diff --git a/src/config/cors.rs b/src/config/cors.rs index b3465bb722..ccc699b077 100644 --- a/src/config/cors.rs +++ b/src/config/cors.rs @@ -7,7 +7,9 @@ use crate::is_default; use crate::merge_right::MergeRight; /// Type to configure Cross-Origin Resource Sharing (CORS) for a server. -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, +)] #[serde(rename_all = "camelCase")] pub struct Cors { /// Indicates whether the server allows credentials (e.g., cookies, diff --git a/src/config/headers.rs b/src/config/headers.rs index dcfca576bd..d714b35482 100644 --- a/src/config/headers.rs +++ b/src/config/headers.rs @@ -8,7 +8,9 @@ use crate::config::KeyValue; use crate::is_default; use crate::merge_right::MergeRight; -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, +)] #[serde(rename_all = "camelCase")] pub struct Headers { #[serde(default, skip_serializing_if = "is_default")] diff --git a/src/config/key_values.rs b/src/config/key_values.rs index 44f55f6269..8e48f48de1 100644 --- a/src/config/key_values.rs +++ b/src/config/key_values.rs @@ -97,54 +97,4 @@ mod tests { // Using the deref trait assert_eq!(kv["a"], "b"); } - - #[test] - fn test_merge_with_both_empty() { - let current = vec![]; - let other = vec![]; - let result = merge_key_value_vecs(¤t, &other); - assert!(result.is_empty()); - } - - #[test] - fn test_merge_with_current_empty() { - let current = vec![]; - let other = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; - let result = merge_key_value_vecs(¤t, &other); - assert_eq!(result.len(), 1); - assert_eq!(result[0].key, "key1"); - assert_eq!(result[0].value, "value1"); - } - - #[test] - fn test_merge_with_other_empty() { - let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; - let other = vec![]; - let result = merge_key_value_vecs(¤t, &other); - assert_eq!(result.len(), 1); - assert_eq!(result[0].key, "key1"); - assert_eq!(result[0].value, "value1"); - } - - #[test] - fn test_merge_with_unique_keys() { - let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; - let other = vec![KeyValue { key: "key2".to_string(), value: "value2".to_string() }]; - let result = merge_key_value_vecs(¤t, &other); - assert_eq!(result.len(), 2); - assert_eq!(result[0].key, "key1"); - assert_eq!(result[0].value, "value1"); - assert_eq!(result[1].key, "key2"); - assert_eq!(result[1].value, "value2"); - } - - #[test] - fn test_merge_with_overlapping_keys() { - let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; - let other = vec![KeyValue { key: "key1".to_string(), value: "value2".to_string() }]; - let result = merge_key_value_vecs(¤t, &other); - assert_eq!(result.len(), 1); - assert_eq!(result[0].key, "key1"); - assert_eq!(result[0].value, "value2"); - } } diff --git a/src/config/server.rs b/src/config/server.rs index d4370fea2f..542d26937a 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -8,7 +8,9 @@ use crate::config::KeyValue; use crate::is_default; use crate::merge_right::MergeRight; -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight)] +#[derive( + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, +)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] /// The `@server` directive, when applied at the schema level, offers a @@ -103,7 +105,9 @@ pub struct ScriptOptions { pub timeout: Option, } -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default, schemars::JsonSchema, MergeRight)] +#[derive( + Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Default, schemars::JsonSchema, MergeRight, +)] pub enum HttpVersion { #[default] HTTP1, diff --git a/src/merge_right.rs b/src/merge_right.rs index 27b605ee67..275e6407ae 100644 --- a/src/merge_right.rs +++ b/src/merge_right.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::sync::Arc; use std::marker::PhantomData; +use std::sync::Arc; pub trait MergeRight { fn merge_right(self, other: Self) -> Self; diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index cc58f8bccc..1ce22fac76 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -27,7 +27,7 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { #name: self.#name.merge_right(other.#name), } }); - + let generics_lt = generics.lt_token; let generics_gt = generics.gt_token; let generics_params = generics.params; From 0273403916a6afc9c68a7ff159ad198ac91eb181 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 17:39:40 +0800 Subject: [PATCH 05/14] Fix some tests. --- Cargo.lock | 70 +++++++++++++++++++------------------- src/config/config.rs | 9 ++++- src/config/key_values.rs | 63 ++++++++++++++++++++++++++++++++++ src/config/server.rs | 17 +++++++++ src/config/telemetry.rs | 1 - src/merge_right.rs | 10 ++++-- tailcall-macros/Cargo.toml | 2 +- tailcall-macros/src/lib.rs | 63 +++++++++++++++++++++++++++++++--- 8 files changed, 191 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5b9f465e7..900e9eece2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,7 +292,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.26.2", - "syn 2.0.52", + "syn 2.0.58", "thiserror", ] @@ -445,7 +445,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -513,7 +513,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -530,7 +530,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -917,7 +917,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1207,7 +1207,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1229,7 +1229,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core 0.20.8", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1308,7 +1308,7 @@ dependencies = [ "quote", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.52", + "syn 2.0.58", "thiserror", ] @@ -1370,7 +1370,7 @@ dependencies = [ "darling 0.20.8", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -1729,7 +1729,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -2887,7 +2887,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -2902,7 +2902,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.2", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3045,7 +3045,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3589,7 +3589,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3666,7 +3666,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3785,7 +3785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3806,7 +3806,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3818,7 +3818,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -3872,7 +3872,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.52", + "syn 2.0.58", "tempfile", "which 4.4.2", ] @@ -3887,7 +3887,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -4622,7 +4622,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -4969,7 +4969,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -4982,7 +4982,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5004,9 +5004,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -5215,7 +5215,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] @@ -5298,7 +5298,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5413,7 +5413,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5520,7 +5520,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5575,7 +5575,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -5905,7 +5905,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -5939,7 +5939,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6361,7 +6361,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -6389,7 +6389,7 @@ dependencies = [ "darling 0.20.8", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] @@ -6424,7 +6424,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.58", ] [[package]] diff --git a/src/config/config.rs b/src/config/config.rs index a9107e8c45..98043cd7e0 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -129,7 +129,7 @@ impl Config { /// Represents a GraphQL type. /// A type can be an object, interface, enum or scalar. #[derive( - Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight )] pub struct Type { /// @@ -324,6 +324,13 @@ pub struct Field { pub protected: Option, } +// It's a terminal implementation of MergeRight +impl MergeRight for Field { + fn merge_right(self, other: Self) -> Self { + other + } +} + impl Field { pub fn has_resolver(&self) -> bool { self.http.is_some() diff --git a/src/config/key_values.rs b/src/config/key_values.rs index 8e48f48de1..c36c53fa8a 100644 --- a/src/config/key_values.rs +++ b/src/config/key_values.rs @@ -26,6 +26,19 @@ pub struct KeyValue { pub value: String, } +pub fn merge_key_value_vecs(current: &[KeyValue], other: &[KeyValue]) -> Vec { + let mut acc: BTreeMap<&String, &String> = + current.iter().map(|kv| (&kv.key, &kv.value)).collect(); + + for kv in other { + acc.insert(&kv.key, &kv.value); + } + + acc.iter() + .map(|(k, v)| KeyValue { key: k.to_string(), value: v.to_string() }) + .collect() +} + impl Serialize for KeyValues { fn serialize(&self, serializer: S) -> Result where @@ -97,4 +110,54 @@ mod tests { // Using the deref trait assert_eq!(kv["a"], "b"); } + + #[test] + fn test_merge_with_both_empty() { + let current = vec![]; + let other = vec![]; + let result = merge_key_value_vecs(¤t, &other); + assert!(result.is_empty()); + } + + #[test] + fn test_merge_with_current_empty() { + let current = vec![]; + let other = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; + let result = merge_key_value_vecs(¤t, &other); + assert_eq!(result.len(), 1); + assert_eq!(result[0].key, "key1"); + assert_eq!(result[0].value, "value1"); + } + + #[test] + fn test_merge_with_other_empty() { + let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; + let other = vec![]; + let result = merge_key_value_vecs(¤t, &other); + assert_eq!(result.len(), 1); + assert_eq!(result[0].key, "key1"); + assert_eq!(result[0].value, "value1"); + } + + #[test] + fn test_merge_with_unique_keys() { + let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; + let other = vec![KeyValue { key: "key2".to_string(), value: "value2".to_string() }]; + let result = merge_key_value_vecs(¤t, &other); + assert_eq!(result.len(), 2); + assert_eq!(result[0].key, "key1"); + assert_eq!(result[0].value, "value1"); + assert_eq!(result[1].key, "key2"); + assert_eq!(result[1].value, "value2"); + } + + #[test] + fn test_merge_with_overlapping_keys() { + let current = vec![KeyValue { key: "key1".to_string(), value: "value1".to_string() }]; + let other = vec![KeyValue { key: "key1".to_string(), value: "value2".to_string() }]; + let result = merge_key_value_vecs(¤t, &other); + assert_eq!(result.len(), 1); + assert_eq!(result[0].key, "key1"); + assert_eq!(result[0].value, "value2"); + } } diff --git a/src/config/server.rs b/src/config/server.rs index 542d26937a..682ee10c7d 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -8,6 +8,8 @@ use crate::config::KeyValue; use crate::is_default; use crate::merge_right::MergeRight; +use super::merge_key_value_vecs; + #[derive( Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, )] @@ -84,6 +86,7 @@ pub struct Server { pub showcase: Option, #[serde(default, skip_serializing_if = "is_default")] + #[merge_right(merge_right_fn = "merge_right_vars")] /// This configuration defines local variables for server operations. Useful /// for storing constant configurations, secrets, or shared information. pub vars: Vec, @@ -99,6 +102,20 @@ pub struct Server { pub workers: Option, } +fn merge_right_vars(mut left: Vec, right: Vec) -> Vec { + left = right.iter().fold(left.to_vec(), |mut acc, kv| { + let position = acc.iter().position(|x| x.key == kv.key); + if let Some(pos) = position { + acc[pos] = kv.clone(); + } else { + acc.push(kv.clone()); + }; + acc + }); + left = merge_key_value_vecs(&left, &right); + left +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, MergeRight)] #[serde(rename_all = "camelCase")] pub struct ScriptOptions { diff --git a/src/config/telemetry.rs b/src/config/telemetry.rs index d618dd780d..cf61043f32 100644 --- a/src/config/telemetry.rs +++ b/src/config/telemetry.rs @@ -189,7 +189,6 @@ mod tests { export: Some(TelemetryExporter::Otlp(OtlpExporter { url: "test-url-2".to_owned(), headers: vec![ - KeyValue { key: "header_a".to_owned(), value: "a".to_owned() }, KeyValue { key: "header_b".to_owned(), value: "b".to_owned() } ] })), diff --git a/src/merge_right.rs b/src/merge_right.rs index 275e6407ae..38f5fb8fc9 100644 --- a/src/merge_right.rs +++ b/src/merge_right.rs @@ -41,10 +41,16 @@ impl MergeRight for Vec { impl MergeRight for BTreeMap where K: Ord, - V: Clone, + V: Clone + MergeRight, { fn merge_right(mut self, other: Self) -> Self { - self.extend(other); + for (other_name, mut other_value) in other { + if let Some(self_value) = self.remove(&other_name) { + other_value = self_value.merge_right(other_value); + } + + self.insert(other_name, other_value); + } self } } diff --git a/tailcall-macros/Cargo.toml b/tailcall-macros/Cargo.toml index 464b4e219e..8b695509b5 100644 --- a/tailcall-macros/Cargo.toml +++ b/tailcall-macros/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" proc-macro = true [dependencies] -syn = { version = "1.0", features = ["derive", "full"] } +syn = { version = "2.0.58", features = ["derive", "full"] } quote = "1.0" proc-macro2 = "1.0" diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 1ce22fac76..5a71d62141 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -2,9 +2,53 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; +use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Fields}; -#[proc_macro_derive(MergeRight)] +const MERGE_RIGHT_FN: &'static str = "merge_right_fn"; +const MERGE_RIGHT: &'static str = "merge_right"; + +#[derive(Default)] +struct Attrs { + merge_right_fn: Option, +} + +fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { + let mut attrs_ret = Attrs::default(); + for attr in attrs { + if attr.path().is_ident(MERGE_RIGHT) { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident(MERGE_RIGHT_FN) { + let p: syn::Expr = meta.value()?.parse()?; + let lit = if let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }) = p + { + let suffix = lit.suffix(); + if !suffix.is_empty() { + return Err(syn::Error::new(lit.span(), format!("unexpected suffix `{}` on string literal", suffix))); + } + lit + } else { + return Err(syn::Error::new(p.span(), format!( + "expected merge_right {} attribute to be a string.", + MERGE_RIGHT_FN + ))); + }; + let expr_path: syn::ExprPath= lit.parse()?; + attrs_ret.merge_right_fn = Some(expr_path); + Ok(()) + } else { + Err(syn::Error::new(attr.span(), "Unknown helper attribute.")) + } + })?; + } + } + Ok(attrs_ret) +} + + +#[proc_macro_derive(MergeRight, attributes(merge_right))] pub fn merge_right_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -22,9 +66,20 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { }; let merge_logic = fields.iter().map(|f| { + let attrs = get_attrs(&f.attrs); + if let Err(err) = attrs { + panic!("{}", err); + } + let attrs = attrs.unwrap(); let name = &f.ident; - quote! { - #name: self.#name.merge_right(other.#name), + if let Some(merge_right_fn) = attrs.merge_right_fn { + quote! { + #name: #merge_right_fn(self.#name, other.#name), + } + } else { + quote! { + #name: self.#name.merge_right(other.#name), + } } }); From d1e4b4613c54d045bea1315eac9888aadf55e7a3 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 17:44:18 +0800 Subject: [PATCH 06/14] Lint fix. --- src/config/config.rs | 2 +- src/config/server.rs | 3 +-- src/config/telemetry.rs | 4 +--- tailcall-macros/src/lib.rs | 45 ++++++++++++++++++++------------------ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 98043cd7e0..80d236f8ee 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -129,7 +129,7 @@ impl Config { /// Represents a GraphQL type. /// A type can be an object, interface, enum or scalar. #[derive( - Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight + Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, )] pub struct Type { /// diff --git a/src/config/server.rs b/src/config/server.rs index 682ee10c7d..6d8549561b 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -3,13 +3,12 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; use tailcall_macros::MergeRight; +use super::merge_key_value_vecs; use crate::config::headers::Headers; use crate::config::KeyValue; use crate::is_default; use crate::merge_right::MergeRight; -use super::merge_key_value_vecs; - #[derive( Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema, MergeRight, )] diff --git a/src/config/telemetry.rs b/src/config/telemetry.rs index cf61043f32..899a4bf31f 100644 --- a/src/config/telemetry.rs +++ b/src/config/telemetry.rs @@ -188,9 +188,7 @@ mod tests { Telemetry { export: Some(TelemetryExporter::Otlp(OtlpExporter { url: "test-url-2".to_owned(), - headers: vec![ - KeyValue { key: "header_b".to_owned(), value: "b".to_owned() } - ] + headers: vec![KeyValue { key: "header_b".to_owned(), value: "b".to_owned() }] })), request_headers: vec!["Api-Key-A".to_string(), "Api-Key-B".to_string(),] } diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 5a71d62141..408d9a578f 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -2,10 +2,11 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Fields}; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Data, DeriveInput, Fields}; -const MERGE_RIGHT_FN: &'static str = "merge_right_fn"; -const MERGE_RIGHT: &'static str = "merge_right"; +const MERGE_RIGHT_FN: &str = "merge_right_fn"; +const MERGE_RIGHT: &str = "merge_right"; #[derive(Default)] struct Attrs { @@ -19,23 +20,26 @@ fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { attr.parse_nested_meta(|meta| { if meta.path.is_ident(MERGE_RIGHT_FN) { let p: syn::Expr = meta.value()?.parse()?; - let lit = if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(lit), - .. - }) = p - { - let suffix = lit.suffix(); - if !suffix.is_empty() { - return Err(syn::Error::new(lit.span(), format!("unexpected suffix `{}` on string literal", suffix))); - } - lit - } else { - return Err(syn::Error::new(p.span(), format!( - "expected merge_right {} attribute to be a string.", - MERGE_RIGHT_FN - ))); - }; - let expr_path: syn::ExprPath= lit.parse()?; + let lit = + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }) = p { + let suffix = lit.suffix(); + if !suffix.is_empty() { + return Err(syn::Error::new( + lit.span(), + format!("unexpected suffix `{}` on string literal", suffix), + )); + } + lit + } else { + return Err(syn::Error::new( + p.span(), + format!( + "expected merge_right {} attribute to be a string.", + MERGE_RIGHT_FN + ), + )); + }; + let expr_path: syn::ExprPath = lit.parse()?; attrs_ret.merge_right_fn = Some(expr_path); Ok(()) } else { @@ -47,7 +51,6 @@ fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { Ok(attrs_ret) } - #[proc_macro_derive(MergeRight, attributes(merge_right))] pub fn merge_right_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); From 5f2f59cf26f9434852f2ab5558a18b6a3bea2aef Mon Sep 17 00:00:00 2001 From: shylock Date: Sun, 14 Apr 2024 18:51:05 +0800 Subject: [PATCH 07/14] Update tailcall-macros/src/lib.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tailcall-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 408d9a578f..6be26e6694 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -113,7 +113,7 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { } }, // Optionally handle or disallow unions - Data::Union(_) => unimplemented!(), + Data::Union(_) => return syn::Error::new_spanned(input, "Union types are not supported by MergeRight").to_compile_error().into(), }; gen.into() From a20c798ed4a299ff0aa31d04cad1a27e74131462 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 18:59:37 +0800 Subject: [PATCH 08/14] Fix move error. --- tailcall-macros/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 6be26e6694..5da3c1bd03 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -55,8 +55,8 @@ fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { pub fn merge_right_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - let name = input.ident; - let generics = input.generics; + let name = input.ident.clone(); + let generics = input.generics.clone(); let gen = match input.data { // Implement for structs Data::Struct(data) => { @@ -113,7 +113,11 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { } }, // Optionally handle or disallow unions - Data::Union(_) => return syn::Error::new_spanned(input, "Union types are not supported by MergeRight").to_compile_error().into(), + Data::Union(_) => { + return syn::Error::new_spanned(input, "Union types are not supported by MergeRight") + .to_compile_error() + .into() + } }; gen.into() From e3ad90072b49d1074ee6039191175398ddef7fe3 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Sun, 14 Apr 2024 19:13:32 +0800 Subject: [PATCH 09/14] Add tests for get_attrs. --- tailcall-macros/src/lib.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 5da3c1bd03..0ad5de3fe9 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -122,3 +122,31 @@ pub fn merge_right_derive(input: TokenStream) -> TokenStream { gen.into() } + +#[cfg(test)] +mod tests { + use syn::{parse_quote, Attribute}; + + use super::*; + + #[test] + fn test_get_attrs_invalid_type() { + let attrs: Vec = vec![parse_quote!(#[merge_right(merge_right_fn = 123)])]; + let result = get_attrs(&attrs); + assert!( + result.is_err(), + "Expected error with non-string type for `merge_right_fn`" + ); + } + + #[test] + fn test_get_attrs_unexpected_suffix() { + let attrs: Vec = + vec![parse_quote!(#[merge_right(merge_right_fn = "some_fn()")])]; + let result = get_attrs(&attrs); + assert!( + result.is_err(), + "Expected error with unexpected suffix on string literal" + ); + } +} From 8ac8965571b107efb144549c00f35ac1ebd51742 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Tue, 16 Apr 2024 11:49:44 +0530 Subject: [PATCH 10/14] clean up merge_right --- src/merge_right.rs | 7 ------- src/primitive.rs | 41 ++++++++++++++--------------------------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/merge_right.rs b/src/merge_right.rs index 38f5fb8fc9..43df242828 100644 --- a/src/merge_right.rs +++ b/src/merge_right.rs @@ -1,5 +1,4 @@ use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::marker::PhantomData; use std::sync::Arc; pub trait MergeRight { @@ -17,12 +16,6 @@ impl MergeRight for Option { } } -impl MergeRight for PhantomData { - fn merge_right(self, other: Self) -> Self { - other - } -} - impl MergeRight for Arc { fn merge_right(self, other: Self) -> Self { let l = Arc::into_inner(self); diff --git a/src/primitive.rs b/src/primitive.rs index a98a064b85..f1ca387931 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,38 +1,25 @@ -use std::num::NonZeroU64; +use std::{marker::PhantomData, num::NonZeroU64}; use crate::merge_right::MergeRight; pub trait Primitive {} -impl Primitive for u64 {} - -impl Primitive for u32 {} - -impl Primitive for u16 {} - -impl Primitive for u8 {} - -impl Primitive for usize {} - -impl Primitive for i64 {} - -impl Primitive for i32 {} - -impl Primitive for i16 {} - -impl Primitive for i8 {} - -impl Primitive for f64 {} - -impl Primitive for f32 {} - impl Primitive for bool {} - impl Primitive for char {} - -impl Primitive for String {} - +impl Primitive for f32 {} +impl Primitive for f64 {} +impl Primitive for i16 {} +impl Primitive for i32 {} +impl Primitive for i64 {} +impl Primitive for i8 {} impl Primitive for NonZeroU64 {} +impl Primitive for String {} +impl Primitive for u16 {} +impl Primitive for u32 {} +impl Primitive for u64 {} +impl Primitive for u8 {} +impl Primitive for usize {} +impl Primitive for PhantomData {} impl MergeRight for A { fn merge_right(self, other: Self) -> Self { From 7a3aeda4d33c97da03fb67b0f7a8d1848debcb33 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Tue, 16 Apr 2024 14:47:38 +0800 Subject: [PATCH 11/14] Move logical to separate file. --- tailcall-macros/src/lib.rs | 145 +-------------------------- tailcall-macros/src/merge_right.rs | 151 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 142 deletions(-) create mode 100644 tailcall-macros/src/merge_right.rs diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 0ad5de3fe9..674f8d6174 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -1,152 +1,13 @@ extern crate proc_macro; use proc_macro::TokenStream; -use quote::quote; -use syn::spanned::Spanned; -use syn::{parse_macro_input, Data, DeriveInput, Fields}; -const MERGE_RIGHT_FN: &str = "merge_right_fn"; -const MERGE_RIGHT: &str = "merge_right"; +mod merge_right; -#[derive(Default)] -struct Attrs { - merge_right_fn: Option, -} +use crate::merge_right::expand_merge_right_derive; -fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { - let mut attrs_ret = Attrs::default(); - for attr in attrs { - if attr.path().is_ident(MERGE_RIGHT) { - attr.parse_nested_meta(|meta| { - if meta.path.is_ident(MERGE_RIGHT_FN) { - let p: syn::Expr = meta.value()?.parse()?; - let lit = - if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }) = p { - let suffix = lit.suffix(); - if !suffix.is_empty() { - return Err(syn::Error::new( - lit.span(), - format!("unexpected suffix `{}` on string literal", suffix), - )); - } - lit - } else { - return Err(syn::Error::new( - p.span(), - format!( - "expected merge_right {} attribute to be a string.", - MERGE_RIGHT_FN - ), - )); - }; - let expr_path: syn::ExprPath = lit.parse()?; - attrs_ret.merge_right_fn = Some(expr_path); - Ok(()) - } else { - Err(syn::Error::new(attr.span(), "Unknown helper attribute.")) - } - })?; - } - } - Ok(attrs_ret) -} #[proc_macro_derive(MergeRight, attributes(merge_right))] pub fn merge_right_derive(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let name = input.ident.clone(); - let generics = input.generics.clone(); - let gen = match input.data { - // Implement for structs - Data::Struct(data) => { - let fields = if let Fields::Named(fields) = data.fields { - fields.named - } else { - // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple - // structs, unit structs) - unimplemented!() - }; - - let merge_logic = fields.iter().map(|f| { - let attrs = get_attrs(&f.attrs); - if let Err(err) = attrs { - panic!("{}", err); - } - let attrs = attrs.unwrap(); - let name = &f.ident; - if let Some(merge_right_fn) = attrs.merge_right_fn { - quote! { - #name: #merge_right_fn(self.#name, other.#name), - } - } else { - quote! { - #name: self.#name.merge_right(other.#name), - } - } - }); - - let generics_lt = generics.lt_token; - let generics_gt = generics.gt_token; - let generics_params = generics.params; - - let generics_del = quote! { - #generics_lt #generics_params #generics_gt - }; - - quote! { - impl #generics_del MergeRight for #name #generics_del { - fn merge_right(self, other: Self) -> Self { - Self { - #(#merge_logic)* - } - } - } - } - } - // Implement for enums - Data::Enum(_) => quote! { - impl MergeRight for #name { - fn merge_right(self, other: Self) -> Self { - other - } - } - }, - // Optionally handle or disallow unions - Data::Union(_) => { - return syn::Error::new_spanned(input, "Union types are not supported by MergeRight") - .to_compile_error() - .into() - } - }; - - gen.into() -} - -#[cfg(test)] -mod tests { - use syn::{parse_quote, Attribute}; - - use super::*; - - #[test] - fn test_get_attrs_invalid_type() { - let attrs: Vec = vec![parse_quote!(#[merge_right(merge_right_fn = 123)])]; - let result = get_attrs(&attrs); - assert!( - result.is_err(), - "Expected error with non-string type for `merge_right_fn`" - ); - } - - #[test] - fn test_get_attrs_unexpected_suffix() { - let attrs: Vec = - vec![parse_quote!(#[merge_right(merge_right_fn = "some_fn()")])]; - let result = get_attrs(&attrs); - assert!( - result.is_err(), - "Expected error with unexpected suffix on string literal" - ); - } + expand_merge_right_derive(input) } diff --git a/tailcall-macros/src/merge_right.rs b/tailcall-macros/src/merge_right.rs new file mode 100644 index 0000000000..dce5c428f5 --- /dev/null +++ b/tailcall-macros/src/merge_right.rs @@ -0,0 +1,151 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{parse_macro_input, Data, DeriveInput, Fields}; + +const MERGE_RIGHT_FN: &str = "merge_right_fn"; +const MERGE_RIGHT: &str = "merge_right"; + +#[derive(Default)] +struct Attrs { + merge_right_fn: Option, +} + +fn get_attrs(attrs: &[syn::Attribute]) -> syn::Result { + let mut attrs_ret = Attrs::default(); + for attr in attrs { + if attr.path().is_ident(MERGE_RIGHT) { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident(MERGE_RIGHT_FN) { + let p: syn::Expr = meta.value()?.parse()?; + let lit = + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. }) = p { + let suffix = lit.suffix(); + if !suffix.is_empty() { + return Err(syn::Error::new( + lit.span(), + format!("unexpected suffix `{}` on string literal", suffix), + )); + } + lit + } else { + return Err(syn::Error::new( + p.span(), + format!( + "expected merge_right {} attribute to be a string.", + MERGE_RIGHT_FN + ), + )); + }; + let expr_path: syn::ExprPath = lit.parse()?; + attrs_ret.merge_right_fn = Some(expr_path); + Ok(()) + } else { + Err(syn::Error::new(attr.span(), "Unknown helper attribute.")) + } + })?; + } + } + Ok(attrs_ret) +} + +pub fn expand_merge_right_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = input.ident.clone(); + let generics = input.generics.clone(); + let gen = match input.data { + // Implement for structs + Data::Struct(data) => { + let fields = if let Fields::Named(fields) = data.fields { + fields.named + } else { + // Adjust this match arm to handle other kinds of struct fields (unnamed/tuple + // structs, unit structs) + unimplemented!() + }; + + let merge_logic = fields.iter().map(|f| { + let attrs = get_attrs(&f.attrs); + if let Err(err) = attrs { + panic!("{}", err); + } + let attrs = attrs.unwrap(); + let name = &f.ident; + if let Some(merge_right_fn) = attrs.merge_right_fn { + quote! { + #name: #merge_right_fn(self.#name, other.#name), + } + } else { + quote! { + #name: self.#name.merge_right(other.#name), + } + } + }); + + let generics_lt = generics.lt_token; + let generics_gt = generics.gt_token; + let generics_params = generics.params; + + let generics_del = quote! { + #generics_lt #generics_params #generics_gt + }; + + quote! { + impl #generics_del MergeRight for #name #generics_del { + fn merge_right(self, other: Self) -> Self { + Self { + #(#merge_logic)* + } + } + } + } + } + // Implement for enums + Data::Enum(_) => quote! { + impl MergeRight for #name { + fn merge_right(self, other: Self) -> Self { + other + } + } + }, + // Optionally handle or disallow unions + Data::Union(_) => { + return syn::Error::new_spanned(input, "Union types are not supported by MergeRight") + .to_compile_error() + .into() + } + }; + + gen.into() +} + +#[cfg(test)] +mod tests { + use syn::{parse_quote, Attribute}; + + use super::*; + + #[test] + fn test_get_attrs_invalid_type() { + let attrs: Vec = vec![parse_quote!(#[merge_right(merge_right_fn = 123)])]; + let result = get_attrs(&attrs); + assert!( + result.is_err(), + "Expected error with non-string type for `merge_right_fn`" + ); + } + + #[test] + fn test_get_attrs_unexpected_suffix() { + let attrs: Vec = + vec![parse_quote!(#[merge_right(merge_right_fn = "some_fn()")])]; + let result = get_attrs(&attrs); + assert!( + result.is_err(), + "Expected error with unexpected suffix on string literal" + ); + } +} From 9587972b92f6a6a21bda51408c3b670827a47554 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Tue, 16 Apr 2024 14:51:16 +0800 Subject: [PATCH 12/14] Lint fix. --- src/primitive.rs | 3 ++- tailcall-macros/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/primitive.rs b/src/primitive.rs index f1ca387931..8c50cb0e1a 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,4 +1,5 @@ -use std::{marker::PhantomData, num::NonZeroU64}; +use std::marker::PhantomData; +use std::num::NonZeroU64; use crate::merge_right::MergeRight; diff --git a/tailcall-macros/src/lib.rs b/tailcall-macros/src/lib.rs index 674f8d6174..b0fd5ed4d1 100644 --- a/tailcall-macros/src/lib.rs +++ b/tailcall-macros/src/lib.rs @@ -6,7 +6,6 @@ mod merge_right; use crate::merge_right::expand_merge_right_derive; - #[proc_macro_derive(MergeRight, attributes(merge_right))] pub fn merge_right_derive(input: TokenStream) -> TokenStream { expand_merge_right_derive(input) From 60856511fdb5729cb806b1d219c78b78d883e381 Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Tue, 16 Apr 2024 15:13:51 +0800 Subject: [PATCH 13/14] Reexport tailcall_macros. --- src/config/config.rs | 2 +- src/config/config_module.rs | 2 +- src/config/cors.rs | 2 +- src/config/headers.rs | 2 +- src/config/server.rs | 2 +- src/config/telemetry.rs | 2 +- src/config/upstream.rs | 2 +- src/lib.rs | 3 +++ src/rest/endpoint_set.rs | 2 +- 9 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index 14f13a9ee4..b7e05bfc64 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -7,7 +7,7 @@ use async_graphql::parser::types::ServiceDocument; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use super::telemetry::Telemetry; use super::{KeyValue, Link, Server, Upstream}; diff --git a/src/config/config_module.rs b/src/config/config_module.rs index 1c545e5092..82dca4181b 100644 --- a/src/config/config_module.rs +++ b/src/config/config_module.rs @@ -6,7 +6,7 @@ use derive_setters::Setters; use jsonwebtoken::jwk::JwkSet; use prost_reflect::prost_types::FileDescriptorSet; use rustls_pki_types::{CertificateDer, PrivateKeyDer}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use crate::blueprint::GrpcMethod; use crate::config::Config; diff --git a/src/config/cors.rs b/src/config/cors.rs index ccc699b077..8e8220f465 100644 --- a/src/config/cors.rs +++ b/src/config/cors.rs @@ -1,6 +1,6 @@ use hyper::header; use serde::{Deserialize, Serialize}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use crate::http::Method; use crate::is_default; diff --git a/src/config/headers.rs b/src/config/headers.rs index d714b35482..e8d1593fca 100644 --- a/src/config/headers.rs +++ b/src/config/headers.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use crate::config::cors::Cors; use crate::config::KeyValue; diff --git a/src/config/server.rs b/src/config/server.rs index 6d8549561b..47ef8149e5 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -1,7 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use super::merge_key_value_vecs; use crate::config::headers::Headers; diff --git a/src/config/telemetry.rs b/src/config/telemetry.rs index 899a4bf31f..9177f7b092 100644 --- a/src/config/telemetry.rs +++ b/src/config/telemetry.rs @@ -1,6 +1,6 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use super::KeyValue; use crate::config::{Apollo, ConfigReaderContext}; diff --git a/src/config/upstream.rs b/src/config/upstream.rs index 3afb85094c..beee167e1c 100644 --- a/src/config/upstream.rs +++ b/src/config/upstream.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use derive_setters::Setters; use serde::{Deserialize, Serialize}; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use crate::is_default; use crate::merge_right::MergeRight; diff --git a/src/lib.rs b/src/lib.rs index 5242bd7ade..46433e9791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,9 @@ pub mod tracing; pub mod try_fold; pub mod valid; +// Re-export everything from `tailcall_macros` as `macros` +pub use tailcall_macros as macros; + use std::borrow::Cow; use std::hash::Hash; use std::num::NonZeroU64; diff --git a/src/rest/endpoint_set.rs b/src/rest/endpoint_set.rs index 1ba926c83c..1f0c56841b 100644 --- a/src/rest/endpoint_set.rs +++ b/src/rest/endpoint_set.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use tailcall_macros::MergeRight; +use crate::macros::MergeRight; use super::endpoint::Endpoint; use super::partial_request::PartialRequest; From a107d39b9b7f9c0d73b80aa8728a5d2676201c7a Mon Sep 17 00:00:00 2001 From: Shylock Hg Date: Tue, 16 Apr 2024 15:17:12 +0800 Subject: [PATCH 14/14] Lint fix. --- src/config/config.rs | 2 +- src/config/config_module.rs | 2 +- src/config/cors.rs | 2 +- src/config/headers.rs | 2 +- src/config/server.rs | 2 +- src/config/telemetry.rs | 2 +- src/config/upstream.rs | 2 +- src/lib.rs | 3 +-- src/rest/endpoint_set.rs | 3 +-- 9 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/config/config.rs b/src/config/config.rs index b7e05bfc64..d294bac5b5 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -7,7 +7,6 @@ use async_graphql::parser::types::ServiceDocument; use derive_setters::Setters; use serde::{Deserialize, Serialize}; use serde_json::Value; -use crate::macros::MergeRight; use super::telemetry::Telemetry; use super::{KeyValue, Link, Server, Upstream}; @@ -17,6 +16,7 @@ use crate::directive::DirectiveCodec; use crate::http::Method; use crate::is_default; use crate::json::JsonSchema; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; use crate::valid::{Valid, Validator}; diff --git a/src/config/config_module.rs b/src/config/config_module.rs index 82dca4181b..266c687b2f 100644 --- a/src/config/config_module.rs +++ b/src/config/config_module.rs @@ -6,10 +6,10 @@ use derive_setters::Setters; use jsonwebtoken::jwk::JwkSet; use prost_reflect::prost_types::FileDescriptorSet; use rustls_pki_types::{CertificateDer, PrivateKeyDer}; -use crate::macros::MergeRight; use crate::blueprint::GrpcMethod; use crate::config::Config; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; use crate::rest::{EndpointSet, Unchecked}; use crate::scalar; diff --git a/src/config/cors.rs b/src/config/cors.rs index 8e8220f465..e9a81af341 100644 --- a/src/config/cors.rs +++ b/src/config/cors.rs @@ -1,9 +1,9 @@ use hyper::header; use serde::{Deserialize, Serialize}; -use crate::macros::MergeRight; use crate::http::Method; use crate::is_default; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; /// Type to configure Cross-Origin Resource Sharing (CORS) for a server. diff --git a/src/config/headers.rs b/src/config/headers.rs index e8d1593fca..71930dbb9b 100644 --- a/src/config/headers.rs +++ b/src/config/headers.rs @@ -1,11 +1,11 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; -use crate::macros::MergeRight; use crate::config::cors::Cors; use crate::config::KeyValue; use crate::is_default; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; #[derive( diff --git a/src/config/server.rs b/src/config/server.rs index 47ef8149e5..f83e88f88a 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -1,12 +1,12 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; -use crate::macros::MergeRight; use super::merge_key_value_vecs; use crate::config::headers::Headers; use crate::config::KeyValue; use crate::is_default; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; #[derive( diff --git a/src/config/telemetry.rs b/src/config/telemetry.rs index 9177f7b092..efaf01f12e 100644 --- a/src/config/telemetry.rs +++ b/src/config/telemetry.rs @@ -1,11 +1,11 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::macros::MergeRight; use super::KeyValue; use crate::config::{Apollo, ConfigReaderContext}; use crate::helpers::headers::to_mustache_headers; use crate::is_default; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; use crate::mustache::Mustache; use crate::valid::Validator; diff --git a/src/config/upstream.rs b/src/config/upstream.rs index beee167e1c..a4d3b9b58b 100644 --- a/src/config/upstream.rs +++ b/src/config/upstream.rs @@ -2,9 +2,9 @@ use std::collections::BTreeSet; use derive_setters::Setters; use serde::{Deserialize, Serialize}; -use crate::macros::MergeRight; use crate::is_default; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; #[derive( diff --git a/src/lib.rs b/src/lib.rs index 46433e9791..6ab74992b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,14 +39,13 @@ pub mod try_fold; pub mod valid; // Re-export everything from `tailcall_macros` as `macros` -pub use tailcall_macros as macros; - use std::borrow::Cow; use std::hash::Hash; use std::num::NonZeroU64; use async_graphql_value::ConstValue; use http::Response; +pub use tailcall_macros as macros; pub trait EnvIO: Send + Sync + 'static { fn get(&self, key: &str) -> Option>; diff --git a/src/rest/endpoint_set.rs b/src/rest/endpoint_set.rs index 1f0c56841b..3cd2597af6 100644 --- a/src/rest/endpoint_set.rs +++ b/src/rest/endpoint_set.rs @@ -1,12 +1,11 @@ use std::sync::Arc; -use crate::macros::MergeRight; - use super::endpoint::Endpoint; use super::partial_request::PartialRequest; use super::Request; use crate::blueprint::Blueprint; use crate::http::RequestContext; +use crate::macros::MergeRight; use crate::merge_right::MergeRight; use crate::rest::operation::OperationQuery; use crate::runtime::TargetRuntime;