{"id":18884172,"url":"https://github.com/dcarbone/terraform-plugin-framework-utils","last_synced_at":"2026-03-04T20:05:58.257Z","repository":{"id":38439956,"uuid":"480812218","full_name":"dcarbone/terraform-plugin-framework-utils","owner":"dcarbone","description":"Utilities for use with the HashiCorp Terraform Plugin Framework","archived":false,"fork":false,"pushed_at":"2025-03-05T11:23:36.000Z","size":142,"stargazers_count":10,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-14T21:13:19.894Z","etag":null,"topics":["terraform","terraform-framework","terraform-plugin","terraform-plugin-framework","terraform-provider"],"latest_commit_sha":null,"homepage":"","language":"Go","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/dcarbone.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-12T13:00:23.000Z","updated_at":"2025-03-05T11:23:39.000Z","dependencies_parsed_at":"2023-10-12T08:25:45.294Z","dependency_job_id":"c2d0d5f3-19ee-4886-a12d-08be37895264","html_url":"https://github.com/dcarbone/terraform-plugin-framework-utils","commit_stats":{"total_commits":67,"total_committers":3,"mean_commits":"22.333333333333332","dds":0.4925373134328358,"last_synced_commit":"68269dc6b302ab02b37e39108353301feee76b40"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcarbone%2Fterraform-plugin-framework-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcarbone%2Fterraform-plugin-framework-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcarbone%2Fterraform-plugin-framework-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dcarbone%2Fterraform-plugin-framework-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dcarbone","download_url":"https://codeload.github.com/dcarbone/terraform-plugin-framework-utils/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248961237,"owners_count":21189993,"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":["terraform","terraform-framework","terraform-plugin","terraform-plugin-framework","terraform-provider"],"created_at":"2024-11-08T07:11:28.611Z","updated_at":"2026-03-04T20:05:58.195Z","avatar_url":"https://github.com/dcarbone.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Terraform Plugin Framework Utilities\nUtilities for use with the\n[HashiCorp Terraform Plugin Framework](https://github.com/hashicorp/terraform-plugin-framework)\n\n[![Documentation](https://img.shields.io/badge/pkg.go.dev-docs-informational)](https://pkg.go.dev/github.com/dcarbone/terraform-plugin-framework-utils)\n\nThis project, much like the framework itself, is a work in progress.  I will try to keep it as up to date with upstream\nchanges as possible but, as always, community help is appreciated!\n\n# Index\n\n* [Version Matrix](#version-matrix)\n* [Installation](#installation)\n* [Type Conversion](#type-conversion)\n* [Attribute Validation](#attribute-validation)\n* [Test Utilities](#test-utilities)\n\n# Version Matrix\n\n| Terraform Plugin Framework | Framework Utils |\n|----------------------------|-----------------|\n| v0.7.0-v0.9.0              | v1              |\n| v0.10.x-v0.15.x            | v2              |\n| v1.x                       | v3              |\n\n# Installation\n```shell\ngo get -u github.com/dcarbone/terraform-plugin-framework-utils/v3@latest\n```\n\n# Type Conversion\n\nConverting between types used internally by Terraform and typical Go types can be somewhat tricky and / or tedious.\n\nTo help with this, I have created a small suite of type conversion utilities designed to make converting to\nand from Terraform and Go easy and obvious.\n\nYou can see the complete list of available conversions here: \n[terraform-plugin-framework-utils/conv](https://github.com/dcarbone/terraform-plugin-framework-utils/blob/main/conv)\n\n# Generic Validation\n\nThe Terraform Plugin Framework has a great set of per-value type validator interfaces that you may implement as needed:\n[validators](https://www.terraform.io/plugin/framework/validation).  This does not always fit the need, however,\nas some validations need not be aware of type, or may benefit from being applicable to multiple types.\n\nTo facilitate this, I have created a few that I have found useful when creating my own providers, and defined a\nsmall wrapper to make creating new validators as simple as [defining a function](validation/validators.go#L20).\n\n## Provided Validators\n\n### Required\n\nFails validation if the attribute is null or unknown\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.Required()\n    },\n}\n```\n\n### RegexpMatch\n\nFails validation if the attribute's value that does not match the user-defined regular expression.  This validator\nwill attempt to convert the attribute to a string first. \n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.RegexpMatch(\"{{ your regex here }}\")\n    },\n}\n```\n\n### RegexpNotMatch\n\nFails validation if the attribute's value matches the user-defined regular expression.  This validator\nwill attempt to convert the attribute to a string first.\n\n```go\n{\n    Validators: []validator.{Type}{\n    \tvalidation.RegexpNotMatch(\"{{ your regex here }}\")\n    },\n}\n```\n\n### Length\n\nFails validation if the attribute's value's length is not within the specified bounds.\n\n```go\n{\n    Validators: []validator.{Type}{\n        // lower limit\n        validation.Length(5, -1),\n\n        // upper limit\n        validation.Length(-1, 10),\n\n        // lower and upper limit\n        validation.Length(5, 10),\n    },\n}\n```\n\n### Compare\n\nFails validation if the attribute's value does not match the configured comparison operation.\n\nSee [comparison.go](validation/comparison.go) for details on what comparison operations are available.  You can add\nyour own [ComparisonFunc](validation/comparison.go#44) using [SetComparisonFunc](validation/comparison.go#229)\n\n```go\n{\n    Validators: []validator.{Type}{\n        // equal\n        validation.Compare(validation.Equal, 5),\n        // string comparisons are case sensitive by default\n        validation.Compare(validation.Equal, \"five\"),\n        // passing true as the 3rd arg executes a case-insensitive comparison with strings\n        validation.Compare(validation.Equal, \"fIve\", true),\n        // you may also equate string slices\n        validation.Compare(validation.Equal, []string{\"one\", \"two\"}),\n        validation.Compare(validation.Equal, []string{\"oNe\", \"twO\"}, true),\n        // you can also assert that a list of ints is equivalent\n        validation.Compare(validation.Equal, []int{1, 2}),\n\n        // less than\n        validation.Compare(validation.LessThan, 10),\n\n        // less than or equal to\n        validation.Compare(validation.LessThanOrEqualTo, 10),\n\n        // greater than\n        validation.Compare(validation.GreaterThan, 5),\n\n        // greater than or equal to\n        validation.Compare(validation.GreaterThanOrEqualTo, 5),\n\n        // not equal\n        validation.Compare(validation.NotEqual, 10),\n        // string comparisons are case sensitive by default\n        validation.Compare(validation.NotEqual, \"ten\"),\n        // passing true as the 3rd arg executes a case-insensitive comparison with strings\n        validation.Compare(validation.NotEqual, \"tEn\", true),\n        // you may also compare string slices\n        validation.Compare(validation.NotEqual, []string{\"one\", \"two\"}),\n        validation.Compare(validation.NotEqual, []string{\"oNe\", \"twO\"}, true),\n        // you can also assert that a list of ints is not equivalent\n        validation.Compare(validation.NotEqual, []int{1, 2}),\n\n        // one of\n        // currently OneOf only works with strings and ints\n        validation.Compare(validation.OneOf, []string{\"one\", \"two\"}),\n        // you can provide true for the 3rd parameter to perform a case-insensitive comparison\n        validation.Compare(validation.OneOf, []string{\"one\", \"two\"}, true),\n        validation.Compare(validation.OneOf []int{1, 2}),\n        \n        // not one of\n        // currently NotOneOf only works with strings and ints\n        validation.Compare(validation.NotOneOf, []string{\"one\", \"two\"}),\n        // you can provide true for the 3rd parameter to perform a case-insensitive comparison\n        validation.Compare(validation.NotOneOf, []string{\"one\", \"two\"}, true),\n        validation.Compare(validation.NotOneOf []int{1, 2}),\n    }\n}\n```\n\n### IsURL\n\nFails validation if the attribute's value is not parseable by `url.Parse`\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.IsUrl()\n    }\n}\n```\n\n### IsDurationString\n\nFails validation if the attribute's value is not parseable by `time.ParseDuration`\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.IsDurationString()\n    }\n}\n```\n\n### EnvVarValued\n\nFails validation if the environment variable name defined by the attribute's value is, itself, not valued at runtime.\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.EnvVarValued()\n    }\n}\n```\n\n### FileIsReadable\n\nFails validation if the file at the path defined in the attribute's value is not readable at runtime.\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.FileIsReadable()\n    }\n}\n```\n\n### MutuallyExclusiveSibling\n\nFails validation if the attribute is valued and the configured sibling attribute is also valued.\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.MutuallyExclusiveSibling(\"{{ sibling field name }}\")\n    }\n}\n```\n\n#### Example\n\n```hcl\n# Example provider Terraform HCL\nprovider \"whatever\" {\n  address = \"http://example.com\"\n  address_env = \"EXAMPLE_ADDR\"\n}\n```\n\n```go\n// Example validators list defined on the `address` attribute's schema\n{\n    Validators: []validator.{Type}{\n        validation.MutuallyExclusiveSibling(\"address_env\")\n    }\n}\n```\n\nAdding the above validator to the `address` attribute's `Validators` list above will require that the `address_env`\nfield must be empty when `address` is defined.  You may also add same validator to the `address_env` attribute, this\ntime pointing at the `address` field.\n\n### MutuallyInclusiveSibling\n\nRequires that two sibling attributes either both be valued or not valued.\n\n```go\n{\n    Validators: []validator.{Type}{\n        validation.MutuallyInclusiveSibling(\"{{ sibling field name }}\")\n    }\n}\n```\n\n#### Example\n\n```hcl\n# Example provider Terraform HCL\nprovider \"whatever\" {\n  ssh_key_file = file(\"local/filepath/ssh.key\")\n  ssh_key_password = null\n}\n```\n\n```go\n// Example validators list defined on the `ssh_key_password` attribute's schema\n{\n    Validators: []validator.{Type}{\n        validation.MutuallyInclusiveSibling(\"ssh_key\")\n    }\n}\n```\n\nAdding the above validator to the `ssh_key_password` attribute's `Validators` list will require that, if the\n`ssh_key_file` attribute is defined so, too, must the `ssh_key_password` attribute be valued.\n\n# Test Utilities\n\nThe Terraform Provider Framework provides an excellent suite of \n[test tools](https://www.terraform.io/plugin/framework/acctests) to use when creating unit and acceptance tests for\nprovider.\n\nFor my uses, I wanted a way to construct hcl config blocks without having to define a heredoc string for each one.\n\nSo I created a few [config utilities](acctest/config.go) to assist with this.\n\n## Example\n\n```go\nfieldMap := map[string]interface{}{\n\t\"address\": \"http://example.com\",\n\t\"token\": acctest.ConfigLiteral(`file(\"/location/on/disk/token\")`),\n\t\"number_of_fish_in_the_sea\": 3500000000000,\n}\nconfHCL := acctest.CompileProviderConfig(\"my_provider\", fieldMap)\n```\n\n```hcl\nprovider \"my_provider\" {\n  address = \"http://example.com\"\n  token = file(\"/location/on/disk/token\")\n  number_of_fish_in_the_sea = 3500000000000\n}\n```\n\nThis can be used with the `acctest.JoinConfigs` func to bring together multiple reusable configuration blocks for \ndifferent tests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcarbone%2Fterraform-plugin-framework-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcarbone%2Fterraform-plugin-framework-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcarbone%2Fterraform-plugin-framework-utils/lists"}