974 lines
43 KiB
Go
974 lines
43 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 personal
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/event/transport"
|
|
)
|
|
|
|
// MessageEventOutput is the stable business-facing output for personal
|
|
// message receive events. Schema output is generated from these tags so the
|
|
// documented fields cannot drift from the values written by consume.
|
|
type MessageEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
MessageID string `json:"message_id" description:"开放消息 ID" format:"open_message_id"`
|
|
ConversationID string `json:"conversation_id" description:"会话 ID" format:"open_conversation_id"`
|
|
Sender string `json:"sender" description:"发送人展示名"`
|
|
SenderOpenDingTalkID string `json:"sender_open_dingtalk_id" description:"发送人开放 ID" format:"open_dingtalk_id"`
|
|
Content string `json:"content" description:"消息正文"`
|
|
CreateTime string `json:"create_time" description:"消息创建时间"`
|
|
EventTime int64 `json:"event_time" description:"消息事件时间戳" format:"timestamp_ms"`
|
|
QuotedMessage *MessageEventContext `json:"quoted_message,omitempty" description:"引用回复所引用的原消息;非引用回复时不输出"`
|
|
ForwardMessages []MessageEventContext `json:"forward_messages,omitempty" description:"合并转发包含的原消息列表;非合并转发时不输出"`
|
|
}
|
|
|
|
// MessageEventContext preserves the business context nested under a quoted
|
|
// reply or merged-forward message. Keep these fields structured instead of
|
|
// parsing the localized outer content summary.
|
|
type MessageEventContext struct {
|
|
MessageID string `json:"message_id" description:"内部消息的开放消息 ID" format:"open_message_id"`
|
|
ConversationID string `json:"conversation_id" description:"内部消息原来所在的会话 ID" format:"open_conversation_id"`
|
|
Sender string `json:"sender" description:"内部消息发送人展示名;服务端未提供时可能为空或为 null 字符串"`
|
|
SenderOpenDingTalkID string `json:"sender_open_dingtalk_id" description:"内部消息发送人开放 ID;服务端未提供时为空" format:"open_dingtalk_id"`
|
|
Content string `json:"content" description:"内部消息正文;媒体消息可能包含 mediaId 等下载定位信息"`
|
|
CreateTime string `json:"create_time" description:"内部消息创建时间"`
|
|
}
|
|
|
|
type ReadEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
MessageID string `json:"message_id" description:"被读取消息的开放消息 ID" format:"open_message_id"`
|
|
ConversationID string `json:"conversation_id" description:"会话 ID" format:"open_conversation_id"`
|
|
Reader string `json:"reader" description:"读取消息的用户展示名"`
|
|
ReaderOpenDingTalkID string `json:"reader_open_dingtalk_id" description:"读取消息的用户开放 ID" format:"open_dingtalk_id"`
|
|
Sender string `json:"sender" description:"原消息发送人展示名"`
|
|
SenderOpenDingTalkID string `json:"sender_open_dingtalk_id" description:"原消息发送人开放 ID" format:"open_dingtalk_id"`
|
|
ReadTime string `json:"read_time" description:"消息读取时间"`
|
|
EventTime int64 `json:"event_time" description:"消息事件时间戳" format:"timestamp_ms"`
|
|
}
|
|
|
|
type RecallEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
MessageID string `json:"message_id" description:"被撤回消息的开放消息 ID" format:"open_message_id"`
|
|
ConversationID string `json:"conversation_id" description:"会话 ID" format:"open_conversation_id"`
|
|
Recaller string `json:"recaller" description:"撤回消息的用户展示名"`
|
|
RecallerOpenDingTalkID string `json:"recaller_open_dingtalk_id" description:"撤回消息的用户开放 ID" format:"open_dingtalk_id"`
|
|
Sender string `json:"sender" description:"原消息发送人展示名"`
|
|
SenderOpenDingTalkID string `json:"sender_open_dingtalk_id" description:"原消息发送人开放 ID" format:"open_dingtalk_id"`
|
|
RecallTime string `json:"recall_time" description:"消息撤回时间"`
|
|
EventTime int64 `json:"event_time" description:"消息事件时间戳" format:"timestamp_ms"`
|
|
}
|
|
|
|
type ReactionEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
MessageID string `json:"message_id" description:"收到表情回应的开放消息 ID" format:"open_message_id"`
|
|
ConversationID string `json:"conversation_id" description:"会话 ID" format:"open_conversation_id"`
|
|
Operator string `json:"operator" description:"执行表情回应操作的用户展示名"`
|
|
OperatorOpenDingTalkID string `json:"operator_open_dingtalk_id" description:"执行表情回应操作的用户开放 ID" format:"open_dingtalk_id"`
|
|
ReactionName string `json:"reaction_name" description:"表情回应名称"`
|
|
ReactionText string `json:"reaction_text" description:"表情回应文本"`
|
|
OperationType string `json:"operation_type" description:"表情回应操作类型"`
|
|
OperationTime string `json:"operation_time" description:"表情回应操作时间"`
|
|
Sender string `json:"sender" description:"原消息发送人展示名"`
|
|
SenderOpenDingTalkID string `json:"sender_open_dingtalk_id" description:"原消息发送人开放 ID" format:"open_dingtalk_id"`
|
|
EventTime int64 `json:"event_time" description:"消息事件时间戳" format:"timestamp_ms"`
|
|
}
|
|
|
|
type baseEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
}
|
|
|
|
// GroupLifecycleEventOutput is intentionally conservative until stable group
|
|
// event payload samples are available. Payload keeps unknown business fields
|
|
// while transport identity and routing metadata remain available only in raw
|
|
// output.
|
|
type GroupLifecycleEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
Payload map[string]any `json:"payload" description:"群生命周期事件业务数据,字段以服务端实际推送为准" additional_properties:"true"`
|
|
}
|
|
|
|
type OAApprovalTaskCreatedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
TaskID string `json:"task_id" description:"审批任务 ID"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批任务状态"`
|
|
CreateTime int64 `json:"create_time" description:"审批任务创建时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批任务事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalTaskFinishedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
TaskID string `json:"task_id" description:"审批任务 ID"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批任务状态"`
|
|
Result string `json:"result" description:"审批任务处理结果,值以服务端实际推送为准"`
|
|
CreateTime int64 `json:"create_time" description:"审批任务创建时间" format:"timestamp_ms"`
|
|
FinishTime int64 `json:"finish_time" description:"审批任务完成时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批任务事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalTaskRedirectedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
TaskID string `json:"task_id" description:"原审批任务 ID"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"原审批任务状态"`
|
|
Result string `json:"result" description:"审批任务转交结果,值以服务端实际推送为准"`
|
|
CreateTime int64 `json:"create_time" description:"原审批任务创建时间" format:"timestamp_ms"`
|
|
FinishTime int64 `json:"finish_time" description:"原审批任务转交完成时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批任务转交事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalInstanceStartedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批实例状态"`
|
|
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批实例事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalInstanceCCOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批实例到达抄送节点时的状态"`
|
|
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批抄送事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalInstanceTerminatedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批实例状态"`
|
|
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
|
FinishTime int64 `json:"finish_time" description:"审批实例终止时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批实例终止事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type OAApprovalInstanceFinishedOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
|
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
|
Title string `json:"title" description:"审批标题"`
|
|
Status string `json:"status" description:"审批实例状态"`
|
|
Result string `json:"result" description:"审批实例处理结果,值以服务端实际推送为准"`
|
|
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
|
FinishTime int64 `json:"finish_time" description:"审批实例完成时间" format:"timestamp_ms"`
|
|
EventTime int64 `json:"event_time" description:"审批实例事件业务时间" format:"timestamp_ms"`
|
|
}
|
|
|
|
type GroupMemberEventOutput struct {
|
|
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
|
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
|
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
|
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
|
ConversationID string `json:"conversation_id" description:"发生成员变更的群会话 ID" format:"open_conversation_id"`
|
|
Operator string `json:"operator" description:"执行成员变更操作的用户展示名,系统操作或成员自行退出时可能为空"`
|
|
OperatorOpenDingTalkID string `json:"operator_open_dingtalk_id" description:"执行成员变更操作的用户开放 ID,系统操作或成员自行退出时可能为空" format:"open_dingtalk_id"`
|
|
Members []GroupMemberEventMember `json:"members" description:"本次加入或退出的成员列表"`
|
|
EventTime int64 `json:"event_time" description:"群成员变更事件时间戳" format:"timestamp_ms"`
|
|
}
|
|
|
|
type GroupMemberEventMember struct {
|
|
Nick string `json:"nick" description:"成员展示名"`
|
|
OpenDingTalkID string `json:"open_dingtalk_id" description:"成员开放 ID" format:"open_dingtalk_id"`
|
|
}
|
|
|
|
type personalEventData struct {
|
|
EventID string `json:"eventId"`
|
|
EventKey string `json:"eventKey"`
|
|
OccurredAtMS int64 `json:"occurredAtMs"`
|
|
SubID string `json:"subId"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
}
|
|
|
|
type personalMessagePayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body struct {
|
|
CreateTime string `json:"createTime"`
|
|
Sender string `json:"sender"`
|
|
OpenMessageID string `json:"openMessageId"`
|
|
SenderOpenDingTalkID string `json:"senderOpenDingTalkId"`
|
|
OpenConversationID string `json:"openConversationId"`
|
|
Content string `json:"content"`
|
|
QuotedMessage *personalMessageContext `json:"quotedMessage"`
|
|
ForwardMessages []personalMessageContext `json:"forwardMessages"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
type personalMessageContext struct {
|
|
CreateTime string `json:"createTime"`
|
|
Sender string `json:"sender"`
|
|
OpenMessageID string `json:"openMessageId"`
|
|
SenderOpenDingTalkID string `json:"senderOpenDingTalkId"`
|
|
OpenConversationID string `json:"openConversationId"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type personalReadPayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body struct {
|
|
MessageID string `json:"openMessageId"`
|
|
ConversationID string `json:"openConversationId"`
|
|
Reader string `json:"reader"`
|
|
ReaderOpenDingTalkID string `json:"readerOpenDingTalkId"`
|
|
Sender string `json:"sender"`
|
|
SenderOpenDingTalkID string `json:"senderOpenDingTalkId"`
|
|
ReadTime string `json:"msgReadTime"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
type personalRecallPayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body struct {
|
|
MessageID string `json:"openMessageId"`
|
|
ConversationID string `json:"openConversationId"`
|
|
Recaller string `json:"recaller"`
|
|
RecallerOpenDingTalkID string `json:"recallerOpenDingTalkId"`
|
|
Sender string `json:"sender"`
|
|
SenderOpenDingTalkID string `json:"senderOpenDingTalkId"`
|
|
RecallTime string `json:"msgRecallTime"`
|
|
} `json:"body"`
|
|
}
|
|
|
|
type personalReactionPayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body personalReactionBody `json:"body"`
|
|
}
|
|
|
|
type personalReactionBody struct {
|
|
MessageID string `json:"openSourceMessageId"`
|
|
ConversationID string `json:"openConversationId"`
|
|
Operator string `json:"oper"`
|
|
OperatorOpenDingTalkID string `json:"-"`
|
|
ReactionName string `json:"emotionName"`
|
|
ReactionText string `json:"emotionText"`
|
|
OperationType string `json:"operateType"`
|
|
OperationTime string `json:"operateTime"`
|
|
Sender string `json:"sender"`
|
|
SenderOpenDingTalkID string `json:"senderOpenDingTalkId"`
|
|
}
|
|
|
|
type personalGroupMemberPayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body personalGroupMemberBody `json:"body"`
|
|
}
|
|
|
|
type personalGroupMemberBody struct {
|
|
ConversationID string `json:"openConversationId"`
|
|
Operator string `json:"operNick"`
|
|
OperatorOpenDingTalkID string `json:"-"`
|
|
Members []personalGroupMemberRecord `json:"members"`
|
|
}
|
|
|
|
type personalGroupMemberRecord struct {
|
|
Nick string `json:"nick"`
|
|
OpenDingTalkID string `json:"openDingTalkId"`
|
|
}
|
|
|
|
type personalOAApprovalPayload struct {
|
|
EventTime int64 `json:"event_time"`
|
|
Body personalOAApprovalBody `json:"body"`
|
|
}
|
|
|
|
type personalOAApprovalBody struct {
|
|
ProcessInstanceID string `json:"processInstanceId"`
|
|
ProcessCode string `json:"processCode"`
|
|
TaskID string `json:"taskId"`
|
|
Title string `json:"title"`
|
|
Status string `json:"status"`
|
|
Result string `json:"result"`
|
|
CreateTime int64 `json:"createTime"`
|
|
FinishTime int64 `json:"finishTime"`
|
|
}
|
|
|
|
func (b *personalReactionBody) UnmarshalJSON(data []byte) error {
|
|
// encoding/json otherwise falls back to case-insensitive field matching.
|
|
// Read this protocol field from a map so only operOpenDingtalkId is accepted.
|
|
type bodyAlias personalReactionBody
|
|
var decoded bodyAlias
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
return err
|
|
}
|
|
|
|
var fields map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &fields); err != nil {
|
|
return err
|
|
}
|
|
if raw, ok := fields["operOpenDingtalkId"]; ok {
|
|
if err := json.Unmarshal(raw, &decoded.OperatorOpenDingTalkID); err != nil {
|
|
return fmt.Errorf("decode operOpenDingtalkId: %w", err)
|
|
}
|
|
}
|
|
|
|
*b = personalReactionBody(decoded)
|
|
return nil
|
|
}
|
|
|
|
func (b *personalGroupMemberBody) UnmarshalJSON(data []byte) error {
|
|
// Keep the protocol spelling strict: encoding/json would otherwise accept
|
|
// operOpenDingTalkId through case-insensitive fallback matching.
|
|
var fields map[string]json.RawMessage
|
|
if err := json.Unmarshal(data, &fields); err != nil {
|
|
return err
|
|
}
|
|
|
|
type bodyAlias personalGroupMemberBody
|
|
var decoded bodyAlias
|
|
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
return err
|
|
}
|
|
if raw, ok := fields["operOpenDingtalkId"]; ok {
|
|
if err := json.Unmarshal(raw, &decoded.OperatorOpenDingTalkID); err != nil {
|
|
return fmt.Errorf("decode operOpenDingtalkId: %w", err)
|
|
}
|
|
}
|
|
|
|
*b = personalGroupMemberBody(decoded)
|
|
return nil
|
|
}
|
|
|
|
// ProjectOutput converts the transport envelope into the stable personal
|
|
// event output. On malformed Data it returns the original envelope together
|
|
// with an error; the formatter logs the warning and still emits that envelope.
|
|
func ProjectOutput(ev transport.Event) (any, error) {
|
|
data, err := decodePersonalEventData(ev.Data)
|
|
if err != nil {
|
|
return ev, fmt.Errorf("decode personal event data: %w", err)
|
|
}
|
|
|
|
eventType := firstNonEmptyOutput(ev.EventType, data.EventKey)
|
|
eventID := firstNonEmptyOutput(data.EventID, ev.EventID)
|
|
timestamp := data.OccurredAtMS
|
|
if timestamp == 0 {
|
|
timestamp = ev.EventBornTime
|
|
}
|
|
subscribeID := firstNonEmptyOutput(ev.SubscribeID, data.SubID)
|
|
|
|
if isMessageReceiveEvent(eventType) {
|
|
var payload personalMessagePayload
|
|
if err := decodeRequiredPayload(data.Payload, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal message payload: %w", err)
|
|
}
|
|
var quotedMessage *MessageEventContext
|
|
if payload.Body.QuotedMessage != nil {
|
|
projected := projectMessageEventContext(*payload.Body.QuotedMessage)
|
|
quotedMessage = &projected
|
|
}
|
|
var forwardMessages []MessageEventContext
|
|
if len(payload.Body.ForwardMessages) > 0 {
|
|
forwardMessages = make([]MessageEventContext, 0, len(payload.Body.ForwardMessages))
|
|
for _, message := range payload.Body.ForwardMessages {
|
|
forwardMessages = append(forwardMessages, projectMessageEventContext(message))
|
|
}
|
|
}
|
|
return MessageEventOutput{
|
|
Type: eventType,
|
|
EventID: eventID,
|
|
Timestamp: timestamp,
|
|
SubscribeID: subscribeID,
|
|
MessageID: payload.Body.OpenMessageID,
|
|
ConversationID: payload.Body.OpenConversationID,
|
|
Sender: payload.Body.Sender,
|
|
SenderOpenDingTalkID: payload.Body.SenderOpenDingTalkID,
|
|
Content: payload.Body.Content,
|
|
CreateTime: payload.Body.CreateTime,
|
|
EventTime: payload.EventTime,
|
|
QuotedMessage: quotedMessage,
|
|
ForwardMessages: forwardMessages,
|
|
}, nil
|
|
}
|
|
|
|
base := baseEventOutput{
|
|
Type: eventType,
|
|
EventID: eventID,
|
|
Timestamp: timestamp,
|
|
SubscribeID: subscribeID,
|
|
}
|
|
switch {
|
|
case isReadEvent(eventType):
|
|
return projectReadEvent(ev, base, data.Payload)
|
|
case isRecallEvent(eventType):
|
|
return projectRecallEvent(ev, base, data.Payload)
|
|
case isReactionEvent(eventType):
|
|
return projectReactionEvent(ev, base, data.Payload)
|
|
case isGroupMemberEvent(eventType):
|
|
return projectGroupMemberEvent(ev, base, data.Payload)
|
|
case isGroupLifecycleEvent(eventType):
|
|
payload, err := decodeConservativePayload(data.Payload)
|
|
if err != nil {
|
|
return ev, fmt.Errorf("decode personal group lifecycle payload: %w", err)
|
|
}
|
|
return GroupLifecycleEventOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
Payload: payload,
|
|
}, nil
|
|
case isOAEvent(eventType):
|
|
return projectOAApprovalEvent(ev, base, data.Payload)
|
|
default:
|
|
return ev, fmt.Errorf("unsupported personal event type %q", eventType)
|
|
}
|
|
}
|
|
|
|
func projectMessageEventContext(message personalMessageContext) MessageEventContext {
|
|
return MessageEventContext{
|
|
MessageID: message.OpenMessageID,
|
|
ConversationID: message.OpenConversationID,
|
|
Sender: message.Sender,
|
|
SenderOpenDingTalkID: message.SenderOpenDingTalkID,
|
|
Content: message.Content,
|
|
CreateTime: message.CreateTime,
|
|
}
|
|
}
|
|
|
|
func decodeConservativePayload(raw json.RawMessage) (map[string]any, error) {
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
|
return nil, fmt.Errorf("payload is missing")
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(trimmed, &payload); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(payload) == 0 {
|
|
return nil, fmt.Errorf("payload is empty")
|
|
}
|
|
for key := range payload {
|
|
switch strings.ToLower(key) {
|
|
case "uid", "corpid", "clientid", "filtersubid", "bizid", "orgid", "sourceid":
|
|
delete(payload, key)
|
|
}
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func projectReadEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
|
var payload personalReadPayload
|
|
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal read payload: %w", err)
|
|
}
|
|
return ReadEventOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
MessageID: payload.Body.MessageID,
|
|
ConversationID: payload.Body.ConversationID,
|
|
Reader: payload.Body.Reader,
|
|
ReaderOpenDingTalkID: payload.Body.ReaderOpenDingTalkID,
|
|
Sender: payload.Body.Sender,
|
|
SenderOpenDingTalkID: payload.Body.SenderOpenDingTalkID,
|
|
ReadTime: payload.Body.ReadTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
}
|
|
|
|
func projectRecallEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
|
var payload personalRecallPayload
|
|
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal recall payload: %w", err)
|
|
}
|
|
return RecallEventOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
MessageID: payload.Body.MessageID,
|
|
ConversationID: payload.Body.ConversationID,
|
|
Recaller: payload.Body.Recaller,
|
|
RecallerOpenDingTalkID: payload.Body.RecallerOpenDingTalkID,
|
|
Sender: payload.Body.Sender,
|
|
SenderOpenDingTalkID: payload.Body.SenderOpenDingTalkID,
|
|
RecallTime: payload.Body.RecallTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
}
|
|
|
|
func projectReactionEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
|
var payload personalReactionPayload
|
|
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal reaction payload: %w", err)
|
|
}
|
|
return ReactionEventOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
MessageID: payload.Body.MessageID,
|
|
ConversationID: payload.Body.ConversationID,
|
|
Operator: payload.Body.Operator,
|
|
OperatorOpenDingTalkID: payload.Body.OperatorOpenDingTalkID,
|
|
ReactionName: payload.Body.ReactionName,
|
|
ReactionText: payload.Body.ReactionText,
|
|
OperationType: payload.Body.OperationType,
|
|
OperationTime: payload.Body.OperationTime,
|
|
Sender: payload.Body.Sender,
|
|
SenderOpenDingTalkID: payload.Body.SenderOpenDingTalkID,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
}
|
|
|
|
func projectGroupMemberEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
|
var payload personalGroupMemberPayload
|
|
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal group member payload: %w", err)
|
|
}
|
|
if strings.TrimSpace(payload.Body.ConversationID) == "" {
|
|
return ev, fmt.Errorf("decode personal group member payload: openConversationId is required")
|
|
}
|
|
if len(payload.Body.Members) == 0 {
|
|
return ev, fmt.Errorf("decode personal group member payload: members is required")
|
|
}
|
|
|
|
members := make([]GroupMemberEventMember, 0, len(payload.Body.Members))
|
|
for _, member := range payload.Body.Members {
|
|
members = append(members, GroupMemberEventMember{
|
|
Nick: member.Nick,
|
|
OpenDingTalkID: member.OpenDingTalkID,
|
|
})
|
|
}
|
|
return GroupMemberEventOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ConversationID: payload.Body.ConversationID,
|
|
Operator: payload.Body.Operator,
|
|
OperatorOpenDingTalkID: payload.Body.OperatorOpenDingTalkID,
|
|
Members: members,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
}
|
|
|
|
func projectOAApprovalEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
|
var payload personalOAApprovalPayload
|
|
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
|
return ev, fmt.Errorf("decode personal OA payload: %w", err)
|
|
}
|
|
if strings.TrimSpace(payload.Body.ProcessInstanceID) == "" {
|
|
return ev, fmt.Errorf("decode personal OA payload: processInstanceId is required")
|
|
}
|
|
if isOAApprovalTaskEvent(base.Type) && strings.TrimSpace(payload.Body.TaskID) == "" {
|
|
return ev, fmt.Errorf("decode personal OA payload: taskId is required for %s", base.Type)
|
|
}
|
|
|
|
switch base.Type {
|
|
case EventOAApprovalTaskCreated:
|
|
return OAApprovalTaskCreatedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
TaskID: payload.Body.TaskID,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
CreateTime: payload.Body.CreateTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalTaskFinished:
|
|
return OAApprovalTaskFinishedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
TaskID: payload.Body.TaskID,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
Result: payload.Body.Result,
|
|
CreateTime: payload.Body.CreateTime,
|
|
FinishTime: payload.Body.FinishTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalTaskRedirected:
|
|
return OAApprovalTaskRedirectedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
TaskID: payload.Body.TaskID,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
Result: payload.Body.Result,
|
|
CreateTime: payload.Body.CreateTime,
|
|
FinishTime: payload.Body.FinishTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalInstanceStarted:
|
|
return OAApprovalInstanceStartedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
CreateTime: payload.Body.CreateTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalInstanceCC:
|
|
return OAApprovalInstanceCCOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
CreateTime: payload.Body.CreateTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalInstanceTerminated:
|
|
return OAApprovalInstanceTerminatedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
CreateTime: payload.Body.CreateTime,
|
|
FinishTime: payload.Body.FinishTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
case EventOAApprovalInstanceFinished:
|
|
return OAApprovalInstanceFinishedOutput{
|
|
Type: base.Type,
|
|
EventID: base.EventID,
|
|
Timestamp: base.Timestamp,
|
|
SubscribeID: base.SubscribeID,
|
|
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
|
ProcessCode: payload.Body.ProcessCode,
|
|
Title: payload.Body.Title,
|
|
Status: payload.Body.Status,
|
|
Result: payload.Body.Result,
|
|
CreateTime: payload.Body.CreateTime,
|
|
FinishTime: payload.Body.FinishTime,
|
|
EventTime: payload.EventTime,
|
|
}, nil
|
|
default:
|
|
return ev, fmt.Errorf("unsupported personal OA event type %q", base.Type)
|
|
}
|
|
}
|
|
|
|
func decodeRequiredPayload(raw json.RawMessage, target any) error {
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
|
return fmt.Errorf("payload is missing")
|
|
}
|
|
|
|
var payloadObject map[string]json.RawMessage
|
|
if err := json.Unmarshal(trimmed, &payloadObject); err != nil {
|
|
return err
|
|
}
|
|
if len(payloadObject) == 0 {
|
|
return fmt.Errorf("payload is empty")
|
|
}
|
|
|
|
body, ok := payloadObject["body"]
|
|
if !ok {
|
|
return fmt.Errorf("payload body is missing")
|
|
}
|
|
body = bytes.TrimSpace(body)
|
|
if len(body) == 0 || bytes.Equal(body, []byte("null")) {
|
|
return fmt.Errorf("payload body is missing")
|
|
}
|
|
var bodyObject map[string]json.RawMessage
|
|
if err := json.Unmarshal(body, &bodyObject); err != nil {
|
|
return fmt.Errorf("decode payload body: %w", err)
|
|
}
|
|
if len(bodyObject) == 0 {
|
|
return fmt.Errorf("payload body is empty")
|
|
}
|
|
|
|
return json.Unmarshal(trimmed, target)
|
|
}
|
|
|
|
func decodePersonalEventData(raw string) (personalEventData, error) {
|
|
encoded := []byte(strings.TrimSpace(raw))
|
|
if len(encoded) == 0 {
|
|
return personalEventData{}, fmt.Errorf("empty data")
|
|
}
|
|
|
|
// Some gateways wrap the JSON object in one or more JSON strings. Peel
|
|
// those wrappers without changing the raw transport envelope.
|
|
for depth := 0; depth < 2; depth++ {
|
|
var quoted string
|
|
if err := json.Unmarshal(encoded, "ed); err != nil {
|
|
break
|
|
}
|
|
encoded = []byte(strings.TrimSpace(quoted))
|
|
}
|
|
|
|
var data personalEventData
|
|
if err := json.Unmarshal(encoded, &data); err != nil {
|
|
return personalEventData{}, err
|
|
}
|
|
if data.EventKey == "" && data.EventID == "" && data.OccurredAtMS == 0 && data.SubID == "" && len(data.Payload) == 0 {
|
|
return personalEventData{}, fmt.Errorf("data is not a personal event object")
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func outputSchema(eventKey string) map[string]any {
|
|
outputType := outputTypeForEvent(eventKey)
|
|
schema := schemaForStruct(outputType)
|
|
properties := schema["properties"].(map[string]any)
|
|
if property, ok := properties["type"].(map[string]any); ok {
|
|
property["enum"] = []string{eventKey}
|
|
}
|
|
return schema
|
|
}
|
|
|
|
func schemaForStruct(t reflect.Type) map[string]any {
|
|
for t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
properties := make(map[string]any, t.NumField())
|
|
for i := 0; i < t.NumField(); i++ {
|
|
field := t.Field(i)
|
|
name := strings.Split(field.Tag.Get("json"), ",")[0]
|
|
if name == "" || name == "-" {
|
|
continue
|
|
}
|
|
property := schemaForType(field.Type)
|
|
if description := field.Tag.Get("description"); description != "" {
|
|
property["description"] = description
|
|
}
|
|
if format := field.Tag.Get("format"); format != "" {
|
|
property["format"] = format
|
|
}
|
|
if field.Tag.Get("additional_properties") == "true" {
|
|
property["additionalProperties"] = true
|
|
}
|
|
properties[name] = property
|
|
}
|
|
return map[string]any{
|
|
"type": "object",
|
|
"properties": properties,
|
|
}
|
|
}
|
|
|
|
func schemaForType(t reflect.Type) map[string]any {
|
|
for t.Kind() == reflect.Pointer {
|
|
t = t.Elem()
|
|
}
|
|
switch t.Kind() {
|
|
case reflect.Struct:
|
|
return schemaForStruct(t)
|
|
case reflect.Slice, reflect.Array:
|
|
return map[string]any{
|
|
"type": "array",
|
|
"items": schemaForType(t.Elem()),
|
|
}
|
|
default:
|
|
return map[string]any{"type": schemaType(t)}
|
|
}
|
|
}
|
|
|
|
func transportEnvelopeSchema(eventKey string) map[string]any {
|
|
eventType := reflect.TypeOf(transport.Event{})
|
|
properties := make(map[string]any, eventType.NumField())
|
|
for i := 0; i < eventType.NumField(); i++ {
|
|
field := eventType.Field(i)
|
|
name := strings.Split(field.Tag.Get("json"), ",")[0]
|
|
property := map[string]any{"type": schemaType(field.Type)}
|
|
switch name {
|
|
case "type":
|
|
property["description"] = "transport frame 类型"
|
|
property["enum"] = []string{string(transport.FrameTypeEvent)}
|
|
case "event_type":
|
|
property["description"] = "事件类型"
|
|
property["enum"] = []string{eventKey}
|
|
case "data":
|
|
property["description"] = "服务端业务 payload JSON 字符串"
|
|
property["content_media_type"] = "application/json"
|
|
case "headers":
|
|
property["description"] = "Stream transport headers"
|
|
property["additionalProperties"] = map[string]any{"type": "string"}
|
|
case "event_id":
|
|
property["description"] = "transport 事件 ID"
|
|
case "subscribe_id":
|
|
property["description"] = "个人事件订阅 ID"
|
|
}
|
|
properties[name] = property
|
|
}
|
|
return map[string]any{
|
|
"type": "object",
|
|
"properties": properties,
|
|
}
|
|
}
|
|
|
|
func outputTypeForEvent(eventKey string) reflect.Type {
|
|
switch {
|
|
case isMessageReceiveEvent(eventKey):
|
|
return reflect.TypeOf(MessageEventOutput{})
|
|
case isReadEvent(eventKey):
|
|
return reflect.TypeOf(ReadEventOutput{})
|
|
case isRecallEvent(eventKey):
|
|
return reflect.TypeOf(RecallEventOutput{})
|
|
case isReactionEvent(eventKey):
|
|
return reflect.TypeOf(ReactionEventOutput{})
|
|
case isGroupMemberEvent(eventKey):
|
|
return reflect.TypeOf(GroupMemberEventOutput{})
|
|
case isGroupLifecycleEvent(eventKey):
|
|
return reflect.TypeOf(GroupLifecycleEventOutput{})
|
|
case eventKey == EventOAApprovalTaskCreated:
|
|
return reflect.TypeOf(OAApprovalTaskCreatedOutput{})
|
|
case eventKey == EventOAApprovalTaskFinished:
|
|
return reflect.TypeOf(OAApprovalTaskFinishedOutput{})
|
|
case eventKey == EventOAApprovalTaskRedirected:
|
|
return reflect.TypeOf(OAApprovalTaskRedirectedOutput{})
|
|
case eventKey == EventOAApprovalInstanceStarted:
|
|
return reflect.TypeOf(OAApprovalInstanceStartedOutput{})
|
|
case eventKey == EventOAApprovalInstanceCC:
|
|
return reflect.TypeOf(OAApprovalInstanceCCOutput{})
|
|
case eventKey == EventOAApprovalInstanceTerminated:
|
|
return reflect.TypeOf(OAApprovalInstanceTerminatedOutput{})
|
|
case eventKey == EventOAApprovalInstanceFinished:
|
|
return reflect.TypeOf(OAApprovalInstanceFinishedOutput{})
|
|
default:
|
|
return reflect.TypeOf(baseEventOutput{})
|
|
}
|
|
}
|
|
|
|
func isReadEvent(eventKey string) bool {
|
|
return eventKey == EventReadO2O || eventKey == EventReadGroup
|
|
}
|
|
|
|
func isRecallEvent(eventKey string) bool {
|
|
return eventKey == EventRecallO2O || eventKey == EventRecallGroup
|
|
}
|
|
|
|
func isReactionEvent(eventKey string) bool {
|
|
return eventKey == EventReactionO2O || eventKey == EventReactionGroup
|
|
}
|
|
|
|
func isGroupMemberEvent(eventKey string) bool {
|
|
return eventKey == EventGroupMemberAdded || eventKey == EventGroupMemberExited
|
|
}
|
|
|
|
func isGroupLifecycleEvent(eventKey string) bool {
|
|
return eventKey == EventGroupUpdated ||
|
|
eventKey == EventGroupDisbanded
|
|
}
|
|
|
|
func isOAEvent(eventKey string) bool {
|
|
return eventKey == EventOAApprovalTaskCreated ||
|
|
eventKey == EventOAApprovalTaskFinished ||
|
|
eventKey == EventOAApprovalTaskRedirected ||
|
|
eventKey == EventOAApprovalInstanceStarted ||
|
|
eventKey == EventOAApprovalInstanceCC ||
|
|
eventKey == EventOAApprovalInstanceTerminated ||
|
|
eventKey == EventOAApprovalInstanceFinished
|
|
}
|
|
|
|
func isOAApprovalTaskEvent(eventKey string) bool {
|
|
return eventKey == EventOAApprovalTaskCreated ||
|
|
eventKey == EventOAApprovalTaskFinished ||
|
|
eventKey == EventOAApprovalTaskRedirected
|
|
}
|
|
|
|
func schemaType(t reflect.Type) string {
|
|
switch t.Kind() {
|
|
case reflect.String:
|
|
return "string"
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
return "integer"
|
|
case reflect.Map, reflect.Struct:
|
|
return "object"
|
|
case reflect.Slice, reflect.Array:
|
|
return "array"
|
|
case reflect.Bool:
|
|
return "boolean"
|
|
default:
|
|
return "object"
|
|
}
|
|
}
|
|
|
|
func firstNonEmptyOutput(values ...string) string {
|
|
for _, value := range values {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|