Skip to main content

xyz_rust/spec/
entry.rs

1// Entry 是一条命令的类型擦除视图:前端唯一可见的面。具体入参与结果
2// 类型藏在 Invoke 闭包内部。
3
4use std::sync::Arc;
5
6use serde_json::{Map, Value};
7
8use crate::ctx::Ctx;
9use crate::errors;
10use crate::spec::JsonMap;
11use crate::spec::command::{CliHints, HTTPHints, MCPHints};
12use crate::spec::field::FieldMeta;
13use crate::spec::schema::Schema;
14
15pub struct Entry {
16    pub name: String,
17    pub summary: String,
18    pub description: String,
19
20    /// 入参 struct 的 JSON Schema;MCP 前端与 OpenAPI 文档直接消费。
21    pub input_schema: Schema,
22
23    /// handler 结果类型的 JSON Schema(MCP tool.outputSchema / OpenAPI
24    /// 响应 schema)。结果类型无法 schematize 时为 None。
25    pub output_schema: Option<Schema>,
26
27    /// 入参 struct 树(tags、默认值、CLI/HTTP 绑定)——生成 flag、路由
28    /// 或绑定器的前端读这里。
29    pub root: FieldMeta,
30
31    pub cli: CliHints,
32    pub http: HTTPHints,
33    pub mcp: MCPHints,
34
35    /// 解码入参——任何前端归约出的 map 形状——补默认、校验并跑 handler。
36    /// 解码与校验失败一律以 Kind::InvalidInput 分类返回。
37    #[allow(clippy::type_complexity)]
38    pub invoke: Box<dyn Fn(&Ctx, &JsonMap) -> errors::Result<Value> + Send + Sync>,
39}
40
41impl std::fmt::Debug for Entry {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.debug_struct("Entry")
44            .field("name", &self.name)
45            .field("input_schema", &self.input_schema)
46            .field("output_schema", &self.output_schema)
47            .field("cli", &self.cli)
48            .field("http", &self.http)
49            .field("mcp", &self.mcp)
50            .finish()
51    }
52}
53
54impl Entry {
55    /// sf: 供前端覆写——默认实现为空。
56    pub(crate) fn _ty() {}
57
58    /// CLI 专属默认值:每个可绑定字段一条,按 JSON 名键控。CLI 前端在
59    /// Invoke 之前把它们注入入参 map;全局 tag 默认由 Invoke 自己补,
60    /// 所以优先级是 CLI 覆盖 > 全局 tag > 零值。
61    pub fn cli_defaults(&self) -> Map<String, Value> {
62        transport_defaults(&self.root, |f| f.cli.default.clone())
63    }
64
65    /// HTTP 专属默认值。
66    pub fn http_defaults(&self) -> Map<String, Value> {
67        transport_defaults(&self.root, |f| f.http.default.clone())
68    }
69
70    /// MCP 专属默认值。MCP 的覆盖同时替换 inputSchema 里的 default。
71    pub fn mcp_defaults(&self) -> Map<String, Value> {
72        transport_defaults(&self.root, |f| f.mcp.default.clone())
73    }
74}
75
76fn transport_defaults(
77    node: &FieldMeta,
78    get: impl Fn(&FieldMeta) -> Option<Value> + Copy,
79) -> Map<String, Value> {
80    let mut out = Map::new();
81    for f in &node.children {
82        if f.skip {
83            continue;
84        }
85        if let Some(d) = get(f) {
86            out.insert(f.json_name.clone(), d);
87        }
88    }
89    out
90}
91
92/// 便捷别名:多条 Entry 的共享句柄。
93pub type SharedEntry = Arc<Entry>;