diff --git a/cmd/json_command_schema_test.go b/cmd/json_command_schema_test.go index 4f5e7d8..0bdcb50 100644 --- a/cmd/json_command_schema_test.go +++ b/cmd/json_command_schema_test.go @@ -74,6 +74,34 @@ func TestPublicCommandSuccessSchemaRejectsInvalidStatus(t *testing.T) { } } +func TestPublicCommandSuccessSchemaRejectsInvalidPrimitiveType(t *testing.T) { + schema := loadJSONValueFile(t, "../docs/json-schema/v1/commands.schema.json") + validator := newSubsetJSONSchemaValidator(t, schema) + + value := decodeJSONValue(t, loadJSONGoldenSuccessOutputs(t)["put"]) + root := value.(map[string]any) + input := root["input"].(map[string]any) + input["recursive"] = "false" + + if err := validator.validate(schema, value, "$"); err == nil { + t.Fatal("invalid primitive type validated successfully") + } +} + +func TestJSONHelpManifestsValidateAgainstPublicManifestSchema(t *testing.T) { + schema := loadJSONValueFile(t, "../docs/json-schema/v1/manifest.schema.json") + validator := newSubsetJSONSchemaValidator(t, schema) + + RootCmd.InitDefaultHelpCmd() + for _, command := range publicCommandSubtree(RootCmd) { + manifest := jsonCommandManifestFor(command) + value := normalizeJSONValue(t, manifest) + if err := validator.validate(schema, value, manifest.Path); err != nil { + t.Fatalf("manifest %q does not validate: %v", manifest.Path, err) + } + } +} + func loadCommandSchemaCatalog(t *testing.T) schemagen.CommandCatalog { t.Helper() @@ -209,11 +237,12 @@ func (v subsetJSONSchemaValidator) validateObjectSchema(schema map[string]any, v } 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 := validateJSONSchemaTypeValue(path, value, typeValue); err != nil { + return err } - if err := validateJSONSchemaType(path, value, typeName); err != nil { + } + if minimumValue, ok := schema["minimum"]; ok { + if err := validateJSONSchemaMinimum(path, value, minimumValue); err != nil { return err } } @@ -236,11 +265,27 @@ func (v subsetJSONSchemaValidator) validateObjectSchema(schema map[string]any, v 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) + if additionalProperties, ok := schema["additionalProperties"]; ok { + switch additionalProperties := additionalProperties.(type) { + case bool: + if !additionalProperties { + for name := range object { + if _, ok := properties[name]; !ok { + return fmt.Errorf("%s has unexpected property %q", path, name) + } + } } + case map[string]any: + for name, propertyValue := range object { + if _, ok := properties[name]; ok { + continue + } + if err := v.validate(additionalProperties, propertyValue, path+"."+name); err != nil { + return err + } + } + default: + return fmt.Errorf("%s additionalProperties is %T, want bool or object", path, additionalProperties) } } for name, propertySchema := range properties { @@ -287,6 +332,29 @@ func (v subsetJSONSchemaValidator) resolveRef(ref string) (any, error) { return current, nil } +func validateJSONSchemaTypeValue(path string, value any, typeValue any) error { + switch typeValue := typeValue.(type) { + case string: + return validateJSONSchemaType(path, value, typeValue) + case []any: + var errors []string + for _, item := range typeValue { + typeName, ok := item.(string) + if !ok { + return fmt.Errorf("%s type entry is %T, want string", path, item) + } + if err := validateJSONSchemaType(path, value, typeName); err == nil { + return nil + } else { + errors = append(errors, err.Error()) + } + } + return fmt.Errorf("%s did not match any allowed type: %s", path, strings.Join(errors, "; ")) + default: + return fmt.Errorf("%s type is %T, want string or array", path, typeValue) + } +} + func validateJSONSchemaType(path string, value any, typeName string) error { switch typeName { case "object": @@ -319,3 +387,18 @@ func validateJSONSchemaType(path string, value any, typeName string) error { } return nil } + +func validateJSONSchemaMinimum(path string, value any, minimumValue any) error { + minimum, ok := minimumValue.(float64) + if !ok { + return fmt.Errorf("%s minimum is %T, want number", path, minimumValue) + } + number, ok := value.(float64) + if !ok { + return fmt.Errorf("%s minimum on non-number %T", path, value) + } + if number < minimum { + return fmt.Errorf("%s = %v, want >= %v", path, number, minimum) + } + return nil +} diff --git a/cmd/json_contract_test.go b/cmd/json_contract_test.go index 6faee0a..b4cf5c7 100644 --- a/cmd/json_contract_test.go +++ b/cmd/json_contract_test.go @@ -653,7 +653,7 @@ func jsonGoldenSuccessOutputExamples() map[string]jsonOperationOutput { "help": newJSONOperationOutput(jsonHelpInput{Help: true, Path: "ls"}, []jsonOperationResult{ newJSONOperationResult(jsonHelpStatusDescribed, jsonHelpKindCommand, nil, jsonCommandManifestFor(lsCmd)), }, nil), - "ls": newJSONOperationOutput(lsInput{Path: "/Reports", Recursive: false, IncludeDeleted: true, OnlyDeleted: false, Long: true, Sort: "name", Reverse: false, Time: "server", TimeFormat: "2006-01-02"}, []jsonOperationResult{ + "ls": newJSONOperationOutput(lsInput{Path: "/Reports", Recursive: false, IncludeDeleted: true, OnlyDeleted: false, Long: true, Sort: "type", Reverse: false, Time: "server", TimeFormat: "2006-01-02"}, []jsonOperationResult{ newJSONOperationResult(lsJSONStatusListed, file.Type, nil, file), }, nil), "logout": newJSONOperationOutput(nil, []jsonOperationResult{ @@ -677,7 +677,7 @@ func jsonGoldenSuccessOutputExamples() map[string]jsonOperationOutput { "rm": newJSONOperationOutput(nil, []jsonOperationResult{ newJSONOperationResult(removeJSONStatusDeleted, file.Type, removeInput{Path: "/Reports/old.pdf", Permanent: false, Recursive: false, Force: false}, file), }, nil), - "search": newJSONOperationOutput(searchInput{Query: "report", Path: "/Reports", Long: true, Sort: "name", Reverse: false, Time: "server", TimeFormat: "2006-01-02"}, []jsonOperationResult{ + "search": newJSONOperationOutput(searchInput{Query: "report", Path: "/Reports", Long: true, Sort: "type", Reverse: false, Time: "server", TimeFormat: "2006-01-02"}, []jsonOperationResult{ newJSONOperationResult(searchJSONStatusFound, folder.Type, nil, folder), }, nil), "share list folder": newJSONOperationOutput(shareFolderListInput{}, []jsonOperationResult{ @@ -686,7 +686,7 @@ func jsonGoldenSuccessOutputExamples() map[string]jsonOperationOutput { "share list link": newJSONOperationOutput(shareLinkListInput{Path: "/Reports/old.pdf", DirectOnly: true}, []jsonOperationResult{ shareLinkJSONOperationResult(shareLinkJSONStatusListed, sharedLink), }, []jsonWarning{{Code: jsonWarningCodeDeprecatedCommand, Message: "use `dbxcli share-link list` instead"}}), - "share-link create": newJSONOperationOutput(shareLinkCreateInput{Path: "/Reports/old.pdf", Access: "viewer", Audience: "public", Expires: "2026-07-01T00:00:00Z", RemoveExpiration: false, AllowDownload: true, DisallowDownload: false, Password: true}, []jsonOperationResult{ + "share-link create": newJSONOperationOutput(shareLinkCreateInput{Path: "/Reports/old.pdf", Access: "max", Audience: "public", Expires: "2026-07-01T00:00:00Z", RemoveExpiration: false, AllowDownload: true, DisallowDownload: false, Password: true}, []jsonOperationResult{ shareLinkJSONOperationResult(shareLinkJSONStatusCreated, sharedLink), }, nil), "share-link download": newJSONOperationOutput(shareLinkDownloadInput{URL: sharedLink.URL, Target: "old.pdf", Path: "/old.pdf", Recursive: false, Password: true}, []jsonOperationResult{ diff --git a/cmd/testdata/json_contract/success_outputs.json b/cmd/testdata/json_contract/success_outputs.json index e4df27c..ef3ae5c 100644 --- a/cmd/testdata/json_contract/success_outputs.json +++ b/cmd/testdata/json_contract/success_outputs.json @@ -419,7 +419,7 @@ ], "warnings": [] }, - "ls": {"ok":true,"schema_version":"1","command":"ls","input":{"path":"/Reports","recursive":false,"include_deleted":true,"only_deleted":false,"long":true,"sort":"name","reverse":false,"time":"server","time_format":"2006-01-02"},"results":[{"status":"listed","kind":"file","result":{"type":"file","path_display":"/Reports/old.pdf","path_lower":"/reports/old.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"},"input":{}}],"warnings":[]}, + "ls": {"ok":true,"schema_version":"1","command":"ls","input":{"path":"/Reports","recursive":false,"include_deleted":true,"only_deleted":false,"long":true,"sort":"type","reverse":false,"time":"server","time_format":"2006-01-02"},"results":[{"status":"listed","kind":"file","result":{"type":"file","path_display":"/Reports/old.pdf","path_lower":"/reports/old.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"},"input":{}}],"warnings":[]}, "logout": {"ok":true,"schema_version":"1","command":"logout","input":{},"results":[{"status":"logged_out","kind":"auth","input":{},"result":{"removed_saved_credentials":true,"remote_token_revoked":true}}],"warnings":[]}, "mkdir": {"ok":true,"schema_version":"1","command":"mkdir","input":{"path":"/Reports/new","parents":true},"results":[{"status":"created","kind":"folder","input":{"path":"/Reports/new","parents":true},"result":{"type":"folder","path_display":"/Reports/new","path_lower":"/reports/new","id":"id:folder"}}],"warnings":[]}, "mv": {"ok":true,"schema_version":"1","command":"mv","input":{},"results":[{"input":{"from_path":"/Reports/copy.pdf","to_path":"/Reports/moved.pdf"},"result":{"type":"file","path_display":"/Reports/moved.pdf","path_lower":"/reports/moved.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"},"status":"moved","kind":"file"}],"warnings":[]}, @@ -427,10 +427,10 @@ "restore": {"ok":true,"schema_version":"1","command":"restore","input":{"path":"/Reports/old.pdf","revision":"015f"},"results":[{"status":"restored","kind":"file","input":{"path":"/Reports/old.pdf","revision":"015f"},"result":{"type":"file","path_display":"/Reports/old.pdf","path_lower":"/reports/old.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"}}],"warnings":[]}, "revs": {"ok":true,"schema_version":"1","command":"revs","input":{"path":"/Reports/old.pdf","long":true,"time":"server","time_format":"2006-01-02"},"results":[{"status":"revision","kind":"file","result":{"type":"file","path_display":"/Reports/old.pdf","path_lower":"/reports/old.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"},"input":{}}],"warnings":[]}, "rm": {"ok":true,"schema_version":"1","command":"rm","input":{},"results":[{"input":{"path":"/Reports/old.pdf","permanent":false,"recursive":false,"force":false},"result":{"type":"file","path_display":"/Reports/old.pdf","path_lower":"/reports/old.pdf","id":"id:file","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z"},"status":"deleted","kind":"file"}],"warnings":[]}, - "search": {"ok":true,"schema_version":"1","command":"search","input":{"query":"report","path":"/Reports","content":false,"long":true,"sort":"name","reverse":false,"time":"server","time_format":"2006-01-02"},"results":[{"status":"found","kind":"folder","result":{"type":"folder","path_display":"/Reports","path_lower":"/reports","id":"id:folder"},"input":{}}],"warnings":[]}, + "search": {"ok":true,"schema_version":"1","command":"search","input":{"query":"report","path":"/Reports","content":false,"long":true,"sort":"type","reverse":false,"time":"server","time_format":"2006-01-02"},"results":[{"status":"found","kind":"folder","result":{"type":"folder","path_display":"/Reports","path_lower":"/reports","id":"id:folder"},"input":{}}],"warnings":[]}, "share list folder": {"ok":true,"schema_version":"1","command":"share list folder","input":{},"results":[{"status":"listed","kind":"shared_folder","result":{"type":"shared_folder","name":"Reports","path_lower":"/reports","shared_folder_id":"sfid:reports","preview_url":"https://www.dropbox.com/preview","access_type":"owner","is_inside_team_folder":false,"is_team_folder":true,"owner_display_names":["Ada Lovelace"],"parent_shared_folder_id":"sfid:parent","parent_folder_name":"Parent","time_invited":"2026-06-25T10:00:00Z","access_inheritance":"inherit"},"input":{}}],"warnings":[]}, "share list link": {"ok":true,"schema_version":"1","command":"share list link","input":{"path":"/Reports/old.pdf","direct_only":true},"results":[{"status":"listed","kind":"file","result":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}},"input":{}}],"warnings":[{"code":"deprecated_command","message":"use `dbxcli share-link list` instead"}]}, - "share-link create": {"ok":true,"schema_version":"1","command":"share-link create","input":{"path":"/Reports/old.pdf","access":"viewer","audience":"public","expires":"2026-07-01T00:00:00Z","allow_download":true,"password":true},"results":[{"status":"created","kind":"file","result":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}},"input":{}}],"warnings":[]}, + "share-link create": {"ok":true,"schema_version":"1","command":"share-link create","input":{"path":"/Reports/old.pdf","access":"max","audience":"public","expires":"2026-07-01T00:00:00Z","allow_download":true,"password":true},"results":[{"status":"created","kind":"file","result":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}},"input":{}}],"warnings":[]}, "share-link download": {"ok":true,"schema_version":"1","command":"share-link download","input":{"url":"https://www.dropbox.com/s/example/old.pdf","target":"old.pdf","path":"/old.pdf","password":true},"results":[{"status":"downloaded","kind":"file","result":{"target":"old.pdf","link":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}}},"input":{}}],"warnings":[]}, "share-link info": {"ok":true,"schema_version":"1","command":"share-link info","input":{"url":"https://www.dropbox.com/s/example/old.pdf","path":"/old.pdf","password":true},"results":[{"status":"found","kind":"file","result":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}},"input":{}}],"warnings":[]}, "share-link list": {"ok":true,"schema_version":"1","command":"share-link list","input":{"path":"/Reports/old.pdf","direct_only":true},"results":[{"status":"listed","kind":"file","result":{"type":"file","url":"https://www.dropbox.com/s/example/old.pdf","name":"old.pdf","path_lower":"/reports/old.pdf","id":"id:shared-file","expires":"2026-07-01T00:00:00Z","rev":"015f","size":123,"server_modified":"2026-06-25T12:00:00Z","client_modified":"2026-06-25T11:00:00Z","permissions":{"resolved_visibility":"public","requested_visibility":"public","effective_audience":"public","access_level":"viewer","can_revoke":true,"allow_download":true,"can_set_expiry":true,"can_remove_expiry":true,"can_allow_download":true,"can_disallow_download":true,"allow_comments":false,"can_set_password":true,"can_remove_password":true,"require_password":true,"can_use_extended_sharing_controls":true}},"input":{}}],"warnings":[]}, diff --git a/docs/automation.md b/docs/automation.md index 2d26053..eef7cc9 100644 --- a/docs/automation.md +++ b/docs/automation.md @@ -74,8 +74,8 @@ 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. +command-specific success validation for `input`, `results`, primitive field +types, statuses, kinds, and warning codes. ## JSON help manifest diff --git a/docs/json-schema/v1/README.md b/docs/json-schema/v1/README.md index 478c276..871dafd 100644 --- a/docs/json-schema/v1/README.md +++ b/docs/json-schema/v1/README.md @@ -60,16 +60,17 @@ 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: +`commands.schema.json` is generated from that catalog plus schema metadata in +the generator: ```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. +The command-specific schema locks which fields may appear, which fields are +required when stable, primitive field types, stable nested objects, result +statuses/kinds, and warning codes. It intentionally avoids over-modeling +Dropbox SDK enum values that dbxcli does not own. ## Command Manifest v1 diff --git a/docs/json-schema/v1/commands.schema.json b/docs/json-schema/v1/commands.schema.json index cca6bd1..79b7ce2 100644 --- a/docs/json-schema/v1/commands.schema.json +++ b/docs/json-schema/v1/commands.schema.json @@ -3,58 +3,131 @@ "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": {} + "account_id": { + "type": "string" + }, + "account_type": { + "type": "string" + }, + "auth": { + "$ref": "#/$defs/account_auth" + }, + "disabled": { + "type": "boolean" + }, + "email": { + "type": "string" + }, + "email_verified": { + "type": "boolean" + }, + "is_paired": { + "type": "boolean" + }, + "is_teammate": { + "type": "boolean" + }, + "locale": { + "type": "string" + }, + "name": { + "$ref": "#/$defs/account_name" + }, + "profile_photo_url": { + "type": "string" + }, + "referral_link": { + "type": "string" + }, + "team": { + "$ref": "#/$defs/account_team" + }, + "team_member_id": { + "type": "string" + }, + "type": { + "enum": [ + "basic", + "full" + ], + "type": "string" + } }, + "required": [ + "account_id", + "disabled", + "email", + "email_verified", + "type" + ], "type": "object" }, "account_auth": { "additionalProperties": false, "properties": { - "auth_file": {}, - "refreshable": {}, - "source": {} + "auth_file": { + "type": "string" + }, + "refreshable": { + "type": "boolean" + }, + "source": { + "type": "string" + } }, + "required": [ + "refreshable", + "source" + ], "type": "object" }, "account_input": { "additionalProperties": false, "properties": { - "account_id": {} + "account_id": { + "type": "string" + } }, "type": "object" }, "account_name": { "additionalProperties": false, "properties": { - "abbreviated_name": {}, - "display_name": {}, - "familiar_name": {}, - "given_name": {}, - "surname": {} + "abbreviated_name": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "familiar_name": { + "type": "string" + }, + "given_name": { + "type": "string" + }, + "surname": { + "type": "string" + } }, "type": "object" }, "account_team": { "additionalProperties": false, "properties": { - "id": {}, - "member_id": {}, - "name": {} + "id": { + "type": "string" + }, + "member_id": { + "type": "string" + }, + "name": { + "type": "string" + } }, + "required": [ + "id", + "name" + ], "type": "object" }, "command_account": { @@ -95,15 +168,44 @@ "command_arg": { "additionalProperties": false, "properties": { - "description": {}, - "enum_values": {}, - "name": {}, - "placement": {}, - "required": {}, - "stream_dash": {}, - "value_kind": {}, - "variadic": {} + "description": { + "type": "string" + }, + "enum_values": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "placement": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "stream_dash": { + "type": "boolean" + }, + "value_kind": { + "type": "string" + }, + "variadic": { + "type": "boolean" + } }, + "required": [ + "description", + "enum_values", + "name", + "placement", + "required", + "stream_dash", + "value_kind", + "variadic" + ], "type": "object" }, "command_cp": { @@ -179,27 +281,79 @@ "command_example": { "additionalProperties": false, "properties": { - "command": {}, - "description": {} + "command": { + "type": "string" + }, + "description": { + "type": "string" + } }, + "required": [ + "command", + "description" + ], "type": "object" }, "command_flag": { "additionalProperties": false, "properties": { - "conflicts": {}, - "default": {}, - "enum_values": {}, - "inherited": {}, - "may_prompt": {}, - "name": {}, - "required": {}, - "sensitive": {}, - "shorthand": {}, - "type": {}, - "usage": {}, - "value_kind": {} + "conflicts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "default": { + "type": "string" + }, + "enum_values": { + "items": { + "type": "string" + }, + "type": "array" + }, + "inherited": { + "type": "boolean" + }, + "may_prompt": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "sensitive": { + "type": "boolean" + }, + "shorthand": { + "type": "string" + }, + "type": { + "type": "string" + }, + "usage": { + "type": "string" + }, + "value_kind": { + "type": "string" + } }, + "required": [ + "conflicts", + "default", + "enum_values", + "inherited", + "may_prompt", + "name", + "required", + "sensitive", + "shorthand", + "type", + "usage", + "value_kind" + ], "type": "object" }, "command_get": { @@ -275,34 +429,109 @@ "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": {} + "default": { + "type": [ + "array", + "boolean", + "integer", + "string" + ] + }, + "description": { + "type": "string" + }, + "enum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "format": { + "type": "string" + }, + "items": { + "$ref": "#/$defs/command_input_property" + }, + "minItems": { + "minimum": 0, + "type": "integer" + }, + "type": { + "enum": [ + "array", + "boolean", + "integer", + "string" + ], + "type": "string" + }, + "writeOnly": { + "type": "boolean" + }, + "x-cli-kind": { + "type": "string" + }, + "x-cli-name": { + "type": "string" + }, + "x-conflicts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "x-inherited": { + "type": "boolean" + }, + "x-may-prompt": { + "type": "boolean" + }, + "x-sensitive": { + "type": "boolean" + }, + "x-shorthand": { + "type": "string" + }, + "x-stream-dash": { + "type": "boolean" + }, + "x-value-kind": { + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "command_input_schema": { "additionalProperties": false, "properties": { - "additionalProperties": {}, - "properties": {}, - "required": {}, - "type": {} + "additionalProperties": { + "const": false + }, + "properties": { + "additionalProperties": { + "$ref": "#/$defs/command_input_property" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object" + } }, + "required": [ + "additionalProperties", + "properties", + "required", + "type" + ], "type": "object" }, "command_logout": { @@ -378,28 +607,125 @@ "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": {} + "aliases": { + "items": { + "type": "string" + }, + "type": "array" + }, + "args": { + "items": { + "$ref": "#/$defs/command_arg" + }, + "type": "array" + }, + "auth_modes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "destructive_level": { + "enum": [ + "admin", + "delete", + "none" + ], + "type": "string" + }, + "dropbox_scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "examples": { + "items": { + "$ref": "#/$defs/command_example" + }, + "type": "array" + }, + "flags": { + "items": { + "$ref": "#/$defs/command_flag" + }, + "type": "array" + }, + "input_schema": { + "$ref": "#/$defs/command_input_schema" + }, + "manifest_version": { + "const": "1" + }, + "may_prompt": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "result_kinds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "result_statuses": { + "items": { + "type": "string" + }, + "type": "array" + }, + "runnable": { + "type": "boolean" + }, + "schema_refs": { + "$ref": "#/$defs/command_schema_refs" + }, + "scope_accuracy": { + "type": "string" + }, + "short": { + "type": "string" + }, + "stdin_stdout": { + "$ref": "#/$defs/command_stdin_stdout" + }, + "supports_structured_output": { + "type": "boolean" + }, + "use": { + "type": "string" + }, + "warning_codes": { + "items": { + "type": "string" + }, + "type": "array" + } }, + "required": [ + "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": { @@ -615,11 +941,23 @@ "command_schema_refs": { "additionalProperties": false, "properties": { - "command_contract": {}, - "command_success_schema": {}, - "error_schema": {}, - "success_schema": {} + "command_contract": { + "type": "string" + }, + "command_success_schema": { + "type": "string" + }, + "error_schema": { + "type": "string" + }, + "success_schema": { + "type": "string" + } }, + "required": [ + "error_schema", + "success_schema" + ], "type": "object" }, "command_search": { @@ -940,11 +1278,25 @@ "command_stdin_stdout": { "additionalProperties": false, "properties": { - "reads_stdin": {}, - "stderr": {}, - "stdout": {}, - "writes_binary_stdout": {} + "reads_stdin": { + "type": "boolean" + }, + "stderr": { + "type": "string" + }, + "stdout": { + "type": "string" + }, + "writes_binary_stdout": { + "type": "boolean" + } }, + "required": [ + "reads_stdin", + "stderr", + "stdout", + "writes_binary_stdout" + ], "type": "object" }, "command_team_20add_2dmember": { @@ -1160,21 +1512,49 @@ "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": {} + "allocated": { + "minimum": 0, + "type": "integer" + }, + "type": { + "type": "string" + }, + "used": { + "minimum": 0, + "type": "integer" + }, + "user_within_team_space_allocated": { + "minimum": 0, + "type": "integer" + }, + "user_within_team_space_limit_type": { + "type": "string" + }, + "user_within_team_space_used_cached": { + "minimum": 0, + "type": "integer" + } }, + "required": [ + "type" + ], "type": "object" }, "du_output": { "additionalProperties": false, "properties": { - "allocation": {}, - "used": {} + "allocation": { + "$ref": "#/$defs/du_allocation" + }, + "used": { + "minimum": 0, + "type": "integer" + } }, + "required": [ + "allocation", + "used" + ], "type": "object" }, "empty": { @@ -1185,141 +1565,353 @@ "get_input": { "additionalProperties": false, "properties": { - "recursive": {}, - "source": {}, - "stdout": {}, - "target": {} + "recursive": { + "type": "boolean" + }, + "source": { + "type": "string" + }, + "stdout": { + "type": "boolean" + }, + "target": { + "type": "string" + } }, + "required": [ + "recursive", + "source", + "stdout", + "target" + ], "type": "object" }, "get_result_input": { "additionalProperties": false, "properties": { - "source": {}, - "target": {} + "source": { + "type": "string" + }, + "target": { + "type": "string" + } }, + "required": [ + "source", + "target" + ], "type": "object" }, "help_input": { "additionalProperties": false, "properties": { - "help": {}, - "path": {} + "help": { + "type": "boolean" + }, + "path": { + "type": "string" + } }, + "required": [ + "help", + "path" + ], "type": "object" }, "logout_result": { "additionalProperties": false, "properties": { - "remote_token_revoked": {}, - "removed_saved_credentials": {} + "remote_token_revoked": { + "type": "boolean" + }, + "removed_saved_credentials": { + "type": "boolean" + } }, + "required": [ + "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": {} + "include_deleted": { + "type": "boolean" + }, + "limit": { + "minimum": 0, + "type": "integer" + }, + "long": { + "type": "boolean" + }, + "only_deleted": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "recursive": { + "type": "boolean" + }, + "reverse": { + "type": "boolean" + }, + "sort": { + "enum": [ + "name", + "size", + "time", + "type" + ], + "type": "string" + }, + "time": { + "enum": [ + "client", + "server" + ], + "type": "string" + }, + "time_format": { + "type": "string" + } }, + "required": [ + "include_deleted", + "long", + "only_deleted", + "path", + "recursive", + "reverse" + ], "type": "object" }, "metadata": { "additionalProperties": false, "properties": { - "client_modified": {}, - "deleted": {}, - "id": {}, - "path_display": {}, - "path_lower": {}, - "rev": {}, - "server_modified": {}, - "size": {}, - "type": {} + "client_modified": { + "format": "date-time", + "type": "string" + }, + "deleted": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "path_display": { + "type": "string" + }, + "path_lower": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "server_modified": { + "format": "date-time", + "type": "string" + }, + "size": { + "minimum": 0, + "type": "integer" + }, + "type": { + "enum": [ + "deleted", + "file", + "folder" + ], + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "mkdir_input": { "additionalProperties": false, "properties": { - "parents": {}, - "path": {} + "parents": { + "type": "boolean" + }, + "path": { + "type": "string" + } }, + "required": [ + "parents", + "path" + ], "type": "object" }, "operation_output": { "additionalProperties": false, "properties": { - "command": {}, - "input": {}, - "ok": {}, - "results": {}, - "schema_version": {}, - "warnings": {} + "command": { + "type": "string" + }, + "input": { + "type": "string" + }, + "ok": { + "const": true + }, + "results": { + "items": { + "$ref": "#/$defs/operation_result" + }, + "type": "array" + }, + "schema_version": { + "const": "1" + }, + "warnings": { + "items": { + "$ref": "#/$defs/warning" + }, + "type": "array" + } }, + "required": [ + "command", + "input", + "ok", + "results", + "schema_version", + "warnings" + ], "type": "object" }, "operation_result": { "additionalProperties": false, "properties": { - "input": {}, - "kind": {}, + "input": { + "type": "object" + }, + "kind": { + "type": "string" + }, "result": {}, - "status": {} + "status": { + "type": "string" + } }, + "required": [ + "input", + "kind", + "result", + "status" + ], "type": "object" }, "put_input": { "additionalProperties": false, "properties": { - "if_exists": {}, - "recursive": {}, - "source": {}, - "stdin": {}, - "target": {} + "if_exists": { + "enum": [ + "fail", + "overwrite", + "skip" + ], + "type": "string" + }, + "recursive": { + "type": "boolean" + }, + "source": { + "type": "string" + }, + "stdin": { + "type": "boolean" + }, + "target": { + "type": "string" + } }, + "required": [ + "if_exists", + "recursive", + "source", + "stdin", + "target" + ], "type": "object" }, "put_result_input": { "additionalProperties": false, "properties": { - "source": {}, - "target": {} + "source": { + "type": "string" + }, + "target": { + "type": "string" + } }, + "required": [ + "source", + "target" + ], "type": "object" }, "relocation_input": { "additionalProperties": false, "properties": { - "from_path": {}, - "to_path": {} + "from_path": { + "type": "string" + }, + "to_path": { + "type": "string" + } }, + "required": [ + "from_path", + "to_path" + ], "type": "object" }, "remove_input": { "additionalProperties": false, "properties": { - "force": {}, - "path": {}, - "permanent": {}, - "recursive": {} + "force": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "permanent": { + "type": "boolean" + }, + "recursive": { + "type": "boolean" + } }, + "required": [ + "force", + "path", + "permanent", + "recursive" + ], "type": "object" }, "restore_input": { "additionalProperties": false, "properties": { - "path": {}, - "revision": {} + "path": { + "type": "string" + }, + "revision": { + "type": "string" + } }, + "required": [ + "path", + "revision" + ], "type": "object" }, "result_account": { @@ -2151,256 +2743,666 @@ "revs_input": { "additionalProperties": false, "properties": { - "limit": {}, - "long": {}, - "path": {}, - "time": {}, - "time_format": {} + "limit": { + "minimum": 0, + "type": "integer" + }, + "long": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "time": { + "enum": [ + "client", + "server" + ], + "type": "string" + }, + "time_format": { + "type": "string" + } }, + "required": [ + "long", + "path" + ], "type": "object" }, "search_input": { "additionalProperties": false, "properties": { - "content": {}, - "limit": {}, - "long": {}, - "order_by": {}, - "path": {}, - "query": {}, - "reverse": {}, - "sort": {}, - "time": {}, - "time_format": {} + "content": { + "type": "boolean" + }, + "limit": { + "minimum": 0, + "type": "integer" + }, + "long": { + "type": "boolean" + }, + "order_by": { + "enum": [ + "modified", + "relevance" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "query": { + "type": "string" + }, + "reverse": { + "type": "boolean" + }, + "sort": { + "enum": [ + "name", + "size", + "time", + "type" + ], + "type": "string" + }, + "time": { + "enum": [ + "client", + "server" + ], + "type": "string" + }, + "time_format": { + "type": "string" + } }, + "required": [ + "content", + "long", + "query", + "reverse" + ], "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": {} + "access_inheritance": { + "type": "string" + }, + "access_type": { + "type": "string" + }, + "is_inside_team_folder": { + "type": "boolean" + }, + "is_team_folder": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner_display_names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "parent_folder_name": { + "type": "string" + }, + "parent_shared_folder_id": { + "type": "string" + }, + "path_lower": { + "type": "string" + }, + "preview_url": { + "type": "string" + }, + "shared_folder_id": { + "type": "string" + }, + "time_invited": { + "format": "date-time", + "type": "string" + }, + "type": { + "enum": [ + "shared_folder" + ], + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "share_link_create_input": { "additionalProperties": false, "properties": { - "access": {}, - "allow_download": {}, - "audience": {}, - "disallow_download": {}, - "expires": {}, - "password": {}, - "path": {}, - "remove_expiration": {} + "access": { + "enum": [ + "editor", + "max", + "viewer" + ], + "type": "string" + }, + "allow_download": { + "type": "boolean" + }, + "audience": { + "enum": [ + "members", + "no-one", + "public", + "team" + ], + "type": "string" + }, + "disallow_download": { + "type": "boolean" + }, + "expires": { + "format": "date-time", + "type": "string" + }, + "password": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "remove_expiration": { + "type": "boolean" + } }, + "required": [ + "path" + ], "type": "object" }, "share_link_download_input": { "additionalProperties": false, "properties": { - "password": {}, - "path": {}, - "recursive": {}, - "target": {}, - "url": {} + "password": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "recursive": { + "type": "boolean" + }, + "target": { + "type": "string" + }, + "url": { + "type": "string" + } }, + "required": [ + "url" + ], "type": "object" }, "share_link_download_result": { "additionalProperties": false, "properties": { - "link": {}, - "target": {} + "link": { + "$ref": "#/$defs/share_link_metadata" + }, + "target": { + "type": "string" + } }, + "required": [ + "target" + ], "type": "object" }, "share_link_info_input": { "additionalProperties": false, "properties": { - "password": {}, - "path": {}, - "url": {} + "password": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "url": { + "type": "string" + } }, + "required": [ + "url" + ], "type": "object" }, "share_link_list_input": { "additionalProperties": false, "properties": { - "direct_only": {}, - "path": {} + "direct_only": { + "type": "boolean" + }, + "path": { + "type": "string" + } }, + "required": [ + "direct_only" + ], "type": "object" }, "share_link_metadata": { "additionalProperties": false, "properties": { - "client_modified": {}, - "expires": {}, - "id": {}, - "name": {}, - "path_lower": {}, - "permissions": {}, - "rev": {}, - "server_modified": {}, - "size": {}, - "type": {}, - "url": {} + "client_modified": { + "format": "date-time", + "type": "string" + }, + "expires": { + "format": "date-time", + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path_lower": { + "type": "string" + }, + "permissions": { + "$ref": "#/$defs/share_link_permissions" + }, + "rev": { + "type": "string" + }, + "server_modified": { + "format": "date-time", + "type": "string" + }, + "size": { + "minimum": 0, + "type": "integer" + }, + "type": { + "enum": [ + "file", + "folder", + "link" + ], + "type": "string" + }, + "url": { + "type": "string" + } }, + "required": [ + "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": {} + "access_level": { + "type": "string" + }, + "allow_comments": { + "type": "boolean" + }, + "allow_download": { + "type": "boolean" + }, + "can_allow_download": { + "type": "boolean" + }, + "can_disallow_download": { + "type": "boolean" + }, + "can_remove_expiry": { + "type": "boolean" + }, + "can_remove_password": { + "type": "boolean" + }, + "can_revoke": { + "type": "boolean" + }, + "can_set_expiry": { + "type": "boolean" + }, + "can_set_password": { + "type": "boolean" + }, + "can_use_extended_sharing_controls": { + "type": "boolean" + }, + "effective_audience": { + "type": "string" + }, + "requested_visibility": { + "type": "string" + }, + "require_password": { + "type": "boolean" + }, + "resolved_visibility": { + "type": "string" + } }, + "required": [ + "allow_comments", + "allow_download", + "can_allow_download", + "can_disallow_download", + "can_remove_expiry", + "can_revoke", + "can_set_expiry" + ], "type": "object" }, "share_link_revoke_input": { "additionalProperties": false, "properties": { - "path": {}, - "url": {} + "path": { + "type": "string" + }, + "url": { + "type": "string" + } }, "type": "object" }, "share_link_revoke_result": { "additionalProperties": false, "properties": { - "link": {}, - "url": {} + "link": { + "$ref": "#/$defs/share_link_metadata" + }, + "url": { + "type": "string" + } }, + "required": [ + "url" + ], "type": "object" }, "share_link_update_input": { "additionalProperties": false, "properties": { - "allow_download": {}, - "audience": {}, - "disallow_download": {}, - "expires": {}, - "password": {}, - "remove_expiration": {}, - "remove_password": {}, - "url": {} + "allow_download": { + "type": "boolean" + }, + "audience": { + "enum": [ + "members", + "no-one", + "public", + "team" + ], + "type": "string" + }, + "disallow_download": { + "type": "boolean" + }, + "expires": { + "format": "date-time", + "type": "string" + }, + "password": { + "type": "boolean" + }, + "remove_expiration": { + "type": "boolean" + }, + "remove_password": { + "type": "boolean" + }, + "url": { + "type": "string" + } }, + "required": [ + "url" + ], "type": "object" }, "team_group": { "additionalProperties": false, "properties": { - "group_external_id": {}, - "group_id": {}, - "group_management_type": {}, - "group_name": {}, - "member_count": {}, - "type": {} + "group_external_id": { + "type": "string" + }, + "group_id": { + "type": "string" + }, + "group_management_type": { + "type": "string" + }, + "group_name": { + "type": "string" + }, + "member_count": { + "minimum": 0, + "type": "integer" + }, + "type": { + "enum": [ + "team_group" + ], + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "team_info": { "additionalProperties": false, "properties": { - "name": {}, - "num_licensed_users": {}, - "num_provisioned_users": {}, - "team_id": {}, - "type": {} + "name": { + "type": "string" + }, + "num_licensed_users": { + "minimum": 0, + "type": "integer" + }, + "num_provisioned_users": { + "minimum": 0, + "type": "integer" + }, + "team_id": { + "type": "string" + }, + "type": { + "enum": [ + "team" + ], + "type": "string" + } }, + "required": [ + "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": {} + "account_id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_verified": { + "type": "boolean" + }, + "external_id": { + "type": "string" + }, + "groups": { + "items": { + "type": "string" + }, + "type": "array" + }, + "invited_on": { + "format": "date-time", + "type": "string" + }, + "is_directory_restricted": { + "type": "boolean" + }, + "joined_on": { + "format": "date-time", + "type": "string" + }, + "member_folder_id": { + "type": "string" + }, + "membership_type": { + "type": "string" + }, + "name": { + "$ref": "#/$defs/account_name" + }, + "persistent_id": { + "type": "string" + }, + "profile_photo_url": { + "type": "string" + }, + "role": { + "type": "string" + }, + "status": { + "type": "string" + }, + "suspended_on": { + "format": "date-time", + "type": "string" + }, + "team_member_id": { + "type": "string" + }, + "type": { + "enum": [ + "team_member" + ], + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "team_member_add_input": { "additionalProperties": false, "properties": { - "email": {}, - "first_name": {}, - "last_name": {} + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + } }, + "required": [ + "email", + "first_name", + "last_name" + ], "type": "object" }, "team_member_add_item": { "additionalProperties": false, "properties": { - "email": {}, - "member": {}, - "tag": {} + "email": { + "type": "string" + }, + "member": { + "$ref": "#/$defs/team_member" + }, + "tag": { + "type": "string" + } }, "type": "object" }, "team_member_mutation": { "additionalProperties": false, "properties": { - "async_job_id": {}, - "results": {}, - "tag": {}, - "type": {} + "async_job_id": { + "type": "string" + }, + "results": { + "items": { + "$ref": "#/$defs/team_member_add_item" + }, + "type": "array" + }, + "tag": { + "type": "string" + }, + "type": { + "enum": [ + "team_member_add", + "team_member_remove" + ], + "type": "string" + } }, + "required": [ + "type" + ], "type": "object" }, "team_member_remove_input": { "additionalProperties": false, "properties": { - "email": {} + "email": { + "type": "string" + } }, + "required": [ + "email" + ], "type": "object" }, "version": { "additionalProperties": false, "properties": { - "sdk_version": {}, - "spec_version": {}, - "version": {} + "sdk_version": { + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "version": { + "type": "string" + } }, + "required": [ + "sdk_version", + "spec_version", + "version" + ], "type": "object" }, "warning": { diff --git a/internal/jsonschema/commands.go b/internal/jsonschema/commands.go index ef722d0..b9a51da 100644 --- a/internal/jsonschema/commands.go +++ b/internal/jsonschema/commands.go @@ -61,7 +61,11 @@ func GenerateCommandSuccessSchema(catalog CommandCatalog) (map[string]any, error } for _, name := range sortedMapKeys(catalog.Definitions) { - defs[name] = objectSchema(catalog.Definitions[name]) + schema, err := objectSchema(name, catalog.Definitions[name]) + if err != nil { + return nil, err + } + defs[name] = schema } commandRefs := make([]any, 0, len(catalog.Commands)) @@ -182,16 +186,41 @@ func commandWarningsSchema(codes []string) map[string]any { } } -func objectSchema(fields []string) map[string]any { +func objectSchema(name string, fields []string) (map[string]any, error) { + knownFields := make(map[string]bool, len(fields)) + for _, field := range fields { + knownFields[field] = true + } + + config := commandDefinitionSchema(name) + for field := range config.Properties { + if !knownFields[field] { + return nil, fmt.Errorf("%s: schema references unknown field %q", name, field) + } + } + for _, field := range config.Required { + if !knownFields[field] { + return nil, fmt.Errorf("%s: required references unknown field %q", name, field) + } + } + properties := make(map[string]any, len(fields)) for _, field := range fields { - properties[field] = map[string]any{} + if schema, ok := config.Properties[field]; ok { + properties[field] = schema + continue + } + properties[field] = defaultPropertySchema(field) } - return map[string]any{ + schema := map[string]any{ "type": "object", "additionalProperties": false, "properties": properties, } + if len(config.Required) > 0 { + schema["required"] = sortedCopy(config.Required) + } + return schema, nil } func warningSchema() map[string]any { diff --git a/internal/jsonschema/definition_schemas.go b/internal/jsonschema/definition_schemas.go new file mode 100644 index 0000000..fadbabf --- /dev/null +++ b/internal/jsonschema/definition_schemas.go @@ -0,0 +1,408 @@ +// 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 + +type definitionSchemaConfig struct { + Required []string + Properties map[string]any +} + +func commandDefinitionSchema(name string) definitionSchemaConfig { + config, ok := commandDefinitionSchemas[name] + if !ok { + return definitionSchemaConfig{} + } + return config +} + +var commandDefinitionSchemas = map[string]definitionSchemaConfig{ + "account": { + Required: []string{"account_id", "disabled", "email", "email_verified", "type"}, + Properties: map[string]any{ + "auth": schemaRef("account_auth"), + "name": schemaRef("account_name"), + "team": schemaRef("account_team"), + "type": stringEnum("basic", "full"), + }, + }, + "account_auth": { + Required: []string{"refreshable", "source"}, + }, + "account_name": { + Properties: stringProperties("abbreviated_name", "display_name", "familiar_name", "given_name", "surname"), + }, + "account_team": { + Required: []string{"id", "name"}, + }, + "command_arg": { + Required: []string{"description", "enum_values", "name", "placement", "required", "stream_dash", "value_kind", "variadic"}, + Properties: map[string]any{ + "enum_values": stringArraySchema(), + "required": booleanSchema(), + "stream_dash": booleanSchema(), + "variadic": booleanSchema(), + }, + }, + "command_example": { + Required: []string{"command", "description"}, + }, + "command_flag": { + Required: []string{"conflicts", "default", "enum_values", "inherited", "may_prompt", "name", "required", "sensitive", "shorthand", "type", "usage", "value_kind"}, + Properties: map[string]any{ + "conflicts": stringArraySchema(), + "enum_values": stringArraySchema(), + "inherited": booleanSchema(), + "may_prompt": booleanSchema(), + "required": booleanSchema(), + "sensitive": booleanSchema(), + }, + }, + "command_input_property": { + Required: []string{"type"}, + Properties: map[string]any{ + "default": map[string]any{"type": []string{"array", "boolean", "integer", "string"}}, + "enum": stringArraySchema(), + "items": schemaRef("command_input_property"), + "minItems": integerSchema(), + "type": stringEnum("array", "boolean", "integer", "string"), + "writeOnly": booleanSchema(), + "x-conflicts": stringArraySchema(), + "x-inherited": booleanSchema(), + "x-may-prompt": booleanSchema(), + "x-sensitive": booleanSchema(), + "x-stream-dash": booleanSchema(), + }, + }, + "command_input_schema": { + Required: []string{"additionalProperties", "properties", "required", "type"}, + Properties: map[string]any{ + "additionalProperties": map[string]any{"const": false}, + "properties": map[string]any{ + "type": "object", + "additionalProperties": schemaRef("command_input_property"), + }, + "required": stringArraySchema(), + "type": map[string]any{"const": "object"}, + }, + }, + "command_manifest": { + Required: []string{ + "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", + }, + Properties: map[string]any{ + "aliases": stringArraySchema(), + "args": arraySchema(schemaRef("command_arg")), + "auth_modes": stringArraySchema(), + "destructive_level": stringEnum("admin", "delete", "none"), + "dropbox_scopes": stringArraySchema(), + "examples": arraySchema(schemaRef("command_example")), + "flags": arraySchema(schemaRef("command_flag")), + "input_schema": schemaRef("command_input_schema"), + "manifest_version": map[string]any{"const": "1"}, + "may_prompt": booleanSchema(), + "result_kinds": stringArraySchema(), + "result_statuses": stringArraySchema(), + "runnable": booleanSchema(), + "schema_refs": schemaRef("command_schema_refs"), + "stdin_stdout": schemaRef("command_stdin_stdout"), + "supports_structured_output": booleanSchema(), + "warning_codes": stringArraySchema(), + }, + }, + "command_schema_refs": { + Required: []string{"error_schema", "success_schema"}, + }, + "command_stdin_stdout": { + Required: []string{"reads_stdin", "stderr", "stdout", "writes_binary_stdout"}, + Properties: map[string]any{ + "reads_stdin": booleanSchema(), + "stderr": stringSchema(), + "stdout": stringSchema(), + "writes_binary_stdout": booleanSchema(), + }, + }, + "du_allocation": { + Required: []string{"type"}, + }, + "du_output": { + Required: []string{"allocation", "used"}, + Properties: map[string]any{ + "allocation": schemaRef("du_allocation"), + }, + }, + "get_input": { + Required: []string{"recursive", "source", "stdout", "target"}, + }, + "get_result_input": { + Required: []string{"source", "target"}, + }, + "help_input": { + Required: []string{"help", "path"}, + }, + "logout_result": { + Required: []string{"remote_token_revoked", "removed_saved_credentials"}, + }, + "ls_input": { + Required: []string{"include_deleted", "long", "only_deleted", "path", "recursive", "reverse"}, + Properties: map[string]any{ + "sort": stringEnum("name", "size", "time", "type"), + "time": stringEnum("client", "server"), + }, + }, + "metadata": { + Required: []string{"type"}, + Properties: map[string]any{ + "type": stringEnum("deleted", "file", "folder"), + }, + }, + "mkdir_input": { + Required: []string{"parents", "path"}, + }, + "operation_output": { + Required: []string{"command", "input", "ok", "results", "schema_version", "warnings"}, + Properties: map[string]any{ + "ok": map[string]any{"const": true}, + "results": arraySchema(schemaRef("operation_result")), + "schema_version": map[string]any{"const": "1"}, + "warnings": arraySchema(schemaRef("warning")), + }, + }, + "operation_result": { + Required: []string{"input", "kind", "result", "status"}, + Properties: map[string]any{ + "input": map[string]any{"type": "object"}, + "result": map[string]any{}, + }, + }, + "put_input": { + Required: []string{"if_exists", "recursive", "source", "stdin", "target"}, + Properties: map[string]any{ + "if_exists": stringEnum("fail", "overwrite", "skip"), + }, + }, + "put_result_input": { + Required: []string{"source", "target"}, + }, + "relocation_input": { + Required: []string{"from_path", "to_path"}, + }, + "remove_input": { + Required: []string{"force", "path", "permanent", "recursive"}, + }, + "restore_input": { + Required: []string{"path", "revision"}, + }, + "revs_input": { + Required: []string{"long", "path"}, + Properties: map[string]any{ + "time": stringEnum("client", "server"), + }, + }, + "search_input": { + Required: []string{"content", "long", "query", "reverse"}, + Properties: map[string]any{ + "order_by": stringEnum("modified", "relevance"), + "sort": stringEnum("name", "size", "time", "type"), + "time": stringEnum("client", "server"), + }, + }, + "share_folder": { + Required: []string{"type"}, + Properties: map[string]any{ + "owner_display_names": stringArraySchema(), + "type": stringEnum("shared_folder"), + }, + }, + "share_link_create_input": { + Required: []string{"path"}, + Properties: map[string]any{ + "access": stringEnum("editor", "max", "viewer"), + "audience": stringEnum("members", "no-one", "public", "team"), + }, + }, + "share_link_download_input": { + Required: []string{"url"}, + }, + "share_link_download_result": { + Required: []string{"target"}, + Properties: map[string]any{ + "link": schemaRef("share_link_metadata"), + }, + }, + "share_link_info_input": { + Required: []string{"url"}, + }, + "share_link_list_input": { + Required: []string{"direct_only"}, + }, + "share_link_metadata": { + Required: []string{"type", "url"}, + Properties: map[string]any{ + "permissions": schemaRef("share_link_permissions"), + "type": stringEnum("file", "folder", "link"), + }, + }, + "share_link_permissions": { + Required: []string{ + "allow_comments", + "allow_download", + "can_allow_download", + "can_disallow_download", + "can_remove_expiry", + "can_revoke", + "can_set_expiry", + }, + }, + "share_link_revoke_result": { + Required: []string{"url"}, + Properties: map[string]any{ + "link": schemaRef("share_link_metadata"), + }, + }, + "share_link_update_input": { + Required: []string{"url"}, + Properties: map[string]any{ + "audience": stringEnum("members", "no-one", "public", "team"), + }, + }, + "team_group": { + Required: []string{"type"}, + Properties: map[string]any{ + "type": stringEnum("team_group"), + }, + }, + "team_info": { + Required: []string{"type"}, + Properties: map[string]any{ + "type": stringEnum("team"), + }, + }, + "team_member": { + Required: []string{"type"}, + Properties: map[string]any{ + "groups": stringArraySchema(), + "name": schemaRef("account_name"), + "type": stringEnum("team_member"), + }, + }, + "team_member_add_input": { + Required: []string{"email", "first_name", "last_name"}, + }, + "team_member_add_item": { + Properties: map[string]any{ + "member": schemaRef("team_member"), + }, + }, + "team_member_mutation": { + Required: []string{"type"}, + Properties: map[string]any{ + "results": arraySchema(schemaRef("team_member_add_item")), + "type": stringEnum("team_member_add", "team_member_remove"), + }, + }, + "team_member_remove_input": { + Required: []string{"email"}, + }, + "version": { + Required: []string{"sdk_version", "spec_version", "version"}, + }, +} + +func defaultPropertySchema(field string) map[string]any { + switch field { + case "aliases", "auth_modes", "conflicts", "dropbox_scopes", "enum", "enum_values", "groups", "owner_display_names", "required", "result_kinds", "result_statuses", "warning_codes", "x-conflicts": + return stringArraySchema() + case "allocated", "limit", "member_count", "num_licensed_users", "num_provisioned_users", "size", "used", "user_within_team_space_allocated", "user_within_team_space_used_cached": + return integerSchema() + case "additionalProperties", "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", "content", "deleted", "direct_only", "disabled", "disallow_download", "email_verified", "force", "help", "include_deleted", "inherited", "is_directory_restricted", "is_inside_team_folder", "is_paired", "is_team_folder", "is_teammate", "long", "may_prompt", "only_deleted", "parents", "password", "permanent", "recursive", "refreshable", "remote_token_revoked", "remove_expiration", "remove_password", "removed_saved_credentials", "require_password", "reverse", "runnable", "sensitive", "stdin", "stdout", "stream_dash", "supports_structured_output", "variadic", "writeOnly", "writes_binary_stdout", "x-inherited", "x-may-prompt", "x-sensitive", "x-stream-dash": + return booleanSchema() + case "client_modified", "expires", "invited_on", "joined_on", "server_modified", "suspended_on", "time_invited": + return dateTimeStringSchema() + default: + return stringSchema() + } +} + +func schemaRef(defName string) map[string]any { + return map[string]any{"$ref": "#/$defs/" + defName} +} + +func stringProperties(fields ...string) map[string]any { + properties := make(map[string]any, len(fields)) + for _, field := range fields { + properties[field] = stringSchema() + } + return properties +} + +func stringSchema() map[string]any { + return map[string]any{"type": "string"} +} + +func booleanSchema() map[string]any { + return map[string]any{"type": "boolean"} +} + +func integerSchema() map[string]any { + return map[string]any{ + "type": "integer", + "minimum": 0, + } +} + +func dateTimeStringSchema() map[string]any { + return map[string]any{ + "type": "string", + "format": "date-time", + } +} + +func stringArraySchema() map[string]any { + return arraySchema(stringSchema()) +} + +func arraySchema(items map[string]any) map[string]any { + return map[string]any{ + "type": "array", + "items": items, + } +} + +func stringEnum(values ...string) map[string]any { + return map[string]any{ + "type": "string", + "enum": sortedCopy(values), + } +}