{"id":32163748,"url":"https://github.com/nshkrdotcom/perimeter","last_synced_at":"2026-02-21T18:01:24.438Z","repository":{"id":302534174,"uuid":"1012794630","full_name":"nshkrdotcom/perimeter","owner":"nshkrdotcom","description":"Advanced typing and type validation mechanism for Elixir - runtime type checking and contract enforcement for BEAM applications","archived":false,"fork":false,"pushed_at":"2025-10-08T11:43:38.000Z","size":311,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-21T19:31:28.760Z","etag":null,"topics":["beam","contracts","development-tools","elixir","erlang-vm","functional-programming","gradual-typing","otp","productivity","runtime-validation","static-analysis","static-typing","type-annotations","type-checking","type-inference","type-safety","type-system","typing","validation"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nshkrdotcom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-02T22:33:24.000Z","updated_at":"2025-10-20T23:46:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"a7f92334-2652-4ab8-ba9d-de227a124c48","html_url":"https://github.com/nshkrdotcom/perimeter","commit_stats":null,"previous_names":["nshkrdotcom/perimeter"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nshkrdotcom/perimeter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fperimeter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fperimeter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fperimeter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fperimeter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nshkrdotcom","download_url":"https://codeload.github.com/nshkrdotcom/perimeter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fperimeter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29689644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T15:51:39.154Z","status":"ssl_error","status_checked_at":"2026-02-21T15:49:03.425Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["beam","contracts","development-tools","elixir","erlang-vm","functional-programming","gradual-typing","otp","productivity","runtime-validation","static-analysis","static-typing","type-annotations","type-checking","type-inference","type-safety","type-system","typing","validation"],"created_at":"2025-10-21T14:38:18.968Z","updated_at":"2026-02-21T18:01:24.432Z","avatar_url":"https://github.com/nshkrdotcom.png","language":"Elixir","readme":"# Perimeter\n\n**An implementation of the \"Defensive Perimeter / Offensive Interior\" design pattern for Elixir.**\n\nPerimeter helps you build robust and maintainable applications by enforcing explicit data contracts at your system's perimeters. This allows you to write simple, assertive, and highly dynamic code in your core logic with confidence.\n\n## Installation\n\n**Note**: This package is not yet published to Hex. To use it, add it as a Git dependency:\n\n```elixir\ndef deps do\n  [\n    {:perimeter, github: \"nshkrdotcom/perimeter\"}\n  ]\nend\n```\n\n## Quick Start\n\n```elixir\ndefmodule MyApp.Accounts do\n  use Perimeter\n\n  # 1. Define a contract for your data\n  defcontract :create_user do\n    required :email, :string, format: ~r/@/\n    required :password, :string, min_length: 12\n    optional :name, :string, max_length: 100\n  end\n\n  # 2. Guard your function with the contract\n  @guard input: :create_user\n  def create_user(params) do\n    # 3. Write simple, assertive code - params are guaranteed valid!\n    {:ok, %{\n      email: params.email,\n      name: Map.get(params, :name, \"Anonymous\")\n    }}\n  end\nend\n\n# Valid input\nMyApp.Accounts.create_user(%{\n  email: \"user@example.com\",\n  password: \"supersecret123\"\n})\n# =\u003e {:ok, %{email: \"user@example.com\", name: \"Anonymous\"}}\n\n# Invalid input raises with clear error message\nMyApp.Accounts.create_user(%{email: \"invalid\", password: \"short\"})\n# =\u003e ** (Perimeter.ValidationError) Validation failed at perimeter with 2 violation(s):\n#      - email: does not match format\n#      - password: must be at least 12 characters (minimum length)\n```\n\n## The Problem: Defensive Coding Everywhere\n\nIn any large system, modules need to exchange data. This often leads to defensive coding throughout your codebase:\n\n```elixir\ndef create_user(params) do\n  # Defensive checks everywhere\n  case get_in(params, [\"user\", \"email\"]) do\n    nil -\u003e {:error, :email_required}\n    email when is_binary(email) -\u003e\n      case validate_email_format(email) do\n        :ok -\u003e\n          case get_in(params, [\"user\", \"password\"]) do\n            nil -\u003e {:error, :password_required}\n            password when byte_size(password) \u003e= 12 -\u003e\n              # Finally, our actual logic!\n              do_create_user(email, password)\n            _ -\u003e {:error, :password_too_short}\n          end\n        :error -\u003e {:error, :invalid_email}\n      end\n    _ -\u003e {:error, :invalid_email_type}\n  end\nend\n```\n\nThis code is:\n- ❌ **Verbose**: Validation mixed with business logic\n- ❌ **Error-prone**: Easy to forget checks or get them wrong\n- ❌ **Hard to maintain**: Changes require updating validation logic scattered everywhere\n- ❌ **Not reusable**: Same validations duplicated across functions\n\n## The Solution: Defensive Perimeter / Offensive Interior\n\nPerimeter implements a three-zone architecture:\n\n```\n┌─────────────────────────────────────────┐\n│       DEFENSIVE PERIMETER (@guard)      │\n│   ┌─────────────────────────────────┐   │\n│   │    TRANSITION LAYER (validate)  │   │\n│   │  ┌───────────────────────────┐  │   │\n│   │  │  OFFENSIVE INTERIOR       │  │   │\n│   │  │  (your business logic)    │  │   │\n│   │  └───────────────────────────┘  │   │\n│   └─────────────────────────────────┘   │\n└─────────────────────────────────────────┘\n```\n\n1. **Defensive Perimeter**: Guards validate all inputs before they enter your function\n2. **Transition Layer**: Automatic normalization and transformation\n3. **Offensive Interior**: Your business logic with guaranteed-valid data\n\n## Features\n\n### Comprehensive Type System\n\n- ✅ Basic types: `:string`, `:integer`, `:float`, `:boolean`, `:atom`, `:map`, `:list`\n- ✅ Typed lists: `{:list, :string}`, `{:list, :integer}`, etc.\n- ✅ Nested maps with full validation\n- ✅ Required and optional fields\n\n### Rich Constraint System\n\n**String constraints:**\n```elixir\nrequired :email, :string, format: ~r/@/\nrequired :username, :string, min_length: 3, max_length: 20\n```\n\n**Number constraints:**\n```elixir\nrequired :age, :integer, min: 18, max: 150\nrequired :price, :float, min: 0.0\n```\n\n**Enum constraints:**\n```elixir\nrequired :role, :atom, in: [:admin, :user, :guest]\nrequired :status, :string, in: [\"active\", \"inactive\"]\n```\n\n### Nested Validation\n\n```elixir\ndefcontract :user do\n  required :email, :string\n  optional :address, :map do\n    required :city, :string\n    required :zip, :string, format: ~r/^\\d{5}$/\n    optional :state, :string\n  end\nend\n```\n\n### Clear Error Messages\n\n```elixir\nMyApp.Accounts.create_user(%{\n  email: \"invalid\",\n  password: \"short\",\n  profile: %{age: 17}\n})\n# ** (Perimeter.ValidationError) Validation failed at perimeter with 3 violation(s):\n#   - email: does not match format\n#   - password: must be at least 12 characters (minimum length)\n#   - profile.age: must be \u003e= 18 (minimum value)\n```\n\n## Real-World Examples\n\n### API Request Handling\n\n```elixir\ndefmodule MyAPI.SearchController do\n  use Perimeter\n\n  defcontract :search_params do\n    required :query, :string, min_length: 1\n    optional :filters, :map do\n      optional :category, :atom, in: [:all, :active, :archived]\n      optional :limit, :integer, min: 1, max: 100\n    end\n  end\n\n  @guard input: :search_params\n  def search(params) do\n    # params.query, params.filters are guaranteed valid\n    MyApp.Search.run(params.query, Map.get(params, :filters, %{}))\n  end\nend\n```\n\n### Configuration Validation\n\n```elixir\ndefmodule MyApp.Config do\n  use Perimeter\n\n  defcontract :database_config do\n    required :host, :string\n    required :port, :integer, min: 1, max: 65535\n    required :database, :string\n    optional :pool_size, :integer, min: 1, max: 100\n  end\n\n  @guard input: :database_config\n  def connect(config) do\n    # config is validated - safe to use directly\n    Ecto.Repo.start_link(\n      hostname: config.host,\n      port: config.port,\n      database: config.database,\n      pool_size: Map.get(config, :pool_size, 10)\n    )\n  end\nend\n```\n\n### Data Processing Pipelines\n\n```elixir\ndefmodule MyApp.DataProcessor do\n  use Perimeter\n\n  defcontract :process_input do\n    required :items, {:list, :map}\n    required :operation, :atom, in: [:transform, :filter, :aggregate]\n    optional :batch_size, :integer, min: 1, max: 1000\n  end\n\n  @guard input: :process_input\n  def process(params) do\n    params.items\n    |\u003e Enum.chunk_every(Map.get(params, :batch_size, 100))\n    |\u003e Enum.map(\u0026apply_operation(\u00261, params.operation))\n  end\nend\n```\n\n## Documentation\n\n### Core Modules\n\n- **`Perimeter`** - Main module, use with `use Perimeter`\n- **`Perimeter.Contract`** - Define contracts with `defcontract`\n- **`Perimeter.Guard`** - Apply guards with `@guard`\n- **`Perimeter.Validator`** - Manual validation API\n- **`Perimeter.ValidationError`** - Exception raised on validation failure\n\n### Design Documentation\n\nFor a deeper understanding of the philosophy and design:\n\n- [Design Philosophy and Principles](docs/PERIMETER_gem_0010.md) - The \"why\" behind the library\n- [Type Perimeters Design](docs/type_perimeters_design.md) - The core innovation\n- [Implementation Guide](docs/PERIMETER_LIBRARY_IMPLEMENTATION_GUIDE.md) - Comprehensive reference\n\n## Testing\n\nPerimeter has comprehensive test coverage:\n\n```bash\nmix test\n# =\u003e 117 tests, 0 failures\n```\n\nTest categories:\n- **Contract tests** (16 tests) - Contract definition and structure\n- **Validator tests** (47 tests) - Validation logic and constraints\n- **Guard tests** (26 tests) - Function perimeter enforcement\n- **Integration tests** (26 tests) - Real-world scenarios\n- **Doctests** (1 test) - Documentation examples\n\n## Solving Common Elixir Anti-Patterns\n\nPerimeter is designed to programmatically guide you away from common Elixir anti-patterns, leading to cleaner and more maintainable code.\n\n| Anti-Pattern                      | How Perimeter Solves It                                                                                              |\n| :-------------------------------- | :------------------------------------------------------------------------------------------------------------------- |\n| **Non-Assertive Map Access**      | Contracts guarantee the shape of data, allowing you to use assertive `map.key` and `%{key: val}` access.              |\n| **Dynamic Atom Creation**         | Contracts validate incoming strings against an explicit list of allowed values, which can then be safely converted to existing atoms. |\n| **Complex `else` Clauses in `with`** | Promotes a single, clear validation step at the beginning of a function, simplifying \"happy path\" logic.          |\n| **Non-Assertive Pattern Matching**  | By validating the data shape at the perimeter, you can write assertive, non-defensive code in the function interior. |\n\nRead the full list of anti-patterns Perimeter helps address in [**ELIXIR_1_20_0_DEV_ANTIPATTERNS.md**](docs/ELIXIR_1_20_0_DEV_ANTIPATTERNS.md).\n\n## Full Documentation\n\nThis library is the result of extensive research and design. The complete documentation, from initial problem analysis to the final architectural blueprint, is available for review.\n\n### Core Design and Philosophy\n\n*   [**Design Philosophy and Principles**](docs/PERIMETER_gem_0010.md) - The \"why\" behind the library, inspired by Elixir's core tenets.\n*   [**Type Perimeters Design**](docs/type_perimeters_design.md) - The original \"Defensive Perimeter / Offensive Interior\" concept.\n*   [**Greenfield Architecture Guide**](docs/PERIMETER_gem_0012.md) - A blueprint for architecting new applications with Perimeter.\n\n### Implementation and Usage Guides\n\n*   [**Implementation Guide**](docs/PERIMETER_LIBRARY_IMPLEMENTATION_GUIDE.md) - A comprehensive guide to the library's internal structure.\n*   [**Migration Strategy Guide**](docs/migration_strategy_guide.md) - How to adopt Perimeter in an existing \"brownfield\" application.\n*   [**Defensive Perimeter Implementation**](docs/defensive_perimeter_implementation.md) - Practical code patterns for implementing the core concepts.\n*   [**Type-Safe Metaprogramming Patterns**](docs/type_safe_metaprogramming_patterns.md) - How to safely combine Perimeter with Elixir's metaprogramming features.\n\n### Best Practices and Specifications\n\n*   [**Type Contract Best Practices**](docs/type_contract_best_practices.md) - Dos and Don'ts for writing effective contracts.\n*   [**Error Handling and Type Safety**](docs/error_handling_type_safety.md) - Patterns for robust error handling at perimeters.\n*   [**Formal Type Relationships Spec**](docs/type_relationships_formal_spec.md) - A formal specification of the type system.\n*   [**Type Enforcement Library Spec**](docs/type_enforcement_library_spec.md) - The detailed API specification for the library.\n*   [**Perimeter Caching Strategies**](docs/PERIMETER_CACHING_STRATEGIES.md) - A plan for ensuring high performance.\n\n### Project Planning\n\n*   [**Project Roadmap**](docs/PERIMETER_gem_0011.md)\n*   [**Implementation Prompts**](docs/PERIMETER_LIBRARY_IMPLEMENTATION_PROMPTS.md)\n\n## Contributing\n\nContributions are welcome! This project is in the planning phase, and feedback on the design is highly encouraged. Please see our contribution guidelines and code of conduct. (Links to be added).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fperimeter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnshkrdotcom%2Fperimeter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fperimeter/lists"}