From a321e3cbd468d64f90dfe2ae8e34a7f94ee93d03 Mon Sep 17 00:00:00 2001 From: Gohlub <62673775+Gohlub@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:57:11 -0400 Subject: [PATCH 1/9] hotfix for WS --- src/build/wit_generator.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index 39cdaebf..6550527c 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -803,9 +803,10 @@ fn process_rust_project(project_path: &Path, api_dir: &Path) -> Result Result Result Date: Sat, 21 Jun 2025 15:10:58 -0400 Subject: [PATCH 2/9] another hotfix --- src/build/caller_utils_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/caller_utils_generator.rs b/src/build/caller_utils_generator.rs index ef76d022..d882f9ab 100644 --- a/src/build/caller_utils_generator.rs +++ b/src/build/caller_utils_generator.rs @@ -675,7 +675,7 @@ crate-type = ["cdylib", "lib"] lib_rs.push_str("pub use hyperware_app_common::AppSendError;\n"); lib_rs.push_str("pub use hyperware_app_common::send;\n"); lib_rs.push_str("use hyperware_app_common::hyperware_process_lib as hyperware_process_lib;\n"); - lib_rs.push_str("use hyperware_process_lib::{Address, Request};\n"); + lib_rs.push_str("pub use hyperware_process_lib::{Address, Request};\n"); lib_rs.push_str("use serde_json::json;\n\n"); // Add interface use statements From 92b0e1ba2e8eba3b8f61038665b1f2c293eff2e2 Mon Sep 17 00:00:00 2001 From: Gohlub <62673775+Gohlub@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:26:59 -0400 Subject: [PATCH 3/9] WIP --- src/build/wit_generator.rs | 106 ++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index 6550527c..38d0f886 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -575,11 +575,32 @@ fn generate_signature_struct( let signature_struct_name = format!("{}-signature-{}", kebab_name, attr_type); // Generate comment for this specific function - let comment = format!( + let mut comment = format!( " // Function signature for: {} ({})", kebab_name, attr_type ); + // For HTTP endpoints, try to extract method and path from attribute + let mut has_explicit_path = false; + if attr_type == "http" { + if let Some((http_method, http_path)) = extract_http_info(&method.attrs)? { + comment.push_str(&format!("\n // HTTP: {} {}", http_method, http_path)); + // Check if path was explicitly defined (not just defaulted) + has_explicit_path = method.attrs.iter().any(|attr| { + if attr.path().is_ident("http") { + let attr_str = format!("{:?}", attr); + attr_str.contains("path") + } + else { + false + } + }); + } else { + // Default path if not specified + comment.push_str(&format!("\n // HTTP: POST /api/{}", kebab_name)); + } + } + // Create struct fields that directly represent function parameters let mut struct_fields = Vec::new(); @@ -591,6 +612,9 @@ fn generate_signature_struct( struct_fields.push(" target: address".to_string()); } + // Count non-self parameters + let mut param_count = 0; + // Process function parameters (skip &self and &mut self) for arg in &method.sig.inputs { if let syn::FnArg::Typed(pat_type) = arg { @@ -600,6 +624,8 @@ fn generate_signature_struct( continue; } + param_count += 1; + // Get original param name let param_orig_name = pat_ident.ident.to_string(); let method_name_for_error = method.sig.ident.to_string(); // Get method name for error messages @@ -636,6 +662,33 @@ fn generate_signature_struct( } } + // Validate parameter requirements + if param_count == 0 && attr_type == "http" && !has_explicit_path { + let method_name = method.sig.ident.to_string(); + bail!( + "\n\nERROR: HTTP handler '{}' is missing required configuration!\n\ + \n\ + HTTP handlers must EITHER:\n\ + 1. Have at least one parameter (beyond &self/&mut self), OR\n\ + 2. Define an explicit path in the attribute\n\ + \n\ + Examples of valid HTTP handlers:\n\ + \n\ + // Option 1: Handler with parameter (path will be /api/{})\n\ + #[http]\n\ + fn {}(&mut self, request: Request) -> Response {{ ... }}\n\ + \n\ + // Option 2: Handler with explicit path (no parameters needed)\n\ + #[http(path = \"/api/status\")]\n\ + fn {}(&mut self) -> Response {{ ... }}\n\ + \n\ + // Option 3: Handler with method and path\n\ + #[http(method = \"GET\", path = \"/api/users\")]\n\ + fn {}(&mut self) -> Vec {{ ... }}\n", + method_name, method_name, method_name, method_name, method_name + ); + } + // Add return type field match &method.sig.output { syn::ReturnType::Type(_, ty) => match rust_type_to_wit(&*ty, used_types) { @@ -681,6 +734,57 @@ fn generate_signature_struct( Ok(record_def) } +// Helper function to extract HTTP method and path from [http] attribute +#[instrument(level = "trace", skip_all)] +fn extract_http_info(attrs: &[Attribute]) -> Result> { + for attr in attrs { + if attr.path().is_ident("http") { + // Convert attribute to string representation for parsing + let attr_str = format!("{:?}", attr); + debug!(attr_str = %attr_str, "HTTP attribute string"); + + let mut method = None; + let mut path = None; + + // Look for method parameter + if let Some(method_pos) = attr_str.find("method") { + if let Some(eq_pos) = attr_str[method_pos..].find('=') { + let start_pos = method_pos + eq_pos + 1; + // Find the quoted value + if let Some(quote_start) = attr_str[start_pos..].find('"') { + let value_start = start_pos + quote_start + 1; + if let Some(quote_end) = attr_str[value_start..].find('"') { + method = Some(attr_str[value_start..value_start + quote_end].to_string()); + } + } + } + } + + // Look for path parameter + if let Some(path_pos) = attr_str.find("path") { + if let Some(eq_pos) = attr_str[path_pos..].find('=') { + let start_pos = path_pos + eq_pos + 1; + // Find the quoted value + if let Some(quote_start) = attr_str[start_pos..].find('"') { + let value_start = start_pos + quote_start + 1; + if let Some(quote_end) = attr_str[value_start..].find('"') { + path = Some(attr_str[value_start..value_start + quote_end].to_string()); + } + } + } + } + + // If we found at least one parameter, return the info + if method.is_some() || path.is_some() { + let final_method = method.unwrap_or_else(|| "POST".to_string()); + let final_path = path.unwrap_or_else(|| "/api".to_string()); + return Ok(Some((final_method, final_path))); + } + } + } + Ok(None) +} + // Helper trait to get TypePath from Type trait AsTypePath { fn as_type_path(&self) -> Option<&syn::TypePath>; From 1d880d29baa5af6947bb78da1d1d36791865d364 Mon Sep 17 00:00:00 2001 From: Gohlub <62673775+Gohlub@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:00:08 -0400 Subject: [PATCH 4/9] hotifx --- src/build/wit_generator.rs | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index 38d0f886..d711ddac 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -662,32 +662,7 @@ fn generate_signature_struct( } } - // Validate parameter requirements - if param_count == 0 && attr_type == "http" && !has_explicit_path { - let method_name = method.sig.ident.to_string(); - bail!( - "\n\nERROR: HTTP handler '{}' is missing required configuration!\n\ - \n\ - HTTP handlers must EITHER:\n\ - 1. Have at least one parameter (beyond &self/&mut self), OR\n\ - 2. Define an explicit path in the attribute\n\ - \n\ - Examples of valid HTTP handlers:\n\ - \n\ - // Option 1: Handler with parameter (path will be /api/{})\n\ - #[http]\n\ - fn {}(&mut self, request: Request) -> Response {{ ... }}\n\ - \n\ - // Option 2: Handler with explicit path (no parameters needed)\n\ - #[http(path = \"/api/status\")]\n\ - fn {}(&mut self) -> Response {{ ... }}\n\ - \n\ - // Option 3: Handler with method and path\n\ - #[http(method = \"GET\", path = \"/api/users\")]\n\ - fn {}(&mut self) -> Vec {{ ... }}\n", - method_name, method_name, method_name, method_name, method_name - ); - } + // HTTP handlers no longer require parameters - they can have zero parameters // Add return type field match &method.sig.output { From b1ce5a50b57748cfc3821db00a6ef4c73efefbd0 Mon Sep 17 00:00:00 2001 From: Gohlub <62673775+Gohlub@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:00:15 -0400 Subject: [PATCH 5/9] removed old artifacts --- src/build/wit_generator.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index d711ddac..920e33d4 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -612,9 +612,6 @@ fn generate_signature_struct( struct_fields.push(" target: address".to_string()); } - // Count non-self parameters - let mut param_count = 0; - // Process function parameters (skip &self and &mut self) for arg in &method.sig.inputs { if let syn::FnArg::Typed(pat_type) = arg { @@ -624,8 +621,6 @@ fn generate_signature_struct( continue; } - param_count += 1; - // Get original param name let param_orig_name = pat_ident.ident.to_string(); let method_name_for_error = method.sig.ident.to_string(); // Get method name for error messages From d4f5bacb7bddbef3393f785d3e2b0415889ed78b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:01:28 +0000 Subject: [PATCH 6/9] Format Rust code using rustfmt --- src/build/wit_generator.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index 920e33d4..a6fdfabf 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -590,8 +590,7 @@ fn generate_signature_struct( if attr.path().is_ident("http") { let attr_str = format!("{:?}", attr); attr_str.contains("path") - } - else { + } else { false } }); @@ -724,7 +723,8 @@ fn extract_http_info(attrs: &[Attribute]) -> Result> { if let Some(quote_start) = attr_str[start_pos..].find('"') { let value_start = start_pos + quote_start + 1; if let Some(quote_end) = attr_str[value_start..].find('"') { - method = Some(attr_str[value_start..value_start + quote_end].to_string()); + method = + Some(attr_str[value_start..value_start + quote_end].to_string()); } } } From e1b8fc4d7cc713841ae7d1fa52945022e1406496 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 26 Jun 2025 17:13:52 -0700 Subject: [PATCH 7/9] build: generate ts bindings for parameter-less #[http] --- src/build/caller_utils_ts_generator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/caller_utils_ts_generator.rs b/src/build/caller_utils_ts_generator.rs index 67e46154..d72ab994 100644 --- a/src/build/caller_utils_ts_generator.rs +++ b/src/build/caller_utils_ts_generator.rs @@ -394,7 +394,7 @@ fn generate_typescript_function(signature: &SignatureStruct) -> (String, String, let request_interface = if param_names.is_empty() { // No parameters case format!( - "export interface {}Request {{\n {}: {{}}\n}}", + "export interface {}Request {{\n {}: null\n}}", pascal_function_name, pascal_function_name ) } else if param_names.len() == 1 { @@ -424,7 +424,7 @@ fn generate_typescript_function(signature: &SignatureStruct) -> (String, String, let data_construction = if param_names.is_empty() { format!( - " const data: {}Request = {{\n {}: {{}},\n }};", + " const data: {}Request = {{\n {}: null,\n }};", pascal_function_name, pascal_function_name ) } else if param_names.len() == 1 { From 531f660352376393a32952793a17c4605fb721ad Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Fri, 27 Jun 2025 05:59:17 -0700 Subject: [PATCH 8/9] build: generalize wit_generator --- src/build/wit_generator.rs | 49 ++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index a6fdfabf..039daf63 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -139,6 +139,41 @@ fn is_wit_primitive_or_builtin(type_name: &str) -> bool { || type_name.starts_with("tuple<") } +// Extract custom type names from a WIT type string (e.g., "list" -> ["foo-bar"]) +fn extract_custom_types_from_wit_type(wit_type: &str) -> Vec { + let mut custom_types = Vec::new(); + + // Skip if it's a primitive type + if is_wit_primitive_or_builtin(wit_type) && !wit_type.contains('<') { + return custom_types; + } + + // Handle composite types like list, option, result, tuple + if let Some(start) = wit_type.find('<') { + if let Some(end) = wit_type.rfind('>') { + let inner = &wit_type[start + 1..end]; + + // Split by comma to handle multiple type parameters + for part in inner.split(',') { + let trimmed = part.trim(); + if !trimmed.is_empty() && trimmed != "_" && !is_wit_primitive_or_builtin(trimmed) { + // Recursively extract from nested types + if trimmed.contains('<') { + custom_types.extend(extract_custom_types_from_wit_type(trimmed)); + } else { + custom_types.push(trimmed.to_string()); + } + } + } + } + } else if !is_wit_primitive_or_builtin(wit_type) { + // It's a non-composite custom type + custom_types.push(wit_type.to_string()); + } + + custom_types +} + // Convert Rust type to WIT type, including downstream types #[instrument(level = "trace", skip_all)] fn rust_type_to_wit(ty: &Type, used_types: &mut HashSet) -> Result { @@ -403,10 +438,10 @@ fn find_and_make_wit_type_def( let field_wit_type = rust_type_to_wit(&f.ty, global_used_types) .wrap_err_with(|| format!("Failed to convert field '{}':'{:?}' in struct '{}'", field_orig_name, f.ty, orig_name))?; - // If the resulting WIT type itself is custom, add it to *local* dependencies - // so the caller knows this struct definition depends on it. - if !is_wit_primitive_or_builtin(&field_wit_type) { - local_dependencies.insert(field_wit_type.clone()); + // Extract any custom types from the field type and add them to local dependencies + // For example, from "list" we extract "participant-info" + for custom_type in extract_custom_types_from_wit_type(&field_wit_type) { + local_dependencies.insert(custom_type); } field_strings.push(format!(" {}: {}", field_kebab_name, field_wit_type)); @@ -465,9 +500,9 @@ fn find_and_make_wit_type_def( ) })?; - // Check if the variant's type is custom and add to local deps - if !is_wit_primitive_or_builtin(&type_result) { - local_dependencies.insert(type_result.clone()); + // Extract any custom types from the variant type and add them to local dependencies + for custom_type in extract_custom_types_from_wit_type(&type_result) { + local_dependencies.insert(custom_type); } variants_wit .push(format!(" {}({})", variant_kebab_name, type_result)); From 0f7544fc412f579a339cc49b379d60a81e787426 Mon Sep 17 00:00:00 2001 From: hosted-fornet Date: Thu, 17 Jul 2025 15:33:16 -0700 Subject: [PATCH 9/9] build: fix compiler warnings --- src/build/wit_generator.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/build/wit_generator.rs b/src/build/wit_generator.rs index 039daf63..15ead1b4 100644 --- a/src/build/wit_generator.rs +++ b/src/build/wit_generator.rs @@ -616,19 +616,9 @@ fn generate_signature_struct( ); // For HTTP endpoints, try to extract method and path from attribute - let mut has_explicit_path = false; if attr_type == "http" { if let Some((http_method, http_path)) = extract_http_info(&method.attrs)? { comment.push_str(&format!("\n // HTTP: {} {}", http_method, http_path)); - // Check if path was explicitly defined (not just defaulted) - has_explicit_path = method.attrs.iter().any(|attr| { - if attr.path().is_ident("http") { - let attr_str = format!("{:?}", attr); - attr_str.contains("path") - } else { - false - } - }); } else { // Default path if not specified comment.push_str(&format!("\n // HTTP: POST /api/{}", kebab_name));