Skip to content

Commit 3cdff04

Browse files
Merge pull request #285 from dropbox/json-version-share-folder
Add version JSON output and share list folder structured output
2 parents d7a24aa + 710045d commit 3cdff04

6 files changed

Lines changed: 529 additions & 26 deletions

File tree

README.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Text output is the default. JSON output is available through the global `--outpu
138138

139139
```sh
140140
$ dbxcli <command> --output=json
141+
$ dbxcli version --output=json
141142
$ dbxcli account --output=json
142143
$ dbxcli du --output=json
143144
$ dbxcli ls --output=json /
@@ -153,12 +154,13 @@ $ dbxcli share-link info --output=json https://www.dropbox.com/s/example/old.pdf
153154
$ dbxcli share-link update --output=json https://www.dropbox.com/s/example/old.pdf --expires 2026-07-01T00:00:00Z
154155
$ dbxcli share-link revoke --output=json https://www.dropbox.com/s/example/old.pdf
155156
$ dbxcli share-link download --output=json https://www.dropbox.com/s/example/old.pdf ./old.pdf
157+
$ dbxcli share list folder --output=json
156158
$ dbxcli mkdir --output=json /new-folder
157159
$ dbxcli rm --output=json /old-file.txt
158160
$ dbxcli restore --output=json /Reports/old.pdf 015f...
159161
```
160162

161-
Structured success output is rolling out command by command. Currently migrated commands are `account`, `du`, `ls`, `search`, `revs`, `cp`, `mv`, `put`, `get`, `share-link create`, `share-link list`, `share-link info`, `share-link update`, `share-link revoke`, `share-link download`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.
163+
Structured success output is rolling out command by command. Currently migrated commands are `version`, `account`, `du`, `ls`, `search`, `revs`, `cp`, `mv`, `put`, `get`, `share-link create`, `share-link list`, `share-link info`, `share-link update`, `share-link revoke`, `share-link download`, `share list folder`, `mkdir`, `rm`, and `restore`. Commands that have not been migrated return a JSON error whose `error.message` is `structured output is not supported for this command yet` when used with `--output=json`.
162164

163165
Command results and JSON errors are written to stdout. Status, progress, human-facing warnings, diagnostics, and verbose logs are written to stderr. JSON errors include a `warnings` array for machine-actionable warnings; it is `[]` when no warnings are present. Successful JSON payloads use the same `warnings` field.
164166

@@ -308,7 +310,25 @@ Entry-list commands such as `ls`, `search`, and `revs` use the operation-style w
308310
}
309311
```
310312

311-
Account and usage commands use the operation-style wrapper with a single result:
313+
Version, account, and usage commands use the operation-style wrapper with a single result:
314+
315+
```json
316+
{
317+
"input": {},
318+
"results": [
319+
{
320+
"kind": "version",
321+
"input": {},
322+
"result": {
323+
"version": "3.4.0",
324+
"sdk_version": "6.0.5",
325+
"spec_version": "c36ba27"
326+
}
327+
}
328+
],
329+
"warnings": []
330+
}
331+
```
312332

