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}