package sdk import ( "bytes" "encoding/json" "errors" "fmt" "sort" ) // 本文件定义面板入口的**条件显示**声明 `when`(design/09 §5.2.1 nav.item: // 「`when` 不满足 ⇒ 条目不出现」,不是灰掉)。 const ( // whenKeyEnabled 是本期唯一支持的条件键:§5.2 的 `"when": { "enabled": true }` // 是设计里出现过的唯一条件形态,§5.6 的验收判据也只有「未启用的插件 ⇒ 入口不出现」。 // 刻意不留第二个键 —— 闭集外的条件属于「先改设计再加」的范畴。 whenKeyEnabled = "enabled" ) var ( // ErrInvalidWhen 报告 when 不是合法的条件对象(非对象、null、或 enabled 不是布尔)。 ErrInvalidWhen = errors.New("sdk: invalid when clause") // ErrUnknownWhenKey 报告 when 里出现了闭集外的条件键。 // // 刻意**报错而不是忽略**:静默忽略会让插件作者以为条件生效了,而入口照常出现 —— // §5.2.1 的口径是「绝不静默丢弃」。这条只能在解码期拦住(结构体解码本身会丢弃未知 // 键),所以这里自己实现 UnmarshalJSON。 ErrUnknownWhenKey = errors.New("sdk: unknown when key") ) // When 是面板入口的条件显示声明。零值语义是「没有条件」,见 paneldecl.Eval。 type When struct { // Enabled 有三个状态,用指针而不是 bool 表达: // - nil:没声明这个条件(含整份 when 缺省的 nil 指针)⇒ 不参与判定; // - &true:要求插件当前已启用(§5.6.1「未启用 ⇒ 入口不出现」); // - &false:要求插件当前**未**启用 —— 条件是「要求值相等」,写 false 不等于没写。 Enabled *bool `json:"enabled,omitempty"` } // UnmarshalJSON 严格解码 when:只接受对象,只接受闭集内的键,enabled 必须是布尔。 // // 为什么不用默认的 struct 解码:默认解码会**静默丢弃**未知键(`{"enabled":true,"x":1}` // 会解码成一个看似正常的 When),插件作者会以为自己写了条件却没有任何效果。这里把 // 未知键变成显式拒绝,由插件管理器按 §5.2.1「该面板不注册 + 显式报错」处理。 func (w *When) UnmarshalJSON(raw []byte) error { trimmed := bytes.TrimSpace(raw) if bytes.Equal(trimmed, []byte("null")) { return fmt.Errorf("%w: null is not a condition set", ErrInvalidWhen) } var fields map[string]json.RawMessage if err := json.Unmarshal(trimmed, &fields); err != nil { return fmt.Errorf("%w: %w", ErrInvalidWhen, err) } // 按键名排序后再判定:多个未知键时错误信息必须确定(否则同一个坏声明在不同进程里 // 报不同的键,排障与测试都会被搞乱)。 keys := make([]string, 0, len(fields)) for k := range fields { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { if k != whenKeyEnabled { return fmt.Errorf("%w: %q", ErrUnknownWhenKey, k) } } rawEnabled, ok := fields[whenKeyEnabled] if !ok { w.Enabled = nil return nil } var enabled bool if err := json.Unmarshal(rawEnabled, &enabled); err != nil { return fmt.Errorf("%w: enabled must be a boolean: %w", ErrInvalidWhen, err) } w.Enabled = &enabled return nil }