mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
[pitboss/grind] deferred session-0014 (20260520T233019Z-6958)
This commit is contained in:
parent
d4fdd83578
commit
ba0f83a855
2 changed files with 201 additions and 3 deletions
|
|
@ -19,8 +19,8 @@ use crate::symbol::Lang;
|
|||
use tree_sitter::Node;
|
||||
|
||||
use super::rust_routes::{
|
||||
bind_rust_path_params, find_method_attribute, find_rust_function, rust_formal_names,
|
||||
source_imports_actix,
|
||||
bind_rust_path_params, find_actix_route_chain, find_method_attribute, find_rust_function,
|
||||
rust_formal_names, source_imports_actix,
|
||||
};
|
||||
|
||||
pub struct RustActixAdapter;
|
||||
|
|
@ -46,7 +46,8 @@ impl FrameworkAdapter for RustActixAdapter {
|
|||
return None;
|
||||
}
|
||||
let func = find_rust_function(ast, file_bytes, &summary.name)?;
|
||||
let (method, path) = find_method_attribute(func, file_bytes)?;
|
||||
let (method, path) = find_method_attribute(func, file_bytes)
|
||||
.or_else(|| find_actix_route_chain(ast, file_bytes, &summary.name))?;
|
||||
let formals = rust_formal_names(func, file_bytes);
|
||||
let request_params = bind_rust_path_params(&formals, &path);
|
||||
Some(FrameworkBinding {
|
||||
|
|
@ -126,4 +127,51 @@ mod tests {
|
|||
.detect(&summary("helper"), tree.root_node(), src)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fires_on_app_new_route_chain() {
|
||||
let src: &[u8] = b"use actix_web::{App, web};\n\
|
||||
fn build() -> App<()> { App::new().route(\"/u/{id}\", web::get().to(show)) }\n\
|
||||
async fn show(id: String) -> String { id }\n";
|
||||
let tree = parse(src);
|
||||
let binding = RustActixAdapter
|
||||
.detect(&summary("show"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
assert_eq!(binding.adapter, "rust-actix");
|
||||
let route = binding.route.expect("route");
|
||||
assert_eq!(route.method, HttpMethod::GET);
|
||||
assert_eq!(route.path, "/u/{id}");
|
||||
let id = binding
|
||||
.request_params
|
||||
.iter()
|
||||
.find(|p| p.name == "id")
|
||||
.unwrap();
|
||||
assert!(matches!(id.source, ParamSource::PathSegment(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fires_on_web_resource_route_chain() {
|
||||
let src: &[u8] = b"use actix_web::{App, web};\n\
|
||||
fn build() -> App<()> { App::new().service(web::resource(\"/save\").route(web::post().to(save))) }\n\
|
||||
async fn save(body: String) -> String { body }\n";
|
||||
let tree = parse(src);
|
||||
let binding = RustActixAdapter
|
||||
.detect(&summary("save"), tree.root_node(), src)
|
||||
.expect("binding");
|
||||
let route = binding.route.expect("route");
|
||||
assert_eq!(route.method, HttpMethod::POST);
|
||||
assert_eq!(route.path, "/save");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chained_builder_requires_handler_match() {
|
||||
let src: &[u8] = b"use actix_web::{App, web};\n\
|
||||
fn build() -> App<()> { App::new().route(\"/x\", web::get().to(other)) }\n\
|
||||
async fn show() -> String { String::new() }\n\
|
||||
async fn other() -> String { String::new() }\n";
|
||||
let tree = parse(src);
|
||||
assert!(RustActixAdapter
|
||||
.detect(&summary("show"), tree.root_node(), src)
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -493,6 +493,156 @@ fn axum_callable_matches(node: Node<'_>, bytes: &[u8], target: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Walk `root` looking for an actix-web chained-builder route registration
|
||||
/// (`App::new().route("/path", web::get().to(handler))` or
|
||||
/// `web::resource("/path").route(web::get().to(handler))`) that wires
|
||||
/// `target` as the handler. Returns `(method, path)` on first match.
|
||||
pub fn find_actix_route_chain<'a>(
|
||||
root: Node<'a>,
|
||||
bytes: &'a [u8],
|
||||
target: &str,
|
||||
) -> Option<(HttpMethod, String)> {
|
||||
let mut hit: Option<(HttpMethod, String)> = None;
|
||||
walk_actix_chain(root, bytes, target, &mut hit);
|
||||
hit
|
||||
}
|
||||
|
||||
fn walk_actix_chain<'a>(
|
||||
node: Node<'a>,
|
||||
bytes: &'a [u8],
|
||||
target: &str,
|
||||
out: &mut Option<(HttpMethod, String)>,
|
||||
) {
|
||||
if out.is_some() {
|
||||
return;
|
||||
}
|
||||
if node.kind() == "call_expression"
|
||||
&& let Some(found) = try_actix_route_call(node, bytes, target)
|
||||
{
|
||||
*out = Some(found);
|
||||
return;
|
||||
}
|
||||
let mut cur = node.walk();
|
||||
for child in node.children(&mut cur) {
|
||||
walk_actix_chain(child, bytes, target, out);
|
||||
}
|
||||
}
|
||||
|
||||
fn try_actix_route_call<'a>(
|
||||
call: Node<'a>,
|
||||
bytes: &'a [u8],
|
||||
target: &str,
|
||||
) -> Option<(HttpMethod, String)> {
|
||||
let func = call.child_by_field_name("function")?;
|
||||
if func.kind() != "field_expression" {
|
||||
return None;
|
||||
}
|
||||
let field = func.child_by_field_name("field")?.utf8_text(bytes).ok()?;
|
||||
if field != "route" {
|
||||
return None;
|
||||
}
|
||||
let args = call.child_by_field_name("arguments")?;
|
||||
let positional: Vec<Node<'_>> = {
|
||||
let mut cur = args.walk();
|
||||
args.named_children(&mut cur)
|
||||
.filter(|c| !matches!(c.kind(), "line_comment" | "block_comment"))
|
||||
.collect()
|
||||
};
|
||||
let (path, verb_node) = match positional.len() {
|
||||
2 => {
|
||||
let path = rust_string_literal(positional[0], bytes)?;
|
||||
(path, positional[1])
|
||||
}
|
||||
1 => {
|
||||
let receiver = func.child_by_field_name("value")?;
|
||||
let path = find_actix_resource_path(receiver, bytes)?;
|
||||
(path, positional[0])
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let (method, handler) = parse_actix_web_verb_to(verb_node, bytes)?;
|
||||
if !axum_callable_matches(handler, bytes, target) {
|
||||
return None;
|
||||
}
|
||||
Some((method, path))
|
||||
}
|
||||
|
||||
/// Parse `web::get().to(handler)` / `web::post().to(handler)` /
|
||||
/// `web::method(Method::PATCH).to(handler)` shapes. Returns
|
||||
/// `(method, handler_node)` on the first matching `.to(...)` call.
|
||||
fn parse_actix_web_verb_to<'a>(
|
||||
node: Node<'a>,
|
||||
bytes: &'a [u8],
|
||||
) -> Option<(HttpMethod, Node<'a>)> {
|
||||
if node.kind() != "call_expression" {
|
||||
return None;
|
||||
}
|
||||
let func = node.child_by_field_name("function")?;
|
||||
if func.kind() != "field_expression" {
|
||||
return None;
|
||||
}
|
||||
let field = func.child_by_field_name("field")?.utf8_text(bytes).ok()?;
|
||||
if field != "to" {
|
||||
return None;
|
||||
}
|
||||
let args = node.child_by_field_name("arguments")?;
|
||||
let handler = {
|
||||
let mut cur = args.walk();
|
||||
args.named_children(&mut cur)
|
||||
.find(|c| !matches!(c.kind(), "line_comment" | "block_comment"))?
|
||||
};
|
||||
let recv = func.child_by_field_name("value")?;
|
||||
if recv.kind() != "call_expression" {
|
||||
return None;
|
||||
}
|
||||
let recv_func = recv.child_by_field_name("function")?;
|
||||
let leaf = match recv_func.kind() {
|
||||
"scoped_identifier" => recv_func
|
||||
.child_by_field_name("name")?
|
||||
.utf8_text(bytes)
|
||||
.ok()?,
|
||||
"identifier" => recv_func.utf8_text(bytes).ok()?,
|
||||
_ => return None,
|
||||
};
|
||||
let method = verb_from_ident(leaf)?;
|
||||
Some((method, handler))
|
||||
}
|
||||
|
||||
/// Walk a receiver-chain backwards looking for the first
|
||||
/// `web::resource(path)` / `web::scope(path)` call. Used when an actix
|
||||
/// route is registered via `web::resource("/x").route(web::get().to(h))`
|
||||
/// (no path argument on the `route` call itself).
|
||||
fn find_actix_resource_path(node: Node<'_>, bytes: &[u8]) -> Option<String> {
|
||||
let mut cur = node;
|
||||
loop {
|
||||
if cur.kind() == "call_expression" {
|
||||
let func = cur.child_by_field_name("function")?;
|
||||
let leaf = match func.kind() {
|
||||
"scoped_identifier" => func
|
||||
.child_by_field_name("name")
|
||||
.and_then(|n| n.utf8_text(bytes).ok())
|
||||
.unwrap_or(""),
|
||||
"identifier" => func.utf8_text(bytes).ok().unwrap_or(""),
|
||||
"field_expression" => {
|
||||
cur = func.child_by_field_name("value")?;
|
||||
continue;
|
||||
}
|
||||
_ => "",
|
||||
};
|
||||
if matches!(leaf, "resource" | "scope") {
|
||||
let args = cur.child_by_field_name("arguments")?;
|
||||
let mut cur_arg = args.walk();
|
||||
let first = args
|
||||
.named_children(&mut cur_arg)
|
||||
.find(|c| !matches!(c.kind(), "line_comment" | "block_comment"))?;
|
||||
return rust_string_literal(first, bytes);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk `root` looking for a `warp::path!("users" / u32)` macro
|
||||
/// invocation that bridges to `target` via `.map(target)` /
|
||||
/// `.and_then(target)`. Returns `(method, path)` on first match.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue