{"id":23222709,"url":"https://github.com/pmunch/labeltry","last_synced_at":"2025-04-05T16:28:59.915Z","repository":{"id":204769789,"uuid":"712490378","full_name":"PMunch/labeltry","owner":"PMunch","description":"A new approach to dealing with exceptions","archived":false,"fork":false,"pushed_at":"2024-05-31T06:40:49.000Z","size":90,"stargazers_count":17,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-11T13:24:59.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PMunch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-31T15:14:40.000Z","updated_at":"2025-01-24T23:04:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"608426a2-aded-4127-a7fe-b3f73d7bfc07","html_url":"https://github.com/PMunch/labeltry","commit_stats":null,"previous_names":["pmunch/labeltry"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PMunch%2Flabeltry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PMunch%2Flabeltry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PMunch%2Flabeltry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PMunch%2Flabeltry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PMunch","download_url":"https://codeload.github.com/PMunch/labeltry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247364744,"owners_count":20927190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-18T23:14:04.986Z","updated_at":"2025-04-05T16:28:59.892Z","avatar_url":"https://github.com/PMunch.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Labeled exceptions\nThis is a small package/experiment to deal with exceptions a bit more\nergonomically. Unlike things like wrapping exceptions in an optional Result type\nthis is designed to not interfere with the regular control flow. The idea is\nthat while libraries can define _why_ something went wrong with exceptions, they\ndon't really allow us to filter on _what_ went wrong. The traditional motiving\nexample is something like a traditional web flow:\n\n```nim\nlet user = try:\n  getUser(userInfo)\nexcept CatchableError as e:\n  echo \"Cannot get user: \" \u0026 e.msg\n  return %*{\"error\": \"Cannot get user \" \u0026 userInfo.name}\nlet news = try:\n  getNewsForUser(user.id)\nexcept CatchableError as e:\n  echo \"Cannot get news for user: \" \u0026 e.msg\n  return %*{\"error\": \"Cannot get news for user \" \u0026 userInfo.name}\nlet relatedNews = try:\n  getRelatedNews(news)\nexcept CatchableError as e:\n  echo \"Cannot get related news for user: \" \u0026 e.msg\n  return %*{\"error\": \"Cannot get related news for user \" \u0026 userInfo.name}\nreturn %*{\"data\": {\"news\": news.value, \"relatedNews\": relatedNews.value}}\n```\n\nHere we have three actions which simply do three actions and use the results\nfrom the past actions while giving fine-grained error messages. This however\nobscures the actual logic in all the error handling. The alternative is to just\nhave everything in one big try/except and end up with coarse error messages.\nWith labeled exceptions however the programmer can throw in some extra\ninformation that allows them to identify the exception later on. This adds the\ncrucial \"_what_ went wrong\" information we need to decouple the exceptions from\nthe application code:\n\n```nim\nlabeledTry:\n  let\n    user = getUser(userInfo) |\u003e User\n    news = getNewsForUser(user.id) |\u003e News\n    relatedNews = getRelatedNews(news) |\u003e Related\n  return %*{\"data\": {\"news\": news.value, \"relatedNews\": relatedNews.value}}\nexcept CatchableError as e:\n  let error = \"Cannot get \" \u0026\n    case getLabel():\n    of User: \"user \" \u0026 userInfo.name\n    of News: \"news for user \" \u0026 userInfo.name\n    of Related: \"related news for user \" \u0026 userInfo.name\n    of NoLabel: \"\u003cunknown\u003e\" # exception thrown without label\n  echo error \u0026 \": \" \u0026 e.msg\n  return %*{\"error\": error}\n```\n\nThis shows us doing the same three things, but labeling each one with an\nidentifier. We then have one common exception handler which despite all the\nerrors being the same exception type can distinguish between where in our code\nthe exception came from. The label is created as an enum, so with a case\nstatement you are guaranteed by Nim that all the cases are covered and that you\ncan't have cases for labels which don't exist. It is also possible to use a\nblock statement to label all exceptions from a block of code. And as an added\nbonus these labels are available in the `finally` branch so you can also know\nwhich parts of your code requires cleanup:\n\n```nim\nlabeledTry:\n  let user = getUser(userInfo) |\u003e User\n  label(News):\n    let\n      news = getNewsForUser(user.id)\n      relatedNews = getRelatedNews(news)\n    return %*{\"data\": {\"news\": news.value, \"relatedNews\": relatedNews.value}}\nexcept CatchableError as e:\n  let error = \"Cannot get \" \u0026\n    case getLabel():\n    of User: \"user \" \u0026 userInfo.name\n    of News: \"news for user \" \u0026 userInfo.name\n    of NoLabel: \"\u003cunknown\u003e\" # exception thrown without label\n  echo error \u0026 \": \" \u0026 e.msg\n  return %*{\"error\": error}\nfinally:\n  if getLabel() != NoLabel:\n    echo \"A Labeled exception was thrown in our code!\"\n```\n\nThis has mostly been an experiment to see what is possible with exceptions and\nhow flexible Nim macros can be. The language is really well suited for this\nkind of small experiments where you can play around with language ideas purely\nwithin your own code. Whether this model of exception handling is actually\nuseful or not depends to be seen.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmunch%2Flabeltry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmunch%2Flabeltry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmunch%2Flabeltry/lists"}