313333
```json
314334
{
@@ -377,6 +397,31 @@ Shared-link commands use the same operation-style wrapper. `share-link create`,
377397

378398
`share-link revoke` uses `revoked` results whose `result` contains the revoked URL and, when available, the shared-link metadata. `share-link download` uses `downloaded` results whose `result` contains the local `target` and `link` metadata.
379399

400+
The legacy `share list folder` command also supports operation-style JSON. It uses `listed` results with `shared_folder` metadata:
401+
402+
```json
403+
{
404+
"input": {},
405+
"results": [
406+
{
407+
"status": "listed",
408+
"kind": "shared_folder",
409+
"result": {
410+
"type": "shared_folder",
411+
"name": "Reports",
412+
"path_lower": "/reports",
413+
"shared_folder_id": "1234567890",
414+
"preview_url": "https://www.dropbox.com/scl/fo/...",
415+
"access_type": "owner",
416+
"is_inside_team_folder": false,
417+
"is_team_folder": false
418+
}
419+
}
420+
],
421+
"warnings": []
422+
}
423+
```
424+
380425
`get --output=json <source> -` and `share-link download --output=json <url> -` are not supported because stdout is reserved for downloaded file bytes when the target is `-`.
381426

382427
In JSON mode, command errors are written to stdout as JSON, including errors from commands that do not yet support structured success output. The process still exits with a non-zero status. Detailed diagnostics may also be written to stderr:

cmd/share-list-folders.go

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,138 @@ package cmd
1616

1717
import (
1818
"fmt"
19+
"io"
1920

21+
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox"
2022
"github.com/dropbox/dropbox-sdk-go-unofficial/v6/dropbox/sharing"
2123
"github.com/spf13/cobra"
2224
)
2325

26+
type sharedFolderClient interface {
27+
ListFolders(*sharing.ListFoldersArgs) (*sharing.ListFoldersResult, error)
28+
ListFoldersContinue(*sharing.ListFoldersContinueArg) (*sharing.ListFoldersResult, error)
29+
}
30+
31+
type shareFolderListInput struct{}
32+
33+
type shareFolderJSONMetadata struct {
34+
Type string `json:"type"`
35+
Name string `json:"name"`
36+
PathLower string `json:"path_lower,omitempty"`
37+
SharedFolderID string `json:"shared_folder_id"`
38+
PreviewURL string `json:"preview_url,omitempty"`
39+
AccessType string `json:"access_type,omitempty"`
40+
IsInsideTeamFolder bool `json:"is_inside_team_folder"`
41+
IsTeamFolder bool `json:"is_team_folder"`
42+
OwnerDisplayNames []string `json:"owner_display_names,omitempty"`
43+
ParentSharedFolderID string `json:"parent_shared_folder_id,omitempty"`
44+
ParentFolderName string `json:"parent_folder_name,omitempty"`
45+
TimeInvited *string `json:"time_invited,omitempty"`
46+
AccessInheritance string `json:"access_inheritance,omitempty"`
47+
}
48+
49+
const (
50+
shareFolderJSONStatusListed = "listed"
51+
shareFolderJSONKindFolder = "shared_folder"
52+
)
53+
54+
var newSharedFolderClient = func(cfg dropbox.Config) sharedFolderClient {
55+
return sharing.New(cfg)
56+
}
57+
2458
func shareListFolders(cmd *cobra.Command, args []string) (err error) {
2559
arg := sharing.NewListFoldersArgs()
2660

27-
dbx := sharing.New(config)
28-
res, err := dbx.ListFolders(arg)
61+
dbx := newSharedFolderClient(config)
62+
entries, err := listSharedFolders(dbx, arg)
2963
if err != nil {
3064
return
3165
}
3266

33-
printFolders(res.Entries)
67+
commandVerboseStatus(cmd, "Listed %d shared folders", len(entries))
68+
69+
return commandOutput(cmd).Render(func(w io.Writer) error {
70+
return renderSharedFolders(w, entries)
71+
}, newJSONOperationOutput(
72+
shareFolderListInput{},
73+
shareFolderJSONOperationResults(shareFolderJSONMetadataListFromDropbox(entries)),
74+
nil,
75+
))
76+
}
77+
78+
func listSharedFolders(dbx sharedFolderClient, arg *sharing.ListFoldersArgs) ([]*sharing.SharedFolderMetadata, error) {
79+
var entries []*sharing.SharedFolderMetadata
80+
res, err := dbx.ListFolders(arg)
81+
if err != nil {
82+
return nil, err
83+
}
84+
entries = append(entries, res.Entries...)
3485

3586
for len(res.Cursor) > 0 {
3687
continueArg := sharing.NewListFoldersContinueArg(res.Cursor)
3788

3889
res, err = dbx.ListFoldersContinue(continueArg)
3990
if err != nil {
40-
return
91+
return nil, err
4192
}
4293

43-
printFolders(res.Entries)
94+
entries = append(entries, res.Entries...)
4495
}
4596

46-
return
97+
return entries, nil
4798
}
4899

49-
func printFolders(entries []*sharing.SharedFolderMetadata) {
100+
func renderSharedFolders(out io.Writer, entries []*sharing.SharedFolderMetadata) error {
50101
for _, f := range entries {
51-
fmt.Printf("%v\t%v\n", f.PathLower, f.PreviewUrl)
102+
if _, err := fmt.Fprintf(out, "%v\t%v\n", f.PathLower, f.PreviewUrl); err != nil {
103+
return err
104+
}
105+
}
106+
107+
return nil
108+
}
109+
110+
func shareFolderJSONMetadataListFromDropbox(entries []*sharing.SharedFolderMetadata) []shareFolderJSONMetadata {
111+
result := make([]shareFolderJSONMetadata, 0, len(entries))
112+
for _, entry := range entries {
113+
result = append(result, shareFolderJSONMetadataFromDropbox(entry))
114+
}
115+
return result
116+
}
117+
118+
func shareFolderJSONMetadataFromDropbox(entry *sharing.SharedFolderMetadata) shareFolderJSONMetadata {
119+
if entry == nil {
120+
return shareFolderJSONMetadata{Type: shareFolderJSONKindFolder}
121+
}
122+
123+
result := shareFolderJSONMetadata{
124+
Type: shareFolderJSONKindFolder,
125+
Name: entry.Name,
126+
PathLower: entry.PathLower,
127+
SharedFolderID: entry.SharedFolderId,
128+
PreviewURL: entry.PreviewUrl,
129+
IsInsideTeamFolder: entry.IsInsideTeamFolder,
130+
IsTeamFolder: entry.IsTeamFolder,
131+
OwnerDisplayNames: entry.OwnerDisplayNames,
132+
ParentSharedFolderID: entry.ParentSharedFolderId,
133+
ParentFolderName: entry.ParentFolderName,
134+
TimeInvited: jsonTime(entry.TimeInvited),
135+
}
136+
if entry.AccessType != nil {
137+
result.AccessType = entry.AccessType.Tag
138+
}
139+
if entry.AccessInheritance != nil {
140+
result.AccessInheritance = entry.AccessInheritance.Tag
141+
}
142+
return result
143+
}
144+
145+
func shareFolderJSONOperationResults(entries []shareFolderJSONMetadata) []jsonOperationResult {
146+
results := make([]jsonOperationResult, 0, len(entries))
147+
for _, entry := range entries {
148+
results = append(results, newJSONOperationResult(shareFolderJSONStatusListed, shareFolderJSONKindFolder, nil, entry))
52149
}
150+
return results
53151
}
54152

55153
var shareListFoldersCmd = &cobra.Command{
@@ -60,4 +158,5 @@ var shareListFoldersCmd = &cobra.Command{
60158

61159
func init() {
62160
shareListCmd.AddCommand(shareListFoldersCmd)
161+
enableStructuredOutput(shareListFoldersCmd)
63162
}

0 commit comments

Comments
 (0)