fix: harden API macro expansion hygiene
This commit is contained in:
@@ -58,6 +58,8 @@
|
||||
//! explicit variants for future transport work; accepting one requires a deliberate macro and
|
||||
//! adapter change rather than silently treating it as JSON.
|
||||
|
||||
extern crate self as api_macros;
|
||||
|
||||
pub use api_macros_impl::api;
|
||||
|
||||
/// A transport-neutral HTTP method.
|
||||
|
||||
@@ -35,6 +35,12 @@ pub trait WrapperApi {
|
||||
async fn search(&self) -> SearchResult<Widget>;
|
||||
}
|
||||
|
||||
#[api]
|
||||
pub trait RawIdentifierApi {
|
||||
#[get("/type", operation_id = "raw.type")]
|
||||
async fn r#type(&self) -> Widget;
|
||||
}
|
||||
|
||||
fn assert_operation_types<O>()
|
||||
where
|
||||
O: Operation<
|
||||
@@ -52,6 +58,8 @@ fn expansion_exposes_deterministic_metadata_and_type_connections() {
|
||||
|
||||
fn assert_named_wrapper<O: Operation<ResponseBody = SearchResult<Widget>>>() {}
|
||||
assert_named_wrapper::<wrapper_api_operations::Search>();
|
||||
let raw = <raw_identifier_api_operations::Type as Operation>::METADATA;
|
||||
assert_eq!(raw.operation_id, "raw.type");
|
||||
|
||||
let operations = <WidgetApiMetadata as ApiContract>::OPERATIONS;
|
||||
assert_eq!(operations.len(), 3);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use std::{fs, path::PathBuf, process::Command};
|
||||
|
||||
#[test]
|
||||
fn macro_expansion_resolves_a_renamed_support_dependency() {
|
||||
let fixture = std::env::temp_dir().join(format!(
|
||||
"api-macros-renamed-dependency-{}",
|
||||
std::process::id()
|
||||
));
|
||||
let _ = fs::remove_dir_all(&fixture);
|
||||
fs::create_dir_all(fixture.join("src")).expect("create fixture source directory");
|
||||
|
||||
let package_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let package_path = package_path
|
||||
.to_str()
|
||||
.expect("package path is UTF-8")
|
||||
.replace('\\', "\\\\");
|
||||
fs::write(
|
||||
fixture.join("Cargo.toml"),
|
||||
format!(
|
||||
r#"[package]
|
||||
name = "renamed-api-consumer"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
yoi-api = {{ package = "api-macros", path = "{package_path}" }}
|
||||
"#,
|
||||
),
|
||||
)
|
||||
.expect("write fixture manifest");
|
||||
fs::write(
|
||||
fixture.join("src/lib.rs"),
|
||||
r#"use yoi_api::{ApiContract, api};
|
||||
|
||||
pub struct Output;
|
||||
|
||||
#[api]
|
||||
pub trait RenamedApi {
|
||||
#[get("/output", operation_id = "renamed.output")]
|
||||
async fn output(&self) -> Output;
|
||||
}
|
||||
|
||||
pub fn operation_count() -> usize {
|
||||
<RenamedApiMetadata as ApiContract>::OPERATIONS.len()
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.expect("write fixture source");
|
||||
|
||||
let output = Command::new(env!("CARGO"))
|
||||
.args(["check", "--quiet", "--offline"])
|
||||
.current_dir(&fixture)
|
||||
.env("CARGO_TARGET_DIR", fixture.join("target"))
|
||||
.output()
|
||||
.expect("run cargo check for renamed dependency fixture");
|
||||
let _ = fs::remove_dir_all(&fixture);
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"renamed dependency fixture failed:\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
use api_macros::api;
|
||||
|
||||
pub struct Response;
|
||||
|
||||
#[api]
|
||||
pub trait MarkerCollision {
|
||||
#[get("/first")]
|
||||
async fn foo_bar(&self) -> Response;
|
||||
|
||||
#[get("/second")]
|
||||
async fn foo__bar(&self) -> Response;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,11 @@
|
||||
error: generated operation marker `FooBar` collides with method `foo_bar`
|
||||
--> tests/ui/marker_collision.rs:11:14
|
||||
|
|
||||
11 | async fn foo__bar(&self) -> Response;
|
||||
| ^^^^^^^^
|
||||
|
||||
error: first method generating marker `FooBar`
|
||||
--> tests/ui/marker_collision.rs:8:14
|
||||
|
|
||||
8 | async fn foo_bar(&self) -> Response;
|
||||
| ^^^^^^^
|
||||
Reference in New Issue
Block a user