Skip to content
Merged
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
16 changes: 14 additions & 2 deletions spec/functional_test/fixtures/rust/axum/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use axum::{extract::{Form, Query}, http::HeaderMap, response::Html, routing::{any, get, post}, Router};
use axum::{
extract::{Form, Query},
http::HeaderMap,
response::Html,
routing::{any, get, on, post, query, query_service, MethodFilter},
Router,
};
use axum_extra::extract::CookieJar;
use tower_http::services::ServeDir;

Expand All @@ -11,6 +17,11 @@ async fn main() {
.route("/foo", get(handler))
.route("/bar", post(handler))
.route("/search", get(search))
.route("/search_query", query(search))
.route("/items", get(handler).query(search))
.route("/svc", query_service(ServeDir::new("assets")))
.route("/filter-query", on(MethodFilter::QUERY, handler))
.route("/filter-combo", on(MethodFilter::GET.or(MethodFilter::QUERY), handler))
.route("/submit", post(submit))
.route("/headers", get(headers))
.route("/session", get(session))
Expand All @@ -29,7 +40,8 @@ async fn main() {
"/api",
Router::new()
.route("/users", get(handler))
.route("/admin", post(handler)),
.route("/admin", post(handler))
.route("/query-nested", query(handler)),
)
// Real applications often build a sub-router in a local binding before
// mounting it. The route must inherit the nest prefix and must not also
Expand Down
12 changes: 12 additions & 0 deletions spec/functional_test/testers/rust/axum_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ expected_endpoints = [
Endpoint.new("/search", "GET", [
Param.new("query", "", "query"),
]),
Endpoint.new("/search_query", "QUERY", [
Param.new("query", "", "query"),
]),
Endpoint.new("/items", "GET"),
Endpoint.new("/items", "QUERY", [
Param.new("query", "", "query"),
]),
Endpoint.new("/svc", "QUERY"),
Endpoint.new("/filter-query", "QUERY"),
Endpoint.new("/filter-combo", "GET"),
Endpoint.new("/filter-combo", "QUERY"),
Endpoint.new("/submit", "POST", [
Param.new("form", "", "form"),
]),
Expand All @@ -26,6 +37,7 @@ expected_endpoints = [
]),
Endpoint.new("/api/users", "GET"),
Endpoint.new("/api/admin", "POST"),
Endpoint.new("/api/query-nested", "QUERY"),
Endpoint.new("/internal/health", "GET"),
Endpoint.new("/v1/projects", "GET"),
Endpoint.new("/v1/projects/{id}", "GET", [
Expand Down
327 changes: 327 additions & 0 deletions spec/unit_test/analyzer/analyzer_axum_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
require "../../spec_helper"
require "../../../src/models/code_locator"
require "../../../src/analyzer/analyzers/rust/axum"

describe Analyzer::Rust::Axum do
options = create_test_options

it "detects basic routing::query routes" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::query, Router};

async fn search_handler() {}

fn app() -> Router {
Router::new()
.route("/search", query(search_handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(1)
endpoints[0].url.should eq("/search")
endpoints[0].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects chained MethodRouter with .query()" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{get, query}, Router};

async fn list_handler() {}
async fn query_handler() {}

fn app() -> Router {
Router::new()
.route("/items", get(list_handler).query(query_handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(2)
endpoints[0].url.should eq("/items")
endpoints[0].method.should eq("GET")
endpoints[1].url.should eq("/items")
endpoints[1].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects query_service routes" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::query_service, Router};

fn app() -> Router {
Router::new()
.route("/svc", query_service(my_svc))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(1)
endpoints[0].url.should eq("/svc")
endpoints[0].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects on(MethodFilter::QUERY, ...)" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{on, MethodFilter}, Router};

async fn handler() {}

fn app() -> Router {
Router::new()
.route("/filter-q", on(MethodFilter::QUERY, handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(1)
endpoints[0].url.should eq("/filter-q")
endpoints[0].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects on(MethodFilter::GET.or(MethodFilter::QUERY), ...)" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{on, MethodFilter}, Router};

async fn handler() {}

fn app() -> Router {
Router::new()
.route("/combo", on(MethodFilter::GET.or(MethodFilter::QUERY), handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(2)
endpoints[0].url.should eq("/combo")
endpoints[0].method.should eq("GET")
endpoints[1].url.should eq("/combo")
endpoints[1].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects on(MethodFilter::GET | MethodFilter::QUERY, ...)" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{on, MethodFilter}, Router};

async fn handler() {}

fn app() -> Router {
Router::new()
.route("/pipe-combo", on(MethodFilter::GET | MethodFilter::QUERY, handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(2)
endpoints[0].url.should eq("/pipe-combo")
endpoints[0].method.should eq("GET")
endpoints[1].url.should eq("/pipe-combo")
endpoints[1].method.should eq("QUERY")

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects on(MethodFilter::all(), ...) and fans out" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{on, MethodFilter}, Router};

async fn handler() {}

fn app() -> Router {
Router::new()
.route("/all-methods", on(MethodFilter::all(), handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
endpoints.size.should eq(7)
endpoints.map(&.method).sort!.should eq(%w[DELETE GET HEAD OPTIONS PATCH POST PUT].sort!)

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "propagates nest prefix to QUERY routes" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{get, on, query, MethodFilter}, Router};

async fn handler() {}

fn api_routes() -> Router {
Router::new()
.route("/search", query(handler))
.route("/items", get(handler).query(handler))
.route("/filter", on(MethodFilter::QUERY, handler))
}

fn app() -> Router {
Router::new()
.nest("/api/v1", api_routes())
.nest("/scoped", Router::new().route("/direct-query", query(handler)))
}
RUST

endpoints = instance.analyze_file(temp_file)
urls_and_methods = endpoints.map { |e| {e.url, e.method} }

urls_and_methods.should contain({"/api/v1/search", "QUERY"})
urls_and_methods.should contain({"/api/v1/items", "GET"})
urls_and_methods.should contain({"/api/v1/items", "QUERY"})
urls_and_methods.should contain({"/api/v1/filter", "QUERY"})
urls_and_methods.should contain({"/scoped/direct-query", "QUERY"})

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects chained .on() and on_service()" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::{get, on, on_service, MethodFilter}, Router};

async fn list_handler() {}
async fn query_handler() {}

fn app() -> Router {
Router::new()
.route("/items", get(list_handler).on(MethodFilter::QUERY, query_handler))
.route("/svc", on_service(MethodFilter::QUERY, my_svc))
.route("/multi", on(MethodFilter::GET.or(MethodFilter::POST).or(MethodFilter::QUERY), list_handler))
}
RUST

endpoints = instance.analyze_file(temp_file)
urls_and_methods = endpoints.map { |e| {e.url, e.method} }

urls_and_methods.should contain({"/items", "GET"})
urls_and_methods.should contain({"/items", "QUERY"})
urls_and_methods.should contain({"/svc", "QUERY"})
urls_and_methods.should contain({"/multi", "GET"})
urls_and_methods.should contain({"/multi", "POST"})
urls_and_methods.should contain({"/multi", "QUERY"})

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "detects custom route builders with query and unauthenticated_query" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
fn app() {
RouteBuilder::new()
.query("/custom-query", handler)
.unauthenticated_query("/open-query", handler);
}
RUST

endpoints = instance.analyze_file(temp_file)
urls_and_methods = endpoints.map { |e| {e.url, e.method} }

urls_and_methods.should contain({"/custom-query", "QUERY"})
urls_and_methods.should contain({"/open-query", "QUERY"})

File.delete(temp_file)
Dir.delete(temp_dir)
end

it "ignores QUERY routes inside #[cfg(test)] mod tests" do
instance = Analyzer::Rust::Axum.new(options)
temp_dir = File.tempname("axum_test")
Dir.mkdir_p(temp_dir)
temp_file = File.join(temp_dir, "test.rs")

File.write(temp_file, <<-RUST)
use axum::{routing::query, Router};

async fn prod_handler() {}
async fn test_handler() {}

fn app() -> Router {
Router::new()
.route("/prod-query", query(prod_handler))
}

#[cfg(test)]
mod tests {
use super::*;

fn test_app() -> Router {
Router::new()
.route("/test-query", query(test_handler))
}
}
RUST

endpoints = instance.analyze_file(temp_file)
urls = endpoints.map(&.url)

urls.should contain("/prod-query")
urls.should_not contain("/test-query")

File.delete(temp_file)
Dir.delete(temp_dir)
end
end
Loading