Skip to content

Commit 12b2df1

Browse files
committed
feat: add Fast API support with state + split into modules
- Add Fast API code generation for pure functions - Add Fast API with state support - Split lib.rs into modules: parse.rs, types.rs, fast.rs, codegen.rs - Bump version to 0.3.0
1 parent 962188b commit 12b2df1

7 files changed

Lines changed: 1000 additions & 360 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "openworkers-glue-v8"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2024"
55
license = "MIT"
66
description = "V8 Glue - Rust to V8 binding macros for OpenWorkers"

src/codegen.rs

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
//! Code generation for V8 callback wrappers.
2+
3+
use quote::quote;
4+
use syn::Type;
5+
6+
use crate::types::{
7+
get_option_inner_type, get_rc_inner_type, get_v8_local_inner_type, v8_local_extraction,
8+
};
9+
10+
/// Generate state extraction code for the slow path.
11+
///
12+
/// Uses V8 context slots to store and retrieve state.
13+
pub fn generate_state_extraction(
14+
has_state: bool,
15+
state_type: &Option<Type>,
16+
) -> proc_macro2::TokenStream {
17+
if !has_state {
18+
return quote! {};
19+
}
20+
21+
if let Some(state_ty) = state_type {
22+
let state_ty_str = quote!(#state_ty).to_string();
23+
24+
// V8 Context::get_slot<T>() returns Option<Rc<T>>.
25+
// So if state_ty is Rc<Counter>, we need to call get_slot::<Counter>()
26+
// to get Option<Rc<Counter>>.
27+
if let Some(inner_ty) = get_rc_inner_type(state_ty) {
28+
quote! {
29+
let Some(state) = scope.get_current_context().get_slot::<#inner_ty>() else {
30+
let msg = v8::String::new(scope, concat!("internal error: state not found for ", #state_ty_str)).unwrap();
31+
let err = v8::Exception::error(scope, msg);
32+
scope.throw_exception(err);
33+
return;
34+
};
35+
}
36+
} else {
37+
// State type is not Rc<T>, try to use it directly
38+
// (this might not work with V8's slot API, but let's try)
39+
quote! {
40+
let Some(state) = scope.get_current_context().get_slot::<#state_ty>() else {
41+
let msg = v8::String::new(scope, concat!("internal error: state not found for ", #state_ty_str)).unwrap();
42+
let err = v8::Exception::error(scope, msg);
43+
scope.throw_exception(err);
44+
return;
45+
};
46+
}
47+
}
48+
} else {
49+
quote! {
50+
compile_error!("Function has 'state' parameter but no state type specified. Use #[glue_v8::method(state = YourStateType)]");
51+
}
52+
}
53+
}
54+
55+
/// Generate argument extraction code for the slow path.
56+
///
57+
/// Handles various types:
58+
/// - Option<T>: None if undefined/null
59+
/// - v8::Local<T>: Direct V8 type extraction
60+
/// - Other types: serde_v8 deserialization
61+
pub fn generate_arg_extractions(
62+
params: &[(syn::Ident, Box<Type>)],
63+
) -> Vec<proc_macro2::TokenStream> {
64+
params
65+
.iter()
66+
.enumerate()
67+
.map(|(i, (name, ty))| {
68+
let idx = i as i32;
69+
70+
// Check if this is an Option<T> type
71+
if let Some(inner_ty) = get_option_inner_type(ty) {
72+
// Optional parameter: None if undefined/null, Some(value) otherwise
73+
let inner_type_str = quote!(#inner_ty).to_string();
74+
let error_prefix = format!("argument {}: expected {}", idx, inner_type_str);
75+
76+
quote! {
77+
let #name: #ty = {
78+
let __v8g_arg = args.get(#idx);
79+
if __v8g_arg.is_undefined() || __v8g_arg.is_null() {
80+
None
81+
} else {
82+
match serde_v8::from_v8_any(scope, __v8g_arg) {
83+
Ok(v) => Some(v),
84+
Err(e) => {
85+
let msg = v8::String::new(scope, &format!("{}: {}", #error_prefix, e)).unwrap();
86+
let err = v8::Exception::type_error(scope, msg);
87+
scope.throw_exception(err);
88+
return;
89+
}
90+
}
91+
}
92+
};
93+
}
94+
} else if let Some(inner_type) = get_v8_local_inner_type(ty) {
95+
// V8 Local type - generate direct extraction
96+
match inner_type.as_str() {
97+
"Function" => v8_local_extraction(name, idx, "Function", "is_function"),
98+
"Object" => v8_local_extraction(name, idx, "Object", "is_object"),
99+
"Array" => v8_local_extraction(name, idx, "Array", "is_array"),
100+
"Uint8Array" => v8_local_extraction(name, idx, "Uint8Array", "is_uint8_array"),
101+
"ArrayBuffer" => {
102+
v8_local_extraction(name, idx, "ArrayBuffer", "is_array_buffer")
103+
}
104+
"String" => v8_local_extraction(name, idx, "String", "is_string"),
105+
"Number" => v8_local_extraction(name, idx, "Number", "is_number"),
106+
"Value" => {
107+
// No type check needed for Value
108+
quote! {
109+
let #name: v8::Local<v8::Value> = args.get(#idx);
110+
}
111+
}
112+
_ => {
113+
// For other V8 types, try generic conversion
114+
let type_str = quote!(#ty).to_string();
115+
let error_msg = format!("argument {}: expected {}", idx, type_str);
116+
117+
quote! {
118+
let #name: #ty = match args.get(#idx).try_into() {
119+
Ok(v) => v,
120+
Err(_) => {
121+
let msg = v8::String::new(scope, #error_msg).unwrap();
122+
let err = v8::Exception::type_error(scope, msg);
123+
scope.throw_exception(err);
124+
return;
125+
}
126+
};
127+
}
128+
}
129+
}
130+
} else {
131+
// Use serde_v8 for regular types
132+
let type_str = quote!(#ty).to_string();
133+
let error_prefix = format!("argument {}: expected {}", idx, type_str);
134+
135+
quote! {
136+
let #name: #ty = match serde_v8::from_v8_any(scope, args.get(#idx)) {
137+
Ok(v) => v,
138+
Err(e) => {
139+
let msg = v8::String::new(scope, &format!("{}: {}", #error_prefix, e)).unwrap();
140+
let err = v8::Exception::type_error(scope, msg);
141+
scope.throw_exception(err);
142+
return;
143+
}
144+
};
145+
}
146+
}
147+
})
148+
.collect()
149+
}
150+
151+
/// Generate the function call and return value handling code.
152+
///
153+
/// Handles:
154+
/// - Promise mode: wrap in Promise, resolve/reject
155+
/// - Result<T, E>: throw on Err, return Ok value
156+
/// - Regular return: convert via serde_v8
157+
/// - No return: just call
158+
pub fn generate_call_and_return(
159+
fn_name: &syn::Ident,
160+
call_args: &[proc_macro2::TokenStream],
161+
has_return: bool,
162+
returns_result: bool,
163+
is_promise: bool,
164+
) -> proc_macro2::TokenStream {
165+
if is_promise {
166+
// Promise mode: wrap in a Promise, handle Result<T, E> if applicable
167+
if returns_result {
168+
quote! {
169+
let resolver = v8::PromiseResolver::new(scope).unwrap();
170+
let promise = resolver.get_promise(scope);
171+
rv.set(promise.into());
172+
173+
match #fn_name(#(#call_args),*) {
174+
Ok(value) => {
175+
if let Ok(v8_value) = serde_v8::to_v8(scope, value) {
176+
resolver.resolve(scope, v8_value);
177+
}
178+
}
179+
Err(err) => {
180+
let err_str = format!("{}", err);
181+
let msg = v8::String::new(scope, &err_str).unwrap();
182+
let error = v8::Exception::error(scope, msg);
183+
resolver.reject(scope, error);
184+
}
185+
}
186+
}
187+
} else if has_return {
188+
// Promise mode but not Result - just resolve with value
189+
quote! {
190+
let resolver = v8::PromiseResolver::new(scope).unwrap();
191+
let promise = resolver.get_promise(scope);
192+
rv.set(promise.into());
193+
194+
let result = #fn_name(#(#call_args),*);
195+
if let Ok(v8_value) = serde_v8::to_v8(scope, result) {
196+
resolver.resolve(scope, v8_value);
197+
}
198+
}
199+
} else {
200+
// Promise mode, no return - resolve with undefined
201+
quote! {
202+
let resolver = v8::PromiseResolver::new(scope).unwrap();
203+
let promise = resolver.get_promise(scope);
204+
rv.set(promise.into());
205+
206+
#fn_name(#(#call_args),*);
207+
resolver.resolve(scope, v8::undefined(scope).into());
208+
}
209+
}
210+
} else if returns_result {
211+
// Not promise mode but returns Result - throw on Err
212+
quote! {
213+
match #fn_name(#(#call_args),*) {
214+
Ok(value) => {
215+
if let Ok(v8_value) = serde_v8::to_v8(scope, value) {
216+
rv.set(v8_value);
217+
}
218+
}
219+
Err(err) => {
220+
let err_str = format!("{}", err);
221+
let msg = v8::String::new(scope, &err_str).unwrap();
222+
let error = v8::Exception::error(scope, msg);
223+
scope.throw_exception(error);
224+
}
225+
}
226+
}
227+
} else if has_return {
228+
quote! {
229+
let result = #fn_name(#(#call_args),*);
230+
if let Ok(v8_result) = serde_v8::to_v8(scope, result) {
231+
rv.set(v8_result);
232+
}
233+
}
234+
} else {
235+
quote! {
236+
#fn_name(#(#call_args),*);
237+
}
238+
}
239+
}

0 commit comments

Comments
 (0)