Skip to content

Commit 7e24c34

Browse files
authored
Merge pull request #20 from hyperware-ai/hf/try-including-parameterless-requests
try including parameterless requests
2 parents 3568d35 + c992f96 commit 7e24c34

File tree

4 files changed

+142
-40
lines changed

4 files changed

+142
-40
lines changed

hyperprocess_macro/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ syn = { version = "2.0", features = ["full"] }
1414
anyhow = "1.0"
1515
futures-util = "0.3"
1616
hyperware_app_common = { path = "../hyperware_app_common" }
17-
#hyperware_process_lib = { version = "1.0.4", features = ["logging"] }
18-
hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "b7c9d27" }
17+
hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "cfd6843" }
1918
once_cell = "1.20.2"
2019
paste = "1.0"
2120
process_macros = "0.1.0"

hyperprocess_macro/src/lib.rs

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ fn generate_request_response_enums(
777777
// Parameter-less handlers are dispatched directly in Phase 1, not through enum deserialization
778778
let request_variants = function_metadata
779779
.iter()
780-
.filter(|func| !func.params.is_empty()) // Only include handlers with parameters
780+
//.filter(|func| !func.params.is_empty()) // Only include handlers with parameters
781781
.map(|func| {
782782
let variant_name = format_ident!("{}", &func.variant_name);
783783
generate_enum_variant(&variant_name, &func.params)
@@ -827,7 +827,7 @@ fn generate_enum_variant(
827827
if params.is_empty() {
828828
// Changed to a struct variant with no fields for functions with no parameters
829829
// This matches the JSON format {"VariantName": {}} sent by the client
830-
quote! { #variant_name{} }
830+
quote! { #variant_name }
831831
} else if params.len() == 1 {
832832
// Simple tuple variant for single parameter
833833
let param_type = &params[0];
@@ -923,11 +923,25 @@ fn generate_response_handling(
923923
quote! {
924924
// Instead of wrapping in HPMResponse enum, directly serialize the result
925925
let response_bytes = serde_json::to_vec(&result).unwrap();
926+
927+
// Get headers from APP_HELPERS if any are set
928+
let headers_opt = hyperware_app_common::APP_HELPERS.with(|ctx| {
929+
let helpers = ctx.borrow();
930+
if helpers.response_headers.is_empty() {
931+
None
932+
} else {
933+
Some(helpers.response_headers.clone())
934+
}
935+
});
936+
926937
hyperware_process_lib::http::server::send_response(
927938
hyperware_process_lib::http::StatusCode::OK,
928-
None,
939+
headers_opt,
929940
response_bytes
930941
);
942+
943+
// Clear headers after sending response
944+
hyperware_app_common::clear_response_headers();
931945
}
932946
}
933947
}
@@ -1238,10 +1252,6 @@ fn generate_message_handlers(
12381252
format!("Handler {} requires a request body", stringify!(#fn_name)).into_bytes()
12391253
);
12401254
}
1241-
1242-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1243-
ctx.borrow_mut().current_path = None;
1244-
});
12451255
return;
12461256
}
12471257
}
@@ -1271,28 +1281,31 @@ fn generate_message_handlers(
12711281
return;
12721282
}
12731283
};
1284+
1285+
// Get headers from APP_HELPERS if any are set
1286+
let headers_opt = hyperware_app_common::APP_HELPERS.with(|ctx| {
1287+
let helpers = ctx.borrow();
1288+
if helpers.response_headers.is_empty() {
1289+
None
1290+
} else {
1291+
Some(helpers.response_headers.clone())
1292+
}
1293+
});
1294+
12741295
hyperware_process_lib::http::server::send_response(
12751296
hyperware_process_lib::http::StatusCode::OK,
1276-
None,
1297+
headers_opt,
12771298
response_bytes
12781299
);
1300+
1301+
// Clear headers after sending response
1302+
hyperware_app_common::clear_response_headers();
12791303
};
12801304

12811305
let handler_body = if handler.is_async {
12821306
quote! {
1283-
// Capture context values before async execution
1284-
let current_path = hyperware_app_common::get_path();
1285-
let current_method = hyperware_app_common::get_http_method();
1286-
12871307
let state_ptr: *mut #self_ty = state;
12881308
hyperware_app_common::hyper! {
1289-
// Restore context in the async task
1290-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1291-
let mut ctx_mut = ctx.borrow_mut();
1292-
ctx_mut.current_path = current_path;
1293-
ctx_mut.current_http_method = current_method;
1294-
});
1295-
12961309
let result = unsafe { (*state_ptr).#fn_name().await };
12971310
#response_handling
12981311
}
@@ -1312,9 +1325,6 @@ fn generate_message_handlers(
13121325
if #path_check && #method_check {
13131326
hyperware_process_lib::logging::debug!("Matched parameter-less handler {} for {} {}", stringify!(#fn_name), http_method, current_path);
13141327
#handler_body
1315-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1316-
ctx.borrow_mut().current_path = None;
1317-
});
13181328
return;
13191329
}
13201330
}
@@ -1390,9 +1400,6 @@ fn generate_message_handlers(
13901400
#http_request_match_arms
13911401
hyperware_app_common::maybe_save_state(&mut *state);
13921402
}
1393-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1394-
ctx.borrow_mut().current_path = None;
1395-
});
13961403
return;
13971404
},
13981405
Err(e) => {
@@ -1418,9 +1425,6 @@ fn generate_message_handlers(
14181425
None,
14191426
error_details.into_bytes()
14201427
);
1421-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1422-
ctx.borrow_mut().current_path = None;
1423-
});
14241428
return;
14251429
}
14261430
}
@@ -1438,9 +1442,6 @@ fn generate_message_handlers(
14381442
None,
14391443
format!("No handler found for {} {}", http_method, current_path).into_bytes(),
14401444
);
1441-
hyperware_app_common::APP_HELPERS.with(|ctx| {
1442-
ctx.borrow_mut().current_path = None;
1443-
});
14441445
},
14451446
hyperware_process_lib::http::server::HttpServerRequest::WebSocketPush { channel_id, message_type } => {
14461447
hyperware_process_lib::logging::debug!("Received WebSocket message on channel {}, type: {:?}", channel_id, message_type);
@@ -1581,7 +1582,7 @@ fn generate_component_impl(
15811582
// Extract values from args for use in the quote macro
15821583
let name = &args.name;
15831584
let endpoints = &args.endpoints;
1584-
let _save_config = &args.save_config;
1585+
let save_config = &args.save_config;
15851586
let wit_world = &args.wit_world;
15861587

15871588
let icon = match &args.icon {
@@ -1655,6 +1656,11 @@ fn generate_component_impl(
16551656
// Initialize our state
16561657
let mut state = hyperware_app_common::initialize_state::<#self_ty>();
16571658

1659+
// Set to persist state according to user setting
1660+
hyperware_app_common::APP_CONTEXT.with(|ctx| {
1661+
ctx.borrow_mut().hidden_state = Some(hyperware_app_common::HiddenState::new(#save_config));
1662+
});
1663+
16581664
// Set up necessary components
16591665
let app_name = #name;
16601666
let app_icon = #icon;
@@ -1691,6 +1697,11 @@ fn generate_component_impl(
16911697
hyperware_app_common::APP_HELPERS.with(|ctx| {
16921698
ctx.borrow_mut().current_message = Some(message.clone());
16931699
});
1700+
1701+
// Store old state if needed (for OnDiff save option)
1702+
// This only stores if old_state is None (first time or after a save)
1703+
hyperware_app_common::store_old_state(&state);
1704+
16941705
match message {
16951706
hyperware_process_lib::Message::Response { body, context, .. } => {
16961707
let correlation_id = context
@@ -1706,6 +1717,12 @@ fn generate_component_impl(
17061717
hyperware_process_lib::Message::Request { .. } => {
17071718
if message.is_local() && message.source().process == "http-server:distro:sys" {
17081719
handle_http_server_message(&mut state, message);
1720+
hyperware_app_common::APP_HELPERS.with(|ctx| {
1721+
let mut ctx_mut = ctx.borrow_mut();
1722+
ctx_mut.current_path = None;
1723+
ctx_mut.current_http_method = None;
1724+
ctx_mut.response_headers = std::collections::HashMap::new();
1725+
});
17091726
} else if message.is_local() {
17101727
handle_local_message(&mut state, message);
17111728
} else {
@@ -1773,15 +1790,15 @@ pub fn hyperprocess(attr: TokenStream, item: TokenStream) -> TokenStream {
17731790
let http_handlers_with_params: Vec<_> = handlers
17741791
.http
17751792
.iter()
1776-
.filter(|h| !h.params.is_empty())
1793+
//.filter(|h| !h.params.is_empty())
17771794
.cloned()
17781795
.collect();
17791796

17801797
// Collect all function metadata that will be represented in the HPMRequest enum.
17811798
// This includes all local and remote handlers, plus HTTP handlers that have parameters.
17821799
let metadata_for_enum: Vec<_> = function_metadata
17831800
.iter()
1784-
.filter(|f| !f.is_http || !f.params.is_empty())
1801+
//.filter(|f| !f.is_http || !f.params.is_empty())
17851802
.cloned()
17861803
.collect();
17871804

hyperware_app_common/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ publish = false
66

77
[dependencies]
88
anyhow = "1.0"
9-
#hyperware_process_lib = { version = "1.0.4", features = ["logging"] }
10-
hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "b7c9d27" }
9+
hyperware_process_lib = { git = "https://github.com/hyperware-ai/process_lib", features = ["logging"], rev = "cfd6843" }
1110
futures-util = "0.3"
1211
once_cell = "1.20.2"
1312
paste = "1.0"

hyperware_app_common/src/lib.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use futures_util::task::noop_waker_ref;
99
use hyperware_process_lib::http::server::HttpServer;
1010
use hyperware_process_lib::logging::info;
1111
use hyperware_process_lib::{
12-
get_state, http, kiprintln, set_state, BuildError, LazyLoadBlob, Message, Request, SendError,
12+
get_state, http, kiprintln, set_state, timer, BuildError, LazyLoadBlob, Message, Request, SendError,
1313
};
1414
use serde::Deserialize;
1515
use serde::Serialize;
@@ -38,6 +38,7 @@ thread_local! {
3838
current_server: None,
3939
current_message: None,
4040
current_http_method: None,
41+
response_headers: HashMap::new(),
4142
});
4243
}
4344

@@ -51,6 +52,7 @@ pub struct AppHelpers {
5152
pub current_server: Option<*mut HttpServer>,
5253
pub current_message: Option<Message>,
5354
pub current_http_method: Option<String>,
55+
pub response_headers: HashMap<String, String>,
5456
}
5557

5658
// Access function for the current path
@@ -67,6 +69,27 @@ pub fn get_http_method() -> Option<String> {
6769
APP_HELPERS.with(|ctx| ctx.borrow().current_http_method.clone())
6870
}
6971

72+
// Set response headers that will be included in the HTTP response
73+
pub fn set_response_headers(headers: HashMap<String, String>) {
74+
APP_HELPERS.with(|ctx| {
75+
ctx.borrow_mut().response_headers = headers;
76+
})
77+
}
78+
79+
// Add a single response header
80+
pub fn add_response_header(key: String, value: String) {
81+
APP_HELPERS.with(|ctx| {
82+
ctx.borrow_mut().response_headers.insert(key, value);
83+
})
84+
}
85+
86+
// Clear all response headers
87+
pub fn clear_response_headers() {
88+
APP_HELPERS.with(|ctx| {
89+
ctx.borrow_mut().response_headers.clear();
90+
})
91+
}
92+
7093
// Access function for the source address of the current message
7194
pub fn source() -> hyperware_process_lib::Address {
7295
APP_HELPERS.with(|ctx| {
@@ -163,6 +186,21 @@ pub enum AppSendError {
163186
BuildError(BuildError),
164187
}
165188

189+
pub async fn sleep(sleep_ms: u64) -> Result<(), AppSendError> {
190+
let request = Request::to(("our", "timer", "distro", "sys"))
191+
.body(timer::TimerAction::SetTimer(sleep_ms))
192+
.expects_response((sleep_ms / 1_000) + 1);
193+
194+
let correlation_id = Uuid::new_v4().to_string();
195+
if let Err(e) = request.context(correlation_id.as_bytes().to_vec()).send() {
196+
return Err(AppSendError::BuildError(e));
197+
}
198+
199+
let _ = ResponseFuture::new(correlation_id).await;
200+
201+
return Ok(());
202+
}
203+
166204
pub async fn send<R>(request: Request) -> Result<R, AppSendError>
167205
where
168206
R: serde::de::DeserializeOwned,
@@ -235,17 +273,21 @@ pub enum SaveOptions {
235273
EveryNMessage(u64),
236274
// Persist State Every N Seconds
237275
EveryNSeconds(u64),
276+
// Persist State Only If Changed
277+
OnDiff,
238278
}
239279
pub struct HiddenState {
240280
save_config: SaveOptions,
241281
message_count: u64,
282+
old_state: Option<Vec<u8>>, // Stores the serialized state from before message processing
242283
}
243284

244285
impl HiddenState {
245286
pub fn new(save_config: SaveOptions) -> Self {
246287
Self {
247288
save_config,
248289
message_count: 0,
290+
old_state: None,
249291
}
250292
}
251293

@@ -263,12 +305,32 @@ impl HiddenState {
263305
}
264306
}
265307
SaveOptions::EveryNSeconds(_) => false, // Handled by timer instead
308+
SaveOptions::OnDiff => false, // Will be handled separately with state comparison
266309
}
267310
}
268311
}
269312

270313
// TODO: We need a timer macro again.
271314

315+
/// Store a snapshot of the current state before processing a message
316+
/// This is used for OnDiff save option to compare state before and after
317+
/// Only stores if old_state is None (i.e., first time or after a save)
318+
pub fn store_old_state<S>(state: &S)
319+
where
320+
S: serde::Serialize,
321+
{
322+
APP_CONTEXT.with(|ctx| {
323+
let mut ctx_mut = ctx.borrow_mut();
324+
if let Some(ref mut hidden_state) = ctx_mut.hidden_state {
325+
if matches!(hidden_state.save_config, SaveOptions::OnDiff) && hidden_state.old_state.is_none() {
326+
if let Ok(s_bytes) = rmp_serde::to_vec(state) {
327+
hidden_state.old_state = Some(s_bytes);
328+
}
329+
}
330+
}
331+
});
332+
}
333+
272334
/// Trait that must be implemented by application state types
273335
pub trait State {
274336
/// Creates a new instance of the state.
@@ -416,10 +478,35 @@ where
416478
APP_CONTEXT.with(|ctx| {
417479
let mut ctx_mut = ctx.borrow_mut();
418480
if let Some(ref mut hidden_state) = ctx_mut.hidden_state {
419-
if hidden_state.should_save_state() {
481+
let should_save = if matches!(hidden_state.save_config, SaveOptions::OnDiff) {
482+
// For OnDiff, compare current state with old state
483+
if let Ok(current_bytes) = rmp_serde::to_vec(state) {
484+
let state_changed = match &hidden_state.old_state {
485+
Some(old_bytes) => old_bytes != &current_bytes,
486+
None => true, // If no old state, consider it changed
487+
};
488+
489+
if state_changed {
490+
true
491+
} else {
492+
false
493+
}
494+
} else {
495+
false
496+
}
497+
} else {
498+
hidden_state.should_save_state()
499+
};
500+
501+
if should_save {
420502
if let Ok(s_bytes) = rmp_serde::to_vec(state) {
421503
kiprintln!("State persisted");
422504
let _ = set_state(&s_bytes);
505+
506+
// Clear old_state after saving so it can be set again on next message
507+
if matches!(hidden_state.save_config, SaveOptions::OnDiff) {
508+
hidden_state.old_state = None;
509+
}
423510
}
424511
}
425512
}

0 commit comments

Comments
 (0)