From c9d5d697934dcba566f27180f3d873df5f72839d Mon Sep 17 00:00:00 2001 From: Joe Allen <49467845+LiJoeAllen@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:34:52 +0800 Subject: [PATCH 1/3] Add PATCH/HEAD/OPTIONS method support --- codegen/src/enu.rs | 9 +++++++++ codegen/src/lib.rs | 15 +++++++++++++++ examples/method.rs | 24 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 examples/method.rs diff --git a/codegen/src/enu.rs b/codegen/src/enu.rs index b7f40db..50ff014 100644 --- a/codegen/src/enu.rs +++ b/codegen/src/enu.rs @@ -6,6 +6,9 @@ pub enum Method { POST, PUT, DELETE, + PATCH, + HEAD, + OPTIONS, } impl Method { @@ -15,6 +18,9 @@ impl Method { Method::POST => "POST", Method::PUT => "PUT", Method::DELETE => "DELETE", + Method::PATCH => "PATCH", + Method::HEAD => "HEAD", + Method::OPTIONS => "OPTIONS", } } pub fn from_str(str: &str) -> Result { @@ -23,6 +29,9 @@ impl Method { "post" | "POST" => Ok(Method::POST), "put" | "PUT" => Ok(Method::PUT), "delete" | "DELETE" => Ok(Method::DELETE), + "patch" | "PATCH" => Ok(Method::PATCH), + "head" | "HEAD" => Ok(Method::HEAD), + "options" | "OPTIONS" => Ok(Method::OPTIONS), _ => Err("unknown request method marker: ".to_string() + str), } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index ccd24fb..1b7e5fd 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -35,6 +35,21 @@ pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream { http_impl(Method::DELETE, attr, item) } +#[proc_macro_attribute] +pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream { + http_impl(Method::PATCH, attr, item) +} + +#[proc_macro_attribute] +pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream { + http_impl(Method::HEAD, attr, item) +} + +#[proc_macro_attribute] +pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream { + http_impl(Method::OPTIONS, attr, item) +} + #[proc_macro_derive(Context, attributes(url_path, query, header, param))] pub fn feign_context(item: TokenStream) -> TokenStream { feign_context_impl(item) diff --git a/examples/method.rs b/examples/method.rs new file mode 100644 index 0000000..39cdd82 --- /dev/null +++ b/examples/method.rs @@ -0,0 +1,24 @@ +use feignhttp::{head, options, patch}; + +#[patch("https://httpbin.org/patch")] +async fn patch() -> feignhttp::Result {} + +#[head("https://httpbin.org/get")] +async fn head() -> feignhttp::Result<()> {} + +#[options("https://httpbin.org")] +async fn options() -> feignhttp::Result {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let r = patch().await?; + println!("patch result: {}", r); + + head().await?; + println!("head ok"); + + let r = options().await?; + println!("options result: {}", r); + + Ok(()) +} From f5cd0668e3aecf742c54b39d0ba369806aee2f31 Mon Sep 17 00:00:00 2001 From: Joe Allen <49467845+LiJoeAllen@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:53:25 +0800 Subject: [PATCH 2/3] Make generated trait client methods inherit trait visibility --- codegen/src/structure.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/codegen/src/structure.rs b/codegen/src/structure.rs index 84b9c18..6ee542b 100644 --- a/codegen/src/structure.rs +++ b/codegen/src/structure.rs @@ -25,7 +25,12 @@ pub fn feign_impl(attr: TokenStream, item: TokenStream) -> TokenStream { let builder_ident = format_ident!("{}Builder", trait_ident); let trait_fn_streams = - match fn_to_streams_for_trait(url.clone(), &item_trait.items, meta_map.clone()) { + match fn_to_streams_for_trait( + url.clone(), + &item_trait.items, + meta_map.clone(), + &item_trait.vis, + ) { Ok(streams) => streams, Err(err) => return err.into_compile_error().into(), }; @@ -286,6 +291,7 @@ fn fn_to_streams_for_trait( url: proc_macro2::TokenStream, items: &[syn::TraitItem], meta_map: HashMap, + trait_vis: &syn::Visibility, ) -> syn::Result> { let base_url = url; let base_meta = meta_map; @@ -314,7 +320,7 @@ fn fn_to_streams_for_trait( meta_map.insert(k, v); } - let item_fn = trait_method_to_item_fn(trait_method); + let item_fn = trait_method_to_item_fn(trait_method, trait_vis); let fn_stream = fn_impl( FnMetadata { url, @@ -332,12 +338,15 @@ fn fn_to_streams_for_trait( Ok(trait_fn_streams) } -fn trait_method_to_item_fn(trait_method: &syn::TraitItemFn) -> syn::ItemFn { +fn trait_method_to_item_fn( + trait_method: &syn::TraitItemFn, + trait_vis: &syn::Visibility, +) -> syn::ItemFn { let sig = &trait_method.sig; syn::ItemFn { attrs: trait_method.attrs.clone(), - vis: syn::Visibility::Inherited, + vis: trait_vis.clone(), sig: syn::Signature { constness: None, asyncness: sig.asyncness, From 2902696801d6d408abbb7aca47f0f7a710cab423 Mon Sep 17 00:00:00 2001 From: Joe Allen <49467845+LiJoeAllen@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:53:28 +0800 Subject: [PATCH 3/3] Support enums as query parameters via ser::to_pairs --- codegen/src/func.rs | 24 ++-- src/ser/map.rs | 261 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+), 8 deletions(-) diff --git a/codegen/src/func.rs b/codegen/src/func.rs index e0db660..8f47ebe 100644 --- a/codegen/src/func.rs +++ b/codegen/src/func.rs @@ -69,7 +69,7 @@ pub fn client_fn_impl(mut item_struct: DataStruct) -> syn::Result syn::Result(t: &T) -> Result, Error> +where + T: Serialize, +{ + let mut serializer = PairsSerializer { + output: Vec::new(), + pending_key: None, + }; + t.serialize(&mut serializer)?; + Ok(serializer.output) +} + +struct PairsSerializer { + output: Vec<(String, String)>, + pending_key: Option, +} + +impl<'a> ser::Serializer for &'a mut PairsSerializer { + type Ok = (); + type Error = Error; + type SerializeMap = Self; + type SerializeStruct = Self; + type SerializeSeq = ser::Impossible<(), Error>; + type SerializeTuple = ser::Impossible<(), Error>; + type SerializeTupleStruct = ser::Impossible<(), Error>; + type SerializeTupleVariant = ser::Impossible<(), Error>; + type SerializeStructVariant = ser::Impossible<(), Error>; + + fn serialize_bool(self, v: bool) -> Result { + self.output.push((String::new(), v.to_string())); + Ok(()) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i64(self, v: i64) -> Result { + self.output.push((String::new(), v.to_string())); + Ok(()) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u64(self, v: u64) -> Result { + self.output.push((String::new(), v.to_string())); + Ok(()) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(f64::from(v)) + } + + fn serialize_f64(self, v: f64) -> Result { + self.output.push((String::new(), v.to_string())); + Ok(()) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + + fn serialize_str(self, v: &str) -> Result { + self.output.push((String::new(), v.to_string())); + Ok(()) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support bytes")), + None::, + )) + } + + fn serialize_none(self) -> Result { + Ok(()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + Ok(()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: ?Sized + Serialize, + { + let v = value.serialize(&mut crate::ser::value::StringSerializer::new())?; + self.output.push((variant.to_string(), v)); + Ok(()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support seq")), + None::, + )) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support tuple")), + None::, + )) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support tuple_struct")), + None::, + )) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support tuple_variant")), + None::, + )) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(self) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(self) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Err(Error::new( + ErrorKind::Serialize(String::from("not support struct_variant")), + None::, + )) + } +} + +impl<'a> ser::SerializeMap for &'a mut PairsSerializer { + type Ok = (); + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let k = key.serialize(&mut crate::ser::value::StringSerializer::new())?; + self.pending_key = Some(k); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let v = value.serialize(&mut crate::ser::value::StringSerializer::new())?; + let key = self.pending_key.take().unwrap_or_default(); + self.output.push((key, v)); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + +impl<'a> ser::SerializeStruct for &'a mut PairsSerializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: ?Sized + Serialize, + { + let v = value.serialize(&mut crate::ser::value::StringSerializer::new())?; + self.output.push((key.to_string(), v)); + Ok(()) + } + + fn end(self) -> Result { + Ok(()) + } +} + impl<'a> ser::Serializer for &'a mut MapSerializer { type Ok = (); type Error = Error;