{"id":38605011,"url":"https://github.com/carlpulley/validated-config","last_synced_at":"2026-01-17T08:37:13.243Z","repository":{"id":52915523,"uuid":"58860927","full_name":"carlpulley/validated-config","owner":"carlpulley","description":"Validated Typesafe configuration","archived":false,"fork":false,"pushed_at":"2021-04-14T07:01:24.000Z","size":1033,"stargazers_count":37,"open_issues_count":4,"forks_count":3,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2023-07-05T04:17:42.127Z","etag":null,"topics":["ficus","scala","shapeless","typesafe-configuration"],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/carlpulley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-15T12:41:28.000Z","updated_at":"2023-01-18T08:24:27.000Z","dependencies_parsed_at":"2022-08-24T14:51:19.109Z","dependency_job_id":null,"html_url":"https://github.com/carlpulley/validated-config","commit_stats":null,"previous_names":[],"tags_count":12,"template":null,"template_full_name":null,"purl":"pkg:github/carlpulley/validated-config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carlpulley%2Fvalidated-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carlpulley%2Fvalidated-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carlpulley%2Fvalidated-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carlpulley%2Fvalidated-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/carlpulley","download_url":"https://codeload.github.com/carlpulley/validated-config/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/carlpulley%2Fvalidated-config/sbom","scorecard":{"id":266402,"data":{"date":"2025-08-11","repo":{"name":"github.com/carlpulley/validated-config","commit":"17193403420174badfb2d7d3c875de5f42d3efb1"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T12:09:22.673Z","repository_id":52915523,"created_at":"2025-08-17T12:09:22.673Z","updated_at":"2025-08-17T12:09:22.673Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28504370,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["ficus","scala","shapeless","typesafe-configuration"],"created_at":"2026-01-17T08:37:11.919Z","updated_at":"2026-01-17T08:37:13.079Z","avatar_url":"https://github.com/carlpulley.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Validated Typesafe Configuration\n\nWhen building reactive applications, it is important to fail early and \nto avoid throwing exceptions. Here, we apply these principles to the\n[Typesafe config](https://github.com/typesafehub/config) library without\nintroducing unnecessary boilerplate code.\n\n[![Build Status](https://secure.travis-ci.org/carlpulley/validated-config.png?tag=1.1.3)](http://travis-ci.org/carlpulley/validated-config)\n[![Maven Central](https://img.shields.io/badge/maven--central-v1.1.3-blue.svg)](http://search.maven.org/#artifactdetails%7Cnet.cakesolutions%7Cvalidated-config_2.12%7C1.1.3%7Cjar)\n[![Apache 2](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](http://www.apache.org/licenses/LICENSE-2.0.txt)\n[![API](https://readthedocs.org/projects/pip/badge/)](https://carlpulley.github.io/validated-config/latest/api#cakesolutions.config.package)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/4cb77ad257344e6185603dceb7b2af65)](https://www.codacy.com/app/c-pulley/validated-config)\n[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/4cb77ad257344e6185603dceb7b2af65)](https://www.codacy.com/app/c-pulley/validated-config)\n\n## Setup and Usage\n\nTo use this library, add the following dependency to your `build.sbt`\nfile:\n```\nlibraryDependencies += \"net.cakesolutions\" %% \"validated-config\" % \"1.1.3\"\n```\n\nTo access the validated Typesafe configuration library code in your\nproject code, simply `import net.cakesolutions.config._`.\n\n## Validating Configuration Values\n\nUsing [Typesafe config](https://github.com/typesafehub/config) we read in and parse configuration files.\nPaths into these files are then retrieved and type checked using [Ficus](https://github.com/iheartradio/ficus).\n\nUsing a lightweight DSL, we are able to then check and validate these\ntype checked values. For example, given that the Typesafe configuration:\n```\ntop-level-name = \"test\"\ntest {\n  nestedVal = 50.68\n  nestedDuration = 4 h\n  nestedList = []\n  context {\n    valueInt = 30\n    valueStr = \"test string\"\n    valueDuration = 12 ms\n    valueStrList = [ \"addr1:10\", \"addr2:20\", \"addr3:30\" ]\n    valueDoubleList = [ 10.2, 20, 0.123 ]\n  }\n}\n```\nhas been parsed and read into an implicit of type `Config`, then we are\nable to validate that the value at the path `test.nestedVal` has type\n`Double` and that it satisfies specified size bounds as follows:\n```scala\ncase object ShouldBeAPercentageValue extends Exception\n\nvalidate[Double](\"test.nestedVal\", ShouldBeAPercentageValue)(n =\u003e 0 \u003c= n \u0026\u0026 n \u003c= 100)\n```\nIf the configuration value at path `test.nestedVal` fails to pass the\npercentage bounds check, then `Validated.Invalid(NonEmptyList.of(ShouldBeAPercentageValue))` is\nreturned. Here, we are using the [cats](https://github.com/typelevel/cats) `Validated` data type.\n\nLikewise, we can enforce that all values in the array at the path\n`test.context.valueStrList` match the regular expression pattern\n`[a-z0-9]+:[0-9]+` as follows:\n```scala\ncase object ShouldBeASocketValue extends Exception\n\nvalidate[List[String]](\"test.context.valueStrList\", ShouldBeASocketValue)(_.matches(\"[a-z0-9]+:[0-9]+\"))\n```\n\nIn some instances, we may not care about checking the value at a\nconfiguration path. In these cases we can use `unchecked`:\n```scala\nunchecked[FiniteDuration](\"test.nestedDuration\")\n```\n\nWhen we require a path to have a value set, then we have two possible\noptions:\n- check if the configuration path exists and is defined\n- or, use a [sentinal value](https://en.wikipedia.org/wiki/Sentinel_value) and validate that the path has a differing value.\n\nWhen configuration paths are specific to your application, then the first\nof these approaches is suitable to use. However, if you are overriding the\nvalues in 3rd party libraries and **require** a value to be set, then it is necessary\nto use sentinal values.\n\nIn the first case, we use `required` as follows:\n```scala\nunchecked[FiniteDuration](required(\"test.nestedDuration\"))\n```\nWhen using `required` with sentinal values, it is necessary to specify what\nthe expected sentinal value is as follows:\n```scala\nunchecked[FiniteDuration](required(\"test.nestedDuration\", \"UNDEFINED\"))\n```\n\n## Building Validated `Config` Instances\n\nBuilding validated configuration case class instances is performed using\nthe methods `via` and `mapN` (where `N` is the number to paths being validated). `via` allows the currently in-scope\nimplicit `Config` instance to be restricted to a specified path. `mapN`\ncan be used to construct the case class instance from a product of `Validated.Valid` values. To do this,\n`mapN` takes a validated tuple of arguments that should be the results of either\nbuilding inner validated case class instances or from using the\n`validate` and `unchecked` methods to validate values at a given path.\n\n## Parsing Custom Configuration Values\n\nAs both `unchecked` and `validate` use [Ficus](https://github.com/iheartradio/ficus) [ValueReader](https://github.com/iheartradio/ficus/blob/master/src/main/scala/net/ceedubs/ficus/readers/ValueReader.scala)'s to parse\nand type check configuration values, we only need to define a [custom extractor](https://github.com/iheartradio/ficus#custom-extraction).\n\n## Example\n\nGiven the following Scala case classes (with [refined](https://github.com/fthomas/refined) refinement types):\n```scala\ncase object NameShouldBeNonEmptyAndLowerCase extends Exception\ncase object ShouldBePositive extends Exception\n\nfinal case class HttpConfig(host: String, port: Int Refined Positive)\nfinal case class Settings(name: String Refined MatchesRegex[W.`\"[a-z0-9_-]+\"`.T], timeout: FiniteDuration, http: HttpConfig)\n```\nand the following Typesafe configuration objects (e.g. stored in a file named `application.conf`):\n```\nname = \"test-data\"\nhttp {\n  host = \"localhost\"\n  host = ${?HTTP_ADDR}\n  port = 80\n  port = ${?HTTP_PORT}\n\n  timeout = 30 s\n}\n```\nthen we can generate a validated `Settings` case class instance as\nfollows:\n```scala\n validateConfig[Settings](\"application.conf\") { implicit config =\u003e\n   Applicative[ValidationFailure].map3(\n     unchecked[String Refined MatchesRegex[W.`\"[a-z0-9_-]+\"`.T]](\"name\"),\n     validate[FiniteDuration](\"http.timeout\", ShouldBePositive)(_ \u003e= 0.seconds),\n     via[HttpConfig](\"http\") { implicit config =\u003e\n       Applicative[ValidationFailure].map2(\n         unchecked[String](\"host\"),\n         unchecked[Int Refined Int](\"port\")\n       )(HttpConfig)\n     }\n   )(Settings)\n }\n```\nInternally, we use `NonEmptyList[ValueError]` for error signal management - however, `validateConfig` aggregates and materialises\nsuch errors as a `ConfigError` instance.\n\n## Secure Validated Configuration\n\nUsing abstract sealed case classes, we can validate configuration\ndata and then ensure that the validated case class instances can\nnot be faked (e.g. via copy constructors or uses of the apply method).\n\nIf we want to do this, then the previous example's case classes could\nbe rewritten as say:\n```scala\npackage net.cakesolutions.example\n\nimport scala.concurrent.duration._\nimport scala.util.Try\n\nimport cats.data.Validated\nimport cats.implicits._\nimport cats.syntax._\nimport eu.timepit.refined._\nimport eu.timepit.refined.api.Refined\nimport eu.timepit.refined.auto._\nimport eu.timepit.refined.numeric._\nimport eu.timepit.refined.string._\n\nimport net.cakesolutions.config._\n\nobject LoadValidatedConfig {\n  sealed abstract case class HttpConfig(host: String, port: Int Refined Positive)\n  sealed abstract case class Settings(name: String Refined MatchesRegex[W.`\"[a-z0-9_-]+\"`.T], timeout: FiniteDuration, http: HttpConfig)\n\n  def apply(): Validated[ConfigError, Settings] =\n    validateConfig[Settings](\"application.conf\") { implicit config =\u003e\n      Applicative[ValidationFailure].map3(\n        unchecked[String Refined MatchesRegex[W.`\"[a-z0-9_-]+\"`.T]](\"name\"),\n        validate[FiniteDuration](\"http.timeout\", ShouldBePositive)(_ \u003e= 0.seconds),\n        via[HttpConfig](\"http\") { implicit config =\u003e\n          Applicative[ValidationFailure].map2(\n            unchecked[String](\"host\"),\n            unchecked[Int Refined Positive](\"port\")\n          )(new HttpConfig(_, _) {})\n        }\n      )(new Settings(_, _, _) {})\n    }\n}\n```\nHere, package `net.cakesolutions.example` is responsible for loading and parsing our configuration files.\n\nAs first reported by [@tpolecat](https://gist.github.com/tpolecat/a5cb0dc9adeacc93f846835ed21c92d2) and discussed further in \n[Enforcing invariants in Scala datatypes](http://www.cakesolutions.net/teamblogs/enforcing-invariants-in-scala-datatypes), the use of an sealed abstract case class\nensures that constructors, copy constructors and companion apply methods are not created \nby the compiler. Hence, the only way that instances of `HttpConfig` and `Settings` can be\ncreated is via the package protected code in the respective implicits - and so\nwe ensure that all such validated configurations are compile time checked as being invariant!\n\n# Prefer to use Scalaz over Cats?\n\nIn this use case, we recommend the use of [shims](https://github.com/djspiewak/shims) to convert from\n[cats](https://typelevel.org/cats/) data structures to [Scalaz](https://github.com/scalaz/scalaz) data structures.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarlpulley%2Fvalidated-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcarlpulley%2Fvalidated-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarlpulley%2Fvalidated-config/lists"}