neva_macros/
lib.rs

1//! A proc macro implementation for configuring tool
2
3use syn::{parse_macro_input, punctuated::Punctuated, Token};
4use proc_macro::TokenStream;
5
6#[cfg(feature = "server")]
7mod server;
8#[cfg(feature = "client")]
9mod client;
10mod shared;
11
12/// Maps the function to a tool
13/// 
14/// # Parameters
15/// * `title` - Tool title.
16/// * `descr` - Tool description.
17/// * `input_schema` - Schema for the tool input.
18/// * `output_schema` - Schema for the tool output.
19/// * `annotations` - Arbitrary [metadata](https://docs.rs/neva/latest/neva/types/tool/struct.ToolAnnotations.html).
20/// * `roles` & `permissions` - Define which users can run the tool when using Streamable HTTP transport with OAuth.
21/// * `middleware` - Middleware list to apply to the tool.
22/// * `no_schema` - Explicitly disables input schema generation if it's not set in `input_schema`.
23/// 
24/// # Simple Example
25/// ```ignore
26/// use neva::prelude::*;
27/// 
28/// #[tool(descr = "Hello world tool")]
29/// async fn say_hello() -> &'static str {
30///     "Hello, world!"
31/// }
32/// ```
33/// 
34/// # Full Example
35/// ```ignore
36/// use neva::prelude::*;
37/// 
38/// #[derive(serde::Deserialize)]
39/// struct Payload {
40///     say: String,
41///     name: String,
42/// }
43/// 
44/// #[json_schema(ser)]
45/// struct Results {
46///     message: String,
47/// }
48/// 
49/// #[tool(
50///     title = "JSON Hello",
51///     descr = "Say from JSON",
52///     roles = ["user"],
53///     permissions = ["read"],
54///     annotations = r#"{
55///         "destructiveHint": false,
56///         "idempotentHint": true,
57///         "openWorldHint": false,
58///         "readOnlyHint": false
59///     }"#,
60///     input_schema = r#"{
61///         "properties": {
62///             "arg": { 
63///                 "type": "object", 
64///                 "description": "A message in JSON format", 
65///                 "properties": {
66///                     "say": { "type": "string", "description": "A message to say" },
67///                     "name": { "type": "string", "description": "A name to whom say Hello" }
68///                 },
69///                 "required": ["say", "name"] 
70///             }
71///         },
72///         "required": ["arg"]
73///     }"#,
74///     output_schema = r#"{
75///         "properties": {
76///             "message": { "type": "string", "description": "A message to say" }
77///         },
78///         "required": ["message"]
79///     }"#
80/// )]
81/// async fn say_json(arg: Json<Payload>) -> Json<Results> {
82///     let result = Results { message: format!("{}, {}!", arg.say, arg.name) };
83///     result.into()
84/// }
85/// ```
86#[proc_macro_attribute]
87#[cfg(feature = "server")]
88pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
89    let function = parse_macro_input!(item as syn::ItemFn);
90    let attr = parse_macro_input!(
91        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
92    );
93    server::tool::expand(&attr, &function)
94        .unwrap_or_else(syn::Error::into_compile_error)
95        .into()
96}
97
98/// Maps the function to a resource template
99///
100/// # Parameters
101/// * `uri` - Resource URI.
102/// * `title` - Resource title.
103/// * `descr` - Resource description.
104/// * `mime` - Resource MIME type.
105/// * `annotations` - Resource content arbitrary [metadata](https://docs.rs/neva/latest/neva/types/struct.Annotations.html).
106/// * `roles` & `permissions` - Define which users can read the resource when using Streamable HTTP transport with OAuth.
107/// 
108/// # Simple Example
109/// ```ignore
110/// use neva::prelude::*;
111/// 
112/// #[resource(uri = "res://{name}"]
113/// async fn get_res(name: String) -> TextResourceContents {
114///     TextResourceContents::new(
115///         format!("res://{name}"),
116///         format!("Some details about resource: {name}"))
117/// }
118/// ```
119///
120/// # Full Example
121/// ```ignore
122/// use neva::prelude::*;
123/// 
124/// #[resource(
125///     uri = "res://{name}",
126///     title = "Read resource",
127///     descr = "Some details about resource",
128///     mime = "text/plain",
129///     roles = ["user"],
130///     permissions = ["read"],
131///     annotations = r#"{
132///         "audience": ["user"],
133///         "priority": 1.0
134///     }"#
135/// )]
136/// async fn get_res(name: String) -> TextResourceContents {
137///     TextResourceContents::new(
138///         format!("res://{name}"),
139///         format!("Some details about resource: {name}"))
140/// }
141/// ```
142#[proc_macro_attribute]
143#[cfg(feature = "server")]
144pub fn resource(attr: TokenStream, item: TokenStream) -> TokenStream {
145    let function = parse_macro_input!(item as syn::ItemFn);
146    let attr = parse_macro_input!(
147        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
148    );
149    server::resource::expand_resource(&attr, &function)
150        .unwrap_or_else(syn::Error::into_compile_error)
151        .into()
152}
153
154/// Maps the list of resources function
155#[proc_macro_attribute]
156#[cfg(feature = "server")]
157pub fn resources(_: TokenStream, item: TokenStream) -> TokenStream {
158    let function = parse_macro_input!(item as syn::ItemFn);
159    server::resource::expand_resources(&function)
160        .unwrap_or_else(syn::Error::into_compile_error)
161        .into()
162}
163
164/// Maps the function to a prompt
165///
166/// # Parameters
167/// * `title` - Prompt title.
168/// * `descr` - Prompt description.
169/// * `args` - Prompt arguments.
170/// * `no_args` - Explicitly disables argument generation if it's not set in `args`.
171/// * `middleware` - Middleware list to apply to the prompt.
172/// * `roles` & `permissions` - Define which users can read the resource when using Streamable HTTP transport with OAuth.
173/// 
174/// # Simple Example
175/// ```ignore
176/// use neva::prelude::*;
177/// 
178/// #[prompt(descr = "Analyze code for potential improvements"]
179/// async fn analyze_code(lang: String) -> PromptMessage {
180///     PromptMessage::user()
181///         .with(format!("Language: {lang}"))
182/// }
183/// ```
184///
185/// # Full Example
186/// ```ignore
187/// use neva::prelude::*;
188///
189/// #[prompt(
190///     title = "Code Analyzer",
191///     descr = "Analyze code for potential improvements",
192///     roles = ["user"],
193///     permissions = ["read"],
194///     args = r#"[
195///         {
196///             "name": "lang", 
197///             "description": "A language to use", 
198///             "required": true
199///         }    
200///     ]"#
201/// )]
202/// async fn analyze_code(lang: String) -> PromptMessage {
203///     PromptMessage::user()
204///         .with(format!("Language: {lang}"))
205/// }
206/// ```
207#[proc_macro_attribute]
208#[cfg(feature = "server")]
209pub fn prompt(attr: TokenStream, item: TokenStream) -> TokenStream {
210    let function = parse_macro_input!(item as syn::ItemFn);
211    let attr = parse_macro_input!(
212        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
213    );
214    server::prompt::expand(&attr, &function)
215        .unwrap_or_else(syn::Error::into_compile_error)
216        .into()
217}
218
219/// Maps the function to a command handler
220///
221/// # Parameters
222/// * `command` - Command name.
223/// * `middleware` - Middleware list to apply to the command.
224/// 
225/// # Example
226/// ```ignore
227/// use neva::prelude::*;
228/// 
229/// #[handler(command = "ping")]
230/// async fn ping_handler() {
231///     println!("pong");
232/// }
233/// ```
234#[proc_macro_attribute]
235#[cfg(feature = "server")]
236pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
237    let function = parse_macro_input!(item as syn::ItemFn);
238    let attr = parse_macro_input!(
239        attr with Punctuated::<syn::Meta, Token![,]>::parse_terminated
240    );
241    server::expand_handler(&attr, &function)
242        .unwrap_or_else(syn::Error::into_compile_error)
243        .into()
244}
245
246/// Maps the elicitation handler function
247/// 
248/// # Example
249/// ```ignore
250/// use neva::prelude::*;
251/// 
252/// #[json_schema(ser)]
253/// struct Contact {
254///     name: String,
255///     email: String,
256///     age: u32,
257/// }
258/// 
259/// #[elicitation]
260/// async fn elicitation_handler(params: ElicitRequestParams) -> impl Into<ElicitResult> {
261///     let contact = Contact {
262///         name: "John".to_string(),
263///         email: "john@email.com".to_string(),
264///         age: 30,
265///     };
266///     elicitation::Validator::new(params)
267///         .validate(contact)
268/// }
269/// ```
270#[proc_macro_attribute]
271#[cfg(feature = "client")]
272pub fn elicitation(_: TokenStream, item: TokenStream) -> TokenStream {
273    let function = parse_macro_input!(item as syn::ItemFn);
274    client::expand_elicitation(&function)
275        .unwrap_or_else(syn::Error::into_compile_error)
276        .into()
277}
278
279/// Maps the sampling handler function
280/// 
281/// # Example
282/// ```ignore
283/// use neva::prelude::*;
284/// 
285/// #[sampling]
286/// async fn sampling_handler(params: CreateMessageRequestParams) -> CreateMessageResult {
287///     CreateMessageResult::assistant()
288///         .with_model("o3-mini")
289///         .with_content("Some response")
290/// }
291/// ```
292#[proc_macro_attribute]
293#[cfg(feature = "client")]
294pub fn sampling(_: TokenStream, item: TokenStream) -> TokenStream {
295    let function = parse_macro_input!(item as syn::ItemFn);
296    client::expand_sampling(&function)
297        .unwrap_or_else(syn::Error::into_compile_error)
298        .into()
299}
300
301/// Provides a utility to extract a JSON schema of this type
302///
303/// # Optional parameters
304/// * `all` - Applies also `derive(Debug, serde::Serialize, serde::Deserialize)`.
305/// * `serde` - Applies also `derive(serde::Serialize, serde::Deserialize)`.
306/// * `ser` - Applies also `derive(serde::Serialize)`.
307/// * `de` - Applies also `derive(serde::Deserialize)`.
308/// 
309/// # Example
310/// ```ignore
311/// use neva::prelude::*;
312/// 
313/// #[json_schema(ser)]
314/// struct Results {
315///     message: String,
316/// }
317/// ```
318#[proc_macro_attribute]
319pub fn json_schema(attr: TokenStream, item: TokenStream) -> TokenStream {
320    let input = parse_macro_input!(item as syn::DeriveInput);
321    let attr = parse_macro_input!(
322        attr with Punctuated::<syn::Path, Token![,]>::parse_terminated
323    );
324    shared::expand_json_schema(&attr, &input)
325        .unwrap_or_else(syn::Error::into_compile_error)
326        .into()
327}