rustra-macros 0.11.0

Rust → TypeScript bridge framework with auto-generated type-safe clients
Documentation
/// `register!` 매크로의 파싱 결과입니다.
///
/// 형태: `<builder_expr>, <fn_ident>, <fn_ident>, ...`
struct RegisterInput {
    /// 패키지 빌더 표현식 (예: `Package::builder("my.pkg")`)
    builder: syn::Expr,
    /// 등록할 `#[command]` 함수 식별자 목록입니다.
    commands: Vec<Ident>,
}

/// `register!` 매크로 입력을 파싱합니다.
///
/// 형태: `<builder>, <fn1>, <fn2>, ...` (쉼표로 구분, 마지막 쉼표는 선택)
impl Parse for RegisterInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let builder: syn::Expr = input.parse()?;
        let _: Token![,] = input.parse()?;

        let mut commands = Vec::new();
        loop {
            let name: Ident = input.parse()?;
            commands.push(name);
            if input.parse::<Token![,]>().is_err() {
                break;
            }
        }

        Ok(RegisterInput { builder, commands })
    }
}

fn command_chain_entry(fn_name: &Ident) -> TokenStream2 {
    let meta_ident = Ident::new(
        &format!("__RUstra_meta_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let execution_ident = Ident::new(
        &format!("__RUstra_execution_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let cap_ident = Ident::new(
        &format!("__RUstra_cap_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let platforms_ident = Ident::new(
        &format!("__RUstra_platforms_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let errors_ident = Ident::new(
        &format!("__RUstra_errors_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let devices_ident = Ident::new(
        &format!("__RUstra_devices_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    let doc_ident = Ident::new(
        &format!("__RUstra_doc_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    // (감사 #5) `#[command]` 가 생성하는 안전 어댑터(`__rustra_register_*`)를
    // 통해 등록한다 — capability 래퍼는 `unsafe fn` 이므로 원본 이름으로는
    // F: Fn 바운드를 통과하지 못하고(무음 드랍 차단), 어댑터는 I/O 타입
    // 추론이 그대로 성립한다.
    let register_ident = Ident::new(
        &format!("__rustra_register_{}", fn_name),
        proc_macro2::Span::call_site(),
    );
    // capability 메타가 Some이면 .require_capability 로 이어 붙인다. 상수가
    // Option<&str> 이므로 if let 체인으로 분기 — None이면 .command 만.
    //
    // (감사 #5) #[command(capability)] 래퍼는 `unsafe fn` 이다(무음 드랍
    // 차단 — 일반 등록 경로의 F: Fn 바운드에서 컴파일 에러). 매크로 등록은
    // 유일하게 허락된 경로로서 명시적 unsafe 클로저로 감싸고, capability 는
    // require_capability_if 로 연결한다 — 권한 부여 없이는 deny-by-default.
    quote! {
        .command(#meta_ident, #register_ident)
        .command_doc(#meta_ident, #doc_ident)
        .command_execution(#meta_ident, #execution_ident)
        .require_capability_if(#meta_ident, #cap_ident)
        .platform_meta_if(#meta_ident, #platforms_ident)
        .errors_meta_if(#meta_ident, #errors_ident)
        .devices_meta_if(#meta_ident, #devices_ident)
    }
}

/// Registers `#[command]` functions with a package builder.
///
/// ```rust
/// use rustra::prelude::*;
///
/// #[bridge_type]
/// struct AddInput { a: i64, b: i64 }
/// #[bridge_type]
/// struct AddOutput { sum: i64 }
///
/// #[command]
/// fn add_numbers(input: AddInput) -> Result<AddOutput> {
///     Ok(AddOutput { sum: input.a + input.b })
/// }
///
/// let pkg = rustra::register!(Package::builder("my.pkg"), add_numbers).build();
/// ```
#[proc_macro]
pub fn register(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as RegisterInput);

    if input.commands.is_empty() {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "register! requires at least one command function after the builder expression",
        )
        .to_compile_error()
        .into();
    }

    let builder = &input.builder;
    let chain: TokenStream2 = input.commands.iter().map(command_chain_entry).collect();

    let expanded = quote! {
        #builder #chain
    };

    expanded.into()
}