Files
larksuite-cli/shortcuts/base/base_role_common.go

122 lines
3.7 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package base
import (
"encoding/json"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
// handleRoleResponse parses the role API response.
// The response has two layers of code/message:
// - Outer: SDK-level code/msg (handled by DoAPI for transport errors)
// - Inner: business-level code/message inside the data object
//
// The data field may be a JSON object (actual behavior) or a JSON string (per doc).
func handleRoleAPIResponse(runtime *common.RuntimeContext, apiResp *larkcore.ApiResp, action string) error {
if _, err := runtime.ClassifyAPIResponse(apiResp); err != nil {
enriched := enrichBaseAPIErrorFromBody(err, apiResp.RawBody, runtime.APIClassifyContext())
return prefixRoleActionError(enriched, action)
}
return handleRoleResponse(runtime, apiResp.RawBody, action)
}
func handleRoleResponse(runtime *common.RuntimeContext, rawBody []byte, action string) error {
var resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(rawBody, &resp); err != nil {
return errs.NewInternalError(errs.SubtypeInvalidResponse, "%s: failed to parse response: %v", action, err).WithCause(err)
}
if resp.Code != 0 {
result := map[string]interface{}{"code": resp.Code, "msg": resp.Msg}
if len(resp.Data) > 0 {
var data interface{}
if json.Unmarshal(resp.Data, &data) == nil {
result["data"] = data
}
}
return baseRoleAPIError(runtime, result, action)
}
if len(resp.Data) == 0 || string(resp.Data) == "null" || string(resp.Data) == `""` {
runtime.Out(map[string]any{"success": true}, nil)
return nil
}
// Parse data
var data any
if err := json.Unmarshal(resp.Data, &data); err != nil {
runtime.Out(map[string]any{"data": string(resp.Data)}, nil)
return nil
}
// If data is a string (double-encoded JSON), try to parse it
if s, ok := data.(string); ok && s != "" {
var inner any
if err := json.Unmarshal([]byte(s), &inner); err == nil {
data = inner
}
}
// Check for business-level error: data may contain its own code/message
if m, ok := data.(map[string]any); ok {
if code, exists := m["code"]; exists {
var codeInt int
switch v := code.(type) {
case float64:
codeInt = int(v)
case int:
codeInt = v
}
if codeInt != 0 {
msg, _ := m["message"].(string)
result := map[string]interface{}{"code": codeInt, "msg": msg, "data": m}
return baseRoleAPIError(runtime, result, action)
}
// code == 0, extract the inner data if present
if innerData, hasInner := m["data"]; hasInner {
// Inner data might be a double-encoded JSON string
if s, ok := innerData.(string); ok && s != "" {
var parsed any
if err := json.Unmarshal([]byte(s), &parsed); err == nil {
runtime.Out(parsed, nil)
return nil
}
}
runtime.Out(innerData, nil)
return nil
}
runtime.Out(map[string]any{"success": true}, nil)
return nil
}
}
runtime.Out(data, nil)
return nil
}
func baseRoleAPIError(runtime *common.RuntimeContext, result map[string]interface{}, action string) error {
return prefixRoleActionError(baseAPIErrorFromResult(result, runtime.APIClassifyContext()), action)
}
// prefixRoleActionError prepends the failed role action ("create role failed",
// "get role failed", ...) to a typed error's message so both the classified
// outer-response path and the parsed-body path carry the same context.
func prefixRoleActionError(err error, action string) error {
if err == nil {
return nil
}
if p, ok := errs.ProblemOf(err); ok && action != "" {
p.Message = action + ": " + p.Message
}
return err
}