golem_rust_macro/
lib.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use proc_macro::TokenStream;
16
17use syn::*;
18
19use crate::transaction::golem_operation_impl;
20
21mod expand;
22mod transaction;
23mod value;
24mod wit_gen;
25
26#[proc_macro_derive(IntoValue, attributes(flatten_value, unit_case))]
27pub fn derive_into_value(input: TokenStream) -> TokenStream {
28    value::derive_into_value(input)
29}
30
31#[proc_macro_derive(FromValueAndType, attributes(flatten_value, unit_case))]
32pub fn derive_from_value_and_type(input: TokenStream) -> TokenStream {
33    value::derive_from_value_and_type(input)
34}
35
36/// Derives `From<>` And `Into<>` typeclasses for wit-bindgen generated data types (e.g. `WitPerson`)
37/// and custom domain data types (e.g. `Person`). So it's possible to write code like this:
38/// ```ignore
39///  let person = Person {
40///     name: "John Doe".to_owned(),
41///     age: 42,
42///  };
43///
44///  let converted: WitPerson = person.into();
45/// ```
46///
47/// `#[wit_type_name(WitPerson)]` is optional. Defines name of the wit-bindgen generated data type. Default is name of this data type prepanded with 'Wit'.
48///
49/// `#[rename_field("age2")]` is optional. Anotates field and specify field name in the other data type, in case it's different.
50///
51/// # Example:
52/// ```
53///  pub struct WitPerson {
54///      pub title: String,
55///      pub age: i32,
56///  }
57///
58///
59///  #[derive(golem_rust_macro::WIT_From_Into)]
60///  #[wit_type_name(WitPerson)]
61///  pub struct Person {
62///
63///      #[rename_field("title")]
64///      pub name: String,
65///      
66///      pub age: i32
67///  }
68/// ```
69#[proc_macro_derive(WIT_From_Into, attributes(wit_type_name, rename_field))]
70pub fn derive(input: TokenStream) -> TokenStream {
71    let mut input = parse_macro_input!(input as DeriveInput);
72
73    expand::expand_wit(&mut input)
74        .unwrap_or_else(syn::Error::into_compile_error)
75        .into()
76}
77
78/// Annotates a module with `#[golem_rust_macro::create_wit_file]` and generates WIT file in the root of your project.
79/// Supports enums, structs, traits and alias types.
80///
81/// # Example:
82/// ```
83///  #[golem_rust_macro::create_wit_file("auction_app.wit")]
84///  mod auction_app {
85///  
86///      struct BidderId {
87///          bidder_id: String,
88///      }
89///  
90///      struct AuctionId {
91///          auction_id: String,
92///      }
93///  
94///      struct Auction {
95///          auction_id: Option<AuctionId>,
96///          name: String,
97///          description: String,
98///          starting_price: f32,
99///          deadline: Deadline,
100///      }
101///  
102///      enum BidResult {
103///          Failure(String),
104///          Success,
105///      }
106///  
107///      type Deadline = u64;
108///  
109///      trait AuctionService {
110///          fn initialize(auction: Auction);
111///  
112///          fn bid(bidder_id: BidderId, price: f32) -> BidResult;
113///  
114///          fn close_auction() -> Option<BidderId>;
115///  
116///          fn create_bidder(name: String, address: String) -> BidderId;
117///  
118///          fn create_auction(
119///              name: String,
120///              description: String,
121///              starting_price: f32,
122///              deadline: u64,
123///          ) -> AuctionId;
124///  
125///          fn get_auctions() -> Vec<Auction>;
126///      }
127///  }
128/// ```
129///
130/// File `auction_app.wit` is then created with the following content.
131///
132/// ```ignore
133/// package auction:app
134///
135/// interface api {
136///     
137///     record bidder-id {
138///         bidder-id: string,
139///     }
140///
141///     record auction-id {
142///         auction-id: string,
143///     }
144///
145///     record auction {
146///         auction-id: option<auction-id>,
147///         name: string,
148///         description: string,
149///         starting-price: float32,
150///         deadline: deadline,
151///     }
152///
153///     variant bid-result {
154///         failure(string),
155///         success
156///     }
157///                 
158///     type deadline = u64
159///
160///     initialize: func(auction: auction)
161///
162///     bid: func(bidder-id: bidder-id, price: float32) -> bid-result
163///
164///     close-auction: func() -> option<bidder-id>
165///
166///     create-bidder: func(name: string, address: string) -> bidder-id
167///
168///     create-auction: func(name: string, description: string, starting-price: float32, deadline: u64) -> auction-id
169///
170///     get-auctions: func() -> list<auction>
171/// }
172///
173/// world golem-service {
174///     export api
175/// }
176///  ```
177#[proc_macro_attribute]
178pub fn create_wit_file(_attr: TokenStream, item: TokenStream) -> TokenStream {
179    let item_moved = item.clone();
180
181    let mut input = parse_macro_input!(item_moved as ItemMod);
182
183    let file_name_result = syn::parse2::<syn::Lit>(_attr.into())
184        .map_or_else(|_| {
185            Ok("generated.wit".to_owned())
186        },
187        |literal| {
188            match literal {
189                syn::Lit::Str(lit) => {
190                    let mut n = lit.value();
191                    if n.ends_with(".wit") {
192                        Ok(n)
193                    } else {
194                        n.push_str(".wit");
195                        Ok(n)
196                    }
197                },
198                _ =>  Err(syn::Error::new(literal.span(), "If you want to specify name of the generated file, please input is as a String, otherwise do not input any attributes. \n Generated file will be 'generated.wit'")),
199            }
200        });
201
202    file_name_result
203        .and_then(|file_name| wit_gen::generate_witfile(&mut input, file_name).to_owned())
204        .unwrap_or_else(syn::Error::into_compile_error)
205        .into()
206}
207
208/// Defines a function as an `Operation` that can be used in transactions
209#[proc_macro_attribute]
210pub fn golem_operation(attr: TokenStream, item: TokenStream) -> TokenStream {
211    golem_operation_impl(attr, item)
212}