mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
4488da0b14
* feat(apps): add +user-id-convert shortcut for Miaoda↔Feishu ID conversion
Wrap the platform id_convert OpenAPI as a read-only shortcut that maps
Miaoda user_id ↔ Feishu open platform IDs (open_id / union_id / Feishu
user_id). It does one thing — conversion — with no local mapping table,
caching, permission pre-check, or direction guessing.
- --convert-type enum → server id_convert_type (10/11/20/21/40)
- --ids: csv / @file / stdin, 1-100 per call, not de-duped, input order
- reconstructs data.missed by diffing input positions against returned
source_ids (server silently drops unresolved IDs), keyed by 0-based index
- meta counters (total/hit_count/missed_count) via pointer fields on
output.Meta so an explicit missed_count: 0 survives omitempty
* test(apps): address review feedback on +user-id-convert
- reject empty --ids CSV entries (e.g. "a,,b") with a typed validation
error instead of silently dropping them, since a dropped entry shifts
every later result's 0-based index and breaks the position-keyed
items/missed contract; add an interior-empty-element test
- reuse common.GetSlice / common.GetString for response projection
(house convention) instead of local asSlice/asString helpers
- requireConvertValidation now asserts CategoryValidation +
SubtypeInvalidArgument via errs.ProblemOf, keeping ValidationError.Param
- table-drive TestResolveConvertType over all five directions so every
--convert-type → id_convert_type mapping (10/11/20/21/40) is protected
* fix(apps): split newline-delimited --ids for +user-id-convert @file/stdin
@file and - (stdin) input arrives verbatim from the framework as
one-ID-per-line text, but parseConvertIDs only split on commas, so such a
block was sent as a single malformed request ID. Treat a newline as
equivalent to a comma, tolerating a file's trailing newline while still
rejecting interior empty entries so position-keyed result indices stay
aligned. Add @file and stdin tests asserting the request body's ids are
split into discrete IDs.
* fix(apps): stringify numeric JSON IDs in +user-id-convert results
Responses decode with json.Number (client.ParseJSONResponse uses
dec.UseNumber()), so a server that emits source_id/target_id as bare
numbers — plausible for the numeric Miaoda user_id form — was silently
coerced to "" by buildConvertResult's strict string assertion: the
source_id got dropped (false not_found) and the target_id blanked
(false success).
Add common.GetStringLoose, which stringifies string/json.Number/int64/
float64 via literal text (large integer IDs keep full precision, never
routed through a lossy float64), and use it for both id reads. Cover it
with a package-level table test plus an end-to-end regression asserting a
numeric-JSON response yields intact, non-blank ids and no false miss.
Also exercise resolveConvertType's non-empty "not a valid direction"
branch directly, since the runner's enum gate preempts it in normal flow.
* test(common): tighten GetStringLoose numeric coverage
Add an int-branch case (was only covering int64) and swap the float64
fixture from 42 — which no formatter would render in exponent form — to
1e-7, whose fixed-point rendering "0.0000001" fails under the 'g' verb.
This turns the "no scientific notation" case into a real guard for the
'f' verb choice, per CodeRabbit review on c64cca39.
67 lines
3.0 KiB
Go
67 lines
3.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package output
|
|
|
|
// Envelope is the standard success response wrapper.
|
|
type Envelope struct {
|
|
OK bool `json:"ok"`
|
|
Identity string `json:"identity,omitempty"`
|
|
DryRun bool `json:"dry_run,omitempty"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Meta *Meta `json:"meta,omitempty"`
|
|
ContentSafetyAlert interface{} `json:"_content_safety_alert,omitempty"`
|
|
Notice map[string]interface{} `json:"_notice,omitempty"`
|
|
}
|
|
|
|
// Meta carries optional metadata in envelope responses.
|
|
//
|
|
// Total/HitCount/MissedCount are batch-lookup counters used by commands that
|
|
// resolve a list of inputs and report per-input hit/miss (e.g.
|
|
// apps +user-id-convert). They are pointers so an unset counter is dropped by
|
|
// omitempty while an explicit zero (e.g. "missed_count": 0 on a full hit) is
|
|
// still emitted — a plain int with omitempty would silently drop the required
|
|
// zero, and without omitempty it would pollute every other domain's meta. Every
|
|
// non-batch command leaves them nil, so this stays invisible outside batch use.
|
|
type Meta struct {
|
|
Count int `json:"count,omitempty"`
|
|
Rollback string `json:"rollback,omitempty"`
|
|
Pagination *PaginationMeta `json:"pagination,omitempty"`
|
|
Total *int `json:"total,omitempty"`
|
|
HitCount *int `json:"hit_count,omitempty"`
|
|
MissedCount *int `json:"missed_count,omitempty"`
|
|
}
|
|
|
|
// PaginationMeta reports how a paginated read ended.
|
|
//
|
|
// It lives in the envelope's meta rather than in the business data because a
|
|
// stop reason is not part of the resource: writing it into data both pollutes
|
|
// the payload and forces the caller to tell an API field apart from one the CLI
|
|
// synthesised. Complete plus NextToken is the whole story — a run either
|
|
// exhausted the endpoint or stopped at --page-limit with somewhere to resume —
|
|
// so there is no separate stop_reason string to keep in sync.
|
|
type PaginationMeta struct {
|
|
// Complete is true only when the server's exhausted state was observed.
|
|
Complete bool `json:"complete"`
|
|
// Pages counts successful API pages included in this result.
|
|
Pages int `json:"pages"`
|
|
// Items counts records after command-level filtering and enrichment.
|
|
Items int `json:"items"`
|
|
// NextToken is the cursor at which an incomplete result can resume.
|
|
NextToken string `json:"next_token,omitempty"`
|
|
}
|
|
|
|
// PendingNotice, if set, returns system-level notices to inject as the
|
|
// "_notice" field in JSON output envelopes. Set by cmd/root.go.
|
|
// Returns nil when there is nothing to report.
|
|
var PendingNotice func() map[string]interface{}
|
|
|
|
// GetNotice returns the current pending notice for struct-based callers.
|
|
// Returns nil when there is nothing to report.
|
|
func GetNotice() map[string]interface{} {
|
|
if PendingNotice == nil {
|
|
return nil
|
|
}
|
|
return PendingNotice()
|
|
}
|