{
  "markdown": "<img alt=\"Logo\" width=\"100px\" src=\"https://github.com/FluentContracts/FluentContracts/raw/master/assets/icon.png\"/>\n\n# FluentContracts\n[![NuGet Version](https://img.shields.io/nuget/v/FluentContracts?style=for-the-badge&logo=nuget&logoColor=white&color=green)](https://www.nuget.org/packages/FluentContracts/)\n[![NuGet Downloads](https://img.shields.io/nuget/dt/FluentContracts?style=for-the-badge&logo=nuget&logoColor=white)](https://www.nuget.org/packages/FluentContracts/)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)\n\nArgument validation that reads like the rule it enforces, and fails with a message that says what\nwas expected of which argument. Inspired by [FluentAssertions](https://github.com/fluentassertions/fluentassertions).\n\n```\ndotnet add package FluentContracts\n```\n\n## Why another validation library\n\n[FluentValidation](https://github.com/FluentValidation/FluentValidation) and `Guard` from the\n[.NET Community Toolkit](https://github.com/CommunityToolkit/dotnet) are excellent, and if you already\nuse them, keep doing so.\n\nThis one exists because guard clauses tend to read as noise: three lines of `if` and `throw` for one\nrule, a `nameof` to keep in sync, an exception type to pick, and a message to write — or, more often,\nnot write. FluentContracts makes the rule the whole statement, the way FluentAssertions does for a\ntest, and puts the argument name, the exception type and a readable message in for you.\n\n## A guard clause, before and after\n\n```csharp\npublic void AddOrder(Order myOrder)\n{\n    if (myOrder == null) throw new ArgumentNullException(nameof(myOrder));\n    if (myOrder.Quantity < 5) throw new ArgumentOutOfRangeException(nameof(myOrder), \"Quantity cannot be less than 5\");\n}\n```\n\n```csharp\npublic void AddOrder(Order myOrder)\n{\n    myOrder.Must().NotBeNull().Satisfy<Order>(o => o.Quantity >= 5, \"Quantity cannot be less than 5\");\n}\n```\n\n`Must()` starts a chain on any value. Every check returns the same chain, so checks follow one\nanother with no glue; `.And.` between them is optional and purely for reading. The argument's name\nis captured at the call site, so exceptions point at the right parameter without `nameof`.\n\n## What every check gives you\n\n**A failure message that names the argument, the expectation and the value.**\n\n```csharp\nport.Must().BeBetween(1, 65535);\n// ArgumentOutOfRangeException: Expected port to be between 1 and 65535, but found 70000. (Parameter 'port')\n\nemail.Must().BeEmailAddress();\n// ArgumentException: Expected email to be a valid email address, but found \"not-an-email\". (Parameter 'email')\n\npages.Must().BeInAscendingOrder();\n// ArgumentException: Expected pages to be in ascending order, but 5 appears before 3. (Parameter 'pages')\n```\n\n**Your own message when you want one.** It is always the last parameter, it replaces the default, and\nit may use `{argument}` and `{value}`:\n\n```csharp\nport.Must().BeBetween(1, 65535, \"{argument} must be a usable port, got {value}\");\n// ArgumentOutOfRangeException: port must be a usable port, got 70000 (Parameter 'port')\n\nenvironment.Must(\"This should be prod\").NotBe(\"test\").NotBeEmpty();\n// one message for every check in the chain; a check's own message still wins\n```\n\n**The validated value back**, so the guard and the read are one expression:\n\n```csharp\nthis.port = config.Port.Must().BeBetween(1, 65535).Value();\n```\n\n`Value()` unwraps a nullable and fails a `null` argument exactly as `NotBeNull` would.\n\n**The right exception type**, without choosing it:\n\n| Failure | Throws |\n|---|---|\n| a `null` argument | `ArgumentNullException` |\n| a comparison, range, sign or NaN check | `ArgumentOutOfRangeException` |\n| everything else — equality, format, containment, type, your own rules | `ArgumentException` |\n\nOr the exception you name: `myOrder.Must().NotBeNull<OrderNullException>()` and\n`Satisfy<Order, OrderQuantityException>(o => o.Quantity >= 5, \"...\")` throw yours.\n\n**Frames you can read.** The library hides its own frames from the stack trace, so a failure points\nat the check you wrote.\n\n## What you can check\n\nEvery type gets `BeNull`/`NotBeNull`, `Be`/`NotBe`, `BeAnyOf`/`NotBeAnyOf` and `Satisfy`; then\neach adds what makes sense for it. The full list is in\n[SupportedContracts.md](docs/SupportedContracts.md).\n\n**Numbers** — `int`, `long`, `short`, `byte`, their unsigned forms, `float`, `double`, `decimal`, and\non .NET 8+ any `INumber<T>` such as `Half`, `Int128` or `BigInteger`:\n\n```csharp\nquantity.Must().BePositive().BeLessOrEqualTo(100);\nretries.Must().BeBetween(1, 5);\npage.Must().BeEven();\nratio.Must().BeFinite();          // neither NaN nor infinity\n```\n\nA comparison never passes on `NaN` and never passes on `null`; `BeNaN` and `BeFinite` are how you\nask about NaN on purpose.\n\n**Text and characters** — shape, format and content:\n\n```csharp\nname.Must().NotBeNullOrWhiteSpace().HaveLengthLessOrEqualTo(64);\ncode.Must().BeAlphanumeric().BeUppercase();\ninput.Must().BeEmailAddress();    // also BeUrl, BeIpAddress, BeGuid, BeBase64, BeHexadecimal, BeCreditCardNumber\npath.Must().BeExistingFile();\nslug.Must().BeMatching(\"^[a-z0-9-]+$\");\ntitle.Must().Contain(\"draft\", StringComparison.OrdinalIgnoreCase);   // Ordinal by default\ninitial.Must().BeLetter().BeUppercase();\n```\n\n**Dates and times** — `DateTime`, `DateTimeOffset`, `TimeSpan`, and on .NET 8+ `DateOnly` and\n`TimeOnly`:\n\n```csharp\nstart.Must().BeInTheFuture().BeWeekday();\ndeadline.Must().BeBetween(start, end);\ntimeout.Must().BeLongerThan(TimeSpan.FromSeconds(1));\nstamp.Must().BeUtc();\nbooking.Must().BeInCurrentYear().NotBeInDecember();\n```\n\nChecks that need the current time take a clock, so tests can pin it:\n`start.Must(dateTimeProvider: clock).BeInTheFuture()`.\n\n**Collections and dictionaries** — arrays, `IList<T>`, `IDictionary<TKey, TValue>`:\n\n```csharp\nitems.Must().NotBeEmpty().HaveCountLessOrEqualTo(100).HaveUniqueItems();\ntags.Must().Contain(\"public\").NotContainNull();\nscores.Must().BeInDescendingOrder().AllSatisfy(s => s >= 0);\nsettings.Must().ContainKey(\"region\").NotContainKey(\"legacy\");\n```\n\n`BeAnyOf`, `Contain` and `ContainAnyOf` take one value or one bracketed set:\n`state.Must().BeAnyOf([\"draft\", \"published\"], \"Not a known state\")`.\n\n**Enums, GUIDs, booleans**:\n\n```csharp\nrole.Must().BeDefined().NotBe(Role.Guest);\nflags.Must().HaveFlag(Permissions.Read);\nid.Must().NotBeEmpty();\nenabled.Must().BeTrue();\n```\n\n**Files, directories, streams and URIs**:\n\n```csharp\nfile.Must().Exist().NotBeEmpty().HaveExtension(\".json\").HaveSizeLessThan(1_000_000);\nfolder.Must().Exist().NotBeReadOnly();\nstream.Must().BeReadable().BeSeekable();\nendpoint.Must().BeAbsolute().BeHttps().HaveHost(\"api.example.com\");\n```\n\n**Any object** — null, type, and a rule of your own:\n\n```csharp\npayload.Must().NotBeNull().BeOfType<OrderPlaced>();\nhandler.Must().BeAssignableTo<IHandler>();\nmyOrder.Must().Satisfy<Order>(o => o.Quantity >= 5);\n```\n\n## Your own rules\n\nA rule you check in more than one place is a specification: a predicate and the phrase that\ncompletes *\"Expected `{argument}` to …\"*. Its failure reads exactly like a built-in check:\n\n```csharp\nstatic readonly ISpecification<string> ValidIban =\n    Spec.From<string>(s => Iban.IsValid(s), \"be a valid IBAN\");\n\niban.Must().Satisfy(ValidIban);\n// ArgumentException: Expected iban to be a valid IBAN, but found \"XX00\". (Parameter 'iban')\n```\n\nRules compose — `ValidIban.And(SepaCountry)` expects *\"be a valid IBAN and be in a SEPA country\"*,\n`ValidIban.Not()` expects *\"not be a valid IBAN\"* — and a rule that needs more room than a lambda\nderives from `Specification<T>` and overrides `IsSatisfiedBy`.\n\nA type of your own can get a contract of its own: derive from `ObjectContract<T, TContract>`, add\nchecks that return the contract, and add a `Must()` extension for the type. Every check above then\nchains with yours.\n\n## The package\n\n- Targets `netstandard2.0` and `net8.0`: .NET Framework 4.6.1+, .NET Core, .NET 5 and later, Mono, Unity.\n- No runtime dependencies.\n- Trimming and Native AOT compatible on `net8.0`, verified on a trimmed and an AOT-published app.\n- A chain is one small object, and on .NET 10 the JIT keeps it off the heap entirely; see\n  [Benchmarks.md](docs/Benchmarks.md).\n- Ships a Roslyn analyzer for the misuses that compile but check the wrong thing (none open at the\n  moment; see [the analyzer project](src/FluentContracts.Analyzers/README.md)).\n- Every public member has XML documentation, so all of the above is in IntelliSense.\n\n## The agent skill\n\nThe rules that make a chain correct — which check to reach for, where the message goes, which\nexception a check throws — are documented, but documentation does not reach a coding agent working in\n*your* project. So they ship as an **agent skill** too: `fluentcontracts`, packaged as a plugin for\nClaude Code, Codex and Gemini CLI.\n\nWith it installed, an agent asked to add argument validation confirms a check exists instead of\nguessing one, chains from `Must()` and ends with `Value()`, keeps the message last, respects the\nsingle bracketed-set rule for `BeAnyOf` and its family, lets the check decide the exception, and\nreaches for `Satisfy` or an `ISpecification<T>` rather than dropping a raw `throw` into a chain.\n\nIt is not in the NuGet package — it is served from this repository, so you install it once per\nmachine and it applies to every project you use FluentContracts in.\n\n### Installing it\n\n**Claude Code** — add this repository as a plugin marketplace, then install the plugin:\n\n```\n/plugin marketplace add FluentContracts/FluentContracts\n/plugin install fluentcontracts@fluentcontracts\n```\n\nPin it to a version by adding the marketplace at a tag instead —\n`FluentContracts/FluentContracts@plugin-v1.0.0`. Every merge that moves the plugin version tags it,\nwhether or not a package was released alongside.\n\n**Codex** — the same repository is a Codex plugin marketplace:\n\n```\ncodex plugin marketplace add https://github.com/FluentContracts/FluentContracts\ncodex plugin install fluentcontracts\n```\n\n**Gemini CLI** — the repository is a Gemini extension, and the skills are discovered from it:\n\n```\ngemini extensions install https://github.com/FluentContracts/FluentContracts\n```\n\n**Any other agent** — the skill is a plain folder. Copy\n[`skills/fluentcontracts`](skills/fluentcontracts) into wherever your harness looks for skills.\n\n### Using it\n\nYou do not invoke it. It carries a description of when it applies, and the agent loads it on its own\nonce the work is about argument validation — \"add guards to this constructor\", \"replace these\n`if`/`throw` blocks\", \"validate the options before we use them\". Naming FluentContracts in the ask\nmakes it certain.\n\nTwo files, both worth reading yourself:\n[`SKILL.md`](skills/fluentcontracts/SKILL.md) is the guidance, and\n[`references/cheatsheet.md`](skills/fluentcontracts/references/cheatsheet.md) is the catalogue of what\nexists plus the rules that decide overloads, messages and exception types.\n\n## Help needed 🙏\n\nThe goal is for this to be exhaustive, safe and stable enough for production use on large projects,\nand help is very welcome: a check that is missing, a message that could read better, a platform that\nis not covered. Open an issue first, then a pull request — [CONTRIBUTING.md](CONTRIBUTING.md) and\n[AGENTS.md](AGENTS.md) describe the conventions, and the latter is written for coding agents as well\nas people.\n\n## Repository 🚧\n\n### Builds\n\n|     Type      | Status                                                                                                                                                                                                                                                     |\n|:-------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n|    Release    | [![Release](https://img.shields.io/github/actions/workflow/status/FluentContracts/FluentContracts/release.yml?branch=master&style=for-the-badge&logo=nuget&logoColor=white&label=Build%20%26%20Release)](https://github.com/FluentContracts/FluentContracts/actions/workflows/release.yml) |\n| Code Coverage | [![Coveralls](https://img.shields.io/coverallsCoverage/github/FluentContracts/FluentContracts?branch=master&style=for-the-badge&logo=coveralls&logoColor=white)](https://coveralls.io/github/FluentContracts/FluentContracts)                                |\n\nPull requests are built and tested on Linux, Windows and macOS by the\n[`pr`](https://github.com/FluentContracts/FluentContracts/actions/workflows/pr.yml) workflow. Every\nmerge into `master` is a release; [CHANGELOG.md](CHANGELOG.md) is the curated account of each one.\n\n### Status\n\n![Alt](https://repobeats.axiom.co/api/embed/5aeeab6e5ce07439108408d66453df63f9379eeb.svg \"Repobeats analytics image\")\n\n### How to build locally\n\nThe .NET SDK version is pinned in `global.json`. Then:\n\n```bash\n./build.sh Test          # compile and run the tests (build.cmd on Windows)\n./build.sh Test Pack     # ...and produce the package in output/packages\n```\n\n## Where to find me 🕵️\n\n[![Blog](https://img.shields.io/badge/Blog-todorov.bg-black.svg?style=for-the-badge&logo=jekyll&logoColor=white)](https://todorov.bg)\n[![X](https://img.shields.io/badge/twitter-%40totollygeek-lightgreen.svg?style=for-the-badge&logo=x&logoColor=white)](https://twitter.com/totollygeek)\n[![LinkedIn](https://img.shields.io/badge/linkedin-totollygeek-blue.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/totollygeek)\n[![Mastodon](https://img.shields.io/badge/Mastodon-%40totollygeek@infosec.exchange-darkblue.svg?style=for-the-badge&logo=mastodon&logoColor=white)](https://infosec.exchange/@totollygeek)\n[![Threads](https://img.shields.io/badge/Threads-%40totollygeek-red.svg?style=for-the-badge&logo=threads&logoColor=white)](https://www.threads.net/@totollygeek)\n[![BlueSky](https://img.shields.io/badge/BlueSky-totollygeek.com-lightblue.svg?style=for-the-badge&logo=bluesky&logoColor=white)](https://bsky.app/profile/totollygeek.com)\n[![Linktree](https://img.shields.io/badge/Linktree-totollygeek-yellow.svg?style=for-the-badge&logo=linktree&logoColor=white)](https://linktr.ee/totollygeek)\n[![Email](https://img.shields.io/badge/Email-fluentcontracts@pm.me-blue.svg?style=for-the-badge&logo=proton&logoColor=white)](mailto://fluentcontracts@pm.me)\n\n## Special thanks 🙇‍♂️\n\n#### [Matthias Koch](https://twitter.com/matkoch87)\n> The creator of [NUKE](https://nuke.build), because I cannot build any .NET project without it and because he helped me tremendously in setting up the repository and everything around this project. (_I have also copy-pasted, like his entire build and some markdown files_ 🤫)\n\n#### [Dennis Doomen](https://twitter.com/ddoomen)\n> The \"[FluentAssertions](https://fluentassertions.com/)\" guy. This whole project was inspired by how that library works and I might have copy-pasted also parts of his repo too 😏\n\n## Technology Sponsors 💻\n<img alt=\"JetBrains Logo\" width=\"300px\" src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png\"/>\n\n> Special thanks to [JetBrains](https://www.jetbrains.com/) for supplying a free license for [Rider](https://www.jetbrains.com/rider/), which is my primary IDE of choice for this project!\n\nIcon made by [IconMonk](https://www.flaticon.com/authors/icon-monk) from [Flaticon](https://www.flaticon.com)\n",
  "bytes": 15536,
  "sha": "d253f9b7c0edb5adbf908973958cdc74c7a54ce498eb25d51b1e290ec8ed369b",
  "repo_slug": "fluentcontracts/fluentcontracts",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_fluentcontracts_fluentcontracts_8a34b907/readme"
}