Skip to content

Commit 1bf7eef

Browse files
committed
fix: migrate contact errors to typed taxonomy
1 parent f3949f0 commit 1bf7eef

7 files changed

Lines changed: 280 additions & 72 deletions

.golangci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,20 @@ linters:
7373
- forbidigo
7474
# errs-typed-only enforced on paths already migrated to errs.NewXxxError.
7575
# Add a path when its migration is complete.
76-
- path-except: (internal/auth/|internal/errcompat/|internal/errclass/|internal/client/|internal/cmdutil/factory\.go|cmd/auth/|cmd/config/|cmd/service/|shortcuts/common/mcp_client\.go|shortcuts/calendar/|shortcuts/drive/|shortcuts/mail/|shortcuts/base/)
76+
- path-except: (internal/auth/|internal/errcompat/|internal/errclass/|internal/client/|internal/cmdutil/factory\.go|cmd/auth/|cmd/config/|cmd/service/|shortcuts/common/mcp_client\.go|shortcuts/calendar/|shortcuts/drive/|shortcuts/mail/|shortcuts/base/|shortcuts/contact/)
7777
text: errs-typed-only
7878
linters:
7979
- forbidigo
8080
# errs-no-bare-wrap enforced on paths fully migrated to typed final
8181
# errors. Scoped separately from errs-typed-only because cmd/auth/,
8282
# cmd/config/ still have residual fmt.Errorf and must not be caught.
83-
- path-except: (shortcuts/drive/|shortcuts/mail/|shortcuts/base/|shortcuts/calendar/|shortcuts/common/mcp_client\.go)
83+
- path-except: (shortcuts/drive/|shortcuts/mail/|shortcuts/base/|shortcuts/calendar/|shortcuts/contact/|shortcuts/common/mcp_client\.go)
8484
text: errs-no-bare-wrap
8585
linters:
8686
- forbidigo
8787
# errs-no-legacy-helper enforced on domains whose shared validation/save
8888
# helpers have migrated to typed final errors.
89-
- path-except: (shortcuts/drive/|shortcuts/mail/|shortcuts/base/|shortcuts/calendar/)
89+
- path-except: (shortcuts/drive/|shortcuts/mail/|shortcuts/base/|shortcuts/calendar/|shortcuts/contact/)
9090
text: errs-no-legacy-helper
9191
linters:
9292
- forbidigo

