{"id":16451010,"url":"https://github.com/adnelson/re-opaque","last_synced_at":"2026-03-02T16:31:51.099Z","repository":{"id":40755470,"uuid":"280590296","full_name":"adnelson/re-opaque","owner":"adnelson","description":"Easy and useful library for creating opaque types","archived":false,"fork":false,"pushed_at":"2023-01-06T11:47:32.000Z","size":851,"stargazers_count":11,"open_issues_count":11,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-26T10:47:59.039Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Reason","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/adnelson.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}},"created_at":"2020-07-18T05:39:17.000Z","updated_at":"2024-11-05T00:36:25.000Z","dependencies_parsed_at":"2023-02-06T00:31:11.224Z","dependency_job_id":null,"html_url":"https://github.com/adnelson/re-opaque","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adnelson%2Fre-opaque","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adnelson%2Fre-opaque/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adnelson%2Fre-opaque/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adnelson%2Fre-opaque/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adnelson","download_url":"https://codeload.github.com/adnelson/re-opaque/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240876055,"owners_count":19871899,"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-10-11T10:06:52.216Z","updated_at":"2026-03-02T16:31:46.041Z","avatar_url":"https://github.com/adnelson.png","language":"Reason","funding_links":[],"categories":[],"sub_categories":[],"readme":"# re-opaque\n\nA ReasonML library for opaque data types with validation.\n\n## Opaque strings\n\nAt runtime, they are just strings. But since each of them is a distinct module, they form distinct types; so you can never e.g. mix up an Email with a Name. In addition, you can set up validation rules and guarantee (short of escape hatches like `Obj.magic`) that all strings in the type will have been validated.\n\n### Example\n\nBelow we create three string-like modules, `UserName`, `Email`, and `MessageText`. Each has its own validation logic (or lack thereof).\n\n```reason\nopen Opaque.String;\n\nmodule UserName: StringType = Make(\n  Validation.Compose(\n    Validation.MinLength({let n = 10;}),\n    Validation.MaxLength({let n = 80;}),\n  ), ()\n);\n\nmodule Email = Opaque.String.Make(Opaque.RegexValidation({\n  let regex = [%re {|/magic email regex/|}];\n}, ()));\n\n// This has no validation (although in practice a max length might be good)\nmodule MessageText = Opaque.String.Make(String.NoValidation);\n```\n\nWe can't create a username that's less than 5 characters:\n\n```reason\n// raises `TooShort(\"bad\", 10)`\nlet badUsername = \"bad\"-\u003eUserName.fromString;\n```\n\nOr an email that doesn't match our regex:\n\n```reason\n// raises `RegexMatchError(\"i am not an email\", \u003csome regex\u003e)`\nlet badEmail = \"i am not an email\"-\u003eEmail.fromString;\n```\n\nThis guarantee means that you have total confidence that you won't be handling invalid data, and pushes error boundaries as early as possible.\n\n### Error handling\n\nThe `fromString` function will raise an `exn` exceptions to on failure. Different validators raise different exceptions, so you can switch on them with `try` or `| exception _` to do error handling.\n\nAlternatively you can use `resultFromString`, which avoids potential error cascades. It's up to you.\n\n```reason\n// Ok(\"probablyok\"), which is typed as result(UserName.t, exn)\nlet goodUsername = \"probablyok\"-\u003eUserName.resultFromString;\n\n// Error(TooShort(\"nope\", 10))\nlet badUsername = \"nope\"-\u003eUserName.resultFromString;\n```\n\n### Extras\n\nThe module also generates `fromJson` and `toJson` functions, allowing easy conversion to/from JSON for the custom types.\n\nThere's also a `MakeStringSet` functor which creates a `Set` module for managing sets of custom types with `Belt.String`. There might be more on this later.\n\n\n## Build\n\n```\nyarn re:build\n\n# Alternatively, in watch mode\nyarn re:watch\n```\n\n## Test\n\n```\nyarn test\n\n# Alternatively, in watch mode\nyarn test:watch\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadnelson%2Fre-opaque","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadnelson%2Fre-opaque","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadnelson%2Fre-opaque/lists"}