Files
Cyrene_v2/pkg/plugins/sdk/panelfield.go
T
AskaEth c9144c1096
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(plugins): 插件「管理面板声明」库——声明模型 + 闭集校验 + catalog(design/09 §5.2/§5.2.1)
纯库、零接线:不碰 gateway/frontend/Docs/.env*(本片刻意不接线,避免与在途切片争用文件面)。

- pkg/plugins/sdk:面板声明类型(JSON tag 与 design/09 §5.2 示例逐字同形);
  字段类型与 section 类型都是**闭集**;When 三态 + **严格 UnmarshalJSON**(未知条件键报错,
  不静默丢)
- internal/capability/paneldecl:闭集校验(47 条拒绝用例,断言的是**分类哨兵 + JSON 路径**,
  不是错误字符串)· catalog(并发安全、返回拷贝、按 (group, order, 登记序号) 的**全序**稳定排序)·
  when 求值(上下文由 Resolver 注入,本包不读环境变量/文件)· 角色过滤(viewer 只留
  status/log/table 且摘掉 rowActions,纯写面板整体隐藏;未知角色 fail-closed)
- 拒绝粒度:插件 id 非法 ⇒ 整插件拒绝;单个面板非法 ⇒ 只拒该面板 + 聚合显式报错(理由见报告)

【我的验收】隔离 worktree(19 文件)build/vet/linux/darwin 全过、69 包全绿;
我亲手打红变异:把同组同序的回退比较  反成  ⇒ TestCatalog_StableOrder FAIL。
(记录一条观察:比较器已靠 seq 构成全序,故 sort.SliceStable 的稳定性是冗余保护 ——
我第一次用「SliceStable→Slice」打不红,正是因为全序与算法无关;这不是测试虚护。)
2026-10-06 08:06:20 +08:00

81 lines
4.1 KiB
Go

package sdk
// 本文件是 design/09-插件生态 §5.2「字段类型集合(闭集)」在 Go 侧的唯一定义,
// 以及字段声明本身(form / status 区块的 `fields[]` 元素)。
// FieldType 是字段控件类型(闭集,§5.2:不许插件自定义渲染器)。
//
// 闭集的意义:每个形状都能复用既有 Miuix 组件(渠道配置那套字段渲染 + 虚拟滚动表格),
// 因此「插件声明 UI」在无任意代码注入的前提下依然够用 —— 配置类插件 90% 的需求就是
// 这几个形状。
type FieldType string
const (
// FieldText 是单行文本(示例 endpoint:接口地址)。
FieldText FieldType = "text"
// FieldSecret 是凭据输入(示例 apiKey)。读侧永不回显明文(§5.4.2),因此
// paneldecl 拒绝它声明默认值。
FieldSecret FieldType = "secret"
// FieldNumber 是数值输入(唯一接受 min/max 边界的类型)。
FieldNumber FieldType = "number"
// FieldSwitch 是布尔开关。
FieldSwitch FieldType = "switch"
// FieldSelect 是单选下拉(选项来自 optionsFrom)。
FieldSelect FieldType = "select"
// FieldMultiSelect 是多选下拉(选项来自 optionsFrom)。
FieldMultiSelect FieldType = "multiSelect"
// FieldDate 是日期。
FieldDate FieldType = "date"
// FieldKV 是键值对(§5.2 的例子:自定义 header)。
FieldKV FieldType = "kv"
)
// Valid 报告 t 是否属于字段类型闭集。
func (t FieldType) Valid() bool {
switch t {
case FieldText, FieldSecret, FieldNumber, FieldSwitch,
FieldSelect, FieldMultiSelect, FieldDate, FieldKV:
return true
default:
return false
}
}
// Sensitive 报告该类型是否承载凭据。§5.4.2 要求读侧永不回显明文(只回「是否已配置」+
// 尾 4 位或哈希前缀),所以敏感字段不得声明任何默认值 —— 校验在 paneldecl。
func (t FieldType) Sensitive() bool { return t == FieldSecret }
// Numeric 报告该类型是否接受 min/max 数值边界。依据:闭集里只有 number 是数值控件,
// date 在声明里是日期字符串,用数值边界表达日期范围只会产生歧义。
func (t FieldType) Numeric() bool { return t == FieldNumber }
// PullsOptions 报告该类型是否需要宿主去拉下拉选项。依据:只有 select / multiSelect 会
// 渲染选项列表,其它类型上的 optionsFrom 是无效声明(会被 paneldecl 拒绝)。
func (t FieldType) PullsOptions() bool { return t == FieldSelect || t == FieldMultiSelect }
// Field 是一个字段声明(§5.2 的 fields 元素,form 与 status 共用)。
type Field struct {
// Key 是配置键 / 行字段名,形如 §5.2 示例的 provider / apiKey / endpoint。
// 它同时是表单的提交键与 status 行的数据键,因此在区块内必须唯一。
Key string `json:"key"`
// Label 是字段标签。缺省时宿主回落到 Key(与 Column.Label 同一取舍)。
Label string `json:"label,omitempty"`
// Type 决定控件,必须落在闭集内。
Type FieldType `json:"type"`
// Required 是**配置值**的必填标记(§5.2 示例的 `"required": true`),不是声明层的
// 必填 —— 声明层的必填只认 Key 与 Type。
Required bool `json:"required,omitempty"`
// Placeholder 是输入提示(示例 `https://...`)。
Placeholder string `json:"placeholder,omitempty"`
// OptionsFrom 是下拉选项来源。§5.2 只给了插件内的一种来源(示例 plugin:providers);
// **只允许 `plugin:` 前缀**,其它一律拒 —— 防「声明里塞任意 URL」。
OptionsFrom string `json:"optionsFrom,omitempty"`
// Default 是字段初始值。§5.2 的示例里没有这个键;它按任务书「若你定义了这些可选键」
// 的口径加入,且对 secret 类型被 paneldecl 硬拒(§5.4.2 读侧无明文)。
Default any `json:"default,omitempty"`
// Min 是数值下界,仅 number 类型可声明(§5.2 示例里没有这个键,同上)。
Min *float64 `json:"min,omitempty"`
// Max 是数值上界,仅 number 类型可声明,且必须 >= Min。
Max *float64 `json:"max,omitempty"`
}