From 2fb60b3975ffbd6ab433253bab57b12536a72862 Mon Sep 17 00:00:00 2001 From: Andrey Markelov Date: Mon, 29 Jun 2026 14:11:20 -0700 Subject: [PATCH] Generate commands.schema.json for per-command success validation Adds a JSON Schema 2020-12 file generated from commands.json that validates command-specific success responses: envelope fields, result status/kind enums, warning codes, and field sets. CI verifies the schema stays in sync via gen-json-schemas, and tests validate all golden outputs against it. --- .github/workflows/ci.yml | 3 + cmd/help_json.go | 7 +- cmd/help_json_test.go | 15 + cmd/help_manifest.go | 7 +- cmd/json_command_schema_test.go | 321 ++ .../json_contract/success_outputs.json | 3 +- .../json_contract/success_schemas.json | 1 + docs/automation.md | 3 + docs/commands/dbxcli_account.md | 1 + docs/commands/dbxcli_cp.md | 1 + docs/commands/dbxcli_du.md | 1 + docs/commands/dbxcli_get.md | 1 + docs/commands/dbxcli_logout.md | 1 + docs/commands/dbxcli_ls.md | 1 + docs/commands/dbxcli_mkdir.md | 1 + docs/commands/dbxcli_mv.md | 1 + docs/commands/dbxcli_put.md | 1 + docs/commands/dbxcli_restore.md | 1 + docs/commands/dbxcli_revs.md | 1 + docs/commands/dbxcli_rm.md | 1 + docs/commands/dbxcli_search.md | 1 + docs/commands/dbxcli_share-link_create.md | 1 + docs/commands/dbxcli_share-link_download.md | 1 + docs/commands/dbxcli_share-link_info.md | 1 + docs/commands/dbxcli_share-link_list.md | 1 + docs/commands/dbxcli_share-link_revoke.md | 1 + docs/commands/dbxcli_share-link_update.md | 1 + docs/commands/dbxcli_share_list_folder.md | 1 + docs/commands/dbxcli_team_add-member.md | 1 + docs/commands/dbxcli_team_info.md | 1 + docs/commands/dbxcli_team_list-groups.md | 1 + docs/commands/dbxcli_team_list-members.md | 1 + docs/commands/dbxcli_team_remove-member.md | 1 + docs/commands/dbxcli_version.md | 1 + docs/json-schema/v1/README.md | 22 +- docs/json-schema/v1/commands.json | 1 + docs/json-schema/v1/commands.schema.json | 2676 +++++++++++++++++ docs/json-schema/v1/manifest.schema.json | 3 + internal/jsonschema/commands.go | 262 ++ tools/gen-docs/main.go | 3 + tools/gen-json-schemas/main.go | 65 + 41 files changed, 3411 insertions(+), 7 deletions(-) create mode 100644 cmd/json_command_schema_test.go create mode 100644 docs/json-schema/v1/commands.schema.json create mode 100644 internal/jsonschema/commands.go create mode 100644 tools/gen-json-schemas/main.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cce3b1c..ffec3e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,11 @@ jobs: with: go-version: "1.25" - run: go run ./tools/gen-docs + - run: go run ./tools/gen-json-schemas - run: git diff --exit-code docs/commands + - run: git diff --exit-code docs/json-schema/v1/commands.schema.json - run: test -z "$(git status --porcelain -- docs/commands)" + - run: test -z "$(git status --porcelain -- docs/json-schema/v1/commands.schema.json)" chocolatey: name: Chocolatey package diff --git a/cmd/help_json.go b/cmd/help_json.go index d79af60..385700f 100644 --- a/cmd/help_json.go +++ b/cmd/help_json.go @@ -84,9 +84,10 @@ type jsonCommandExample struct { } type jsonCommandSchemaRefs struct { - SuccessSchema string `json:"success_schema"` - ErrorSchema string `json:"error_schema"` - CommandContract string `json:"command_contract,omitempty"` + SuccessSchema string `json:"success_schema"` + ErrorSchema string `json:"error_schema"` + CommandContract string `json:"command_contract,omitempty"` + CommandSuccessSchema string `json:"command_success_schema,omitempty"` } type jsonCommandStdinStdout struct { diff --git a/cmd/help_json_test.go b/cmd/help_json_test.go index dd19cbb..844c239 100644 --- a/cmd/help_json_test.go +++ b/cmd/help_json_test.go @@ -291,6 +291,9 @@ func TestJSONHelpManifestV1MachineFields(t *testing.T) { if put.SchemaRefs.CommandContract != "docs/json-schema/v1/commands.json#/commands/put" { t.Fatalf("put command_contract = %q", put.SchemaRefs.CommandContract) } + if put.SchemaRefs.CommandSuccessSchema != "docs/json-schema/v1/commands.schema.json#/$defs/command_put" { + t.Fatalf("put command_success_schema = %q", put.SchemaRefs.CommandSuccessSchema) + } if len(put.Examples) == 0 { t.Fatal("put examples = empty, want examples") } @@ -499,6 +502,18 @@ func TestJSONHelpManifestRegistryAudit(t *testing.T) { t.Errorf("%s command contract file %q is not readable: %v", path, commandManifestContractFile, err) } } + if manifest.SupportsStructuredOutput && manifest.SchemaRefs.CommandSuccessSchema == "" { + t.Errorf("%s supports structured output but has no command success schema ref", path) + } + if manifest.SchemaRefs.CommandSuccessSchema != "" { + wantPrefix := commandManifestCommandSchema + "#/$defs/command_" + if !strings.HasPrefix(manifest.SchemaRefs.CommandSuccessSchema, wantPrefix) { + t.Errorf("%s command success schema = %q, want prefix %q", path, manifest.SchemaRefs.CommandSuccessSchema, wantPrefix) + } + if _, err := os.Stat(filepath.Join("..", commandManifestCommandSchema)); err != nil { + t.Errorf("%s command success schema file %q is not readable: %v", path, commandManifestCommandSchema, err) + } + } } } diff --git a/cmd/help_manifest.go b/cmd/help_manifest.go index 41eccd5..e859c61 100644 --- a/cmd/help_manifest.go +++ b/cmd/help_manifest.go @@ -1,6 +1,9 @@ package cmd -import "github.com/spf13/pflag" +import ( + "github.com/dropbox/dbxcli/v3/internal/jsonschema" + "github.com/spf13/pflag" +) const ( commandManifestVersion = "1" @@ -9,6 +12,7 @@ const ( commandManifestSuccessSchema = "docs/json-schema/v1/success.schema.json" commandManifestErrorSchema = "docs/json-schema/v1/error.schema.json" commandManifestContractFile = "docs/json-schema/v1/commands.json" + commandManifestCommandSchema = "docs/json-schema/v1/commands.schema.json" ) type jsonCommandManifestMetadata struct { @@ -420,6 +424,7 @@ func commandManifestSchemaRefs(path string, supportsStructuredOutput bool) jsonC if supportsStructuredOutput || path == "help" { if _, ok := commandContractRegistry[path]; ok { refs.CommandContract = commandManifestContractFile + "#/commands/" + path + refs.CommandSuccessSchema = commandManifestCommandSchema + "#/$defs/" + jsonschema.CommandDefinitionName(path) } } return refs diff --git a/cmd/json_command_schema_test.go b/cmd/json_command_schema_test.go new file mode 100644 index 0000000..4f5e7d8 --- /dev/null +++ b/cmd/json_command_schema_test.go @@ -0,0 +1,321 @@ +// Copyright © 2026 Dropbox, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "encoding/json" + "fmt" + "math" + "os" + "reflect" + "strings" + "testing" + + schemagen "github.com/dropbox/dbxcli/v3/internal/jsonschema" +) + +func TestPublicJSONCommandSuccessSchemaMatchesGeneratedCatalog(t *testing.T) { + catalog := loadCommandSchemaCatalog(t) + + generated, err := schemagen.GenerateCommandSuccessSchema(catalog) + if err != nil { + t.Fatalf("generate command success schema: %v", err) + } + + got := loadJSONValueFile(t, "../docs/json-schema/v1/commands.schema.json") + want := normalizeJSONValue(t, generated) + if reflect.DeepEqual(got, want) { + return + } + + gotJSON, _ := json.MarshalIndent(got, "", " ") + wantJSON, _ := json.MarshalIndent(want, "", " ") + t.Fatalf("public command success schema = %s, want %s", gotJSON, wantJSON) +} + +func TestGoldenSuccessOutputsValidateAgainstPublicCommandSuccessSchema(t *testing.T) { + schema := loadJSONValueFile(t, "../docs/json-schema/v1/commands.schema.json") + validator := newSubsetJSONSchemaValidator(t, schema) + + for command, raw := range loadJSONGoldenSuccessOutputs(t) { + t.Run(command, func(t *testing.T) { + value := decodeJSONValue(t, raw) + if err := validator.validate(schema, value, "$"); err != nil { + t.Fatalf("golden success output does not validate: %v", err) + } + }) + } +} + +func TestPublicCommandSuccessSchemaRejectsInvalidStatus(t *testing.T) { + schema := loadJSONValueFile(t, "../docs/json-schema/v1/commands.schema.json") + validator := newSubsetJSONSchemaValidator(t, schema) + + value := decodeJSONValue(t, loadJSONGoldenSuccessOutputs(t)["cp"]) + root := value.(map[string]any) + results := root["results"].([]any) + first := results[0].(map[string]any) + first["status"] = "not-a-real-status" + + if err := validator.validate(schema, value, "$"); err == nil { + t.Fatal("invalid status validated successfully") + } +} + +func loadCommandSchemaCatalog(t *testing.T) schemagen.CommandCatalog { + t.Helper() + + data := readJSONFile(t, "../docs/json-schema/v1/commands.json") + catalog, err := schemagen.DecodeCommandCatalog(data) + if err != nil { + t.Fatalf("decode command catalog: %v", err) + } + return catalog +} + +func loadJSONValueFile(t *testing.T, file string) any { + t.Helper() + return decodeJSONValue(t, readJSONFile(t, file)) +} + +func readJSONFile(t *testing.T, file string) []byte { + t.Helper() + data, err := os.ReadFile(file) + if err != nil { + t.Fatalf("read %s: %v", file, err) + } + return data +} + +func decodeJSONValue(t *testing.T, data []byte) any { + t.Helper() + + var value any + if err := json.Unmarshal(data, &value); err != nil { + t.Fatalf("decode JSON value: %v", err) + } + return value +} + +func normalizeJSONValue(t *testing.T, value any) any { + t.Helper() + + data, err := json.Marshal(value) + if err != nil { + t.Fatalf("marshal JSON value: %v", err) + } + return decodeJSONValue(t, data) +} + +type subsetJSONSchemaValidator struct { + root any +} + +func newSubsetJSONSchemaValidator(t *testing.T, root any) subsetJSONSchemaValidator { + t.Helper() + if _, ok := root.(map[string]any); !ok { + t.Fatalf("schema root is %T, want object", root) + } + return subsetJSONSchemaValidator{root: root} +} + +func (v subsetJSONSchemaValidator) validate(schema any, value any, path string) error { + switch schema := schema.(type) { + case bool: + if schema { + return nil + } + return fmt.Errorf("%s is disallowed by false schema", path) + case map[string]any: + return v.validateObjectSchema(schema, value, path) + default: + return fmt.Errorf("%s schema has unsupported type %T", path, schema) + } +} + +func (v subsetJSONSchemaValidator) validateObjectSchema(schema map[string]any, value any, path string) error { + if refValue, ok := schema["$ref"]; ok { + ref, ok := refValue.(string) + if !ok { + return fmt.Errorf("%s $ref is %T, want string", path, refValue) + } + resolved, err := v.resolveRef(ref) + if err != nil { + return fmt.Errorf("%s: %w", path, err) + } + return v.validate(resolved, value, path) + } + + if allOf, ok := schema["allOf"]; ok { + items, ok := allOf.([]any) + if !ok { + return fmt.Errorf("%s allOf is %T, want array", path, allOf) + } + for i, item := range items { + if err := v.validate(item, value, fmt.Sprintf("%s allOf[%d]", path, i)); err != nil { + return err + } + } + } + + if oneOf, ok := schema["oneOf"]; ok { + items, ok := oneOf.([]any) + if !ok { + return fmt.Errorf("%s oneOf is %T, want array", path, oneOf) + } + matches := 0 + for _, item := range items { + if err := v.validate(item, value, path); err == nil { + matches++ + } + } + if matches != 1 { + return fmt.Errorf("%s matched %d oneOf schemas, want 1", path, matches) + } + return nil + } + + if constValue, ok := schema["const"]; ok && !reflect.DeepEqual(value, constValue) { + return fmt.Errorf("%s = %v, want const %v", path, value, constValue) + } + + if enumValue, ok := schema["enum"]; ok { + enum, ok := enumValue.([]any) + if !ok { + return fmt.Errorf("%s enum is %T, want array", path, enumValue) + } + found := false + for _, allowed := range enum { + if reflect.DeepEqual(value, allowed) { + found = true + break + } + } + if !found { + return fmt.Errorf("%s = %v, want one of %v", path, value, enum) + } + } + + if typeValue, ok := schema["type"]; ok { + typeName, ok := typeValue.(string) + if !ok { + return fmt.Errorf("%s type is %T, want string", path, typeValue) + } + if err := validateJSONSchemaType(path, value, typeName); err != nil { + return err + } + } + + object, hasObject := value.(map[string]any) + if requiredValue, ok := schema["required"]; ok { + if !hasObject { + return fmt.Errorf("%s required fields on non-object %T", path, value) + } + for _, field := range requiredValue.([]any) { + name := field.(string) + if _, ok := object[name]; !ok { + return fmt.Errorf("%s missing required field %q", path, name) + } + } + } + + if propertiesValue, ok := schema["properties"]; ok { + if !hasObject { + return fmt.Errorf("%s properties on non-object %T", path, value) + } + properties := propertiesValue.(map[string]any) + if additionalProperties, ok := schema["additionalProperties"].(bool); ok && !additionalProperties { + for name := range object { + if _, ok := properties[name]; !ok { + return fmt.Errorf("%s has unexpected property %q", path, name) + } + } + } + for name, propertySchema := range properties { + if propertyValue, ok := object[name]; ok { + if err := v.validate(propertySchema, propertyValue, path+"."+name); err != nil { + return err + } + } + } + } + + if itemsValue, ok := schema["items"]; ok { + array, ok := value.([]any) + if !ok { + return fmt.Errorf("%s items on non-array %T", path, value) + } + for i, item := range array { + if err := v.validate(itemsValue, item, fmt.Sprintf("%s[%d]", path, i)); err != nil { + return err + } + } + } + + return nil +} + +func (v subsetJSONSchemaValidator) resolveRef(ref string) (any, error) { + if !strings.HasPrefix(ref, "#/") { + return nil, fmt.Errorf("unsupported ref %q", ref) + } + current := v.root + for _, token := range strings.Split(strings.TrimPrefix(ref, "#/"), "/") { + token = strings.ReplaceAll(strings.ReplaceAll(token, "~1", "/"), "~0", "~") + object, ok := current.(map[string]any) + if !ok { + return nil, fmt.Errorf("ref %q traversed into %T", ref, current) + } + next, ok := object[token] + if !ok { + return nil, fmt.Errorf("ref %q missing token %q", ref, token) + } + current = next + } + return current, nil +} + +func validateJSONSchemaType(path string, value any, typeName string) error { + switch typeName { + case "object": + if _, ok := value.(map[string]any); !ok { + return fmt.Errorf("%s is %T, want object", path, value) + } + case "array": + if _, ok := value.([]any); !ok { + return fmt.Errorf("%s is %T, want array", path, value) + } + case "string": + if _, ok := value.(string); !ok { + return fmt.Errorf("%s is %T, want string", path, value) + } + case "boolean": + if _, ok := value.(bool); !ok { + return fmt.Errorf("%s is %T, want boolean", path, value) + } + case "integer": + number, ok := value.(float64) + if !ok || number != math.Trunc(number) { + return fmt.Errorf("%s is %T:%v, want integer", path, value, value) + } + case "number": + if _, ok := value.(float64); !ok { + return fmt.Errorf("%s is %T, want number", path, value) + } + default: + return fmt.Errorf("%s has unsupported schema type %q", path, typeName) + } + return nil +} diff --git a/cmd/testdata/json_contract/success_outputs.json b/cmd/testdata/json_contract/success_outputs.json index 92cfbbc..e4df27c 100644 --- a/cmd/testdata/json_contract/success_outputs.json +++ b/cmd/testdata/json_contract/success_outputs.json @@ -391,7 +391,8 @@ "schema_refs": { "success_schema": "docs/json-schema/v1/success.schema.json", "error_schema": "docs/json-schema/v1/error.schema.json", - "command_contract": "docs/json-schema/v1/commands.json#/commands/ls" + "command_contract": "docs/json-schema/v1/commands.json#/commands/ls", + "command_success_schema": "docs/json-schema/v1/commands.schema.json#/$defs/command_ls" }, "dropbox_scopes": [ "files.metadata.read" diff --git a/cmd/testdata/json_contract/success_schemas.json b/cmd/testdata/json_contract/success_schemas.json index 6fa2756..e97108e 100644 --- a/cmd/testdata/json_contract/success_schemas.json +++ b/cmd/testdata/json_contract/success_schemas.json @@ -115,6 +115,7 @@ ], "command_schema_refs": [ "command_contract", + "command_success_schema", "error_schema", "success_schema" ], diff --git a/docs/automation.md b/docs/automation.md index 8acf736..2d26053 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -73,6 +73,9 @@ a non-zero status: The full JSON command catalog, stable error codes, and schemas live in [json-schema/v1](json-schema/v1/README.md). +Use `commands.schema.json` from that directory when a caller needs +command-specific success validation for `input`, `results`, statuses, kinds, +and warning codes. ## JSON help manifest diff --git a/docs/commands/dbxcli_account.md b/docs/commands/dbxcli_account.md index f383ccb..0d0eec9 100644 --- a/docs/commands/dbxcli_account.md +++ b/docs/commands/dbxcli_account.md @@ -41,6 +41,7 @@ dbxcli account [flags] [] * Result statuses: `found` * Result kinds: `account` * JSON contract: `docs/json-schema/v1/commands.json#/commands/account` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_account` ### SEE ALSO diff --git a/docs/commands/dbxcli_cp.md b/docs/commands/dbxcli_cp.md index 8587f53..1e0d9da 100644 --- a/docs/commands/dbxcli_cp.md +++ b/docs/commands/dbxcli_cp.md @@ -35,6 +35,7 @@ dbxcli cp [flags] [more sources] * Result statuses: `copied`, `skipped` * Result kinds: `deleted`, `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/cp` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_cp` ### SEE ALSO diff --git a/docs/commands/dbxcli_du.md b/docs/commands/dbxcli_du.md index bfa7c2c..1d163f1 100644 --- a/docs/commands/dbxcli_du.md +++ b/docs/commands/dbxcli_du.md @@ -33,6 +33,7 @@ dbxcli du [flags] * Result statuses: `reported` * Result kinds: `space_usage` * JSON contract: `docs/json-schema/v1/commands.json#/commands/du` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_du` ### SEE ALSO diff --git a/docs/commands/dbxcli_get.md b/docs/commands/dbxcli_get.md index e6edfd8..41b96f0 100644 --- a/docs/commands/dbxcli_get.md +++ b/docs/commands/dbxcli_get.md @@ -53,6 +53,7 @@ dbxcli get [flags] [] * Result statuses: `created`, `downloaded`, `existing` * Result kinds: `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/get` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_get` ### SEE ALSO diff --git a/docs/commands/dbxcli_logout.md b/docs/commands/dbxcli_logout.md index 8015237..e22bdff 100644 --- a/docs/commands/dbxcli_logout.md +++ b/docs/commands/dbxcli_logout.md @@ -43,6 +43,7 @@ dbxcli logout [flags] * Result kinds: `auth` * Warning codes: `token_revoke_failed` * JSON contract: `docs/json-schema/v1/commands.json#/commands/logout` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_logout` ### SEE ALSO diff --git a/docs/commands/dbxcli_ls.md b/docs/commands/dbxcli_ls.md index 801e546..69016e0 100644 --- a/docs/commands/dbxcli_ls.md +++ b/docs/commands/dbxcli_ls.md @@ -53,6 +53,7 @@ dbxcli ls [flags] [] * Result statuses: `listed` * Result kinds: `deleted`, `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/ls` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_ls` ### SEE ALSO diff --git a/docs/commands/dbxcli_mkdir.md b/docs/commands/dbxcli_mkdir.md index 8d690dd..9eeee37 100644 --- a/docs/commands/dbxcli_mkdir.md +++ b/docs/commands/dbxcli_mkdir.md @@ -35,6 +35,7 @@ dbxcli mkdir [flags] * Result statuses: `created`, `existing` * Result kinds: `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/mkdir` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_mkdir` ### SEE ALSO diff --git a/docs/commands/dbxcli_mv.md b/docs/commands/dbxcli_mv.md index e751f97..44d93c0 100644 --- a/docs/commands/dbxcli_mv.md +++ b/docs/commands/dbxcli_mv.md @@ -35,6 +35,7 @@ dbxcli mv [flags] [more sources] * Result statuses: `moved`, `skipped` * Result kinds: `deleted`, `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/mv` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_mv` ### SEE ALSO diff --git a/docs/commands/dbxcli_put.md b/docs/commands/dbxcli_put.md index 1c723f2..9303c23 100644 --- a/docs/commands/dbxcli_put.md +++ b/docs/commands/dbxcli_put.md @@ -64,6 +64,7 @@ dbxcli put [flags] [] * Result kinds: `file`, `folder` * Warning codes: `skipped_symlink` * JSON contract: `docs/json-schema/v1/commands.json#/commands/put` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_put` ### SEE ALSO diff --git a/docs/commands/dbxcli_restore.md b/docs/commands/dbxcli_restore.md index 018df61..091a70b 100644 --- a/docs/commands/dbxcli_restore.md +++ b/docs/commands/dbxcli_restore.md @@ -48,6 +48,7 @@ dbxcli restore [flags] * Result statuses: `restored` * Result kinds: `file` * JSON contract: `docs/json-schema/v1/commands.json#/commands/restore` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_restore` ### SEE ALSO diff --git a/docs/commands/dbxcli_revs.md b/docs/commands/dbxcli_revs.md index 5f8d8fd..dcbb666 100644 --- a/docs/commands/dbxcli_revs.md +++ b/docs/commands/dbxcli_revs.md @@ -38,6 +38,7 @@ dbxcli revs [flags] * Result statuses: `revision` * Result kinds: `file` * JSON contract: `docs/json-schema/v1/commands.json#/commands/revs` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_revs` ### SEE ALSO diff --git a/docs/commands/dbxcli_rm.md b/docs/commands/dbxcli_rm.md index bcd833d..85c694f 100644 --- a/docs/commands/dbxcli_rm.md +++ b/docs/commands/dbxcli_rm.md @@ -38,6 +38,7 @@ dbxcli rm [flags] * Result statuses: `deleted`, `permanently_deleted` * Result kinds: `deleted`, `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/rm` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_rm` ### SEE ALSO diff --git a/docs/commands/dbxcli_search.md b/docs/commands/dbxcli_search.md index 5634905..7e39bf4 100644 --- a/docs/commands/dbxcli_search.md +++ b/docs/commands/dbxcli_search.md @@ -42,6 +42,7 @@ dbxcli search [flags] [path-scope] * Result statuses: `found` * Result kinds: `deleted`, `file`, `folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/search` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_search` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_create.md b/docs/commands/dbxcli_share-link_create.md index c98f98f..7e26847 100644 --- a/docs/commands/dbxcli_share-link_create.md +++ b/docs/commands/dbxcli_share-link_create.md @@ -59,6 +59,7 @@ dbxcli share-link create [flags] * Result statuses: `created`, `existing` * Result kinds: `file`, `folder`, `link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link create` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20create` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_download.md b/docs/commands/dbxcli_share-link_download.md index fa992cb..361549f 100644 --- a/docs/commands/dbxcli_share-link_download.md +++ b/docs/commands/dbxcli_share-link_download.md @@ -59,6 +59,7 @@ dbxcli share-link download [target] [flags] * Result statuses: `downloaded` * Result kinds: `file`, `folder`, `link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link download` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20download` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_info.md b/docs/commands/dbxcli_share-link_info.md index d789168..72f6923 100644 --- a/docs/commands/dbxcli_share-link_info.md +++ b/docs/commands/dbxcli_share-link_info.md @@ -50,6 +50,7 @@ dbxcli share-link info [flags] * Result statuses: `found` * Result kinds: `file`, `folder`, `link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link info` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20info` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_list.md b/docs/commands/dbxcli_share-link_list.md index 34ff97e..3fb8c36 100644 --- a/docs/commands/dbxcli_share-link_list.md +++ b/docs/commands/dbxcli_share-link_list.md @@ -46,6 +46,7 @@ dbxcli share-link list [path] [flags] * Result statuses: `listed` * Result kinds: `file`, `folder`, `link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link list` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20list` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_revoke.md b/docs/commands/dbxcli_share-link_revoke.md index 5c67739..b47f634 100644 --- a/docs/commands/dbxcli_share-link_revoke.md +++ b/docs/commands/dbxcli_share-link_revoke.md @@ -46,6 +46,7 @@ dbxcli share-link revoke [url] [flags] * Result statuses: `revoked` * Result kinds: `file`, `folder`, `link`, `shared_link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link revoke` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20revoke` ### SEE ALSO diff --git a/docs/commands/dbxcli_share-link_update.md b/docs/commands/dbxcli_share-link_update.md index ba508fd..b571935 100644 --- a/docs/commands/dbxcli_share-link_update.md +++ b/docs/commands/dbxcli_share-link_update.md @@ -58,6 +58,7 @@ dbxcli share-link update [flags] * Result statuses: `updated` * Result kinds: `file`, `folder`, `link` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share-link update` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_2dlink_20update` ### SEE ALSO diff --git a/docs/commands/dbxcli_share_list_folder.md b/docs/commands/dbxcli_share_list_folder.md index 716ae85..bd1777f 100644 --- a/docs/commands/dbxcli_share_list_folder.md +++ b/docs/commands/dbxcli_share_list_folder.md @@ -33,6 +33,7 @@ dbxcli share list folder [flags] * Result statuses: `listed` * Result kinds: `shared_folder` * JSON contract: `docs/json-schema/v1/commands.json#/commands/share list folder` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_share_20list_20folder` ### SEE ALSO diff --git a/docs/commands/dbxcli_team_add-member.md b/docs/commands/dbxcli_team_add-member.md index c8bc8ed..60a3767 100644 --- a/docs/commands/dbxcli_team_add-member.md +++ b/docs/commands/dbxcli_team_add-member.md @@ -35,6 +35,7 @@ dbxcli team add-member [flags] * Result statuses: `added`, `completed`, `started` * Result kinds: `team_member` * JSON contract: `docs/json-schema/v1/commands.json#/commands/team add-member` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_team_20add_2dmember` ### SEE ALSO diff --git a/docs/commands/dbxcli_team_info.md b/docs/commands/dbxcli_team_info.md index 086624b..8200489 100644 --- a/docs/commands/dbxcli_team_info.md +++ b/docs/commands/dbxcli_team_info.md @@ -33,6 +33,7 @@ dbxcli team info [flags] * Result statuses: `found` * Result kinds: `team` * JSON contract: `docs/json-schema/v1/commands.json#/commands/team info` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_team_20info` ### SEE ALSO diff --git a/docs/commands/dbxcli_team_list-groups.md b/docs/commands/dbxcli_team_list-groups.md index 5d62b44..8418440 100644 --- a/docs/commands/dbxcli_team_list-groups.md +++ b/docs/commands/dbxcli_team_list-groups.md @@ -33,6 +33,7 @@ dbxcli team list-groups [flags] * Result statuses: `listed` * Result kinds: `team_group` * JSON contract: `docs/json-schema/v1/commands.json#/commands/team list-groups` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_team_20list_2dgroups` ### SEE ALSO diff --git a/docs/commands/dbxcli_team_list-members.md b/docs/commands/dbxcli_team_list-members.md index 27a1525..b1a91fb 100644 --- a/docs/commands/dbxcli_team_list-members.md +++ b/docs/commands/dbxcli_team_list-members.md @@ -33,6 +33,7 @@ dbxcli team list-members [flags] * Result statuses: `listed` * Result kinds: `team_member` * JSON contract: `docs/json-schema/v1/commands.json#/commands/team list-members` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_team_20list_2dmembers` ### SEE ALSO diff --git a/docs/commands/dbxcli_team_remove-member.md b/docs/commands/dbxcli_team_remove-member.md index 692e6ec..23e9c53 100644 --- a/docs/commands/dbxcli_team_remove-member.md +++ b/docs/commands/dbxcli_team_remove-member.md @@ -35,6 +35,7 @@ dbxcli team remove-member [flags] * Result statuses: `completed`, `removed`, `started` * Result kinds: `team_member` * JSON contract: `docs/json-schema/v1/commands.json#/commands/team remove-member` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_team_20remove_2dmember` ### SEE ALSO diff --git a/docs/commands/dbxcli_version.md b/docs/commands/dbxcli_version.md index ebc5dc2..7ce6e1c 100644 --- a/docs/commands/dbxcli_version.md +++ b/docs/commands/dbxcli_version.md @@ -33,6 +33,7 @@ dbxcli version [flags] * Result statuses: `reported` * Result kinds: `version` * JSON contract: `docs/json-schema/v1/commands.json#/commands/version` +* JSON success schema: `docs/json-schema/v1/commands.schema.json#/$defs/command_version` ### SEE ALSO diff --git a/docs/json-schema/v1/README.md b/docs/json-schema/v1/README.md index 8240b00..478c276 100644 --- a/docs/json-schema/v1/README.md +++ b/docs/json-schema/v1/README.md @@ -10,6 +10,9 @@ These schemas describe the stable top-level JSON envelopes emitted by JSON help. - `commands.json` documents command-specific input/result payload names, result statuses, result kinds, and warning codes. +- `commands.schema.json` validates command-specific success responses using + `commands.json`: exact command names, top-level input fields, per-result + input/result fields, result statuses/kinds, and warning codes. Successful responses always include: @@ -57,6 +60,16 @@ continues to print text help, and shell-completion protocol commands remain text-only. The current JSON-enabled command paths are listed in `commands.json`. +`commands.schema.json` is generated from that catalog: + +```sh +go run ./tools/gen-json-schemas +``` + +The command-specific schema intentionally starts with field-set and enum +validation. It locks which fields may appear, which result statuses/kinds are +valid for each command, and which warning codes are valid. It does not yet +describe every nested primitive type. ## Command Manifest v1 @@ -96,6 +109,11 @@ affect a no-auth command may be omitted from that command's `input_schema`. Sensitive inputs are marked with `writeOnly` and `x-sensitive`; flag conflicts are listed in `x-conflicts`. +For commands with structured JSON output, `schema_refs.command_success_schema` +points to the command-specific definition inside `commands.schema.json`. +`schema_refs.command_contract` points to the source catalog entry in +`commands.json`. + `scope_accuracy` is currently `audited_best_effort` for commands with audited manifest metadata. Scope metadata is intended for planning and diagnostics; Dropbox API errors remain the source of truth at runtime. @@ -134,5 +152,5 @@ Stable error codes: | `command_failed` | Fallback for failures without a more specific stable code. | Command-specific `input` and `result` payload contracts are listed in -`commands.json` and locked by the golden contract fixtures under -`cmd/testdata/json_contract/`. +`commands.json`, validated through `commands.schema.json`, and locked by the +golden contract fixtures under `cmd/testdata/json_contract/`. diff --git a/docs/json-schema/v1/commands.json b/docs/json-schema/v1/commands.json index 6fa2756..e97108e 100644 --- a/docs/json-schema/v1/commands.json +++ b/docs/json-schema/v1/commands.json @@ -115,6 +115,7 @@ ], "command_schema_refs": [ "command_contract", + "command_success_schema", "error_schema", "success_schema" ], diff --git a/docs/json-schema/v1/commands.schema.json b/docs/json-schema/v1/commands.schema.json new file mode 100644 index 0000000..cca6bd1 --- /dev/null +++ b/docs/json-schema/v1/commands.schema.json @@ -0,0 +1,2676 @@ +{ + "$defs": { + "account": { + "additionalProperties": false, + "properties": { + "account_id": {}, + "account_type": {}, + "auth": {}, + "disabled": {}, + "email": {}, + "email_verified": {}, + "is_paired": {}, + "is_teammate": {}, + "locale": {}, + "name": {}, + "profile_photo_url": {}, + "referral_link": {}, + "team": {}, + "team_member_id": {}, + "type": {} + }, + "type": "object" + }, + "account_auth": { + "additionalProperties": false, + "properties": { + "auth_file": {}, + "refreshable": {}, + "source": {} + }, + "type": "object" + }, + "account_input": { + "additionalProperties": false, + "properties": { + "account_id": {} + }, + "type": "object" + }, + "account_name": { + "additionalProperties": false, + "properties": { + "abbreviated_name": {}, + "display_name": {}, + "familiar_name": {}, + "given_name": {}, + "surname": {} + }, + "type": "object" + }, + "account_team": { + "additionalProperties": false, + "properties": { + "id": {}, + "member_id": {}, + "name": {} + }, + "type": "object" + }, + "command_account": { + "additionalProperties": false, + "properties": { + "command": { + "const": "account" + }, + "input": { + "$ref": "#/$defs/account_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_account" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_account" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_arg": { + "additionalProperties": false, + "properties": { + "description": {}, + "enum_values": {}, + "name": {}, + "placement": {}, + "required": {}, + "stream_dash": {}, + "value_kind": {}, + "variadic": {} + }, + "type": "object" + }, + "command_cp": { + "additionalProperties": false, + "properties": { + "command": { + "const": "cp" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_cp" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_cp" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_du": { + "additionalProperties": false, + "properties": { + "command": { + "const": "du" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_du" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_du" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_example": { + "additionalProperties": false, + "properties": { + "command": {}, + "description": {} + }, + "type": "object" + }, + "command_flag": { + "additionalProperties": false, + "properties": { + "conflicts": {}, + "default": {}, + "enum_values": {}, + "inherited": {}, + "may_prompt": {}, + "name": {}, + "required": {}, + "sensitive": {}, + "shorthand": {}, + "type": {}, + "usage": {}, + "value_kind": {} + }, + "type": "object" + }, + "command_get": { + "additionalProperties": false, + "properties": { + "command": { + "const": "get" + }, + "input": { + "$ref": "#/$defs/get_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_get" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_get" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_help": { + "additionalProperties": false, + "properties": { + "command": { + "const": "help" + }, + "input": { + "$ref": "#/$defs/help_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_help" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_help" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_input_property": { + "additionalProperties": false, + "properties": { + "default": {}, + "description": {}, + "enum": {}, + "format": {}, + "items": {}, + "minItems": {}, + "type": {}, + "writeOnly": {}, + "x-cli-kind": {}, + "x-cli-name": {}, + "x-conflicts": {}, + "x-inherited": {}, + "x-may-prompt": {}, + "x-sensitive": {}, + "x-shorthand": {}, + "x-stream-dash": {}, + "x-value-kind": {} + }, + "type": "object" + }, + "command_input_schema": { + "additionalProperties": false, + "properties": { + "additionalProperties": {}, + "properties": {}, + "required": {}, + "type": {} + }, + "type": "object" + }, + "command_logout": { + "additionalProperties": false, + "properties": { + "command": { + "const": "logout" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_logout" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_logout" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_ls": { + "additionalProperties": false, + "properties": { + "command": { + "const": "ls" + }, + "input": { + "$ref": "#/$defs/ls_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_ls" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_ls" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_manifest": { + "additionalProperties": false, + "properties": { + "aliases": {}, + "args": {}, + "auth_modes": {}, + "destructive_level": {}, + "dropbox_scopes": {}, + "examples": {}, + "flags": {}, + "input_schema": {}, + "manifest_version": {}, + "may_prompt": {}, + "path": {}, + "result_kinds": {}, + "result_statuses": {}, + "runnable": {}, + "schema_refs": {}, + "scope_accuracy": {}, + "short": {}, + "stdin_stdout": {}, + "supports_structured_output": {}, + "use": {}, + "warning_codes": {} + }, + "type": "object" + }, + "command_mkdir": { + "additionalProperties": false, + "properties": { + "command": { + "const": "mkdir" + }, + "input": { + "$ref": "#/$defs/mkdir_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_mkdir" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_mkdir" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_mv": { + "additionalProperties": false, + "properties": { + "command": { + "const": "mv" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_mv" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_mv" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_put": { + "additionalProperties": false, + "properties": { + "command": { + "const": "put" + }, + "input": { + "$ref": "#/$defs/put_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_put" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_put" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_restore": { + "additionalProperties": false, + "properties": { + "command": { + "const": "restore" + }, + "input": { + "$ref": "#/$defs/restore_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_restore" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_restore" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_revs": { + "additionalProperties": false, + "properties": { + "command": { + "const": "revs" + }, + "input": { + "$ref": "#/$defs/revs_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_revs" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_revs" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_rm": { + "additionalProperties": false, + "properties": { + "command": { + "const": "rm" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_rm" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_rm" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_schema_refs": { + "additionalProperties": false, + "properties": { + "command_contract": {}, + "command_success_schema": {}, + "error_schema": {}, + "success_schema": {} + }, + "type": "object" + }, + "command_search": { + "additionalProperties": false, + "properties": { + "command": { + "const": "search" + }, + "input": { + "$ref": "#/$defs/search_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_search" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_search" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_20list_20folder": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share list folder" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_20list_20folder" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_20list_20folder" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_20list_20link": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share list link" + }, + "input": { + "$ref": "#/$defs/share_link_list_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_20list_20link" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_20list_20link" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20create": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link create" + }, + "input": { + "$ref": "#/$defs/share_link_create_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20create" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20create" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20download": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link download" + }, + "input": { + "$ref": "#/$defs/share_link_download_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20download" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20download" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20info": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link info" + }, + "input": { + "$ref": "#/$defs/share_link_info_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20info" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20info" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20list": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link list" + }, + "input": { + "$ref": "#/$defs/share_link_list_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20list" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20list" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20revoke": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link revoke" + }, + "input": { + "$ref": "#/$defs/share_link_revoke_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20revoke" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20revoke" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_share_2dlink_20update": { + "additionalProperties": false, + "properties": { + "command": { + "const": "share-link update" + }, + "input": { + "$ref": "#/$defs/share_link_update_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_share_2dlink_20update" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_share_2dlink_20update" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_stdin_stdout": { + "additionalProperties": false, + "properties": { + "reads_stdin": {}, + "stderr": {}, + "stdout": {}, + "writes_binary_stdout": {} + }, + "type": "object" + }, + "command_team_20add_2dmember": { + "additionalProperties": false, + "properties": { + "command": { + "const": "team add-member" + }, + "input": { + "$ref": "#/$defs/team_member_add_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_team_20add_2dmember" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_team_20add_2dmember" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_team_20info": { + "additionalProperties": false, + "properties": { + "command": { + "const": "team info" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_team_20info" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_team_20info" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_team_20list_2dgroups": { + "additionalProperties": false, + "properties": { + "command": { + "const": "team list-groups" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_team_20list_2dgroups" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_team_20list_2dgroups" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_team_20list_2dmembers": { + "additionalProperties": false, + "properties": { + "command": { + "const": "team list-members" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_team_20list_2dmembers" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_team_20list_2dmembers" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_team_20remove_2dmember": { + "additionalProperties": false, + "properties": { + "command": { + "const": "team remove-member" + }, + "input": { + "$ref": "#/$defs/team_member_remove_input" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_team_20remove_2dmember" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_team_20remove_2dmember" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "command_version": { + "additionalProperties": false, + "properties": { + "command": { + "const": "version" + }, + "input": { + "$ref": "#/$defs/empty" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/result_version" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "$ref": "#/$defs/warnings_version" + } + }, + "required": [ + "ok", + "schema_version", + "command", + "input", + "results", + "warnings" + ], + "type": "object" + }, + "du_allocation": { + "additionalProperties": false, + "properties": { + "allocated": {}, + "type": {}, + "used": {}, + "user_within_team_space_allocated": {}, + "user_within_team_space_limit_type": {}, + "user_within_team_space_used_cached": {} + }, + "type": "object" + }, + "du_output": { + "additionalProperties": false, + "properties": { + "allocation": {}, + "used": {} + }, + "type": "object" + }, + "empty": { + "additionalProperties": false, + "properties": {}, + "type": "object" + }, + "get_input": { + "additionalProperties": false, + "properties": { + "recursive": {}, + "source": {}, + "stdout": {}, + "target": {} + }, + "type": "object" + }, + "get_result_input": { + "additionalProperties": false, + "properties": { + "source": {}, + "target": {} + }, + "type": "object" + }, + "help_input": { + "additionalProperties": false, + "properties": { + "help": {}, + "path": {} + }, + "type": "object" + }, + "logout_result": { + "additionalProperties": false, + "properties": { + "remote_token_revoked": {}, + "removed_saved_credentials": {} + }, + "type": "object" + }, + "ls_input": { + "additionalProperties": false, + "properties": { + "include_deleted": {}, + "limit": {}, + "long": {}, + "only_deleted": {}, + "path": {}, + "recursive": {}, + "reverse": {}, + "sort": {}, + "time": {}, + "time_format": {} + }, + "type": "object" + }, + "metadata": { + "additionalProperties": false, + "properties": { + "client_modified": {}, + "deleted": {}, + "id": {}, + "path_display": {}, + "path_lower": {}, + "rev": {}, + "server_modified": {}, + "size": {}, + "type": {} + }, + "type": "object" + }, + "mkdir_input": { + "additionalProperties": false, + "properties": { + "parents": {}, + "path": {} + }, + "type": "object" + }, + "operation_output": { + "additionalProperties": false, + "properties": { + "command": {}, + "input": {}, + "ok": {}, + "results": {}, + "schema_version": {}, + "warnings": {} + }, + "type": "object" + }, + "operation_result": { + "additionalProperties": false, + "properties": { + "input": {}, + "kind": {}, + "result": {}, + "status": {} + }, + "type": "object" + }, + "put_input": { + "additionalProperties": false, + "properties": { + "if_exists": {}, + "recursive": {}, + "source": {}, + "stdin": {}, + "target": {} + }, + "type": "object" + }, + "put_result_input": { + "additionalProperties": false, + "properties": { + "source": {}, + "target": {} + }, + "type": "object" + }, + "relocation_input": { + "additionalProperties": false, + "properties": { + "from_path": {}, + "to_path": {} + }, + "type": "object" + }, + "remove_input": { + "additionalProperties": false, + "properties": { + "force": {}, + "path": {}, + "permanent": {}, + "recursive": {} + }, + "type": "object" + }, + "restore_input": { + "additionalProperties": false, + "properties": { + "path": {}, + "revision": {} + }, + "type": "object" + }, + "result_account": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/account_input" + }, + "kind": { + "enum": [ + "account" + ] + }, + "result": { + "$ref": "#/$defs/account" + }, + "status": { + "enum": [ + "found" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_cp": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/relocation_input" + }, + "kind": { + "enum": [ + "deleted", + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "copied", + "skipped" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_du": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "space_usage" + ] + }, + "result": { + "$ref": "#/$defs/du_output" + }, + "status": { + "enum": [ + "reported" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_get": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/get_result_input" + }, + "kind": { + "enum": [ + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "created", + "downloaded", + "existing" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_help": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "command" + ] + }, + "result": { + "$ref": "#/$defs/command_manifest" + }, + "status": { + "enum": [ + "described" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_logout": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "auth" + ] + }, + "result": { + "$ref": "#/$defs/logout_result" + }, + "status": { + "enum": [ + "already_logged_out", + "logged_out" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_ls": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "deleted", + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_mkdir": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/mkdir_input" + }, + "kind": { + "enum": [ + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "created", + "existing" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_mv": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/relocation_input" + }, + "kind": { + "enum": [ + "deleted", + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "moved", + "skipped" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_put": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/put_result_input" + }, + "kind": { + "enum": [ + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "created", + "existing", + "skipped", + "uploaded" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_restore": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/restore_input" + }, + "kind": { + "enum": [ + "file" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "restored" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_revs": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "revision" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_rm": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/remove_input" + }, + "kind": { + "enum": [ + "deleted", + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "deleted", + "permanently_deleted" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_search": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "deleted", + "file", + "folder" + ] + }, + "result": { + "$ref": "#/$defs/metadata" + }, + "status": { + "enum": [ + "found" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_20list_20folder": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "shared_folder" + ] + }, + "result": { + "$ref": "#/$defs/share_folder" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_20list_20link": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_metadata" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20create": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_metadata" + }, + "status": { + "enum": [ + "created", + "existing" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20download": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_download_result" + }, + "status": { + "enum": [ + "downloaded" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20info": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_metadata" + }, + "status": { + "enum": [ + "found" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20list": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_metadata" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20revoke": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link", + "shared_link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_revoke_result" + }, + "status": { + "enum": [ + "revoked" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_share_2dlink_20update": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "file", + "folder", + "link" + ] + }, + "result": { + "$ref": "#/$defs/share_link_metadata" + }, + "status": { + "enum": [ + "updated" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_team_20add_2dmember": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/team_member_add_input" + }, + "kind": { + "enum": [ + "team_member" + ] + }, + "result": { + "$ref": "#/$defs/team_member_mutation" + }, + "status": { + "enum": [ + "added", + "completed", + "started" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_team_20info": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "team" + ] + }, + "result": { + "$ref": "#/$defs/team_info" + }, + "status": { + "enum": [ + "found" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_team_20list_2dgroups": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "team_group" + ] + }, + "result": { + "$ref": "#/$defs/team_group" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_team_20list_2dmembers": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "team_member" + ] + }, + "result": { + "$ref": "#/$defs/team_member" + }, + "status": { + "enum": [ + "listed" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_team_20remove_2dmember": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/team_member_remove_input" + }, + "kind": { + "enum": [ + "team_member" + ] + }, + "result": { + "$ref": "#/$defs/team_member_mutation" + }, + "status": { + "enum": [ + "completed", + "removed", + "started" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "result_version": { + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/empty" + }, + "kind": { + "enum": [ + "version" + ] + }, + "result": { + "$ref": "#/$defs/version" + }, + "status": { + "enum": [ + "reported" + ] + } + }, + "required": [ + "status", + "kind", + "input", + "result" + ], + "type": "object" + }, + "revs_input": { + "additionalProperties": false, + "properties": { + "limit": {}, + "long": {}, + "path": {}, + "time": {}, + "time_format": {} + }, + "type": "object" + }, + "search_input": { + "additionalProperties": false, + "properties": { + "content": {}, + "limit": {}, + "long": {}, + "order_by": {}, + "path": {}, + "query": {}, + "reverse": {}, + "sort": {}, + "time": {}, + "time_format": {} + }, + "type": "object" + }, + "share_folder": { + "additionalProperties": false, + "properties": { + "access_inheritance": {}, + "access_type": {}, + "is_inside_team_folder": {}, + "is_team_folder": {}, + "name": {}, + "owner_display_names": {}, + "parent_folder_name": {}, + "parent_shared_folder_id": {}, + "path_lower": {}, + "preview_url": {}, + "shared_folder_id": {}, + "time_invited": {}, + "type": {} + }, + "type": "object" + }, + "share_link_create_input": { + "additionalProperties": false, + "properties": { + "access": {}, + "allow_download": {}, + "audience": {}, + "disallow_download": {}, + "expires": {}, + "password": {}, + "path": {}, + "remove_expiration": {} + }, + "type": "object" + }, + "share_link_download_input": { + "additionalProperties": false, + "properties": { + "password": {}, + "path": {}, + "recursive": {}, + "target": {}, + "url": {} + }, + "type": "object" + }, + "share_link_download_result": { + "additionalProperties": false, + "properties": { + "link": {}, + "target": {} + }, + "type": "object" + }, + "share_link_info_input": { + "additionalProperties": false, + "properties": { + "password": {}, + "path": {}, + "url": {} + }, + "type": "object" + }, + "share_link_list_input": { + "additionalProperties": false, + "properties": { + "direct_only": {}, + "path": {} + }, + "type": "object" + }, + "share_link_metadata": { + "additionalProperties": false, + "properties": { + "client_modified": {}, + "expires": {}, + "id": {}, + "name": {}, + "path_lower": {}, + "permissions": {}, + "rev": {}, + "server_modified": {}, + "size": {}, + "type": {}, + "url": {} + }, + "type": "object" + }, + "share_link_permissions": { + "additionalProperties": false, + "properties": { + "access_level": {}, + "allow_comments": {}, + "allow_download": {}, + "can_allow_download": {}, + "can_disallow_download": {}, + "can_remove_expiry": {}, + "can_remove_password": {}, + "can_revoke": {}, + "can_set_expiry": {}, + "can_set_password": {}, + "can_use_extended_sharing_controls": {}, + "effective_audience": {}, + "requested_visibility": {}, + "require_password": {}, + "resolved_visibility": {} + }, + "type": "object" + }, + "share_link_revoke_input": { + "additionalProperties": false, + "properties": { + "path": {}, + "url": {} + }, + "type": "object" + }, + "share_link_revoke_result": { + "additionalProperties": false, + "properties": { + "link": {}, + "url": {} + }, + "type": "object" + }, + "share_link_update_input": { + "additionalProperties": false, + "properties": { + "allow_download": {}, + "audience": {}, + "disallow_download": {}, + "expires": {}, + "password": {}, + "remove_expiration": {}, + "remove_password": {}, + "url": {} + }, + "type": "object" + }, + "team_group": { + "additionalProperties": false, + "properties": { + "group_external_id": {}, + "group_id": {}, + "group_management_type": {}, + "group_name": {}, + "member_count": {}, + "type": {} + }, + "type": "object" + }, + "team_info": { + "additionalProperties": false, + "properties": { + "name": {}, + "num_licensed_users": {}, + "num_provisioned_users": {}, + "team_id": {}, + "type": {} + }, + "type": "object" + }, + "team_member": { + "additionalProperties": false, + "properties": { + "account_id": {}, + "email": {}, + "email_verified": {}, + "external_id": {}, + "groups": {}, + "invited_on": {}, + "is_directory_restricted": {}, + "joined_on": {}, + "member_folder_id": {}, + "membership_type": {}, + "name": {}, + "persistent_id": {}, + "profile_photo_url": {}, + "role": {}, + "status": {}, + "suspended_on": {}, + "team_member_id": {}, + "type": {} + }, + "type": "object" + }, + "team_member_add_input": { + "additionalProperties": false, + "properties": { + "email": {}, + "first_name": {}, + "last_name": {} + }, + "type": "object" + }, + "team_member_add_item": { + "additionalProperties": false, + "properties": { + "email": {}, + "member": {}, + "tag": {} + }, + "type": "object" + }, + "team_member_mutation": { + "additionalProperties": false, + "properties": { + "async_job_id": {}, + "results": {}, + "tag": {}, + "type": {} + }, + "type": "object" + }, + "team_member_remove_input": { + "additionalProperties": false, + "properties": { + "email": {} + }, + "type": "object" + }, + "version": { + "additionalProperties": false, + "properties": { + "sdk_version": {}, + "spec_version": {}, + "version": {} + }, + "type": "object" + }, + "warning": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "warnings_account": { + "items": false, + "type": "array" + }, + "warnings_cp": { + "items": false, + "type": "array" + }, + "warnings_du": { + "items": false, + "type": "array" + }, + "warnings_get": { + "items": false, + "type": "array" + }, + "warnings_help": { + "items": false, + "type": "array" + }, + "warnings_logout": { + "items": { + "allOf": [ + { + "$ref": "#/$defs/warning" + }, + { + "properties": { + "code": { + "enum": [ + "token_revoke_failed" + ] + } + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "warnings_ls": { + "items": false, + "type": "array" + }, + "warnings_mkdir": { + "items": false, + "type": "array" + }, + "warnings_mv": { + "items": false, + "type": "array" + }, + "warnings_put": { + "items": { + "allOf": [ + { + "$ref": "#/$defs/warning" + }, + { + "properties": { + "code": { + "enum": [ + "skipped_symlink" + ] + } + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "warnings_restore": { + "items": false, + "type": "array" + }, + "warnings_revs": { + "items": false, + "type": "array" + }, + "warnings_rm": { + "items": false, + "type": "array" + }, + "warnings_search": { + "items": false, + "type": "array" + }, + "warnings_share_20list_20folder": { + "items": false, + "type": "array" + }, + "warnings_share_20list_20link": { + "items": { + "allOf": [ + { + "$ref": "#/$defs/warning" + }, + { + "properties": { + "code": { + "enum": [ + "deprecated_command" + ] + } + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "warnings_share_2dlink_20create": { + "items": false, + "type": "array" + }, + "warnings_share_2dlink_20download": { + "items": false, + "type": "array" + }, + "warnings_share_2dlink_20info": { + "items": false, + "type": "array" + }, + "warnings_share_2dlink_20list": { + "items": false, + "type": "array" + }, + "warnings_share_2dlink_20revoke": { + "items": false, + "type": "array" + }, + "warnings_share_2dlink_20update": { + "items": false, + "type": "array" + }, + "warnings_team_20add_2dmember": { + "items": false, + "type": "array" + }, + "warnings_team_20info": { + "items": false, + "type": "array" + }, + "warnings_team_20list_2dgroups": { + "items": false, + "type": "array" + }, + "warnings_team_20list_2dmembers": { + "items": false, + "type": "array" + }, + "warnings_team_20remove_2dmember": { + "items": false, + "type": "array" + }, + "warnings_version": { + "items": false, + "type": "array" + } + }, + "$id": "https://raw.githubusercontent.com/dropbox/dbxcli/master/docs/json-schema/v1/commands.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "$ref": "#/$defs/command_account" + }, + { + "$ref": "#/$defs/command_cp" + }, + { + "$ref": "#/$defs/command_du" + }, + { + "$ref": "#/$defs/command_get" + }, + { + "$ref": "#/$defs/command_help" + }, + { + "$ref": "#/$defs/command_logout" + }, + { + "$ref": "#/$defs/command_ls" + }, + { + "$ref": "#/$defs/command_mkdir" + }, + { + "$ref": "#/$defs/command_mv" + }, + { + "$ref": "#/$defs/command_put" + }, + { + "$ref": "#/$defs/command_restore" + }, + { + "$ref": "#/$defs/command_revs" + }, + { + "$ref": "#/$defs/command_rm" + }, + { + "$ref": "#/$defs/command_search" + }, + { + "$ref": "#/$defs/command_share_20list_20folder" + }, + { + "$ref": "#/$defs/command_share_20list_20link" + }, + { + "$ref": "#/$defs/command_share_2dlink_20create" + }, + { + "$ref": "#/$defs/command_share_2dlink_20download" + }, + { + "$ref": "#/$defs/command_share_2dlink_20info" + }, + { + "$ref": "#/$defs/command_share_2dlink_20list" + }, + { + "$ref": "#/$defs/command_share_2dlink_20revoke" + }, + { + "$ref": "#/$defs/command_share_2dlink_20update" + }, + { + "$ref": "#/$defs/command_team_20add_2dmember" + }, + { + "$ref": "#/$defs/command_team_20info" + }, + { + "$ref": "#/$defs/command_team_20list_2dgroups" + }, + { + "$ref": "#/$defs/command_team_20list_2dmembers" + }, + { + "$ref": "#/$defs/command_team_20remove_2dmember" + }, + { + "$ref": "#/$defs/command_version" + } + ], + "title": "dbxcli command-specific JSON success responses", + "type": "object" +} diff --git a/docs/json-schema/v1/manifest.schema.json b/docs/json-schema/v1/manifest.schema.json index f79ab77..d0a4590 100644 --- a/docs/json-schema/v1/manifest.schema.json +++ b/docs/json-schema/v1/manifest.schema.json @@ -262,6 +262,9 @@ }, "command_contract": { "type": "string" + }, + "command_success_schema": { + "type": "string" } } }, diff --git a/internal/jsonschema/commands.go b/internal/jsonschema/commands.go new file mode 100644 index 0000000..ef722d0 --- /dev/null +++ b/internal/jsonschema/commands.go @@ -0,0 +1,262 @@ +// Copyright © 2026 Dropbox, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package jsonschema + +import ( + "encoding/json" + "fmt" + "sort" +) + +const ( + Draft202012 = "https://json-schema.org/draft/2020-12/schema" + CommandSuccessSchemaID = "https://raw.githubusercontent.com/dropbox/dbxcli/master/docs/json-schema/v1/commands.schema.json" +) + +type CommandCatalog struct { + Definitions map[string][]string `json:"definitions"` + Commands map[string]CommandContract `json:"commands"` +} + +type CommandContract struct { + TopLevel string `json:"top_level"` + ResultWrapper string `json:"result_wrapper"` + Input string `json:"input"` + ResultInput *string `json:"result_input"` + Result string `json:"result"` + Statuses []string `json:"statuses"` + Kinds []string `json:"kinds"` + Warnings []string `json:"warnings"` +} + +func DecodeCommandCatalog(data []byte) (CommandCatalog, error) { + var catalog CommandCatalog + if err := json.Unmarshal(data, &catalog); err != nil { + return CommandCatalog{}, err + } + if len(catalog.Definitions) == 0 { + return CommandCatalog{}, fmt.Errorf("command catalog has no definitions") + } + if len(catalog.Commands) == 0 { + return CommandCatalog{}, fmt.Errorf("command catalog has no commands") + } + return catalog, nil +} + +func GenerateCommandSuccessSchema(catalog CommandCatalog) (map[string]any, error) { + defs := map[string]any{ + "warning": warningSchema(), + } + + for _, name := range sortedMapKeys(catalog.Definitions) { + defs[name] = objectSchema(catalog.Definitions[name]) + } + + commandRefs := make([]any, 0, len(catalog.Commands)) + for _, command := range sortedMapKeys(catalog.Commands) { + contract := catalog.Commands[command] + if err := validateCommandContract(command, contract, catalog.Definitions); err != nil { + return nil, err + } + + commandDef := commandSchemaDefinitionName(command) + resultDef := commandResultDefinitionName(command) + warningDef := commandWarningDefinitionName(command) + + defs[resultDef] = commandResultSchema(contract) + defs[warningDef] = commandWarningsSchema(contract.Warnings) + defs[commandDef] = commandSuccessSchema(command, contract, resultDef, warningDef) + commandRefs = append(commandRefs, ref(commandDef)) + } + + return map[string]any{ + "$schema": Draft202012, + "$id": CommandSuccessSchemaID, + "title": "dbxcli command-specific JSON success responses", + "type": "object", + "oneOf": commandRefs, + "$defs": defs, + }, nil +} + +func MarshalCanonical(value any) ([]byte, error) { + data, err := json.MarshalIndent(value, "", " ") + if err != nil { + return nil, err + } + return append(data, '\n'), nil +} + +func validateCommandContract(command string, contract CommandContract, definitions map[string][]string) error { + if contract.TopLevel != "operation_output" { + return fmt.Errorf("%s: unsupported top_level %q", command, contract.TopLevel) + } + if contract.ResultWrapper != "operation_result" { + return fmt.Errorf("%s: unsupported result_wrapper %q", command, contract.ResultWrapper) + } + for _, refName := range []string{contract.Input, contract.Result} { + if _, ok := definitions[refName]; !ok { + return fmt.Errorf("%s: unknown definition %q", command, refName) + } + } + if contract.ResultInput == nil { + return fmt.Errorf("%s: missing result_input", command) + } + if _, ok := definitions[*contract.ResultInput]; !ok { + return fmt.Errorf("%s: unknown result_input definition %q", command, *contract.ResultInput) + } + if len(contract.Statuses) == 0 { + return fmt.Errorf("%s: missing statuses", command) + } + if len(contract.Kinds) == 0 { + return fmt.Errorf("%s: missing kinds", command) + } + return nil +} + +func commandSuccessSchema(command string, contract CommandContract, resultDef string, warningDef string) map[string]any { + return map[string]any{ + "type": "object", + "additionalProperties": false, + "required": []string{"ok", "schema_version", "command", "input", "results", "warnings"}, + "properties": map[string]any{ + "ok": map[string]any{"const": true}, + "schema_version": map[string]any{"const": "1"}, + "command": map[string]any{"const": command}, + "input": ref(contract.Input), + "results": map[string]any{ + "type": "array", + "items": ref(resultDef), + }, + "warnings": ref(warningDef), + }, + } +} + +func commandResultSchema(contract CommandContract) map[string]any { + return map[string]any{ + "type": "object", + "additionalProperties": false, + "required": []string{"status", "kind", "input", "result"}, + "properties": map[string]any{ + "status": map[string]any{"enum": sortedCopy(contract.Statuses)}, + "kind": map[string]any{"enum": sortedCopy(contract.Kinds)}, + "input": ref(*contract.ResultInput), + "result": ref(contract.Result), + }, + } +} + +func commandWarningsSchema(codes []string) map[string]any { + if len(codes) == 0 { + return map[string]any{ + "type": "array", + "items": false, + } + } + return map[string]any{ + "type": "array", + "items": map[string]any{ + "allOf": []any{ + ref("warning"), + map[string]any{ + "type": "object", + "properties": map[string]any{ + "code": map[string]any{"enum": sortedCopy(codes)}, + }, + }, + }, + }, + } +} + +func objectSchema(fields []string) map[string]any { + properties := make(map[string]any, len(fields)) + for _, field := range fields { + properties[field] = map[string]any{} + } + return map[string]any{ + "type": "object", + "additionalProperties": false, + "properties": properties, + } +} + +func warningSchema() map[string]any { + return map[string]any{ + "type": "object", + "additionalProperties": false, + "required": []string{"code", "message"}, + "properties": map[string]any{ + "code": map[string]any{"type": "string"}, + "message": map[string]any{"type": "string"}, + "path": map[string]any{"type": "string"}, + }, + } +} + +func ref(defName string) map[string]any { + return map[string]any{"$ref": "#/$defs/" + defName} +} + +func commandSchemaDefinitionName(command string) string { + return CommandDefinitionName(command) +} + +func commandResultDefinitionName(command string) string { + return schemaDefinitionName("result", command) +} + +func commandWarningDefinitionName(command string) string { + return schemaDefinitionName("warnings", command) +} + +func CommandDefinitionName(command string) string { + return schemaDefinitionName("command", command) +} + +func schemaDefinitionName(prefix string, value string) string { + result := prefix + "_" + for _, r := range value { + switch { + case r >= 'a' && r <= 'z': + result += string(r) + case r >= 'A' && r <= 'Z': + result += string(r) + case r >= '0' && r <= '9': + result += string(r) + case r == '_': + result += string(r) + default: + result += fmt.Sprintf("_%x", r) + } + } + return result +} + +func sortedMapKeys[V any](values map[string]V) []string { + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func sortedCopy(values []string) []string { + copied := append([]string{}, values...) + sort.Strings(copied) + return copied +} diff --git a/tools/gen-docs/main.go b/tools/gen-docs/main.go index a7960ea..c541dc8 100644 --- a/tools/gen-docs/main.go +++ b/tools/gen-docs/main.go @@ -229,6 +229,9 @@ func commandMetadataSection(command *cobra.Command) []byte { if manifest.SchemaRefs.CommandContract != "" { buf.WriteString("* JSON contract: `" + manifest.SchemaRefs.CommandContract + "`\n") } + if manifest.SchemaRefs.CommandSuccessSchema != "" { + buf.WriteString("* JSON success schema: `" + manifest.SchemaRefs.CommandSuccessSchema + "`\n") + } buf.WriteString("\n") return buf.Bytes() diff --git a/tools/gen-json-schemas/main.go b/tools/gen-json-schemas/main.go new file mode 100644 index 0000000..c88c90f --- /dev/null +++ b/tools/gen-json-schemas/main.go @@ -0,0 +1,65 @@ +// Copyright © 2026 Dropbox, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" + + "github.com/dropbox/dbxcli/v3/internal/jsonschema" +) + +const ( + commandCatalogPath = "docs/json-schema/v1/commands.json" + commandSuccessSchemaPath = "docs/json-schema/v1/commands.schema.json" +) + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run() error { + if _, err := os.Stat("go.mod"); err != nil { + return fmt.Errorf("run from repository root: %w", err) + } + + data, err := os.ReadFile(commandCatalogPath) + if err != nil { + return fmt.Errorf("read %s: %w", commandCatalogPath, err) + } + + catalog, err := jsonschema.DecodeCommandCatalog(data) + if err != nil { + return fmt.Errorf("decode %s: %w", commandCatalogPath, err) + } + + schema, err := jsonschema.GenerateCommandSuccessSchema(catalog) + if err != nil { + return fmt.Errorf("generate command success schema: %w", err) + } + + encoded, err := jsonschema.MarshalCanonical(schema) + if err != nil { + return fmt.Errorf("marshal command success schema: %w", err) + } + + if err := os.WriteFile(commandSuccessSchemaPath, encoded, 0o644); err != nil { + return fmt.Errorf("write %s: %w", commandSuccessSchemaPath, err) + } + return nil +}