Skip to main content

spider_macro/
lib.rs

1//! # spider-macro
2//!
3//! Procedural macros used by the `spider-*` workspace.
4//!
5//! Right now this crate is intentionally small: it mainly provides
6//! [`scraped_item`], the attribute macro used to turn plain structs into item
7//! types that fit the crawler and pipeline APIs.
8//!
9//! ## Dependencies
10//!
11//! ```toml
12//! [dependencies]
13//! spider-macro = "0.1.12"
14//! serde = { version = "1.0", features = ["derive"] }
15//! serde_json = "1.0"
16//! ```
17//!
18//! ## Example
19//!
20//! ```rust,ignore
21//! use spider_macro::scraped_item;
22//!
23//! #[scraped_item]
24//! struct Article {
25//!     title: String,
26//!     content: String,
27//! }
28//!
29//! // `Article` now implements Serialize, Deserialize, Clone, Debug,
30//! // and the ScrapedItem trait expected by the rest of the workspace.
31//! ```
32
33extern crate proc_macro;
34
35use proc_macro::TokenStream;
36use quote::quote;
37use syn::{ItemStruct, parse_macro_input};
38
39/// Attribute macro for defining a scraped item type.
40///
41/// This macro:
42/// 1. Implements `ScrapedItem`
43/// 2. Adds `Serialize` and `Deserialize`
44/// 3. Adds `Clone` and `Debug`
45///
46/// # Dependencies
47///
48/// Your project must include `serde` and `serde_json` as direct dependencies:
49///
50/// ```toml
51/// [dependencies]
52/// serde = { version = "1.0", features = ["derive"] }
53/// serde_json = "1.0"
54/// ```
55#[proc_macro_attribute]
56pub fn scraped_item(_attr: TokenStream, item: TokenStream) -> TokenStream {
57    let ast = parse_macro_input!(item as ItemStruct);
58    let name = &ast.ident;
59
60    let expanded = quote! {
61        #[derive(
62            ::serde::Serialize,
63            ::serde::Deserialize,
64            Clone,
65            Debug
66        )]
67        #ast
68
69        impl ScrapedItem for #name {
70            fn as_any(&self) -> &dyn ::std::any::Any {
71                self
72            }
73
74            fn box_clone(&self) -> Box<dyn ScrapedItem + Send + Sync> {
75                Box::new(self.clone())
76            }
77
78            fn to_json_value(&self) -> ::serde_json::Value {
79                match ::serde_json::to_value(self) {
80                    Ok(value) => value,
81                    Err(err) => panic!("failed to serialize ScrapedItem '{}': {}", stringify!(#name), err),
82                }
83            }
84        }
85    };
86
87    TokenStream::from(expanded)
88}