lint/errscontract/rule_no_legacy_common_helper_call.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
// common replacements or construct an errs.* typed error directly.
1717
var migratedCommonHelperPaths = []string{
1818
"shortcuts/base/",
19+
"shortcuts/contact/",
1920
"shortcuts/drive/",
2021
"shortcuts/mail/",
2122
"shortcuts/calendar/",

lint/errscontract/rule_no_legacy_envelope_literal.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
// appending their path prefix here.
1818
var migratedEnvelopePaths = []string{
1919
"shortcuts/base/",
20+
"shortcuts/contact/",
2021
"shortcuts/drive/",
2122
"shortcuts/mail/",
2223
"shortcuts/calendar/",

shortcuts/contact/contact_get_user.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ var ContactGetUser = common.Shortcut{
2828
},
2929
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
3030
if runtime.Str("user-id") == "" && runtime.IsBot() {
31-
return common.FlagErrorf("bot identity cannot get current user info, specify --user-id")
31+
return common.ValidationErrorf("bot identity cannot get current user info, specify --user-id").
32+
WithParam("--user-id")
3233
}
3334
return nil
3435
},
@@ -63,7 +64,7 @@ var ContactGetUser = common.Shortcut{
6364

6465
if userId == "" {
6566
// Current user
66-
data, err := runtime.CallAPI("GET", "/open-apis/authen/v1/user_info", nil, nil)
67+
data, err := runtime.CallAPITyped("GET", "/open-apis/authen/v1/user_info", nil, nil)
6768
if err != nil {
6869
return err
6970
}
@@ -87,7 +88,7 @@ var ContactGetUser = common.Shortcut{
8788

8889
if runtime.IsBot() {
8990
// Bot identity: GET /contact/v3/users/:user_id (full profile)
90-
data, err := runtime.CallAPI("GET", "/open-apis/contact/v3/users/"+url.PathEscape(userId),
91+
data, err := runtime.CallAPITyped("GET", "/open-apis/contact/v3/users/"+url.PathEscape(userId),
9192
map[string]interface{}{"user_id_type": userIdType}, nil)
9293
if err != nil {
9394
return err
@@ -110,7 +111,7 @@ var ContactGetUser = common.Shortcut{
110111
}
111112

112113
// User identity: POST /contact/v3/users/basic_batch (lightweight)
113-
data, err := runtime.CallAPI("POST", "/open-apis/contact/v3/users/basic_batch",
114+
data, err := runtime.CallAPITyped("POST", "/open-apis/contact/v3/users/basic_batch",
114115
map[string]interface{}{"user_id_type": userIdType},
115116
map[string]interface{}{"user_ids": []string{userId}})
116117
if err != nil {

shortcuts/contact/contact_search_user.go

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"unicode/utf8"
1717

18+
"github.com/larksuite/cli/errs"
1819
"github.com/larksuite/cli/internal/core"
1920
"github.com/larksuite/cli/internal/output"
2021
"github.com/larksuite/cli/shortcuts/common"
@@ -216,19 +217,17 @@ func executeSearchUserSingle(ctx context.Context, runtime *common.RuntimeContext
216217
if err != nil {
217218
return err
218219
}
219-
if apiResp.StatusCode != http.StatusOK {
220-
return output.ErrAPI(apiResp.StatusCode, http.StatusText(apiResp.StatusCode), string(apiResp.RawBody))
221-
}
222220

223-
var resp searchUserAPIEnvelope
224-
if err := json.Unmarshal(apiResp.RawBody, &resp); err != nil {
225-
return output.ErrWithHint(output.ExitInternal, "validation", "unmarshal response failed", err.Error())
221+
data, err := runtime.ClassifyAPIResponse(apiResp)
222+
if err != nil {
223+
return err
226224
}
227-
if resp.Code != 0 {
228-
return output.ErrAPI(resp.Code, resp.Msg, string(apiResp.RawBody))
225+
respData, err := decodeSearchUserAPIData(data)
226+
if err != nil {
227+
return err
229228
}
230229

231-
users, hasMore := projectUsers(resp.Data, runtime.Str("lang"), runtime.Config.Brand)
230+
users, hasMore := projectUsers(respData, runtime.Str("lang"), runtime.Config.Brand)
232231
out := searchUserResponse{Users: users, HasMore: hasMore}
233232

234233
runtime.OutFormat(out, &output.Meta{Count: len(users)}, func(w io.Writer) {
@@ -245,6 +244,20 @@ func executeSearchUserSingle(ctx context.Context, runtime *common.RuntimeContext
245244
return nil
246245
}
247246

247+
func decodeSearchUserAPIData(data map[string]interface{}) (*searchUserAPIData, error) {
248+
raw, err := json.Marshal(data)
249+
if err != nil {
250+
return nil, errs.NewInternalError(errs.SubtypeInvalidResponse, "marshal search user response data failed").
251+
WithCause(err)
252+
}
253+
var out searchUserAPIData
254+
if err := json.Unmarshal(raw, &out); err != nil {
255+
return nil, errs.NewInternalError(errs.SubtypeInvalidResponse, "decode search user response data failed").
256+
WithCause(err)
257+
}
258+
return &out, nil
259+
}
260+
248261
func isHumanReadableFormat(format string) bool {
249262
return format == "pretty" || format == "table"
250263
}
@@ -373,52 +386,74 @@ func rowFromItem(item *searchUserAPIItem, lang string, brand core.LarkBrand) sea
373386

374387
func validateSearchUser(runtime *common.RuntimeContext) error {
375388
if !hasAnySearchInput(runtime) {
376-
return common.FlagErrorf(
389+
return common.ValidationErrorf(
377390
"specify at least one of --query, --queries, --user-ids, --has-chatted, --has-enterprise-email, --exclude-external-users, --left-organization",
391+
).WithParams(
392+
errs.InvalidParam{Name: "--query", Reason: "required; specify at least one search input"},
393+
errs.InvalidParam{Name: "--queries", Reason: "required; specify at least one search input"},
394+
errs.InvalidParam{Name: "--user-ids", Reason: "required; specify at least one search input"},
395+
errs.InvalidParam{Name: "--has-chatted", Reason: "required; specify at least one search input"},
396+
errs.InvalidParam{Name: "--has-enterprise-email", Reason: "required; specify at least one search input"},
397+
errs.InvalidParam{Name: "--exclude-external-users", Reason: "required; specify at least one search input"},
398+
errs.InvalidParam{Name: "--left-organization", Reason: "required; specify at least one search input"},
378399
)
379400
}
380401

381402
queriesRaw := strings.TrimSpace(runtime.Str("queries"))
382403
if queriesRaw != "" {
383404
if strings.TrimSpace(runtime.Str("query")) != "" {
384-
return common.FlagErrorf("--query and --queries are mutually exclusive")
405+
return common.ValidationErrorf("--query and --queries are mutually exclusive").
406+
WithParams(
407+
errs.InvalidParam{Name: "--query", Reason: "mutually exclusive with --queries"},
408+
errs.InvalidParam{Name: "--queries", Reason: "mutually exclusive with --query"},
409+
)
385410
}
386411
if strings.TrimSpace(runtime.Str("user-ids")) != "" {
387-
return common.FlagErrorf("--user-ids and --queries are mutually exclusive")
412+
return common.ValidationErrorf("--user-ids and --queries are mutually exclusive").
413+
WithParams(
414+
errs.InvalidParam{Name: "--user-ids", Reason: "mutually exclusive with --queries"},
415+
errs.InvalidParam{Name: "--queries", Reason: "mutually exclusive with --user-ids"},
416+
)
388417
}
389418
queries := parseAndDedupQueries(queriesRaw)
390419
if len(queries) == 0 {
391-
return common.FlagErrorf("--queries: no valid query parsed from %q (separate entries with ',')", queriesRaw)
420+
return common.ValidationErrorf("--queries: no valid query parsed from %q (separate entries with ',')", queriesRaw).
421+
WithParam("--queries")
392422
}
393423
if len(queries) > maxFanoutQueries {
394-
return common.FlagErrorf("--queries: must be at most %d entries (got %d)", maxFanoutQueries, len(queries))
424+
return common.ValidationErrorf("--queries: must be at most %d entries (got %d)", maxFanoutQueries, len(queries)).
425+
WithParam("--queries")
395426
}
396427
for _, q := range queries {
397428
if utf8.RuneCountInString(q) > maxSearchUserQueryChars {
398-
return common.FlagErrorf("--queries: entry %q exceeds %d characters", q, maxSearchUserQueryChars)
429+
return common.ValidationErrorf("--queries: entry %q exceeds %d characters", q, maxSearchUserQueryChars).
430+
WithParam("--queries")
399431
}
400432
}
401433
}
402434

403435
if q := strings.TrimSpace(runtime.Str("query")); q != "" {
404436
if utf8.RuneCountInString(q) > maxSearchUserQueryChars {
405-
return common.FlagErrorf("--query: length must be between 1 and %d characters", maxSearchUserQueryChars)
437+
return common.ValidationErrorf("--query: length must be between 1 and %d characters", maxSearchUserQueryChars).
438+
WithParam("--query")
406439
}
407440
}
408441

409442
if raw := strings.TrimSpace(runtime.Str("user-ids")); raw != "" {
410-
ids, err := common.ResolveOpenIDs("--user-ids", common.SplitCSV(raw), runtime)
443+
ids, err := common.ResolveOpenIDsTyped("--user-ids", common.SplitCSV(raw), runtime)
411444
if err != nil {
412445
return err
413446
}
414447
if len(ids) == 0 {
415-
return common.FlagErrorf("--user-ids: no valid open_id parsed from %q (separate entries with ',')", raw)
448+
return common.ValidationErrorf("--user-ids: no valid open_id parsed from %q (separate entries with ',')", raw).
449+
WithParam("--user-ids")
416450
}
417451
if len(ids) > maxSearchUserUserIDs {
418-
return common.FlagErrorf("--user-ids: must be at most %d entries", maxSearchUserUserIDs)
452+
return common.ValidationErrorf("--user-ids: must be at most %d entries", maxSearchUserUserIDs).
453+
WithParam("--user-ids")
419454
}
420455
for _, id := range ids {
421-
if _, err := common.ValidateUserID(id); err != nil {
456+
if _, err := common.ValidateUserIDTyped("--user-ids", id); err != nil {
422457
return err
423458
}
424459
}
@@ -429,15 +464,16 @@ func validateSearchUser(runtime *common.RuntimeContext) error {
429464
// silent wrong-result bugs.
430465
for _, bf := range searchUserBoolFilters {
431466
if runtime.Cmd.Flags().Changed(bf.Flag) && !runtime.Bool(bf.Flag) {
432-
return common.FlagErrorf(
467+
return common.ValidationErrorf(
433468
"--%s: pass the flag to enable the filter; omit it to disable filtering (=false is rejected to prevent silent wrong results)",
434469
bf.Flag,
435-
)
470+
).WithParam("--" + bf.Flag)
436471
}
437472
}
438473

439474
if n := runtime.Int("page-size"); n < 1 || n > maxSearchUserPageSize {
440-
return common.FlagErrorf("--page-size: must be between 1 and %d", maxSearchUserPageSize)
475+
return common.ValidationErrorf("--page-size: must be between 1 and %d", maxSearchUserPageSize).
476+
WithParam("--page-size")
441477
}
442478
return nil
443479
}
@@ -473,7 +509,7 @@ func buildSearchUserBody(runtime *common.RuntimeContext) (*searchUserAPIRequest,
473509
hasFilter := false
474510

475511
if raw := strings.TrimSpace(runtime.Str("user-ids")); raw != "" {
476-
ids, err := common.ResolveOpenIDs("--user-ids", common.SplitCSV(raw), runtime)
512+
ids, err := common.ResolveOpenIDsTyped("--user-ids", common.SplitCSV(raw), runtime)
477513
if err != nil {
478514
return nil, err
479515
}

0 commit comments

Comments
 (0)