Skip to main content

napp_macro/
lib.rs

1//! 应用入口与业务 initializer 属性宏。
2//!
3//! `#[application]` 在业务二进制内生成静态组件描述、路由收集工厂和同步进程入口;
4//! `#[initializer]` 把完整 `Initialization` trait impl 登记到同一二进制的静态集合。运行时会把
5//! 静态项与 Service 启动 Hook 动态登记项冻结为统一依赖计划,在 `Prepare` 后、`Seal` 前严格执行
6//! 全部 `before -> initialize -> after` 三轮,全部成功前不开放入站能力。
7//!
8//! 生成的 UserHook 可通过 Application 登记一次性业务停机 future,不需要额外属性或组件字符串。
9//! 宏不创建第二个信号或关闭 owner;任务顺序、共享预算、失败报告与取消后的所有权释放由运行时负责。
10
11use std::collections::HashSet;
12
13use nasa_macro_support::runtime_root;
14use proc_macro::TokenStream;
15use proc_macro2::TokenStream as TokenStream2;
16use quote::{format_ident, quote};
17use syn::{
18    parse_macro_input, punctuated::Punctuated, Expr, FnArg, GenericArgument, ItemFn, ItemImpl, Lit,
19    LitStr, Meta, Path, PathArguments, ReturnType, Token, Type,
20};
21
22/// 业务作用:把业务异步 `main` 转换为统一生命周期进程入口。
23///
24/// # 支持的组件字符串
25///
26/// `attr` 可以为空;非空时只接受下面 17 个区分大小写的精确字符串,不支持别名:
27///
28/// - `"log"`:启用两阶段日志。Bootstrap 先建立早期控制台日志,最终配置就绪后再安装文件日志,
29///   并支持运行期日志级别热更新;需要 `nasa` 的 `log` feature。
30/// - `"nacos-config"`:启用 Nacos 配置中心。启动时拉取远端配置 overlay,运行期监听配置变化并按
31///   last-known-good 规则热刷新;需要 `nacos-config` feature,真实连接 Nacos 还需要 `nacos-sdk`。
32/// - `"telemetry"`:启用有界 OpenTelemetry span 管道与受管停机 flush;需要 `telemetry` feature。
33/// - `"partition"`:启用保序分 lane 执行器。容量计划可由 YAML `partition` 提供,或在 UserHook
34///   提交;容器在 Prepare 创建执行器、发布强类型句柄并监督动态健康与停机排空;需要
35///   `partition` feature。
36/// - `"grpc"`:启用受管 gRPC service registry 与 listener。业务在 UserHook 登记 generated server,
37///   容器在 Ready 自动装配、绑定端口、监督 serve 所有权并在停机时排空;需要 `grpc` feature。
38/// - `"db"`:启用 MySQL/PostgreSQL 数据源。启动时按 driver 校验并探测地址、鉴权和数据库,创建连接池、
39///   注册应用资源,同时注入对应的事务与 Mapper 运行时;需要 `tx` 或 `tx-pgsql` feature。
40/// - `"redis"`:启用 Redis 客户端。启动时校验配置、探测 standalone/cluster 拓扑并建立受管客户端,
41///   停机时由容器显式关闭;需要 `redis` feature。
42/// - `"redis-job"`:启用多 source RedisJob 长生命周期运行时,并隐式加入 Redis;需要
43///   `redis-job` feature。
44/// - `"cache"`:启用由容器拥有的两级缓存运行时与可选跨节点失效广播;需要 `cache` feature。
45/// - `"saga"`:启用 Saga Ready 门禁、只读能力发布与 durable timer 监督,并隐式加入 DB 与
46///   Outbox。业务在 UserHook 通过 `configure_saga` 提交 Orchestrator 或参与方计划;需要
47///   `saga-runtime` 或 `saga-runtime-pgsql` feature。
48/// - `"kafka"`:启用受管 Kafka producer/consumer。负责 broker 探测、consumer 收集与启动、动态
49///   readiness、运行期健康监控、停止消费和 producer flush;需要 `kafka` feature。
50/// - `"outbox"`:启用事务型 Outbox dispatcher。业务在 UserHook 提交发布计划,组件负责持续投递、
51///   readiness、退避与停机;需要 `outbox` feature。该组件隐式加入 DB;声明 `"saga"` 时也会自动
52///   纳入 Outbox,无需重复书写。
53///
54/// 隐式加入只负责补齐缺失依赖,不构成互斥约束。`("saga")`、`("saga", "db")` 与
55/// `("saga", "db", "outbox")` 会生成相同的组件图;只有同一个字符串在属性中重复出现才会拒绝。
56/// - `"auth"`:启用 OAuth Resource Server/JWKS warmup、刷新和 readiness;必须同时声明 `"web"`,
57///   需要 Web/OAuth 能力。
58/// - `"web"`:启用 HTTP MVC 服务。自动收集 mapping 端点,安装 `/healthz`、`/readyz` 和请求观测,
59///   绑定监听器并在停机时停止接流、排空在途请求;需要 `web` feature。
60/// - `"ws"`:启用 TCP/WebSocket 长连接服务。业务在启动 Hook 中配置鉴权和 endpoint,容器负责监听、
61///   会话服务、集群数据面接入与优雅排空;需要 `ws` feature,Redis/Kafka 集群子能力另开对应 feature。
62/// - `"nacos-discovery"`:启用服务发现和注册。创建带负载均衡的出站 REST runtime,在服务 Ready 后
63///   注册本实例,停机时先从注册中心摘流再关闭客户端;需要 `nacos-discovery` feature,真实 Nacos
64///   provider 还需要 `nacos-sdk`。
65/// - `"scheduling"`:启用定时任务。收集 `#[scheduled]` 任务,在 Application Ready 后统一启动并在
66///   停机时停止;需要 `scheduling` feature,Redis 选主的集群调度使用 `scheduling-cluster`。
67///
68/// `"hystrix"`、`"grafana"`、`"mapper"` 等是门面 feature 或函数级能力,**不是**组件字符串。
69///
70/// # YAML 创建受管单源与多源
71///
72/// MySQL、PostgreSQL、Redis 与 Kafka 的 endpoint、凭据、池和客户端参数必须来自最终 YAML;Application 在启动期
73/// 创建并冻结完整命名表,业务 `main` 只取得受管句柄,不自行建池或连接。三类资源的配置形态如下:
74///
75/// | 资源 | 单源 | 多源 | 默认入口 |
76/// | --- | --- | --- | --- |
77/// | MySQL | `database` | `datasources.<name>` | `app.default_datasource().await` |
78/// | PostgreSQL | `database` | `datasources.<name>` | `app.default_pg_datasource().await` |
79/// | Redis | 扁平 `redis` | `redis.properties.<qualifier>` | `app.default_redis().await` |
80/// | Kafka | `kafka` | `kafkas.<client>` | `app.default_kafka()` |
81///
82/// 单源根与多源根互斥。数据库单源固定发布为 `default`;Redis 单源的持久身份是 `primary`,查询边界
83/// 同时接受 `default`;Kafka 单 client 省略 `client_name` 时默认为 `default`。多源示例:
84///
85/// ```yaml
86/// datasources:
87///   default:
88///     url: ${APP_PRIMARY_DB_URL}
89///   reporting:
90///     url: ${APP_REPORTING_DB_URL}
91/// outbox:
92///   datasource_ref: reporting
93/// saga:
94///   role: orchestrator
95///   plan_mode: custom
96///   database_bootstrap: application
97///   datasource_ref: reporting
98///
99/// redis:
100///   properties:
101///     primary:
102///       url: ${APP_PRIMARY_REDIS_URL}
103///       namespace: orders
104///       profile: RustV2
105///     sessions:
106///       url: ${APP_SESSION_REDIS_URL}
107///       namespace: sessions
108///       profile: RustV2
109///
110/// kafkas:
111///   default:
112///     bootstrap_servers: ${APP_PRIMARY_KAFKA_BOOTSTRAP_SERVERS}
113///   audit:
114///     bootstrap_servers: ${APP_AUDIT_KAFKA_BOOTSTRAP_SERVERS}
115/// ```
116///
117/// `outbox.datasource_ref` 选择 Outbox 数据源;managed Saga 使用角色作用域内的 `datasource_ref`,custom
118/// Saga 使用顶层 `saga.datasource_ref`。Cache、缓存失效广播和 Scheduling 使用各自的 `redis_ref`
119/// 选择 `redis.properties`。Kafka consumer/producer 通过 client name 选择
120/// `kafkas`。这些引用在首次网络握手前复验,不存在时不会回退到默认或唯一实例。UserHook 中的
121/// `configure_saga`、`configure_kafka`、`configure_redis_jobs` 等入口只提交业务定义和处理逻辑,
122/// 不负责建立基础设施 source。完整字段与单源示例见 `napp` README。
123///
124/// 保序执行器的通用容量也可完全由 YAML 提供,不需要在 `main` 构造计划:
125///
126/// ```yaml
127/// partition:
128///   partitions: 16
129///   queue_capacity: 1024
130///   global_inflight: 16384
131///   max_lanes: 4096
132///   shutdown_timeout_ms: 5000
133/// ```
134///
135/// # 声明顺序:与业务书写顺序无关
136///
137/// **业务侧不需要按启动顺序书写组件字符串**:宏接受任意顺序,内部按唯一的规范启动顺序
138/// (`CANONICAL_COMPONENT_ORDER`:log → nacos-config → telemetry → db → redis → cache →
139/// partition → saga → kafka → outbox → redis-job → grpc → auth → web → ws → nacos-discovery → scheduling)自动规范化后再生成组件列表。因此
140/// `#[application("web", "log", "kafka")]` 与 `#[application("log", "kafka", "web")]` 完全等价,
141/// 都按 log → kafka → web 启动、严格反序停机。宏仍会拒绝未知组件名和重复声明。
142///
143/// 示例(任意顺序均可,等价于规范顺序):
144///
145/// ```ignore
146/// #[nasa::application(
147///     "log",
148///     "nacos-config",
149///     "telemetry",
150///     "partition",
151///     "grpc",
152///     "redis",
153///     "cache",
154///     "saga",
155///     "kafka",
156///     "auth",
157///     "web",
158///     "ws",
159///     "nacos-discovery",
160///     "scheduling"
161/// )]
162/// async fn main(app: nasa::Application) -> anyhow::Result<()> {
163///     // 声明 Saga 后 DB 与 Outbox 已纳入生命周期,这里只提交业务计划和其它组件定制。
164///     Ok(())
165/// }
166/// ```
167///
168/// 参数说明:
169/// - `attr`:按任意书写顺序声明的零个或多个受支持组件字符串。
170/// - `item`:零参数或接收一个 `Application` 的异步主函数。
171///
172/// 返回:入口合法时生成同步进程入口、规范组件描述和业务启动 Hook;合同非法时生成定位到调用处的
173/// 编译错误。
174#[proc_macro_attribute]
175pub fn application(attr: TokenStream, item: TokenStream) -> TokenStream {
176    let components =
177        parse_macro_input!(attr with Punctuated::<LitStr, Token![,]>::parse_terminated);
178    let function = parse_macro_input!(item as ItemFn);
179    match expand_application(components.into_iter().collect(), function) {
180        Ok(expanded) => expanded.into(),
181        Err(error) => error.to_compile_error().into(),
182    }
183}
184
185/// 业务作用:把完整 `Initialization` trait impl 登记为业务二进制内的静态 initializer,
186/// 并与 Service 启动 Hook 动态登记的 initializer 合并成同一份冻结计划。
187///
188/// 本属性只能标注安全、正向、无 impl 泛型参数的 `Initialization` trait impl,不能标注单个方法、
189/// 固有 impl、unsafe impl 或其它 trait。被登记的实例会严格参与三轮全局屏障:全部 `before` 完成后
190/// 才进入全部 `initialize`,全部 `initialize` 完成后才进入全部 `after`。
191///
192/// # 执行阶段
193///
194/// Service 模式的时序为:业务 `UserHook` 完成登记并冻结 initializer 计划,组件完成 `Prepare`
195/// (包括 migration 和出站依赖门禁),然后调用条件工厂并执行三轮 initializer 屏障。只有全部成功后,
196/// Runner 才进入 `Seal`、组件 `Ready`、staged task 激活和 `mark_ready()`;因此 initializer 执行期间
197/// Web/WS listener、consumer 和服务发现尚未对外接流。
198///
199/// Batch 模式只收集静态 `one-shot` initializer,在组件 `Prepare` 之后、业务工作负载被 poll 之前
200/// 执行条件工厂和三轮屏障;`hosted` initializer 会被拒绝,Batch 工作负载中也不能动态补登记
201/// initializer。`hosted` initializer 暂存的长期任务只在 Service 的组件 `Ready` 全部成功后交给
202/// Supervisor,任务主体还会继续等待 `mark_ready()`,不会与初始化阶段并发执行。
203///
204/// # 属性
205///
206/// - `name = "..."`:可选。应用内唯一的 canonical 身份,只允许 ASCII 小写字母、数字、`_`、`-`、`.`,
207///   长度为 1..=128 字节。省略时从 impl 的实现类型名派生 kebab-case,例如
208///   `OrderCacheInitialization` 派生为 `order-cache-initialization`;无法稳定派生时必须显式声明。
209///   派生结果仍是依赖名、日志字段和指标 label 使用的稳定业务身份,重命名实现类型会同步改变该身份;
210///   需要跨发布保持依赖引用和观测连续性时必须显式填写 `name`。
211/// - `order = ...`:可选 `i32` 整数字面量,默认 `100000`。依赖条件相同且当前都可执行时,数值越小
212///   越先执行;数值相同按 `name` 升序消除平局。`requires` 依赖边始终优先于 `order`,低 `order`
213///   不能越过尚未完成的依赖。
214/// - `requires = ["..."]`:可选,默认空。声明本项执行前必须成功启用并完成同阶段调用的 initializer,
215///   最多 32 项;缺失、重复、自依赖或依赖环都会拒绝启动。
216/// - `kind = "one-shot" | "hosted"`:可选,默认 `"one-shot"`。`hosted` 只允许 Service 模式,
217///   并可在 `after` 暂存 Ready 后启动的长期任务或 readiness;`one-shot` 只执行有界初始化。
218/// - `factory = path`:可选异步条件工厂。签名必须为
219///   `async fn(Application) -> ApplicationResult<Option<T>>`;`Some(T)` 启用本项,`None` 表示条件未命中,
220///   `Err` 阻止应用接流。省略时实现类型必须实现 `Default`。
221///
222/// # 示例
223///
224/// ```ignore
225/// #[derive(Default)]
226/// struct OrderCacheInitialization;
227///
228/// #[nasa::initializer(order = 200, requires = ["schema"])]
229/// impl nasa::application::Initialization for OrderCacheInitialization {
230///     // 实现 before / initialize / after 中实际需要的阶段。
231/// }
232/// ```
233///
234/// 参数说明:
235/// - `attr`:`name/order/requires/kind/factory` 元数据。
236/// - `item`:无 impl 泛型参数的安全、正向 `Initialization` trait impl。
237///
238/// 返回:原 trait impl、类型擦除工厂和 linkme 静态描述;合同非法时返回定位到属性或 impl 的编译错误。
239#[proc_macro_attribute]
240pub fn initializer(attr: TokenStream, item: TokenStream) -> TokenStream {
241    let metas = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
242    let item_impl = parse_macro_input!(item as ItemImpl);
243    match expand_initializer(metas, item_impl) {
244        Ok(expanded) => expanded.into(),
245        Err(error) => error.to_compile_error().into(),
246    }
247}
248
249/// 业务作用:把异步业务函数转换为静态 RedisJob 定义与 Handler descriptor,受管组件会自动收集。
250///
251/// 参数说明:`attr` 为调度、source 与 Worker 合同,`item` 为无 receiver、无泛型的异步函数。
252///
253/// 返回:声明与函数签名合法时生成静态登记项;未知属性、冲突调度或重复注入返回编译错误。
254#[proc_macro_attribute]
255pub fn redis_job(attr: TokenStream, item: TokenStream) -> TokenStream {
256    let metas = parse_macro_input!(attr with Punctuated::<Meta, Token![,]>::parse_terminated);
257    let function = parse_macro_input!(item as ItemFn);
258    match expand_redis_job(metas, function) {
259        Ok(expanded) => expanded.into(),
260        Err(error) => error.to_compile_error().into(),
261    }
262}
263
264/// RedisJob 属性解析后的封闭宏期表示。
265#[derive(Default)]
266struct RedisJobArgs {
267    name: Option<LitStr>,
268    qualifier: Option<LitStr>,
269    worker: Option<LitStr>,
270    trigger: Option<LitStr>,
271    cron: Option<LitStr>,
272    zone: Option<LitStr>,
273    fixed_rate_ms: Option<syn::LitInt>,
274    fixed_delay_ms: Option<syn::LitInt>,
275    concurrency: Option<LitStr>,
276    misfire: Option<LitStr>,
277    timeout_ms: Option<syn::LitInt>,
278    max_attempts: Option<syn::LitInt>,
279    retry_delay_ms: Option<syn::LitInt>,
280    contract_revision: Option<syn::LitInt>,
281    schema: Option<LitStr>,
282    codecs: Option<Vec<LitStr>>,
283    fanout_receipt_timeout_ms: Option<syn::LitInt>,
284    fanout_receipt_max_retries: Option<syn::LitInt>,
285    fanout_failure_policy: Option<LitStr>,
286    definition_revision: Option<syn::LitInt>,
287}
288
289/// RedisJob 函数参数的宏期注入类别。
290enum RedisJobParameter {
291    Application,
292    Context,
293    Payload(Box<Type>),
294}
295
296/// 业务作用:校验 RedisJob 函数并生成与编程式 plan 共用的静态 descriptor。
297///
298/// 参数说明:`metas` 为属性项,`function` 为业务异步函数语法树。
299///
300/// 返回:合同合法时返回函数、Handler 适配器与 linkme 描述;否则返回定位到声明处的错误。
301fn expand_redis_job(
302    metas: Punctuated<Meta, Token![,]>,
303    function: ItemFn,
304) -> syn::Result<TokenStream2> {
305    if function.sig.asyncness.is_none() {
306        return Err(syn::Error::new_spanned(
307            function.sig.fn_token,
308            "redis_job function must be async",
309        ));
310    }
311    if function.sig.receiver().is_some() || !function.sig.generics.params.is_empty() {
312        return Err(syn::Error::new_spanned(
313            &function.sig,
314            "redis_job function cannot have a receiver or generic parameters",
315        ));
316    }
317    if function.sig.inputs.len() > 3 {
318        return Err(syn::Error::new_spanned(
319            &function.sig.inputs,
320            "redis_job function accepts at most Application, JobContext, and one payload",
321        ));
322    }
323    let args = parse_redis_job_args(&metas)?;
324    validate_redis_job_args(&args)?;
325    let runtime = runtime_root("application", "napp")
326        .map_err(|message| syn::Error::new_spanned(&function.sig.ident, message))?;
327    let mut parameters = Vec::new();
328    let mut has_application = false;
329    let mut has_context = false;
330    let mut has_payload = false;
331    for input in &function.sig.inputs {
332        let FnArg::Typed(argument) = input else {
333            return Err(syn::Error::new_spanned(
334                input,
335                "redis_job does not accept self",
336            ));
337        };
338        if matches!(argument.ty.as_ref(), Type::Reference(_)) {
339            return Err(syn::Error::new_spanned(
340                &argument.ty,
341                "redis_job parameters must be owned values",
342            ));
343        }
344        let kind = classify_redis_job_parameter(&argument.ty);
345        match &kind {
346            RedisJobParameter::Application if has_application => {
347                return Err(syn::Error::new_spanned(
348                    &argument.ty,
349                    "Application can only be injected once",
350                ));
351            }
352            RedisJobParameter::Context if has_context => {
353                return Err(syn::Error::new_spanned(
354                    &argument.ty,
355                    "JobContext can only be injected once",
356                ));
357            }
358            RedisJobParameter::Payload(_) if has_payload => {
359                return Err(syn::Error::new_spanned(
360                    &argument.ty,
361                    "redis_job accepts only one payload",
362                ));
363            }
364            RedisJobParameter::Application => has_application = true,
365            RedisJobParameter::Context => has_context = true,
366            RedisJobParameter::Payload(_) => has_payload = true,
367        }
368        parameters.push(kind);
369    }
370    let function_name = &function.sig.ident;
371    let derived_name = function_name.to_string();
372    let name = args.name.clone().unwrap_or_else(|| {
373        LitStr::new(
374            derived_name.strip_prefix("r#").unwrap_or(&derived_name),
375            function_name.span(),
376        )
377    });
378    let builder_steps = redis_job_builder_steps(&args, &runtime)?;
379    let handler_field = has_application.then(|| quote!(application: #runtime::WeakApplication,));
380    let handler_value = if has_application {
381        quote!(__NasaRedisJobHandler {
382            application: application.downgrade()
383        })
384    } else {
385        quote!({
386            let _ = application;
387            __NasaRedisJobHandler {}
388        })
389    };
390    let application_clone = has_application.then(|| {
391        quote! {
392            let application = match self.application.upgrade() {
393                ::std::option::Option::Some(application) => application,
394                ::std::option::Option::None => {
395                    return ::std::boxed::Box::pin(async {
396                        #runtime::__private::nadis::job::JobOutcome::retry(
397                            "Application lifecycle ended before RedisJob invocation"
398                        )
399                    });
400                }
401            };
402        }
403    });
404    let declared_codecs = args
405        .codecs
406        .clone()
407        .unwrap_or_else(|| vec![LitStr::new("json", proc_macro2::Span::call_site())]);
408    let codec_wires = declared_codecs
409        .iter()
410        .map(|codec| match codec.value().to_ascii_lowercase().as_str() {
411            "json" => Ok(LitStr::new("JSON", codec.span())),
412            "protobuf" => Ok(LitStr::new("PROTOBUF", codec.span())),
413            "raw" => Ok(LitStr::new("RAW", codec.span())),
414            _ => Err(syn::Error::new_spanned(codec, "redis_job codec is unknown")),
415        })
416        .collect::<syn::Result<Vec<_>>>()?;
417    let codec_guard = quote! {
418        if !matches!(context.wire_codec.as_str(), #(#codec_wires)|*) {
419            return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
420                "payload codec is outside the static Worker contract"
421            );
422        }
423    };
424    let mut call_arguments = Vec::new();
425    let mut payload_decode = TokenStream2::new();
426    for parameter in parameters {
427        match parameter {
428            RedisJobParameter::Application => call_arguments.push(quote!(application)),
429            RedisJobParameter::Context => call_arguments.push(quote!(context.clone())),
430            RedisJobParameter::Payload(payload_type) => {
431                payload_decode = if declared_codecs.len() == 1 {
432                    match declared_codecs[0].value().to_ascii_lowercase().as_str() {
433                        "json" => quote! {
434                            let payload: #payload_type = match #runtime::__private::nadis::job::decode_json_payload(context.payload()) {
435                                ::std::result::Result::Ok(value) => value,
436                                ::std::result::Result::Err(error) => {
437                                    return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
438                                        ::std::format!("payload JSON decode rejected: {error}")
439                                    );
440                                }
441                            };
442                        },
443                        "protobuf" => quote! {
444                            let payload: #payload_type = match
445                                <#payload_type as #runtime::__private::prost::Message>::decode(context.payload())
446                            {
447                                ::std::result::Result::Ok(value) => value,
448                                ::std::result::Result::Err(error) => {
449                                    return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
450                                        ::std::format!("payload Protobuf decode rejected: {error}")
451                                    );
452                                }
453                            };
454                        },
455                        "raw" => quote! {
456                            let payload: #payload_type = match
457                                <#payload_type as ::std::convert::TryFrom<::std::vec::Vec<u8>>>::try_from(
458                                    context.payload().to_vec()
459                                )
460                            {
461                                ::std::result::Result::Ok(value) => value,
462                                ::std::result::Result::Err(error) => {
463                                    return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
464                                        ::std::format!("payload RAW decode rejected: {error}")
465                                    );
466                                }
467                            };
468                        },
469                        _ => unreachable!("codec 已在宏期校验"),
470                    }
471                } else {
472                    quote! {
473                        let codec = match #runtime::__private::nadis::job::JobWireCodec::parse(
474                            context.wire_codec.as_str()
475                        ) {
476                            ::std::option::Option::Some(codec) => codec,
477                            ::std::option::Option::None => {
478                                return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
479                                    "payload codec is not a known wire value"
480                                );
481                            }
482                        };
483                        if codec == #runtime::__private::nadis::job::JobWireCodec::Json {
484                            if let ::std::result::Result::Err(error) =
485                                #runtime::__private::nadis::job::validate_json_payload(context.payload())
486                            {
487                                return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
488                                    ::std::format!("payload JSON validation rejected: {error}")
489                                );
490                            }
491                        }
492                        let payload: #payload_type = match
493                            <#payload_type as #runtime::__private::nadis::job::JobParameter>::decode(
494                                codec,
495                                context.payload(),
496                            )
497                        {
498                            ::std::result::Result::Ok(value) => value,
499                            ::std::result::Result::Err(error) => {
500                                return #runtime::__private::nadis::job::JobOutcome::fail_permanent(
501                                    ::std::format!("payload decode rejected: {error}")
502                                );
503                            }
504                        };
505                    }
506                };
507                call_arguments.push(quote!(payload));
508            }
509        }
510    }
511
512    Ok(quote! {
513        #function
514
515        const _: () = {
516            /// 业务作用:保存宏入口冻结的 Application 句柄,并把 RedisJob 执行上下文适配给业务函数。
517            struct __NasaRedisJobHandler { #handler_field }
518
519            impl #runtime::__private::nadis::job::JobHandler for __NasaRedisJobHandler {
520                /// 业务作用:把已取得执行权的冻结上下文适配到静态业务函数,并统一归一化返回值。
521                ///
522                /// 参数说明:`execution` 为当前 attempt 的身份、payload 与副作用门禁。
523                ///
524                /// 返回:业务 future 完成后得到可提交的封闭 Job 结果。
525                fn handle<'a>(
526                    &'a self,
527                    execution: &'a #runtime::__private::nadis::job::JobExecution,
528                ) -> #runtime::__private::nadis::job::JobHandlerFuture<'a> {
529                    use #runtime::__private::nadis::job::IntoJobHandlerResult as _;
530                    let context = execution.clone();
531                    #application_clone
532                    ::std::boxed::Box::pin(async move {
533                        #codec_guard
534                        #payload_decode
535                        #function_name(#(#call_arguments),*)
536                            .await
537                            .into_job_handler_result()
538                    })
539                }
540            }
541
542            /// 业务作用:构造静态任务定义与 Handler,使宏声明进入核心 plan 的同一冻结门禁。
543            ///
544            /// 参数说明:`application` 仅在业务签名声明 Application 时注入。
545            ///
546            /// 返回:定义合法时返回类型擦除登记;合同字段非法时拒绝 Prepare。
547            fn __nasa_redis_job_factory(
548                application: #runtime::Application,
549            ) -> #runtime::ApplicationResult<(
550                #runtime::__private::nadis::job::JobDefinition,
551                ::std::sync::Arc<dyn #runtime::__private::nadis::job::JobHandler>,
552            )> {
553                let mut builder = #runtime::__private::nadis::job::JobDefinition::builder(#name);
554                #(#builder_steps)*
555                let definition = builder.build().map_err(|error| {
556                    #runtime::ApplicationError::with_source(
557                        #runtime::ComponentId::RedisJob,
558                        #runtime::ApplicationPhase::Prepare,
559                        "invalid #[redis_job] definition",
560                        error,
561                    )
562                })?;
563                let handler: ::std::sync::Arc<dyn #runtime::__private::nadis::job::JobHandler> =
564                    ::std::sync::Arc::new(#handler_value);
565                ::std::result::Result::Ok((definition, handler))
566            }
567
568            #[#runtime::__private::linkme::distributed_slice(#runtime::COLLECTED_REDIS_JOBS)]
569            #[linkme(crate = #runtime::__private::linkme)]
570            static __NASA_REDIS_JOB_DESCRIPTOR: #runtime::RedisJobDescriptor =
571                #runtime::RedisJobDescriptor::__new(
572                    __nasa_redis_job_factory,
573                    concat!(module_path!(), ":", file!(), ":", line!()),
574                );
575        };
576    })
577}
578
579/// 业务作用:只按无歧义的框架类型路径识别注入,其余唯一类型作为业务 payload。
580///
581/// 参数说明:`ty` 为业务函数的拥有式参数类型。
582///
583/// 返回:Application、JobContext 或 payload 三类之一。
584fn classify_redis_job_parameter(ty: &Type) -> RedisJobParameter {
585    if let Type::Path(path) = ty {
586        let segments: Vec<String> = path
587            .path
588            .segments
589            .iter()
590            .map(|segment| segment.ident.to_string())
591            .collect();
592        let unqualified = segments.len() == 1;
593        if let Some(name) = segments.last() {
594            if name == "Application"
595                && (unqualified
596                    || matches!(segments.as_slice(), [root, value] if matches!(root.as_str(), "nasa" | "napp") && value == "Application"))
597            {
598                return RedisJobParameter::Application;
599            }
600            let framework_job_path = segments.len() >= 2
601                && segments
602                    .get(segments.len() - 2)
603                    .is_some_and(|segment| segment == "job");
604            if matches!(name.as_str(), "JobContext" | "JobExecution")
605                && (unqualified || framework_job_path)
606            {
607                return RedisJobParameter::Context;
608            }
609        }
610    }
611    RedisJobParameter::Payload(Box::new(ty.clone()))
612}
613
614/// 业务作用:解析 RedisJob 属性键并拒绝重复、未知或非字面量输入。
615///
616/// 参数说明:`metas` 为属性内按源码顺序出现的项。
617///
618/// 返回:完整宏期参数;合同非法时返回编译错误。
619fn parse_redis_job_args(metas: &Punctuated<Meta, Token![,]>) -> syn::Result<RedisJobArgs> {
620    let mut result = RedisJobArgs::default();
621    let mut seen = HashSet::new();
622    for meta in metas {
623        let Meta::NameValue(value) = meta else {
624            return Err(syn::Error::new_spanned(
625                meta,
626                "redis_job attributes must use `key = value`",
627            ));
628        };
629        let key = value
630            .path
631            .get_ident()
632            .map(ToString::to_string)
633            .ok_or_else(|| {
634                syn::Error::new_spanned(
635                    &value.path,
636                    "redis_job attribute key must be an identifier",
637                )
638            })?;
639        if !seen.insert(key.clone()) {
640            return Err(syn::Error::new_spanned(
641                meta,
642                "redis_job attribute key is repeated",
643            ));
644        }
645        macro_rules! string_field {
646            ($field:ident) => {{
647                result.$field = Some(parse_job_string(&value.value, &key)?);
648            }};
649        }
650        macro_rules! integer_field {
651            ($field:ident) => {{
652                result.$field = Some(parse_job_integer(&value.value, &key)?);
653            }};
654        }
655        match key.as_str() {
656            "name" => string_field!(name),
657            "qualifier" => string_field!(qualifier),
658            "worker" => string_field!(worker),
659            "trigger" => string_field!(trigger),
660            "cron" => string_field!(cron),
661            "zone" => string_field!(zone),
662            "fixed_rate_ms" => integer_field!(fixed_rate_ms),
663            "fixed_delay_ms" => integer_field!(fixed_delay_ms),
664            "concurrency" => string_field!(concurrency),
665            "misfire" => string_field!(misfire),
666            "timeout_ms" => integer_field!(timeout_ms),
667            "max_attempts" => integer_field!(max_attempts),
668            "retry_delay_ms" => integer_field!(retry_delay_ms),
669            "contract_revision" => integer_field!(contract_revision),
670            "schema" => string_field!(schema),
671            "fanout_receipt_timeout_ms" => integer_field!(fanout_receipt_timeout_ms),
672            "fanout_receipt_max_retries" => integer_field!(fanout_receipt_max_retries),
673            "fanout_failure_policy" => string_field!(fanout_failure_policy),
674            "definition_revision" => integer_field!(definition_revision),
675            "codecs" => {
676                let Expr::Array(array) = &value.value else {
677                    return Err(syn::Error::new_spanned(
678                        &value.value,
679                        "redis_job codecs must be an array of strings",
680                    ));
681                };
682                result.codecs = Some(
683                    array
684                        .elems
685                        .iter()
686                        .map(|item| parse_job_string(item, "codecs"))
687                        .collect::<syn::Result<Vec<_>>>()?,
688                );
689            }
690            _ => {
691                return Err(syn::Error::new_spanned(
692                    &value.path,
693                    format!("unknown redis_job attribute `{key}`"),
694                ))
695            }
696        }
697    }
698    Ok(result)
699}
700
701/// 业务作用:在生成 descriptor 前拒绝字面量可确定的 schedule、触发类型、codec 与正数边界冲突。
702///
703/// 参数说明:`args` 为已经完成类型解析的宏属性。
704///
705/// 返回:静态合同自洽时成功;需要运行期 Cron/名称语义的部分仍交核心 builder 复验。
706fn validate_redis_job_args(args: &RedisJobArgs) -> syn::Result<()> {
707    if args.codecs.as_ref().is_some_and(Vec::is_empty) {
708        return Err(syn::Error::new(
709            proc_macro2::Span::call_site(),
710            "redis_job codecs must not be empty",
711        ));
712    }
713    let schedule_count = usize::from(args.cron.is_some())
714        + usize::from(args.fixed_rate_ms.is_some())
715        + usize::from(args.fixed_delay_ms.is_some());
716    if schedule_count > 1 {
717        return Err(syn::Error::new(
718            proc_macro2::Span::call_site(),
719            "redis_job cron, fixed_rate_ms, and fixed_delay_ms are mutually exclusive",
720        ));
721    }
722    if args
723        .trigger
724        .as_ref()
725        .is_some_and(|trigger| trigger.value().eq_ignore_ascii_case("fanout_only"))
726        && schedule_count != 0
727    {
728        return Err(syn::Error::new_spanned(
729            args.trigger.as_ref().expect("trigger 已确认存在"),
730            "redis_job fanout_only must not declare a schedule",
731        ));
732    }
733    for (field, value) in [
734        ("fixed_rate_ms", args.fixed_rate_ms.as_ref()),
735        ("fixed_delay_ms", args.fixed_delay_ms.as_ref()),
736        ("timeout_ms", args.timeout_ms.as_ref()),
737        ("max_attempts", args.max_attempts.as_ref()),
738        ("retry_delay_ms", args.retry_delay_ms.as_ref()),
739        ("contract_revision", args.contract_revision.as_ref()),
740        (
741            "fanout_receipt_timeout_ms",
742            args.fanout_receipt_timeout_ms.as_ref(),
743        ),
744        ("definition_revision", args.definition_revision.as_ref()),
745    ] {
746        if value.is_some_and(|literal| matches!(literal.base10_parse::<u64>(), Ok(0))) {
747            return Err(syn::Error::new_spanned(
748                value.expect("value 已确认存在"),
749                format!("redis_job {field} must be greater than zero"),
750            ));
751        }
752    }
753    Ok(())
754}
755
756/// 业务作用:读取 RedisJob 字符串字面量。
757///
758/// 参数说明:`expression` 为属性值,`field` 为诊断字段名。
759///
760/// 返回:字符串字面量;其它表达式返回编译错误。
761fn parse_job_string(expression: &Expr, field: &str) -> syn::Result<LitStr> {
762    match expression {
763        Expr::Lit(value) => match &value.lit {
764            Lit::Str(literal) => Ok(literal.clone()),
765            _ => Err(syn::Error::new_spanned(
766                expression,
767                format!("redis_job {field} must be a string literal"),
768            )),
769        },
770        _ => Err(syn::Error::new_spanned(
771            expression,
772            format!("redis_job {field} must be a string literal"),
773        )),
774    }
775}
776
777/// 业务作用:读取 RedisJob 非负整数字面量并保留原始跨度用于生成代码。
778///
779/// 参数说明:`expression` 为属性值,`field` 为诊断字段名。
780///
781/// 返回:整数字面量;负数或其它表达式返回编译错误。
782fn parse_job_integer(expression: &Expr, field: &str) -> syn::Result<syn::LitInt> {
783    match expression {
784        Expr::Lit(value) => match &value.lit {
785            Lit::Int(literal) => Ok(literal.clone()),
786            _ => Err(syn::Error::new_spanned(
787                expression,
788                format!("redis_job {field} must be a non-negative integer literal"),
789            )),
790        },
791        _ => Err(syn::Error::new_spanned(
792            expression,
793            format!("redis_job {field} must be a non-negative integer literal"),
794        )),
795    }
796}
797
798/// 业务作用:把已解析属性转换为 JobDefinitionBuilder 调用并校验封闭枚举文本。
799///
800/// 参数说明:`args` 为宏期完整属性。
801///
802/// 返回:按稳定顺序排列的 builder 语句;未知枚举值返回编译错误。
803fn redis_job_builder_steps(
804    args: &RedisJobArgs,
805    runtime: &TokenStream2,
806) -> syn::Result<Vec<TokenStream2>> {
807    let mut steps = Vec::new();
808    macro_rules! scalar_step {
809        ($field:ident, $method:ident) => { if let Some(value) = &args.$field { steps.push(quote!(builder = builder.$method(#value);)); } };
810    }
811    scalar_step!(qualifier, qualifier);
812    scalar_step!(worker, worker_name);
813    if let Some(trigger) = &args.trigger {
814        match trigger.value().to_ascii_lowercase().as_str() {
815            "scheduled" => {}
816            "fanout_only" => steps.push(quote!(builder = builder.fanout_only();)),
817            _ => {
818                return Err(syn::Error::new_spanned(
819                    trigger,
820                    "redis_job trigger must be `scheduled` or `fanout_only`",
821                ))
822            }
823        }
824    }
825    if let Some(cron) = &args.cron {
826        let zone = args
827            .zone
828            .clone()
829            .unwrap_or_else(|| LitStr::new("UTC", cron.span()));
830        steps.push(quote! {
831            builder = builder.cron(#cron, #zone).map_err(|error| {
832                #runtime::ApplicationError::with_source(
833                    #runtime::ComponentId::RedisJob,
834                    #runtime::ApplicationPhase::Prepare,
835                    "invalid #[redis_job] cron declaration",
836                    error,
837                )
838            })?;
839        });
840    } else if let Some(zone) = &args.zone {
841        return Err(syn::Error::new_spanned(
842            zone,
843            "redis_job zone requires cron",
844        ));
845    }
846    scalar_step!(fixed_rate_ms, fixed_rate_ms);
847    scalar_step!(fixed_delay_ms, fixed_delay_ms);
848    if let Some(value) = &args.concurrency {
849        let variant = match value.value().to_ascii_lowercase().as_str() {
850            "serial_queue" => quote!(#runtime::__private::nadis::job::JobConcurrency::SerialQueue),
851            "discard_if_running" => {
852                quote!(#runtime::__private::nadis::job::JobConcurrency::DiscardIfRunning)
853            }
854            "parallel" => quote!(#runtime::__private::nadis::job::JobConcurrency::Parallel),
855            _ => {
856                return Err(syn::Error::new_spanned(
857                    value,
858                    "redis_job concurrency is unknown",
859                ))
860            }
861        };
862        steps.push(quote!(builder = builder.concurrency(#variant);));
863    }
864    if let Some(value) = &args.misfire {
865        let variant = match value.value().to_ascii_lowercase().as_str() {
866            "do_nothing" => quote!(#runtime::__private::nadis::job::JobMisfire::DoNothing),
867            "fire_once_now" => quote!(#runtime::__private::nadis::job::JobMisfire::FireOnceNow),
868            "catch_up" => quote!(#runtime::__private::nadis::job::JobMisfire::CatchUp),
869            _ => {
870                return Err(syn::Error::new_spanned(
871                    value,
872                    "redis_job misfire is unknown",
873                ))
874            }
875        };
876        steps.push(quote!(builder = builder.misfire(#variant);));
877    }
878    scalar_step!(timeout_ms, timeout_ms);
879    scalar_step!(max_attempts, max_attempts);
880    scalar_step!(retry_delay_ms, retry_delay_ms);
881    scalar_step!(contract_revision, contract_revision);
882    scalar_step!(schema, schema_id);
883    if let Some(codecs) = &args.codecs {
884        let mut variants = Vec::new();
885        for codec in codecs {
886            variants.push(match codec.value().to_ascii_lowercase().as_str() {
887                "json" => quote!(#runtime::__private::nadis::job::JobWireCodec::Json),
888                "protobuf" => quote!(#runtime::__private::nadis::job::JobWireCodec::Protobuf),
889                "raw" => quote!(#runtime::__private::nadis::job::JobWireCodec::Raw),
890                _ => return Err(syn::Error::new_spanned(codec, "redis_job codec is unknown")),
891            });
892        }
893        steps.push(quote!(builder = builder.codecs([#(#variants),*]);));
894    }
895    if args.fanout_receipt_timeout_ms.is_some() || args.fanout_receipt_max_retries.is_some() {
896        let timeout = args
897            .fanout_receipt_timeout_ms
898            .clone()
899            .unwrap_or_else(|| syn::LitInt::new("2000", proc_macro2::Span::call_site()));
900        let retries = args
901            .fanout_receipt_max_retries
902            .clone()
903            .unwrap_or_else(|| syn::LitInt::new("3", proc_macro2::Span::call_site()));
904        steps.push(quote!(builder = builder.fanout_receipt(#timeout, #retries);));
905    }
906    if let Some(value) = &args.fanout_failure_policy {
907        let variant = match value.value().to_ascii_lowercase().as_str() {
908            "reassign_on_failure" => {
909                quote!(#runtime::__private::nadis::job::JobFanoutFailurePolicy::ReassignOnFailure)
910            }
911            "strict_snapshot" => {
912                quote!(#runtime::__private::nadis::job::JobFanoutFailurePolicy::StrictSnapshot)
913            }
914            "best_effort" => {
915                quote!(#runtime::__private::nadis::job::JobFanoutFailurePolicy::BestEffort)
916            }
917            _ => {
918                return Err(syn::Error::new_spanned(
919                    value,
920                    "redis_job fanout_failure_policy is unknown",
921                ))
922            }
923        };
924        steps.push(quote!(builder = builder.fanout_failure_policy(#variant);));
925    }
926    scalar_step!(definition_revision, definition_revision);
927    Ok(steps)
928}
929
930/// initializer 属性完成字面量校验后的内部参数。
931struct InitializerArgs {
932    name: LitStr,
933    order: Option<i32>,
934    requires: Vec<LitStr>,
935    kind: InitializerKindArg,
936    factory: Option<Path>,
937}
938
939/// initializer 生存类型的封闭宏层表示。
940enum InitializerKindArg {
941    OneShot,
942    Hosted,
943}
944
945/// 业务作用:校验 initializer impl 形状并生成独立的静态收集项。
946///
947/// 参数说明:
948/// - `metas`:属性内的名值参数。
949/// - `item_impl`:被标注的 trait impl 语法树。
950///
951/// 返回:元数据、工厂和 trait 合同均合法时返回展开代码。
952fn expand_initializer(
953    metas: Punctuated<Meta, Token![,]>,
954    item_impl: ItemImpl,
955) -> syn::Result<TokenStream2> {
956    verify_initializer_impl(&item_impl)?;
957    let args = parse_initializer_args(&metas, &item_impl.self_ty)?;
958    let runtime = runtime_root("application", "napp")
959        .map_err(|message| syn::Error::new_spanned(&item_impl.self_ty, message))?;
960    let initializer_type = (*item_impl.self_ty).clone();
961    let name = args.name;
962    let order = args
963        .order
964        .map(|value| quote!(#value))
965        .unwrap_or_else(|| quote!(#runtime::DEFAULT_INITIALIZER_ORDER));
966    let requires = args.requires;
967    let kind = match args.kind {
968        InitializerKindArg::OneShot => quote!(#runtime::InitializerKind::OneShot),
969        InitializerKindArg::Hosted => quote!(#runtime::InitializerKind::Hosted),
970    };
971    let construct = match args.factory {
972        Some(factory) => quote! {
973            let result: #runtime::ApplicationResult<::std::option::Option<#initializer_type>> =
974                #factory(application).await;
975            let initializer = result?;
976            ::std::result::Result::Ok(initializer.map(|value| {
977                ::std::boxed::Box::new(value)
978                    as ::std::boxed::Box<dyn #runtime::Initialization>
979            }))
980        },
981        None => quote! {
982            let _ = application;
983            let value: #initializer_type =
984                <#initializer_type as ::std::default::Default>::default();
985            ::std::result::Result::Ok(::std::option::Option::Some(
986                ::std::boxed::Box::new(value)
987                    as ::std::boxed::Box<dyn #runtime::Initialization>
988            ))
989        },
990    };
991
992    Ok(quote! {
993        #item_impl
994
995        const _: () = {
996            /// 业务作用:把强类型条件工厂适配为 Application Runner 可收集的对象安全工厂。
997            ///
998            /// 参数说明:
999            /// - `application`:Prepare 成功后的容器所有权副本。
1000            ///
1001            /// 返回:`Some` 表示当前配置启用,`None` 表示条件未命中,失败则拒绝接流。
1002            fn __nasa_initializer_factory(
1003                application: #runtime::Application,
1004            ) -> #runtime::ApplicationFuture<
1005                'static,
1006                ::std::option::Option<
1007                    ::std::boxed::Box<dyn #runtime::Initialization>
1008                >,
1009            > {
1010                ::std::boxed::Box::pin(async move { #construct })
1011            }
1012
1013            #[#runtime::__private::linkme::distributed_slice(#runtime::COLLECTED_INITIALIZERS)]
1014            #[linkme(crate = #runtime::__private::linkme)]
1015            static __NASA_INITIALIZER_DESCRIPTOR: #runtime::InitializerDescriptor =
1016                #runtime::InitializerDescriptor::__new(
1017                    #name,
1018                    #order,
1019                    &[#(#requires),*],
1020                    #kind,
1021                    __nasa_initializer_factory,
1022                    concat!(module_path!(), ":", file!(), ":", line!()),
1023                );
1024        };
1025    })
1026}
1027
1028/// 业务作用:把 initializer 属性参数解析为封闭内部元数据。
1029///
1030/// 参数说明:
1031/// - `metas`:属性中按源码顺序出现的参数。
1032/// - `self_type`:未声明 `name` 时用于派生默认 canonical 名称的实现类型。
1033///
1034/// 返回:已确定名称与可选顺序/依赖/类型/工厂;重复键、未知键或非法字面量返回编译错误。
1035fn parse_initializer_args(
1036    metas: &Punctuated<Meta, Token![,]>,
1037    self_type: &Type,
1038) -> syn::Result<InitializerArgs> {
1039    let mut name = None;
1040    let mut order = None;
1041    let mut requires = None;
1042    let mut kind = None;
1043    let mut factory = None;
1044    for meta in metas {
1045        let Meta::NameValue(value) = meta else {
1046            return Err(syn::Error::new_spanned(
1047                meta,
1048                "initializer attributes must use `key = value` syntax",
1049            ));
1050        };
1051        let key = value
1052            .path
1053            .get_ident()
1054            .map(ToString::to_string)
1055            .ok_or_else(|| {
1056                syn::Error::new_spanned(
1057                    &value.path,
1058                    "initializer attribute key must be an identifier",
1059                )
1060            })?;
1061        match key.as_str() {
1062            "name" => set_once(&mut name, parse_string_expr(&value.value, "name")?, meta)?,
1063            "order" => {
1064                let parsed = parse_initializer_order(&value.value)?;
1065                set_once(&mut order, parsed, meta)?;
1066            }
1067            "requires" => {
1068                let Expr::Array(array) = &value.value else {
1069                    return Err(syn::Error::new_spanned(
1070                        &value.value,
1071                        "initializer requires must be an array of string literals",
1072                    ));
1073                };
1074                let mut parsed = Vec::with_capacity(array.elems.len());
1075                for element in &array.elems {
1076                    parsed.push(parse_string_expr(element, "requires entry")?);
1077                }
1078                set_once(&mut requires, parsed, meta)?;
1079            }
1080            "kind" => {
1081                let literal = parse_string_expr(&value.value, "kind")?;
1082                let parsed = match literal.value().as_str() {
1083                    "one-shot" => InitializerKindArg::OneShot,
1084                    "hosted" => InitializerKindArg::Hosted,
1085                    _ => {
1086                        return Err(syn::Error::new_spanned(
1087                            literal,
1088                            "initializer kind must be `one-shot` or `hosted`",
1089                        ));
1090                    }
1091                };
1092                set_once(&mut kind, parsed, meta)?;
1093            }
1094            "factory" => {
1095                let Expr::Path(path) = &value.value else {
1096                    return Err(syn::Error::new_spanned(
1097                        &value.value,
1098                        "initializer factory must be a function path",
1099                    ));
1100                };
1101                set_once(&mut factory, path.path.clone(), meta)?;
1102            }
1103            _ => {
1104                return Err(syn::Error::new_spanned(
1105                    &value.path,
1106                    "unknown initializer attribute key",
1107                ));
1108            }
1109        }
1110    }
1111
1112    let name = match name {
1113        Some(name) => name,
1114        None => default_initializer_name(self_type)?,
1115    };
1116    validate_initializer_name(&name, "initializer name")?;
1117    let requires = requires.unwrap_or_default();
1118    if requires.len() > 32 {
1119        return Err(syn::Error::new_spanned(
1120            &name,
1121            "initializer requires cannot contain more than 32 entries",
1122        ));
1123    }
1124    let mut seen = HashSet::new();
1125    for required in &requires {
1126        validate_initializer_name(required, "initializer dependency")?;
1127        if required.value() == name.value() {
1128            return Err(syn::Error::new_spanned(
1129                required,
1130                "initializer cannot require itself",
1131            ));
1132        }
1133        if !seen.insert(required.value()) {
1134            return Err(syn::Error::new_spanned(
1135                required,
1136                "initializer dependency is repeated",
1137            ));
1138        }
1139    }
1140    Ok(InitializerArgs {
1141        name,
1142        order,
1143        requires,
1144        kind: kind.unwrap_or(InitializerKindArg::OneShot),
1145        factory,
1146    })
1147}
1148
1149/// 业务作用:从被标注的具体实现类型派生可用于依赖、日志和指标的默认 initializer 身份。
1150///
1151/// 参数说明:
1152/// - `self_type`:`impl Initialization for Type` 中的 `Type`。
1153///
1154/// 返回:路径末段类型名转为 canonical kebab-case;无法稳定派生时要求调用方显式声明 `name`。
1155fn default_initializer_name(self_type: &Type) -> syn::Result<LitStr> {
1156    let Type::Path(path) = self_type else {
1157        return Err(syn::Error::new_spanned(
1158            self_type,
1159            "initializer name cannot be derived from this type; declare `name` explicitly",
1160        ));
1161    };
1162    if path.qself.is_some() {
1163        return Err(syn::Error::new_spanned(
1164            self_type,
1165            "initializer name cannot be derived from a qualified self type; declare `name` explicitly",
1166        ));
1167    }
1168    let segment = path.path.segments.last().ok_or_else(|| {
1169        syn::Error::new_spanned(
1170            self_type,
1171            "initializer name cannot be derived from this type; declare `name` explicitly",
1172        )
1173    })?;
1174    let identifier = segment.ident.to_string();
1175    let name = canonicalize_type_name(&identifier).ok_or_else(|| {
1176        syn::Error::new_spanned(
1177            &segment.ident,
1178            "initializer type name cannot form a canonical name; declare `name` explicitly",
1179        )
1180    })?;
1181    let name = LitStr::new(&name, segment.ident.span());
1182    validate_initializer_name(&name, "derived initializer name")?;
1183    Ok(name)
1184}
1185
1186/// 业务作用:把 Rust 类型标识符确定性转换成 initializer canonical kebab-case。
1187///
1188/// 参数说明:
1189/// - `identifier`:实现类型的最后一个 Rust 路径标识符。
1190///
1191/// 返回:ASCII 字母、数字和下划线可转换时返回小写名称;其它字符或空结果返回 `None`。
1192fn canonicalize_type_name(identifier: &str) -> Option<String> {
1193    let identifier = identifier.strip_prefix("r#").unwrap_or(identifier);
1194    let bytes = identifier.as_bytes();
1195    let mut output = String::with_capacity(bytes.len());
1196    let mut pending_separator = false;
1197    for (index, byte) in bytes.iter().copied().enumerate() {
1198        if byte == b'_' {
1199            pending_separator = !output.is_empty();
1200            continue;
1201        }
1202        if !byte.is_ascii_alphanumeric() {
1203            return None;
1204        }
1205        let previous = index
1206            .checked_sub(1)
1207            .and_then(|value| bytes.get(value))
1208            .copied();
1209        let next = bytes.get(index + 1).copied();
1210        let word_boundary = byte.is_ascii_uppercase()
1211            && (previous.is_some_and(|value| value.is_ascii_lowercase() || value.is_ascii_digit())
1212                || (previous.is_some_and(|value| value.is_ascii_uppercase())
1213                    && next.is_some_and(|value| value.is_ascii_lowercase())));
1214        if (pending_separator || word_boundary) && !output.is_empty() && !output.ends_with('-') {
1215            output.push('-');
1216        }
1217        output.push(byte.to_ascii_lowercase() as char);
1218        pending_separator = false;
1219    }
1220    while output.ends_with('-') {
1221        output.pop();
1222    }
1223    (!output.is_empty()).then_some(output)
1224}
1225
1226/// 业务作用:解析 initializer 的有符号稳定优先级,保证属性入口与运行时 `i32` 合同一致。
1227///
1228/// 参数说明:
1229/// - `expression`:属性 `order` 等号右侧的表达式。
1230///
1231/// 返回:正负整数字面量在 `i32` 范围内时返回其值;其它表达式或越界值返回编译错误。
1232fn parse_initializer_order(expression: &Expr) -> syn::Result<i32> {
1233    let invalid = || {
1234        syn::Error::new_spanned(
1235            expression,
1236            "initializer order must be an i32 integer literal",
1237        )
1238    };
1239    let signed = match expression {
1240        Expr::Lit(expr) => match &expr.lit {
1241            Lit::Int(value) => value.base10_parse::<i64>().map_err(|_| invalid())?,
1242            _ => return Err(invalid()),
1243        },
1244        Expr::Unary(expr) if matches!(expr.op, syn::UnOp::Neg(_)) => match expr.expr.as_ref() {
1245            Expr::Lit(expr) => match &expr.lit {
1246                Lit::Int(value) => value
1247                    .base10_parse::<i64>()
1248                    .ok()
1249                    .and_then(i64::checked_neg)
1250                    .ok_or_else(invalid)?,
1251                _ => return Err(invalid()),
1252            },
1253            _ => return Err(invalid()),
1254        },
1255        _ => return Err(invalid()),
1256    };
1257    i32::try_from(signed).map_err(|_| invalid())
1258}
1259
1260/// 业务作用:校验属性只标注可静态收集的安全正向 `Initialization` impl。
1261///
1262/// 参数说明:
1263/// - `item_impl`:待校验的 impl 块。
1264///
1265/// 返回:形状可用时成功;固有、unsafe、负向、泛型或错误 trait 时返回编译错误。
1266fn verify_initializer_impl(item_impl: &ItemImpl) -> syn::Result<()> {
1267    if item_impl.unsafety.is_some() {
1268        return Err(syn::Error::new_spanned(
1269            item_impl.unsafety,
1270            "initializer cannot annotate an unsafe impl",
1271        ));
1272    }
1273    if !item_impl.generics.params.is_empty() {
1274        return Err(syn::Error::new_spanned(
1275            &item_impl.generics,
1276            "initializer impl cannot declare generic parameters",
1277        ));
1278    }
1279    let Some((polarity, trait_path, _)) = &item_impl.trait_ else {
1280        return Err(syn::Error::new_spanned(
1281            &item_impl.self_ty,
1282            "initializer must annotate an Initialization trait impl",
1283        ));
1284    };
1285    if polarity.is_some() {
1286        return Err(syn::Error::new_spanned(
1287            polarity,
1288            "initializer cannot annotate a negative impl",
1289        ));
1290    }
1291    if trait_path
1292        .segments
1293        .last()
1294        .is_none_or(|segment| segment.ident != "Initialization")
1295    {
1296        return Err(syn::Error::new_spanned(
1297            trait_path,
1298            "initializer trait path must end with Initialization",
1299        ));
1300    }
1301    Ok(())
1302}
1303
1304/// 业务作用:解析 initializer 属性中必须是字符串的表达式。
1305///
1306/// 参数说明:
1307/// - `expression`:待解析的属性值。
1308/// - `field`:出错时的稳定字段名。
1309///
1310/// 返回:字符串字面量;其它表达式返回编译错误。
1311fn parse_string_expr(expression: &Expr, field: &str) -> syn::Result<LitStr> {
1312    match expression {
1313        Expr::Lit(expr) => match &expr.lit {
1314            Lit::Str(value) => Ok(value.clone()),
1315            _ => Err(syn::Error::new_spanned(
1316                expression,
1317                format!("initializer {field} must be a string literal"),
1318            )),
1319        },
1320        _ => Err(syn::Error::new_spanned(
1321            expression,
1322            format!("initializer {field} must be a string literal"),
1323        )),
1324    }
1325}
1326
1327/// 业务作用:保证同一 initializer 属性键只设置一次。
1328///
1329/// 参数说明:
1330/// - `slot`:当前字段已解析的可选值。
1331/// - `value`:本次准备写入的值。
1332/// - `meta`:重复时用于定位的属性项。
1333///
1334/// 返回:首次写入成功;重复键返回编译错误。
1335fn set_once<T>(slot: &mut Option<T>, value: T, meta: &Meta) -> syn::Result<()> {
1336    if slot.is_some() {
1337        return Err(syn::Error::new_spanned(
1338            meta,
1339            "initializer attribute key is repeated",
1340        ));
1341    }
1342    *slot = Some(value);
1343    Ok(())
1344}
1345
1346/// 业务作用:在宏展开前校验 initializer 与依赖的 canonical 名称合同。
1347///
1348/// 参数说明:
1349/// - `name`:待校验的字符串字面量。
1350/// - `field`:出错时的稳定字段分类。
1351///
1352/// 返回:1..=128 字节且仅含小写 ASCII、数字、`_`/`-`/`.` 时成功。
1353fn validate_initializer_name(name: &LitStr, field: &str) -> syn::Result<()> {
1354    let value = name.value();
1355    if value.is_empty() || value.len() > 128 {
1356        return Err(syn::Error::new_spanned(
1357            name,
1358            format!("{field} must contain between 1 and 128 bytes"),
1359        ));
1360    }
1361    if !value.bytes().all(|byte| {
1362        byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'_' | b'-' | b'.')
1363    }) {
1364        return Err(syn::Error::new_spanned(
1365            name,
1366            format!("{field} must contain only lowercase ASCII letters, digits, `_`, `-`, or `.`"),
1367        ));
1368    }
1369    Ok(())
1370}
1371
1372/// 业务作用:校验入口契约并生成静态描述、业务 Hook 包装和同步主函数。
1373///
1374/// 参数说明:
1375/// - `components`:属性中按源码顺序出现的组件字面量。
1376/// - `function`:已经解析的业务异步主函数。
1377///
1378/// 返回:入口合同与组件声明合法时返回完整展开;路径、签名或组件非法时返回定位明确的宏错误。
1379fn expand_application(
1380    components: Vec<LitStr>,
1381    mut function: ItemFn,
1382) -> syn::Result<proc_macro2::TokenStream> {
1383    validate_function(&function)?;
1384    let component_names = validate_components(&components)?;
1385    let runtime = runtime_root("application", "napp")
1386        .map_err(|message| syn::Error::new_spanned(&function.sig.ident, message))?;
1387
1388    let has_web = component_names.iter().any(|name| name == "web");
1389    let component_variants = component_names
1390        .iter()
1391        .map(|name| component_variant(name))
1392        .collect::<syn::Result<Vec<_>>>()?;
1393    let feature_modules = component_names
1394        .iter()
1395        .map(|name| component_feature_module(name))
1396        .collect::<syn::Result<Vec<_>>>()?;
1397    let accepts_application = function.sig.inputs.len() == 1;
1398    function.sig.ident = format_ident!("__nasa_user_main");
1399
1400    let hook = if accepts_application {
1401        quote!(|application| __nasa_user_main(application))
1402    } else {
1403        quote!(|_application| __nasa_user_main())
1404    };
1405    let web_items = if has_web {
1406        quote! {
1407            #runtime::__private::naweb::mvc_router!(#runtime::Application);
1408
1409            /// 业务作用:把业务 crate 内收集的 nominal 路由项投影成稳定诊断元数据。
1410            ///
1411            /// 参数说明: 无。
1412            ///
1413            /// 返回:只包含静态方法、路径和处理函数身份的路由元数据。
1414            fn __nasa_route_meta() -> ::std::vec::Vec<#runtime::RouteMeta> {
1415                crate::__mvc::ROUTES
1416                    .iter()
1417                    .map(|entry| #runtime::RouteMeta {
1418                        method: entry.method,
1419                        path: entry.path,
1420                        handler: entry.handler,
1421                        produces: entry.produces,
1422                        consumes: entry.consumes,
1423                        request_schema: entry.request_schema,
1424                        response_schema: entry.response_schema,
1425                        query_parameters: entry.query_parameters,
1426                        header_parameters: entry.header_parameters,
1427                        success_status: entry.success_status,
1428                        additional_responses: entry.additional_responses,
1429                        streaming: entry.streaming,
1430                        auth_required: ::core::matches!(
1431                            entry.policy.auth,
1432                            #runtime::__private::naweb::AuthRequirement::Required
1433                        ),
1434                    })
1435                    .collect()
1436            }
1437
1438            /// 业务作用:构造只含自动收集端点、尚未补齐状态的业务路由。
1439            ///
1440            /// 状态刻意不在这里补:`configure_router` 的定制与框架探针都必须先作用在
1441            /// `Router<Application>` 上,`with_state` 由运行时在装配顺序末尾统一执行。
1442            ///
1443            /// 参数说明:
1444            /// - `context`:由 napp Ready 构造,保证 interceptor 与 handler 使用同一个 Application clone。
1445            ///
1446            /// 返回:路由和安全流水线装配成功时返回统一状态 Router;冲突或合同错误时拒绝监听。
1447            fn __nasa_build_router(
1448                context: #runtime::WebBuildContext,
1449            ) -> #runtime::ApplicationResult<
1450                #runtime::__private::axum::Router<#runtime::Application>,
1451            > {
1452                context.build(|router, mapping_runtime, mapping_plan, application| {
1453                    crate::__mvc::try_register_all(
1454                        router,
1455                        mapping_runtime,
1456                        mapping_plan,
1457                        application,
1458                    )
1459                })
1460            }
1461        }
1462    } else {
1463        quote! {}
1464    };
1465    let spec_web = if has_web {
1466        quote!(
1467            .with_web_route_meta(__nasa_route_meta)
1468            .with_web_factory(__nasa_build_router)
1469        )
1470    } else {
1471        quote! {}
1472    };
1473
1474    Ok(quote! {
1475        #[doc(hidden)]
1476        pub mod __nasa_application_must_be_at_crate_root {}
1477        use crate::__nasa_application_must_be_at_crate_root as _;
1478        #(
1479            const _: () = #runtime::components::#feature_modules::FEATURE_CHECK;
1480        )*
1481
1482        #web_items
1483        #function
1484
1485        /// 业务作用:在生成入口附近约束业务 Hook 的可移动性、生命周期和错误类型。
1486        ///
1487        /// 该薄包装不执行 Hook;它把类型错误定位到业务入口,而不是延后到任务监督器内部。
1488        ///
1489        /// 参数说明:
1490        /// - `hook`:拥有业务主函数调用的闭包,接收统一 Application 并返回受监督 future。
1491        ///
1492        /// 返回:类型合同成立时原样返回 Hook,供同步入口唯一消费。
1493        fn __nasa_require_user_hook<F, Fut, E>(hook: F) -> F
1494        where
1495            F: ::std::ops::FnOnce(#runtime::Application) -> Fut + ::std::marker::Send + 'static,
1496            Fut: ::std::future::Future<Output = ::std::result::Result<(), E>>
1497                + ::std::marker::Send
1498                + 'static,
1499            E: ::std::convert::Into<#runtime::__private::anyhow::Error> + 'static,
1500        {
1501            hook
1502        }
1503
1504        /// 业务作用:创建运行时静态描述并把业务 Hook 交给统一同步入口。
1505        ///
1506        /// 参数说明: 无。
1507        ///
1508        /// 返回:业务正常完成或优雅停机时返回成功退出码;启动、运行或停机失败返回对应非零退出码。
1509        fn main() -> ::std::process::ExitCode {
1510            #runtime::run(
1511                #runtime::ApplicationSpec::new(&[
1512                    #(#runtime::ComponentId::#component_variants),*
1513                ])
1514                .with_default_name(env!("CARGO_PKG_NAME"))
1515                #spec_web,
1516                __nasa_require_user_hook(#hook),
1517            )
1518        }
1519    })
1520}
1521
1522/// 业务作用:校验业务主函数的名称、异步形态、参数和返回结果形状。
1523///
1524/// 参数说明:
1525/// - `function`:属性直接标注的业务函数语法树。
1526///
1527/// 返回:名称、异步形态、参数、返回类型和 runtime 所有权均合法时成功,否则返回编译错误。
1528fn validate_function(function: &ItemFn) -> syn::Result<()> {
1529    if function.sig.ident != "main" {
1530        return Err(syn::Error::new_spanned(
1531            &function.sig.ident,
1532            "application attribute must be attached to the crate main function",
1533        ));
1534    }
1535    if function.sig.asyncness.is_none() {
1536        return Err(syn::Error::new_spanned(
1537            function.sig.fn_token,
1538            "application main must be async and must not use another runtime entry attribute",
1539        ));
1540    }
1541    if !function.sig.generics.params.is_empty() {
1542        return Err(syn::Error::new_spanned(
1543            &function.sig.generics,
1544            "application main cannot declare generics",
1545        ));
1546    }
1547    if function.sig.inputs.len() > 1 {
1548        return Err(syn::Error::new_spanned(
1549            &function.sig.inputs,
1550            "application main accepts at most one Application parameter",
1551        ));
1552    }
1553    if let Some(argument) = function.sig.inputs.first() {
1554        validate_application_parameter(argument)?;
1555    }
1556    validate_return_type(&function.sig.output)?;
1557    for attribute in &function.attrs {
1558        let segments = attribute
1559            .path()
1560            .segments
1561            .iter()
1562            .map(|segment| segment.ident.to_string())
1563            .collect::<Vec<_>>();
1564        if segments.first().is_some_and(|name| name == "tokio")
1565            && segments.last().is_some_and(|name| name == "main")
1566        {
1567            return Err(syn::Error::new_spanned(
1568                attribute,
1569                "remove the other runtime entry attribute because application owns the runtime",
1570            ));
1571        }
1572        if segments
1573            .last()
1574            .is_some_and(|name| matches!(name.as_str(), "EnableScheduling" | "EnableAsync"))
1575        {
1576            return Err(syn::Error::new_spanned(
1577                attribute,
1578                "declare the scheduling component in application instead of using an entry attribute",
1579            ));
1580        }
1581    }
1582    Ok(())
1583}
1584
1585/// 业务作用:校验唯一可选参数的类型为统一 Application。
1586///
1587/// 参数说明:
1588/// - `argument`:业务主函数声明的唯一函数参数。
1589///
1590/// 返回:参数为统一 `Application` 类型时成功;receiver 或其它类型返回编译错误。
1591fn validate_application_parameter(argument: &FnArg) -> syn::Result<()> {
1592    let FnArg::Typed(argument) = argument else {
1593        return Err(syn::Error::new_spanned(
1594            argument,
1595            "application main cannot use a receiver parameter",
1596        ));
1597    };
1598    let Type::Path(path) = argument.ty.as_ref() else {
1599        return Err(syn::Error::new_spanned(
1600            &argument.ty,
1601            "application main parameter must be Application",
1602        ));
1603    };
1604    if path
1605        .path
1606        .segments
1607        .last()
1608        .is_none_or(|segment| segment.ident != "Application")
1609    {
1610        return Err(syn::Error::new_spanned(
1611            &argument.ty,
1612            "application main parameter must be Application",
1613        ));
1614    }
1615    Ok(())
1616}
1617
1618/// 业务作用:校验业务主函数返回单元成功值的 `Result`。
1619///
1620/// 参数说明:
1621/// - `output`:业务主函数声明的返回类型。
1622///
1623/// 返回:返回类型为单元成功值的 `Result` 时成功,其它形态返回编译错误。
1624fn validate_return_type(output: &ReturnType) -> syn::Result<()> {
1625    let ReturnType::Type(_, output_type) = output else {
1626        return Err(syn::Error::new_spanned(
1627            output,
1628            "application main must return anyhow::Result<()>",
1629        ));
1630    };
1631    let Type::Path(path) = output_type.as_ref() else {
1632        return Err(syn::Error::new_spanned(
1633            output_type,
1634            "application main must return anyhow::Result<()>",
1635        ));
1636    };
1637    let Some(result) = path.path.segments.last() else {
1638        return Err(syn::Error::new_spanned(
1639            output_type,
1640            "application main must return anyhow::Result<()>",
1641        ));
1642    };
1643    let PathArguments::AngleBracketed(arguments) = &result.arguments else {
1644        return Err(syn::Error::new_spanned(
1645            output_type,
1646            "application main must return anyhow::Result<()>",
1647        ));
1648    };
1649    let unit_success = matches!(
1650        arguments.args.first(),
1651        Some(GenericArgument::Type(Type::Tuple(tuple))) if tuple.elems.is_empty()
1652    );
1653    if result.ident != "Result" || arguments.args.len() != 1 || !unit_success {
1654        return Err(syn::Error::new_spanned(
1655            output_type,
1656            "application main must return anyhow::Result<()>",
1657        ));
1658    }
1659    Ok(())
1660}
1661
1662/// 规范启动顺序:业务侧可以按任意顺序书写组件字符串,napp 由此秩统一规范化。
1663///
1664/// 该数组既是合法组件白名单,也是唯一的规范启动顺序:配置先于资源,DB 先于 Saga/Outbox,
1665/// transport 先于业务入口。新增组件时必须按依赖与反向停机关系插入。
1666const CANONICAL_COMPONENT_ORDER: [&str; 17] = [
1667    "log",
1668    "nacos-config",
1669    "telemetry",
1670    "db",
1671    "redis",
1672    "cache",
1673    "partition",
1674    "saga",
1675    "kafka",
1676    "outbox",
1677    "redis-job",
1678    "grpc",
1679    "auth",
1680    "web",
1681    "ws",
1682    "nacos-discovery",
1683    "scheduling",
1684];
1685
1686/// 业务作用:校验组件名称与重复项,并按规范启动顺序排序返回,确保书写顺序不改变生命周期。
1687///
1688/// 业务侧无需按启动顺序书写 `#[application(...)]`:本函数接受任意顺序,拒绝未知名称和重复项,
1689/// 然后按 [`CANONICAL_COMPONENT_ORDER`] 排序。运行时因此始终收到规范顺序的组件列表。
1690///
1691/// 参数说明:
1692/// - `components`:属性中以任意顺序提供的字符串字面量。
1693///
1694/// 返回:名称全部合法且唯一时返回规范顺序;未知名称或重复声明返回定位到属性项的错误。
1695fn validate_components(components: &[LitStr]) -> syn::Result<Vec<String>> {
1696    let mut seen = HashSet::new();
1697    let mut names = Vec::with_capacity(components.len());
1698    for component in components.iter() {
1699        let name = component.value();
1700        if !CANONICAL_COMPONENT_ORDER.contains(&name.as_str()) {
1701            return Err(syn::Error::new_spanned(
1702                component,
1703                format!("unknown application component `{name}`"),
1704            ));
1705        }
1706        if !seen.insert(name.clone()) {
1707            return Err(syn::Error::new_spanned(
1708                component,
1709                format!("application component `{name}` is declared more than once"),
1710            ));
1711        }
1712        names.push(name);
1713    }
1714    // Saga 的本地闭环必然包含同 driver 的 Inbox 与 Outbox;Inbox 没有独立生命周期,DB 与 Outbox
1715    // 只在缺失时补入。业务显式写出依赖仍收敛为同一组件图,不应误判重复。
1716    // Kafka/Redis 等 transport 不在这里推断。
1717    if seen.contains("saga") && seen.insert("outbox".to_string()) {
1718        names.push("outbox".to_string());
1719    }
1720    if seen.contains("outbox") && seen.insert("db".to_string()) {
1721        names.push("db".to_string());
1722    }
1723    // RedisJob 的全部控制面都绑定受管 Redis source;显式声明上层能力即可形成完整组件图。
1724    if seen.contains("redis-job") && seen.insert("redis".to_string()) {
1725        names.push("redis".to_string());
1726    }
1727    // 顺序无关:按规范秩排序,业务书写顺序不再影响启动/停机顺序。
1728    names.sort_by_key(|name| {
1729        CANONICAL_COMPONENT_ORDER
1730            .iter()
1731            .position(|canonical| canonical == name)
1732            .expect("name validated against CANONICAL_COMPONENT_ORDER above")
1733    });
1734    Ok(names)
1735}
1736
1737/// 业务作用:把规范化组件名称转换为运行时枚举变体。
1738///
1739/// 参数说明:
1740/// - `name`:已经通过白名单校验的组件名称。
1741///
1742/// 返回:名称可映射时返回对应枚举标识;内部传入未校验名称时返回宏展开错误。
1743fn component_variant(name: &str) -> syn::Result<syn::Ident> {
1744    let variant = match name {
1745        "log" => "Log",
1746        "nacos-config" => "NacosConfig",
1747        "db" => "Db",
1748        "redis" => "Redis",
1749        "redis-job" => "RedisJob",
1750        "telemetry" => "Telemetry",
1751        "cache" => "Cache",
1752        "partition" => "Partition",
1753        "grpc" => "Grpc",
1754        "saga" => "Saga",
1755        "kafka" => "Kafka",
1756        "outbox" => "Outbox",
1757        "auth" => "Auth",
1758        "web" => "Web",
1759        "ws" => "Ws",
1760        "nacos-discovery" => "NacosDiscovery",
1761        "scheduling" => "Scheduling",
1762        _ => {
1763            return Err(syn::Error::new(
1764                proc_macro2::Span::call_site(),
1765                "component name was not validated",
1766            ));
1767        }
1768    };
1769    Ok(format_ident!("{variant}"))
1770}
1771
1772/// 业务作用:把已校验组件名称转换为编译期能力探测模块。
1773///
1774/// 参数说明:
1775/// - `name`:已经通过组件白名单和顺序校验的规范名称。
1776///
1777/// 返回:返回供展开代码引用的能力模块标识;内部传入未校验名称时返回宏展开错误。
1778fn component_feature_module(name: &str) -> syn::Result<syn::Ident> {
1779    match name {
1780        "log" | "db" | "redis" | "telemetry" | "cache" | "partition" | "grpc" | "saga"
1781        | "kafka" | "outbox" | "auth" | "web" | "ws" | "scheduling" => Ok(format_ident!("{name}")),
1782        "redis-job" => Ok(format_ident!("redis_job")),
1783        "nacos-config" => Ok(format_ident!("nacos_config")),
1784        "nacos-discovery" => Ok(format_ident!("nacos_discovery")),
1785        _ => Err(syn::Error::new(
1786            proc_macro2::Span::call_site(),
1787            "component name was not validated",
1788        )),
1789    }
1790}