44package base
55
66import (
7+ "errors"
8+ "fmt"
79 "strings"
810
9- "github.com/larksuite/cli/internal/output"
11+ "github.com/larksuite/cli/errs"
12+ "github.com/larksuite/cli/extension/fileio"
13+ "github.com/larksuite/cli/internal/errclass"
1014 "github.com/larksuite/cli/internal/util"
1115)
1216
@@ -24,74 +28,182 @@ func handleBaseAPIResult(result interface{}, err error, action string) (map[stri
2428// structured ErrAPI, with server-provided message/hint promoted to the top level.
2529func handleBaseAPIResultAny (result interface {}, err error , action string ) (interface {}, error ) {
2630 if err != nil {
27- return nil , output . Errorf ( output . ExitAPI , "api_error" , "%s: %s" , action , err )
31+ return nil , baseAPIBoundaryError ( err , action )
2832 }
2933
30- resultMap , _ := result .(map [string ]interface {})
34+ resultMap , ok := result .(map [string ]interface {})
35+ if ! ok || resultMap == nil {
36+ return nil , errs .NewInternalError (errs .SubtypeInvalidResponse , "%s: API returned a malformed response envelope" , action )
37+ }
38+ if _ , exists := resultMap ["code" ]; ! exists {
39+ return nil , errs .NewInternalError (errs .SubtypeInvalidResponse , "%s: API response is missing code" , action )
40+ }
3141 code , _ := util .ToFloat64 (resultMap ["code" ])
3242 if code == 0 {
3343 return resultMap ["data" ], nil
3444 }
3545
36- larkCode := int (code )
37- msg := extractDataErrorMessage (resultMap )
38- if strings .TrimSpace (msg ) == "" {
39- msg , _ = resultMap ["msg" ].(string )
46+ return nil , baseAPIErrorFromResult (resultMap , errclass.ClassifyContext {})
47+ }
48+
49+ func baseFlagErrorf (format string , args ... any ) error {
50+ msg := fmt .Sprintf (format , args ... )
51+ err := errs .NewValidationError (errs .SubtypeInvalidArgument , "%s" , msg )
52+ if param := firstFlagParam (msg ); param != "" {
53+ err = err .WithParam (param )
54+ }
55+ if cause := firstErrorArg (args ); cause != nil {
56+ err = err .WithCause (cause )
4057 }
58+ return err
59+ }
4160
42- detail := extractErrorDetail ( resultMap )
43- apiErr := output . ErrAPI ( larkCode , msg , detail )
44- hint := extractErrorHint ( resultMap )
45- if apiErr . Detail != nil && apiErr . Detail . Hint == "" && hint != "" {
46- apiErr . Detail . Hint = hint
61+ func baseValidationErrorf ( format string , args ... any ) error {
62+ msg := fmt . Sprintf ( format , args ... )
63+ err := errs . NewValidationError ( errs . SubtypeInvalidArgument , "%s" , msg )
64+ if param := firstFlagParam ( msg ); param != "" {
65+ err = err . WithParam ( param )
4766 }
48- if apiErr . Detail != nil {
49- apiErr . Detail . Detail = cleanEmptyBaseErrorDetail ( detail )
67+ if cause := firstErrorArg ( args ); cause != nil {
68+ err = err . WithCause ( cause )
5069 }
51- return nil , apiErr
70+ return err
5271}
5372
54- func cleanEmptyBaseErrorDetail (detail interface {}) interface {} {
55- detailMap , ok := detail .(map [string ]interface {})
56- if ! ok {
57- return nil
73+ func firstFlagParam (msg string ) string {
74+ idx := strings .Index (msg , "--" )
75+ if idx < 0 {
76+ return ""
77+ }
78+ end := idx + 2
79+ for end < len (msg ) {
80+ ch := msg [end ]
81+ if (ch >= 'a' && ch <= 'z' ) || (ch >= 'A' && ch <= 'Z' ) || (ch >= '0' && ch <= '9' ) || ch == '-' {
82+ end ++
83+ continue
84+ }
85+ break
5886 }
59- for key , value := range detailMap {
60- if value == nil {
61- delete (detailMap , key )
87+ if end == idx + 2 {
88+ return ""
89+ }
90+ return msg [idx :end ]
91+ }
92+
93+ func firstErrorArg (args []any ) error {
94+ for _ , arg := range args {
95+ if err , ok := arg .(error ); ok {
96+ return err
6297 }
6398 }
64- if len (detailMap ) == 0 {
99+ return nil
100+ }
101+
102+ func baseInputStatError (err error ) error {
103+ if err == nil {
65104 return nil
66105 }
67- return detailMap
106+ if errors .Is (err , fileio .ErrPathValidation ) {
107+ return errs .NewValidationError (errs .SubtypeInvalidArgument , "unsafe file path: %s" , err ).WithCause (err )
108+ }
109+ return errs .NewValidationError (errs .SubtypeInvalidArgument , "cannot read file: %s" , err ).WithCause (err )
68110}
69111
70- func extractErrorDetail ( resultMap map [ string ] interface {}) interface {} {
71- if detail , ok := nonNilMapValue ( resultMap , "error" ); ok {
72- return detail
112+ func baseSaveError ( err error ) error {
113+ if err == nil {
114+ return nil
73115 }
74- data , _ := resultMap ["data" ].(map [string ]interface {})
75- if detail , ok := nonNilMapValue (data , "error" ); ok {
76- return detail
116+ var me * fileio.MkdirError
117+ switch {
118+ case errors .Is (err , fileio .ErrPathValidation ):
119+ return errs .NewValidationError (errs .SubtypeInvalidArgument , "unsafe output path: %s" , err ).WithCause (err )
120+ case errors .As (err , & me ):
121+ return errs .NewInternalError (errs .SubtypeFileIO , "cannot create parent directory: %s" , err ).WithCause (err )
122+ default :
123+ return errs .NewInternalError (errs .SubtypeFileIO , "cannot create file: %s" , err ).WithCause (err )
77124 }
78- return nil
79125}
80126
81- func nonNilMapValue ( src map [ string ] interface {}, key string ) ( interface {}, bool ) {
82- if src == nil {
83- return nil , false
127+ func baseAPIBoundaryError ( err error , action string ) error {
128+ if _ , ok := errs . ProblemOf ( err ); ok {
129+ return err
84130 }
85- value , ok := src [key ]
86- if ! ok {
87- return nil , false
131+ return errs .NewNetworkError (errs .SubtypeNetworkTransport , "%s: %s" , action , err ).WithCause (err )
132+ }
133+
134+ func baseUploadAttachmentError (filePath string , err error ) error {
135+ if p , ok := errs .ProblemOf (err ); ok {
136+ p .Message = fmt .Sprintf ("failed to upload attachment %s: %s" , filePath , p .Message )
137+ return err
88138 }
89- switch value .(type ) {
90- case nil :
91- return nil , false
92- default :
93- return value , true
139+ return errs .NewInternalError (errs .SubtypeSDKError , "failed to upload attachment %s: %s" , filePath , err ).WithCause (err )
140+ }
141+
142+ func baseAPIErrorFromResult (resultMap map [string ]interface {}, cc errclass.ClassifyContext ) error {
143+ if resultMap == nil {
144+ return errs .NewInternalError (errs .SubtypeInvalidResponse , "API returned a malformed response envelope" )
145+ }
146+ if msg := extractDataErrorMessage (resultMap ); msg != "" {
147+ resultMap ["msg" ] = msg
148+ }
149+ hint := extractErrorHint (resultMap )
150+ if logID := extractBaseErrorLogID (resultMap ); logID != "" {
151+ resultMap ["log_id" ] = logID
152+ }
153+ err := errclass .BuildAPIError (resultMap , cc )
154+ if err == nil {
155+ return nil
156+ }
157+ if p , ok := errs .ProblemOf (err ); ok && hint != "" {
158+ p .Hint = hint
159+ }
160+ return err
161+ }
162+
163+ func enrichBaseAPIErrorFromBody (err error , body []byte , cc errclass.ClassifyContext ) error {
164+ if _ , ok := errs .ProblemOf (err ); ! ok {
165+ return err
166+ }
167+ result , parseErr := decodeBaseV3Response (body )
168+ if parseErr != nil {
169+ return err
170+ }
171+ enriched := baseAPIErrorFromResult (result , cc )
172+ if enriched == nil {
173+ return err
174+ }
175+ src , _ := errs .ProblemOf (enriched )
176+ dst , _ := errs .ProblemOf (err )
177+ if src != nil && dst != nil {
178+ dst .Message = src .Message
179+ dst .Hint = src .Hint
180+ dst .LogID = src .LogID
181+ }
182+ return err
183+ }
184+
185+ func extractBaseErrorLogID (resultMap map [string ]interface {}) string {
186+ for _ , key := range []string {"log_id" , "logid" } {
187+ if logID , _ := resultMap [key ].(string ); strings .TrimSpace (logID ) != "" {
188+ return strings .TrimSpace (logID )
189+ }
190+ }
191+ if detail , ok := resultMap ["error" ].(map [string ]interface {}); ok {
192+ for _ , key := range []string {"log_id" , "logid" } {
193+ if logID , _ := detail [key ].(string ); strings .TrimSpace (logID ) != "" {
194+ return strings .TrimSpace (logID )
195+ }
196+ }
94197 }
198+ data , _ := resultMap ["data" ].(map [string ]interface {})
199+ if detail , ok := data ["error" ].(map [string ]interface {}); ok {
200+ for _ , key := range []string {"log_id" , "logid" } {
201+ if logID , _ := detail [key ].(string ); strings .TrimSpace (logID ) != "" {
202+ return strings .TrimSpace (logID )
203+ }
204+ }
205+ }
206+ return ""
95207}
96208
97209func extractErrorHint (resultMap map [string ]interface {}) string {
0 commit comments