{"action":"created","comment":{"url":"https://api.github.com/repos/twitchtv/twirp/pulls/comments/167715932","pull_request_review_id":95973999,"id":167715932,"diff_hunk":"@@ -151,30 +148,91 @@ Content-Length: 27\n {\"message\":\"Hello, World!\"}\n ```\n \n-## Errors\n \n-Twirp error responses are always JSON-encoded, regardless of\n-the request's Content-Type, with a corresponding\n-`Content-Type: application/json` header. This ensures that\n-the errors are human-readable in any setting.\n+## Errors\n \n-Twirp errors are a JSON object with three keys:\n+Erros are non-200 JSON responses with the keys:\n \n-* **code**: One of the Twirp error codes as a string.\n-* **msg**: A human-readable message describing the error\n-  as a string.\n-* **meta**: An object with string keys and values holding\n-  arbitrary additional metadata describing the error.\n+* **code**: (string) One of the valid Twirp error codes.\n+* **msg**: (string) Human-readable message describing the error.\n+* **meta**: (optional object) arbitrary additional metadata \n+  describing the error. Keys and values must be strings.\n \n Example:\n+\n+```json\n+{\n+  \"code\": \"permission_denied\",\n+  \"msg\": \"thou shall not pass\",\n+  \"meta\": {\n+    \"target\": \"Balrog\"\n+  }\n+}\n ```\n+\n+### Error Responses are JSON\n+\n+Twirp error responses are always JSON-encoded, the response Content-Type\n+header is always `application/json`, regardless of the request's \n+Content-Type (protbuf requests also respond with json errors).\n+This ensures that errors are human-readable and easy to parse by intermediary proxies.\n+\n+Example Error Response:\n+\n+```\n+HTTP/1.1 500 Internal Server Error\n+Content-Type: application/json\n+Content-Length: 48\n+\n+{\"code\":\"internal\",\"msg\":\"Something went wrong\"}\n+```\n+\n+### Twirp Error Codes\n+\n+The possible values for the Error \"code\" are intentionaly similar to\n+[gRPC status codes](https://godoc.org/google.golang.org/grpc/codes).\n+\n+Twirp services must respond with status `200` for non-error responses.\n+For error responses, the staus depends on the Twirp Error Code.\n+\n+| Twirp Error Code | HTTP Status | Description\n+| ---- | ---- | ----\n+| canceled            | 408 | The operation was cancelled.\n+| unknown             | 500 | An unknown error occurred. For example, this can be used when handling errors raised by APIs that do not return any error information.\n+| invalid_argument    | 400 | The client specified an invalid argument. This indicates arguments that are invalid regardless of the state of the system (i.e. a malformed file name, required argument, number out of range, etc.).\n+| deadline_exceeded   | 408 | Operation expired before completion. For operations that change the state of the system, this error may be returned even if the operation has completed successfully (timeout).\n+| not_found           | 404 | Some requested entity was not found.\n+| bad_route           | 404 | The requested URL path wasn't routable to a Twirp service and method. This is returned by generated server code and should not be returned by application code (use \"not_found\" or \"unimplemented\" instead).\n+| already_exists      | 409 | An attempt to create an entity failed because one already exists.\n+| permission_denied   | 403 | The caller does not have permission to execute the specified operation. It must not be used if the caller cannot be identified (use \"unauthenticated\" instead).\n+| unauthenticated     | 401 | The request does not have valid authentication credentials for the operation.\n+| resource_exhausted  | 403 | Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.\n+| failed_precondition | 412 | The operation was rejected because the system is not in a state required for the operation's execution. For example, doing an rmdir operation on a directory that is non-empty, or on a non-directory object, or when having conflicting read-modify-write on the same resource.\n+| aborted             | 409 | The operation was aborted, typically due to a concurrency issue like sequencer check failures, transaction aborts, etc.\n+| out_of_range        | 400 | The operation was attempted past the valid range. For example, seeking or reading past end of a paginated collection. Unlike \"invalid_argument\", this error indicates a problem that may be fixed if the system state changes (i.e. adding more items to the collection). There is a fair bit of overlap between \"failed_precondition\" and \"out_of_range\". We recommend using \"out_of_range\" (the more specific error) when it applies so that callers who are iterating through a space can easily look for an \"out_of_range\" error to detect when they are done.\n+| unimplemented       | 501 | The operation is not implemented or not supported/enabled in this service.\n+| internal            | 500 | When some invariants expected by the underlying system have been broken. In other words, something bad happened in the library or backend service. Twirp specific issues like wire and serialization problems are also reported as \"internal\" errors.\n+| unavailable         | 503 | The service is currently unavailable. This is most likely a transient condition and may be corrected by retrying with a backoff.\n+| dataloss            | 500 | The operation resulted in unrecoverable data loss or corruption.\n+\n+\n+### Metadata\n+\n+In adition to `code` and `msg`, Twirp Error responses may have a `meta` field with\n+arbitrary string metadata. For example:\n+\n+```json\n {\n-    \"code\": \"permission_denied\",\n-    \"msg\": \"thou shall not pass\",\n-    \"meta\": {\n-        \"target\": \"Balrog\"\n-    }\n+  \"code\": \"invalid_argument\",\n+  \"msg\": \"please use a smaller size\",\n+  \"meta\": {\n+    \"argument\": \"size\",\n+    \"max_allowed\": \"1000\"\n+  }\n }\n ```\n \n-For more information, see https://github.com/twitchtv/twirp/wiki/Errors.\n+Error metadata can only have string values. This is to simplify error parsing by clients.","path":"docs/spec_v5.md","position":121,"original_position":121,"commit_id":"3cec78522cdd12b4d6d2cefefd4c8b2ea987da77","original_commit_id":"3cec78522cdd12b4d6d2cefefd4c8b2ea987da77","user":{"login":"spenczar","id":880413,"avatar_url":"https://avatars1.githubusercontent.com/u/880413?v=4","gravatar_id":"","url":"https://api.github.com/users/spenczar","html_url":"https://github.com/spenczar","followers_url":"https://api.github.com/users/spenczar/followers","following_url":"https://api.github.com/users/spenczar/following{/other_user}","gists_url":"https://api.github.com/users/spenczar/gists{/gist_id}","starred_url":"https://api.github.com/users/spenczar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/spenczar/subscriptions","organizations_url":"https://api.github.com/users/spenczar/orgs","repos_url":"https://api.github.com/users/spenczar/repos","events_url":"https://api.github.com/users/spenczar/events{/privacy}","received_events_url":"https://api.github.com/users/spenczar/received_events","type":"User","site_admin":false},"body":"I don't think we need this paragraph. Let's put it in `docs/errors.md` instead.","created_at":"2018-02-12T23:08:24Z","updated_at":"2018-02-12T23:15:58Z","html_url":"https://github.com/twitchtv/twirp/pull/77#discussion_r167715932","pull_request_url":"https://api.github.com/repos/twitchtv/twirp/pulls/77","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/comments/167715932"},"html":{"href":"https://github.com/twitchtv/twirp/pull/77#discussion_r167715932"},"pull_request":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/77"}}},"pull_request":{"url":"https://api.github.com/repos/twitchtv/twirp/pulls/77","id":168493191,"html_url":"https://github.com/twitchtv/twirp/pull/77","diff_url":"https://github.com/twitchtv/twirp/pull/77.diff","patch_url":"https://github.com/twitchtv/twirp/pull/77.patch","issue_url":"https://api.github.com/repos/twitchtv/twirp/issues/77","number":77,"state":"open","locked":false,"title":"docs/spec_v5_errors.md","user":{"login":"marioizquierdo","id":242139,"avatar_url":"https://avatars0.githubusercontent.com/u/242139?v=4","gravatar_id":"","url":"https://api.github.com/users/marioizquierdo","html_url":"https://github.com/marioizquierdo","followers_url":"https://api.github.com/users/marioizquierdo/followers","following_url":"https://api.github.com/users/marioizquierdo/following{/other_user}","gists_url":"https://api.github.com/users/marioizquierdo/gists{/gist_id}","starred_url":"https://api.github.com/users/marioizquierdo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marioizquierdo/subscriptions","organizations_url":"https://api.github.com/users/marioizquierdo/orgs","repos_url":"https://api.github.com/users/marioizquierdo/repos","events_url":"https://api.github.com/users/marioizquierdo/events{/privacy}","received_events_url":"https://api.github.com/users/marioizquierdo/received_events","type":"User","site_admin":false},"body":"The current `docs/errors.md` page was only useful for Go services. This PR defines Twirp errors on a wire protocol level. This will be useful for services and clients in other languages.\r\n\r\nBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.\r\n","created_at":"2018-02-12T03:11:04Z","updated_at":"2018-02-12T23:15:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4f95ec0cefa6260a101ce76a2202c932cf12ec6c","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/twitchtv/twirp/pulls/77/commits","review_comments_url":"https://api.github.com/repos/twitchtv/twirp/pulls/77/comments","review_comment_url":"https://api.github.com/repos/twitchtv/twirp/pulls/comments{/number}","comments_url":"https://api.github.com/repos/twitchtv/twirp/issues/77/comments","statuses_url":"https://api.github.com/repos/twitchtv/twirp/statuses/3cec78522cdd12b4d6d2cefefd4c8b2ea987da77","head":{"label":"marioizquierdo:master","ref":"master","sha":"3cec78522cdd12b4d6d2cefefd4c8b2ea987da77","user":{"login":"marioizquierdo","id":242139,"avatar_url":"https://avatars0.githubusercontent.com/u/242139?v=4","gravatar_id":"","url":"https://api.github.com/users/marioizquierdo","html_url":"https://github.com/marioizquierdo","followers_url":"https://api.github.com/users/marioizquierdo/followers","following_url":"https://api.github.com/users/marioizquierdo/following{/other_user}","gists_url":"https://api.github.com/users/marioizquierdo/gists{/gist_id}","starred_url":"https://api.github.com/users/marioizquierdo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marioizquierdo/subscriptions","organizations_url":"https://api.github.com/users/marioizquierdo/orgs","repos_url":"https://api.github.com/users/marioizquierdo/repos","events_url":"https://api.github.com/users/marioizquierdo/events{/privacy}","received_events_url":"https://api.github.com/users/marioizquierdo/received_events","type":"User","site_admin":false},"repo":{"id":121193478,"name":"twirp","full_name":"marioizquierdo/twirp","owner":{"login":"marioizquierdo","id":242139,"avatar_url":"https://avatars0.githubusercontent.com/u/242139?v=4","gravatar_id":"","url":"https://api.github.com/users/marioizquierdo","html_url":"https://github.com/marioizquierdo","followers_url":"https://api.github.com/users/marioizquierdo/followers","following_url":"https://api.github.com/users/marioizquierdo/following{/other_user}","gists_url":"https://api.github.com/users/marioizquierdo/gists{/gist_id}","starred_url":"https://api.github.com/users/marioizquierdo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marioizquierdo/subscriptions","organizations_url":"https://api.github.com/users/marioizquierdo/orgs","repos_url":"https://api.github.com/users/marioizquierdo/repos","events_url":"https://api.github.com/users/marioizquierdo/events{/privacy}","received_events_url":"https://api.github.com/users/marioizquierdo/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/marioizquierdo/twirp","description":"A simple RPC framework with protobuf service definitions","fork":true,"url":"https://api.github.com/repos/marioizquierdo/twirp","forks_url":"https://api.github.com/repos/marioizquierdo/twirp/forks","keys_url":"https://api.github.com/repos/marioizquierdo/twirp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/marioizquierdo/twirp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/marioizquierdo/twirp/teams","hooks_url":"https://api.github.com/repos/marioizquierdo/twirp/hooks","issue_events_url":"https://api.github.com/repos/marioizquierdo/twirp/issues/events{/number}","events_url":"https://api.github.com/repos/marioizquierdo/twirp/events","assignees_url":"https://api.github.com/repos/marioizquierdo/twirp/assignees{/user}","branches_url":"https://api.github.com/repos/marioizquierdo/twirp/branches{/branch}","tags_url":"https://api.github.com/repos/marioizquierdo/twirp/tags","blobs_url":"https://api.github.com/repos/marioizquierdo/twirp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/marioizquierdo/twirp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/marioizquierdo/twirp/git/refs{/sha}","trees_url":"https://api.github.com/repos/marioizquierdo/twirp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/marioizquierdo/twirp/statuses/{sha}","languages_url":"https://api.github.com/repos/marioizquierdo/twirp/languages","stargazers_url":"https://api.github.com/repos/marioizquierdo/twirp/stargazers","contributors_url":"https://api.github.com/repos/marioizquierdo/twirp/contributors","subscribers_url":"https://api.github.com/repos/marioizquierdo/twirp/subscribers","subscription_url":"https://api.github.com/repos/marioizquierdo/twirp/subscription","commits_url":"https://api.github.com/repos/marioizquierdo/twirp/commits{/sha}","git_commits_url":"https://api.github.com/repos/marioizquierdo/twirp/git/commits{/sha}","comments_url":"https://api.github.com/repos/marioizquierdo/twirp/comments{/number}","issue_comment_url":"https://api.github.com/repos/marioizquierdo/twirp/issues/comments{/number}","contents_url":"https://api.github.com/repos/marioizquierdo/twirp/contents/{+path}","compare_url":"https://api.github.com/repos/marioizquierdo/twirp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/marioizquierdo/twirp/merges","archive_url":"https://api.github.com/repos/marioizquierdo/twirp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/marioizquierdo/twirp/downloads","issues_url":"https://api.github.com/repos/marioizquierdo/twirp/issues{/number}","pulls_url":"https://api.github.com/repos/marioizquierdo/twirp/pulls{/number}","milestones_url":"https://api.github.com/repos/marioizquierdo/twirp/milestones{/number}","notifications_url":"https://api.github.com/repos/marioizquierdo/twirp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/marioizquierdo/twirp/labels{/name}","releases_url":"https://api.github.com/repos/marioizquierdo/twirp/releases{/id}","deployments_url":"https://api.github.com/repos/marioizquierdo/twirp/deployments","created_at":"2018-02-12T03:06:47Z","updated_at":"2018-02-12T03:06:49Z","pushed_at":"2018-02-12T22:02:53Z","git_url":"git://github.com/marioizquierdo/twirp.git","ssh_url":"git@github.com:marioizquierdo/twirp.git","clone_url":"https://github.com/marioizquierdo/twirp.git","svn_url":"https://github.com/marioizquierdo/twirp","homepage":null,"size":1118,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"twitchtv:master","ref":"master","sha":"b596491b2e18c4453ff5ba8016a98fb1e663874e","user":{"login":"twitchtv","id":1795021,"avatar_url":"https://avatars2.githubusercontent.com/u/1795021?v=4","gravatar_id":"","url":"https://api.github.com/users/twitchtv","html_url":"https://github.com/twitchtv","followers_url":"https://api.github.com/users/twitchtv/followers","following_url":"https://api.github.com/users/twitchtv/following{/other_user}","gists_url":"https://api.github.com/users/twitchtv/gists{/gist_id}","starred_url":"https://api.github.com/users/twitchtv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitchtv/subscriptions","organizations_url":"https://api.github.com/users/twitchtv/orgs","repos_url":"https://api.github.com/users/twitchtv/repos","events_url":"https://api.github.com/users/twitchtv/events{/privacy}","received_events_url":"https://api.github.com/users/twitchtv/received_events","type":"Organization","site_admin":false},"repo":{"id":117710726,"name":"twirp","full_name":"twitchtv/twirp","owner":{"login":"twitchtv","id":1795021,"avatar_url":"https://avatars2.githubusercontent.com/u/1795021?v=4","gravatar_id":"","url":"https://api.github.com/users/twitchtv","html_url":"https://github.com/twitchtv","followers_url":"https://api.github.com/users/twitchtv/followers","following_url":"https://api.github.com/users/twitchtv/following{/other_user}","gists_url":"https://api.github.com/users/twitchtv/gists{/gist_id}","starred_url":"https://api.github.com/users/twitchtv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twitchtv/subscriptions","organizations_url":"https://api.github.com/users/twitchtv/orgs","repos_url":"https://api.github.com/users/twitchtv/repos","events_url":"https://api.github.com/users/twitchtv/events{/privacy}","received_events_url":"https://api.github.com/users/twitchtv/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/twitchtv/twirp","description":"A simple RPC framework with protobuf service definitions","fork":false,"url":"https://api.github.com/repos/twitchtv/twirp","forks_url":"https://api.github.com/repos/twitchtv/twirp/forks","keys_url":"https://api.github.com/repos/twitchtv/twirp/keys{/key_id}","collaborators_url":"https://api.github.com/repos/twitchtv/twirp/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/twitchtv/twirp/teams","hooks_url":"https://api.github.com/repos/twitchtv/twirp/hooks","issue_events_url":"https://api.github.com/repos/twitchtv/twirp/issues/events{/number}","events_url":"https://api.github.com/repos/twitchtv/twirp/events","assignees_url":"https://api.github.com/repos/twitchtv/twirp/assignees{/user}","branches_url":"https://api.github.com/repos/twitchtv/twirp/branches{/branch}","tags_url":"https://api.github.com/repos/twitchtv/twirp/tags","blobs_url":"https://api.github.com/repos/twitchtv/twirp/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/twitchtv/twirp/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/twitchtv/twirp/git/refs{/sha}","trees_url":"https://api.github.com/repos/twitchtv/twirp/git/trees{/sha}","statuses_url":"https://api.github.com/repos/twitchtv/twirp/statuses/{sha}","languages_url":"https://api.github.com/repos/twitchtv/twirp/languages","stargazers_url":"https://api.github.com/repos/twitchtv/twirp/stargazers","contributors_url":"https://api.github.com/repos/twitchtv/twirp/contributors","subscribers_url":"https://api.github.com/repos/twitchtv/twirp/subscribers","subscription_url":"https://api.github.com/repos/twitchtv/twirp/subscription","commits_url":"https://api.github.com/repos/twitchtv/twirp/commits{/sha}","git_commits_url":"https://api.github.com/repos/twitchtv/twirp/git/commits{/sha}","comments_url":"https://api.github.com/repos/twitchtv/twirp/comments{/number}","issue_comment_url":"https://api.github.com/repos/twitchtv/twirp/issues/comments{/number}","contents_url":"https://api.github.com/repos/twitchtv/twirp/contents/{+path}","compare_url":"https://api.github.com/repos/twitchtv/twirp/compare/{base}...{head}","merges_url":"https://api.github.com/repos/twitchtv/twirp/merges","archive_url":"https://api.github.com/repos/twitchtv/twirp/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/twitchtv/twirp/downloads","issues_url":"https://api.github.com/repos/twitchtv/twirp/issues{/number}","pulls_url":"https://api.github.com/repos/twitchtv/twirp/pulls{/number}","milestones_url":"https://api.github.com/repos/twitchtv/twirp/milestones{/number}","notifications_url":"https://api.github.com/repos/twitchtv/twirp/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/twitchtv/twirp/labels{/name}","releases_url":"https://api.github.com/repos/twitchtv/twirp/releases{/id}","deployments_url":"https://api.github.com/repos/twitchtv/twirp/deployments","created_at":"2018-01-16T16:37:30Z","updated_at":"2018-02-12T15:55:50Z","pushed_at":"2018-02-12T22:02:55Z","git_url":"git://github.com/twitchtv/twirp.git","ssh_url":"git@github.com:twitchtv/twirp.git","clone_url":"https://github.com/twitchtv/twirp.git","svn_url":"https://github.com/twitchtv/twirp","homepage":null,"size":1103,"stargazers_count":2095,"watchers_count":2095,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":63,"mirror_url":null,"archived":false,"open_issues_count":11,"license":null,"forks":63,"open_issues":11,"watchers":2095,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/77"},"html":{"href":"https://github.com/twitchtv/twirp/pull/77"},"issue":{"href":"https://api.github.com/repos/twitchtv/twirp/issues/77"},"comments":{"href":"https://api.github.com/repos/twitchtv/twirp/issues/77/comments"},"review_comments":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/77/comments"},"review_comment":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/twitchtv/twirp/pulls/77/commits"},"statuses":{"href":"https://api.github.com/repos/twitchtv/twirp/statuses/3cec78522cdd12b4d6d2cefefd4c8b2ea987da77"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"spenczar"}}