* dws event * fix consume fail * test: add stream ticket injection probe * feat: add portal ticket stream mode * user event * fix: allow portal ticket normal without app secret * event * user event * eventType filter * refactor(event): 优化IPC端点路径处理和改进相关测试 - 用dwsevent.IPCEndpoint替代原先根据GOOS判断的路径逻辑 - 新增event包实现Unix socket路径长度限制及长路径fallback机制 - 添加endpoint_test.go覆盖路径短长及唯一性的单元测试 - 修改busctl模块使用统一的IPC端点获取方法,避免重复实现 - transport_unix.go新增checkSocketPath函数检查路径长度,防止EINVAL错误 - 在监听和连接Unix socket时加入路径限制检查,提升错误明晰度 - 去除多个文件中无用的runtime导入,简化代码依赖 * opt * event skill * default value * install script event * fix: remove subscribe id event fanout filter * fix(personal): 修正指定发送人消息描述错误 * more im event * filter subId * fix: align personal event schema with stream payload * fix: avoid duplicate app helper name * feat: simplify personal event schemas * feat: simplify event schema output * docs: refine dingtalk event skill references * feat: align personal event consume flags * fix event stop and status visibility * hide app event public entrypoints * hide incomplete personal sender event * remove external event reference comments * chore(event): prepare official release * fix(event): harden personal stream lifecycle --------- Co-authored-by: 玉澜 <yulan.wqy@alibaba-inc.com> Co-authored-by: zhengyubai <zhengyubai618@gmail.com>
158 lines
5.5 KiB
Go
158 lines
5.5 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package consume
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/registry"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/transport"
|
|
)
|
|
|
|
// Format identifies the wire shape `dws event consume` writes per event.
|
|
// Values mirror dws's global -f/--format flag vocabulary (defined in
|
|
// internal/output) with the subset that makes sense for streaming.
|
|
type Format string
|
|
|
|
const (
|
|
// FormatNDJSON is the default: one compact JSON object per line.
|
|
// Pipe-friendly; one event per `read` line. Recommended for agents.
|
|
FormatNDJSON Format = "ndjson"
|
|
// FormatJSON pretty-prints each event as multi-line JSON. NOT a
|
|
// JSON array — still NDJSON-style (one document per output unit),
|
|
// just with indentation. See plan §3.1 输出约束 note about why we
|
|
// do not emit a JSON array for an unbounded stream.
|
|
FormatJSON Format = "json"
|
|
// FormatPretty is the same as FormatJSON in v1; reserved for future
|
|
// human-friendly colorisation. Kept distinct so we never silently
|
|
// degrade `--format pretty` to compact-ndjson.
|
|
FormatPretty Format = "pretty"
|
|
// FormatRaw writes only the SDK's original Data string (one per
|
|
// event, newline-terminated). Useful when piping into jq / a tool
|
|
// that wants the cloud payload verbatim without our envelope.
|
|
FormatRaw Format = "raw"
|
|
// FormatCompact runs the per-event-type compact processor (see
|
|
// registry.LookupProcessor) and emits one flattened JSON line.
|
|
FormatCompact Format = "compact"
|
|
)
|
|
|
|
// NormalizeFormat maps a raw flag value to a supported Format. Values
|
|
// outside the event command's supported set fall back to NDJSON with the
|
|
// fallback flag set true — callers SHOULD warn on stderr when fallback is
|
|
// true and the original value was non-empty (e.g. user passed
|
|
// --format table which has no meaning for an event stream).
|
|
//
|
|
// Empty input maps to NDJSON without a fallback warning.
|
|
func NormalizeFormat(raw string) (f Format, fellback bool) {
|
|
switch raw {
|
|
case "":
|
|
return FormatNDJSON, false
|
|
case string(FormatNDJSON):
|
|
return FormatNDJSON, false
|
|
case string(FormatJSON):
|
|
return FormatJSON, false
|
|
case string(FormatPretty):
|
|
return FormatPretty, false
|
|
case string(FormatRaw):
|
|
return FormatRaw, false
|
|
case string(FormatCompact):
|
|
return FormatCompact, false
|
|
default:
|
|
// Includes table/csv from the global -f vocabulary, plus any
|
|
// typo. Fall back to ndjson (the safe stream default) and let
|
|
// the caller stderr-WARN.
|
|
return FormatNDJSON, true
|
|
}
|
|
}
|
|
|
|
// Formatter renders a transport.Event into the byte stream the sink writes
|
|
// out. Implementations append their own line terminator when appropriate
|
|
// (NDJSON / Raw add '\n'; Pretty/JSON embed newlines in the JSON itself).
|
|
type Formatter interface {
|
|
Render(ev transport.Event) ([]byte, error)
|
|
}
|
|
|
|
// NewFormatter returns a Formatter for the given Format. Compact wraps
|
|
// registry.LookupProcessor so adding a new specialised compactor is just
|
|
// a registry-side change. Returns an error only if format is an internally
|
|
// unsupported value (defensive — NormalizeFormat guarantees the input is
|
|
// one of the constants).
|
|
func NewFormatter(format Format) (Formatter, error) {
|
|
switch format {
|
|
case FormatNDJSON:
|
|
return &ndjsonFormatter{}, nil
|
|
case FormatJSON, FormatPretty:
|
|
return &prettyFormatter{}, nil
|
|
case FormatRaw:
|
|
return &rawFormatter{}, nil
|
|
case FormatCompact:
|
|
return &compactFormatter{}, nil
|
|
default:
|
|
return nil, fmt.Errorf("consume: unsupported format %q", format)
|
|
}
|
|
}
|
|
|
|
// ndjsonFormatter encodes each Event as one compact JSON line + '\n'.
|
|
type ndjsonFormatter struct{}
|
|
|
|
func (ndjsonFormatter) Render(ev transport.Event) ([]byte, error) {
|
|
b, err := json.Marshal(ev)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(b, '\n'), nil
|
|
}
|
|
|
|
// prettyFormatter encodes each Event as multi-line indented JSON + '\n'.
|
|
// json.MarshalIndent does not append a trailing newline; we add one so
|
|
// successive events are visually separated in the output.
|
|
type prettyFormatter struct{}
|
|
|
|
func (prettyFormatter) Render(ev transport.Event) ([]byte, error) {
|
|
b, err := json.MarshalIndent(ev, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(b, '\n'), nil
|
|
}
|
|
|
|
// rawFormatter writes ev.Data verbatim. If Data is already JSON it stays
|
|
// JSON; if it's some other string format it stays that. A trailing newline
|
|
// is appended so successive raw events are separable.
|
|
type rawFormatter struct{}
|
|
|
|
func (rawFormatter) Render(ev transport.Event) ([]byte, error) {
|
|
out := make([]byte, 0, len(ev.Data)+1)
|
|
out = append(out, ev.Data...)
|
|
if len(ev.Data) == 0 || ev.Data[len(ev.Data)-1] != '\n' {
|
|
out = append(out, '\n')
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// compactFormatter dispatches to the registry per event_type and writes
|
|
// the flattened map as one compact JSON line + '\n'.
|
|
type compactFormatter struct{}
|
|
|
|
func (compactFormatter) Render(ev transport.Event) ([]byte, error) {
|
|
p := registry.LookupProcessor(ev.EventType)
|
|
v := p(ev)
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(b, '\n'), nil
|
|
}
|