Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 31 additions & 36 deletions crates/rapid-web-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,12 @@ struct Handler {
is_nested: bool,
}

// Currently, the rapid file-based router will only support GET, POST, DELETE, and PUT request formats (we could support patch if needed)
/// The two canonical handler types in Rapid's file-based router.
/// Legacy HTTP method-specific types (get, post, put, delete, patch) have been
/// deprecated in favor of `query` (read operations) and `mutation` (write operations).
enum RouteHandler {
Query(Handler),
Mutation(Handler),
Get(Handler),
Post(Handler),
Delete(Handler),
Put(Handler),
Patch(Handler),
}

/// Macro for generated rapid route handlers based on the file system
Expand Down Expand Up @@ -223,11 +220,6 @@ pub fn routes(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let idents = route_handlers
.into_iter()
.map(|it| match it {
RouteHandler::Get(route_handler) => generate_handler_tokens(route_handler, parsed_path, "get"),
RouteHandler::Post(route_handler) => generate_handler_tokens(route_handler, parsed_path, "post"),
RouteHandler::Delete(route_handler) => generate_handler_tokens(route_handler, parsed_path, "delete"),
RouteHandler::Put(route_handler) => generate_handler_tokens(route_handler, parsed_path, "put"),
RouteHandler::Patch(route_handler) => generate_handler_tokens(route_handler, parsed_path, "patch"),
RouteHandler::Query(route_handler) => generate_handler_tokens(route_handler, parsed_path, "query"),
RouteHandler::Mutation(route_handler) => generate_handler_tokens(route_handler, parsed_path, "mutation"),
})
Expand Down Expand Up @@ -390,48 +382,51 @@ fn generate_handler_tokens(route_handler: Handler, parsed_path: &str, handler_ty

// Output our idents based on the handler types
match handler_type {
// Check if we got a query or mutation..
"query" => {
// If we got a query type we want to generate routes for `get` request types (`delete` could get moved to here too...?)
// Query handlers map to HTTP GET requests
quote!(
.route(#rapid_routes_path, web::get().to(#handler::#parsed_handler_type)#(#middleware_idents)*)
)
}
"mutation" => {
// If we got a mutation type we want to generate routes for each of the following (all at the same path):
// `post`, `put`, `patch`, `delete`
// Mutation handlers map to all write HTTP methods (POST, PUT, PATCH, DELETE)
quote!(
.route(#rapid_routes_path, web::post().to(#handler::#parsed_handler_type)#(#middleware_idents)*)
.route(#rapid_routes_path, web::put().to(#handler::#parsed_handler_type)#(#middleware_idents)*)
.route(#rapid_routes_path, web::patch().to(#handler::#parsed_handler_type)#(#middleware_idents)*)
.route(#rapid_routes_path, web::delete().to(#handler::#parsed_handler_type)#(#middleware_idents)*)
)
}
// Currently we still support declaring handlers with a very specific HTTP type (ex: `get` or `post` etc)
// ^^^ Eventually, what was described above should get deprecated
_ => quote!(.route(#rapid_routes_path, web::#parsed_handler_type().to(#handler::#parsed_handler_type)#(#middleware_idents)*)),
_ => unreachable!("Invalid handler type: '{}'. Only 'query' and 'mutation' are supported.", handler_type),
}
}

/// Function for parsing a route file and making sure it contains a valid handler
/// If it does, we want to push the valid handler to the handlers array
/// Note: no need to support HEAD and OPTIONS requests
/// Valid handler function names that Rapid recognizes.
/// Only `query` and `mutation` are canonical; legacy HTTP method names are
/// accepted for backwards compatibility but mapped to query/mutation.
const VALID_HANDLER_NAMES: &[&str] = &["query", "mutation", "get", "post", "put", "delete", "patch"];

/// Function for parsing a route file and making sure it contains a valid handler.
/// If it does, we push the valid handler to the handlers array.
///
/// Handlers named `query` or `get` are treated as `Query` handlers.
/// Handlers named `mutation`, `post`, `put`, `delete`, or `patch` are treated as `Mutation` handlers.
/// Legacy HTTP method names (`get`, `post`, `put`, `delete`, `patch`) are deprecated.
fn parse_handlers(route_handlers: &mut Vec<RouteHandler>, file_contents: String, handler: Handler) {
// TODO: we need to depricate everything except for `query` and `mutation`
if file_contents.contains("async fn get") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Get(handler))
} else if file_contents.contains("async fn post") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Post(handler))
} else if file_contents.contains("async fn delete") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Delete(handler))
} else if file_contents.contains("async fn put") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Put(handler))
} else if file_contents.contains("async fn patch") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Patch(handler))
} else if file_contents.contains("async fn query") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Query(handler))
} else if file_contents.contains("async fn mutation") && validate_route_handler(&file_contents) {
route_handlers.push(RouteHandler::Mutation(handler))
if !validate_route_handler(&file_contents) {
return;
}

for name in VALID_HANDLER_NAMES {
let fn_signature = format!("async fn {}", name);
if file_contents.contains(&fn_signature) {
match *name {
"query" | "get" => route_handlers.push(RouteHandler::Query(handler)),
"mutation" | "post" | "put" | "delete" | "patch" => route_handlers.push(RouteHandler::Mutation(handler)),
_ => unreachable!(),
}
return;
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion crates/rapid-web-codegen/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ pub fn reverse_route_path(route_path: String) -> String {
new_route_path
}

// TODO: this is a clone from the rapid-web crate utils (at some point we need a rapid-utils crate so that we can avoid duplication)
// NOTE: This is duplicated from rapid-web/src/shift/util.rs because proc-macro crates
// cannot export non-macro items. Consider creating a shared `rapid-web-utils` crate to
// eliminate this duplication.
/// Method for checking if a handler function is valid
/// Handlers are only valid if they have a "#[rapid_handler]" macro on them
pub fn is_valid_handler(macro_name: &str, attributes: Vec<syn::Attribute>) -> bool {
Expand Down
33 changes: 4 additions & 29 deletions crates/rapid-web/src/shift/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,6 @@ pub fn generate_handler_types(routes_path: PathBuf, converter: &mut TypescriptCo
}

match request_type {
HandlerRequestType::Get => {
handlers.push(Handler::Query(TypedQueryHandler {
request_type,
path,
query_params,
output_type,
route_key,
}));
}
HandlerRequestType::Query => {
handlers.push(Handler::Query(TypedQueryHandler {
request_type,
Expand All @@ -150,7 +141,7 @@ pub fn generate_handler_types(routes_path: PathBuf, converter: &mut TypescriptCo
route_key,
}));
}
_ => {
HandlerRequestType::Mutation => {
handlers.push(Handler::Mutation(TypedMutationHandler {
request_type,
query_params,
Expand Down Expand Up @@ -206,15 +197,7 @@ pub fn create_typescript_types(out_dir: PathBuf, route_dir: PathBuf, type_genera
let is_dynamic_route_path = is_dynamic_route(&route_path);

let spacing = space(2);
let request_type = match query.request_type {
HandlerRequestType::Post => "post",
HandlerRequestType::Put => "put",
HandlerRequestType::Delete => "delete",
HandlerRequestType::Get => "get",
HandlerRequestType::Patch => "patch",
HandlerRequestType::Query => "query",
HandlerRequestType::Mutation => "mutation",
};
let request_type = query.request_type.as_str();

if let Some(query_params_type) = query.query_params {
let query_type = query_params_type.typescript_type;
Expand Down Expand Up @@ -257,15 +240,7 @@ pub fn create_typescript_types(out_dir: PathBuf, route_dir: PathBuf, type_genera
let route_path = mutation.route_key.value;
let spacing = space(2);
let is_dynamic_route_path = is_dynamic_route(&route_path);
let request_type = match mutation.request_type {
HandlerRequestType::Post => "post",
HandlerRequestType::Put => "put",
HandlerRequestType::Delete => "delete",
HandlerRequestType::Get => "get",
HandlerRequestType::Patch => "patch",
HandlerRequestType::Query => "query",
HandlerRequestType::Mutation => "mutation",
};
let request_type = mutation.request_type.as_str();

if let Some(query_params_type) = mutation.query_params {
// We only want to add query params if the TS type has already been generated
Expand Down Expand Up @@ -390,7 +365,7 @@ pub fn generate_routes(routes_dir: &str) -> String {

let handler_type = match get_handler_type(&route_file_contents) {
Some(name) => name,
None => String::from("get"),
None => String::from("query"),
};

// Construct our routes object
Expand Down
91 changes: 65 additions & 26 deletions crates/rapid-web/src/shift/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ use syn::{parse_file, Expr, File as SynFile, Generics, Item, Lit, Type};
pub const GENERATED_TS_FILE_MESSAGE: &str =
"// @generated automatically by Rapid-web (https://rapidframework.dev). DO NOT CHANGE OR EDIT THIS FILE!";

/// Valid handler function names that Rapid recognizes as route handlers.
/// Only `query` and `mutation` are the canonical handler types.
/// The legacy HTTP method names (`get`, `post`, `put`, `delete`, `patch`) are
/// deprecated and will be mapped to `query` or `mutation` automatically.
pub const VALID_HANDLER_NAMES: &[&str] = &["query", "mutation", "get", "post", "put", "delete", "patch"];

#[derive(Debug)]
pub enum TypeClass {
InputBody,
Expand All @@ -13,17 +19,67 @@ pub enum TypeClass {
Return,
}

/// Represents the two canonical handler types in Rapid.
///
/// - `Query` maps to HTTP GET (read operations)
/// - `Mutation` maps to HTTP POST/PUT/PATCH/DELETE (write operations)
///
/// The legacy HTTP method-specific handler types (`get`, `post`, `put`, `delete`, `patch`)
/// have been deprecated in favor of `query` and `mutation`.
#[derive(Debug, Clone, PartialEq)]
pub enum HandlerRequestType {
Get,
Post,
Delete,
Put,
Patch,
Query,
Mutation,
}

impl HandlerRequestType {
/// Parses a handler function name into the corresponding `HandlerRequestType`.
///
/// - `"query"` and `"get"` map to `Query`
/// - `"mutation"`, `"post"`, `"put"`, `"delete"`, and `"patch"` map to `Mutation`
///
/// Legacy HTTP method names (`get`, `post`, `put`, `delete`, `patch`) are accepted
/// for backwards compatibility but will emit a deprecation warning.
///
/// Unknown function names default to `Query` with a warning.
pub fn from_function_name(name: &str) -> Self {
match name {
"query" => HandlerRequestType::Query,
"mutation" => HandlerRequestType::Mutation,
// Deprecated: legacy HTTP method names are mapped to query/mutation
"get" => {
eprintln!(
"[rapid-web] DEPRECATION WARNING: Handler function name 'get' is deprecated. Use 'query' instead."
);
HandlerRequestType::Query
}
"post" | "put" | "patch" | "delete" => {
eprintln!(
"[rapid-web] DEPRECATION WARNING: Handler function name '{}' is deprecated. Use 'mutation' instead.",
name
);
HandlerRequestType::Mutation
}
_ => {
eprintln!(
"[rapid-web] WARNING: Unknown handler function name '{}'. Defaulting to 'query'. Valid names are: query, mutation.",
name
);
HandlerRequestType::Query
}
}
}

/// Returns the canonical string representation of this handler type.
/// Always returns either `"query"` or `"mutation"`.
pub fn as_str(&self) -> &'static str {
match self {
HandlerRequestType::Query => "query",
HandlerRequestType::Mutation => "mutation",
}
}
}

#[derive(Debug)]
pub struct HandlerType {
pub type_value: Option<Type>,
Expand All @@ -40,7 +96,8 @@ pub fn extract_handler_types(route_source: &str) -> Option<Vec<Option<HandlerTyp
if is_valid_handler("rapid_handler", function.attrs) {
let mut function_types: Vec<Option<HandlerType>> = Vec::new();
let arg_types = function.sig.inputs.iter();
let function_name = function.sig.ident;
let function_name = function.sig.ident.to_string();
let handler_type = HandlerRequestType::from_function_name(&function_name);

for type_value in arg_types {
if let syn::FnArg::Typed(typed) = type_value {
Expand All @@ -49,33 +106,15 @@ pub fn extract_handler_types(route_source: &str) -> Option<Vec<Option<HandlerTyp
function_types.push(Some(HandlerType {
type_value: Some(rust_type),
class: type_class,
handler_type: match function_name.to_string().as_str() {
"get" => HandlerRequestType::Get,
"post" => HandlerRequestType::Post,
"delete" => HandlerRequestType::Delete,
"put" => HandlerRequestType::Put,
"patch" => HandlerRequestType::Patch,
"query" => HandlerRequestType::Query,
"mutation" => HandlerRequestType::Mutation,
_ => HandlerRequestType::Get,
},
handler_type: handler_type.clone(),
}));
}
}

function_types.push(Some(HandlerType {
type_value: None,
class: Some(TypeClass::Return),
handler_type: match function_name.to_string().as_str() {
"get" => HandlerRequestType::Get,
"post" => HandlerRequestType::Post,
"delete" => HandlerRequestType::Delete,
"put" => HandlerRequestType::Put,
"patch" => HandlerRequestType::Patch,
"query" => HandlerRequestType::Query,
"mutation" => HandlerRequestType::Mutation,
_ => HandlerRequestType::Get,
},
handler_type: handler_type.clone(),
}));

return Some(function_types);
Expand Down
34 changes: 12 additions & 22 deletions crates/rapid-web/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{server::RAPID_SERVER_CONFIG, shift::util::is_valid_handler};
use super::{server::RAPID_SERVER_CONFIG, shift::util::{is_valid_handler, VALID_HANDLER_NAMES}};
use colorful::{Color, Colorful};
use core::panic;
use log::warn;
Expand Down Expand Up @@ -37,9 +37,12 @@ pub fn check_for_invalid_handlers(dir: &str) {
}
}

/// Note: this is a dupe of a function in the rapid-web-codegen crate (ideally we create a rapid-web-utils crate at some point)
/// Helper function for checking if a rapid route file is valid
/// We need this so that we can generate actix-web routes for only valid route files
/// NOTE: This is duplicated from rapid-web-codegen/src/utils.rs because proc-macro crates
/// cannot export non-macro items. Consider creating a shared `rapid-web-utils` crate to
/// eliminate this duplication.
///
/// Helper function for checking if a rapid route file is valid.
/// We need this so that we can generate actix-web routes for only valid route files.
pub fn validate_route_handler(handler_source: &String) -> bool {
// Check if the file is actually valid rust code
// If not, we want to output a invalid route rusult (false)
Expand Down Expand Up @@ -70,25 +73,12 @@ pub fn validate_route_handler(handler_source: &String) -> bool {
has_rapid_handler && handler_count == 1
}

/// Make sure there is a valid function with the correct HTTP method
/// Make sure there is a valid function with a recognized handler name.
/// Accepted names: `query`, `mutation` (canonical), plus deprecated `get`, `post`, `put`, `delete`, `patch`.
pub fn is_valid_route_function(file_contents: &str) -> bool {
if file_contents.contains("async fn get") {
return true;
} else if file_contents.contains("async fn post") {
return true;
} else if file_contents.contains("async fn delete") {
return true;
} else if file_contents.contains("async fn put") {
return true;
} else if file_contents.contains("async fn patch") {
return true;
} else if file_contents.contains("async fn query") {
return true;
} else if file_contents.contains("async fn mutation") {
return true;
}

false
VALID_HANDLER_NAMES
.iter()
.any(|name| file_contents.contains(&format!("async fn {}", name)))
}

/// Function for getting the routes directory from the rapid config file
Expand Down
4 changes: 2 additions & 2 deletions packages/rapid-react/src/__tests__/bolt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface Handlers {
queries: {
route: {
output: any;
type: 'get';
type: 'query';
isDynamic: false;
};
};
Expand All @@ -25,7 +25,7 @@ interface Handlers {
const routes = {
route: {
url: '/route',
type: 'get',
type: 'query',
},
} as const;

Expand Down
Loading
Loading