1

I'm making a web API client. I want to create functions that correspond to the available API endpoints.

In some cases, the URL is always the same. Then, manually calling the API looks something like this:

let things_list_url = "https://example.com/api/things/list"; let things_list: Vec<SomeThing> = make_request(GET, thing_list_url).into(); 

The macro I'm using for this looks like:

macro_rules! api_request { ($name: ident, $method: ident, $path: expr, $return_type: ty) => { pub fn $name() -> $return_type { let action_url = format!("https://example.com/api{}", $path); let action_result = make_request($method, action_url); action_result.into() } }; } api_request!(get_things_list, GET, "/things/list", Vec<SomeThing>); fn main() { println!("{:?}", get_things_list()); } 

A similar pattern works for when the API call has a body, as long as the URL remains the same.

Some other endpoints have parameters in their URL. Manually calling them looks like:

let thing = SomeThing { id: 123, ...}; let thing_status_url = format!("https://example.com/api/things/{}/status", thing.id); let thing_status: SomeThingStatus = make_request(GET, thing_status_url).into(); 

However, my attempt at making a macro for this does not work. For simplicity, let's assume there is only one argument to the format! call:

macro_rules! api_request_with_path { ($name: ident, $method: ident, $request_type: ty, $return_type: ty, $path_format_string: expr, $path_format_arg: expr) => { pub fn $name( arg: $request_type ) -> $return_type { let action_url_fragment = format!($path_format_string, $path_format_arg); let action_url = format!("https://example.com/api{}", action_url_fragment); let action_result = make_request($method, action_url); action_result.into() } }; } api_request_with_path!(get_thing_status, GET, SomeThing, SomeThingStatus, "things/{}/status", arg.id); 

This fails, because I'm passing an expression that includes arg -- the argument to the generated function -- but the arg does not exist at the scope where the macro call is. How can I provide the macro with a way to turn the argument of type $request_type into a URL string?

1 Answer 1

2
  1. You can pass a partial expression in as a raw token sequence
  2. You should probably accept a literal for the format string instead of an expression
macro_rules! api_request_with_path { ($name: ident, $method: ident, $request_type: ty, $return_type: ty, $path_format_string: literal, $($path_format_arg: tt)*) => { pub fn $name( arg: $request_type ) -> $return_type { let action_url_fragment = format!($path_format_string, arg $($path_format_arg)*); let action_url = format!("https://example.com/api{}", action_url_fragment); let action_result = make_request($method, action_url); action_result.into() } }; } api_request_with_path!(get_thing_status, GET, SomeThing, SomeThingStatus, "things/{}/status", .id); 

If that isn't flexible enough, you can accept a closure instead

macro_rules! api_request_with_path { ($name: ident, $method: ident, $request_type: ty, $return_type: ty, $path_format_string: literal, $path_format_arg: expr) => { pub fn $name( arg: $request_type ) -> $return_type { let action_url_fragment = format!($path_format_string, $path_format_arg(arg)); let action_url = format!("https://example.com/api{}", action_url_fragment); let action_result = make_request($method, action_url); action_result.into() } }; } api_request_with_path!(get_thing_status, GET, SomeThing, SomeThingStatus, "things/{}/status", |arg: SomeThing| arg.id); 

If you want to avoid needing to specify the closure parameter type, you can tokenize the closure:

macro_rules! api_request_with_path { ($name: ident, $method: ident, $request_type: ty, $return_type: ty, $path_format_string: literal, |$path_format_arg_var: ident| $($path_format_arg: tt)*) => { pub fn $name( arg: $request_type ) -> $return_type { let closure = |$path_format_arg_var: $request_type| $($path_format_arg)*; let action_url_fragment = format!($path_format_string, closure(arg)); let action_url = format!("https://example.com/api{}", action_url_fragment); let action_result = make_request($method, action_url); action_result.into() } }; } api_request_with_path!(get_thing_status, GET, SomeThing, SomeThingStatus, "things/{}/status", |arg| arg.id); 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.