{"id":20206268,"url":"https://github.com/c0stra/fluent-validation-support","last_synced_at":"2025-03-03T09:43:17.330Z","repository":{"id":96130546,"uuid":"137122391","full_name":"c0stra/fluent-validation-support","owner":"c0stra","description":"Library, providing ready to use procedural or fluent builders of various validation criteria as well as base for development of completely custom fluent, domain specific validators.","archived":false,"fork":false,"pushed_at":"2023-05-09T21:00:13.000Z","size":279,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-13T20:48:11.966Z","etag":null,"topics":["fluent-api","fluent-builders","fluent-interface","validation-criteria"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c0stra.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-12T20:10:15.000Z","updated_at":"2021-10-17T19:23:41.000Z","dependencies_parsed_at":"2023-08-18T03:00:31.649Z","dependency_job_id":null,"html_url":"https://github.com/c0stra/fluent-validation-support","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c0stra%2Ffluent-validation-support","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c0stra%2Ffluent-validation-support/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c0stra%2Ffluent-validation-support/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c0stra%2Ffluent-validation-support/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c0stra","download_url":"https://codeload.github.com/c0stra/fluent-validation-support/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241644542,"owners_count":19996177,"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":["fluent-api","fluent-builders","fluent-interface","validation-criteria"],"created_at":"2024-11-14T05:22:36.482Z","updated_at":"2025-03-03T09:43:17.300Z","avatar_url":"https://github.com/c0stra.png","language":"Java","readme":"# Transparent validation framework\n[![Released version](https://img.shields.io/maven-central/v/foundation.fluent.api/fluent-validation-support.svg)](https://search.maven.org/#search%7Cga%7C1%7Cfluent-validation-support)\n\nThe goal of this framework is to provide way of definition of any complex validation criteria, and get maximum detail,\nwhen they are applied on tested data.\n\nThere are many ready to use factories and builders, but there is also support for simple definition of domain specific\nbuilders (language).\n\nLet's demonstrate the idea on following example validation. The following code throws assertion failure with\ndetailed drill-down of individual checks done within the whole evaluation.\n\n```java\nPerson johnDoe = new Person(\"John Doe\", 45, MALE);\nAssert.that(johnDoe, BasicChecks.\u003cPerson\u003edsl()\n        .withField(\"name\", Person::getName).equalTo(\"John Doe\")\n        .withField(\"age\", Person::getAge).matching(ComparisonChecks.moreThan(20))\n        .withField(\"gender\", Person::getGender).matching(oneOf(FEMALE)));\n```\n\nError message drill down looks like:\n```java\nAssertion failed: expected: ((name \u003cJohn Doe\u003e and age \u003e 20) and gender One of [FEMALE]) but was: test.Person@68be2bc2\n\t+ expected: gender One of [FEMALE] but was: \u003cMALE\u003e\n```\nSee that first line displays whole expectation description (logical and of all items we've added), and actual value\n(whole root object passed to the validation), and second line drills down to root cause of the failure, and explicitly\ndescribes, that it was caused by evaluation of expectation of one individual field: \"gender\", and displays it's actual\nvalue.\n\nSo the key features are:\n1. Define complex validation criteria\n2. Provide base for simple development of either custom criteria implementation or building domain specific builders\n3. Get detailed information about evaluation of the expectation on actual data (transparent validation)\n4. Implement custom presenters of that evaluation detail\n\nThis libraray comes with the concept of completely decoupled **expectation description** from **expectation application**.\n\n- **Expectation description** in fact means, that we are first preparing whole complex expectation (and we do not have any\nreal data for testing available yet). Chapters 1.3 to 1.5 are only dealing with expectation description.\n\n- **Expectation application** on the other hand means, that we apply the prepared expectation on our data, and get the\nresult, which can carry detailed information about the expectation evaluation. Chapters 1.1, 1.2, 2 and 3 are about\nthe evaluation and getting it's full detail.\n\n\n## 1. Validation \n\n### 1.1 Evaluation\nChecks can be used as simple (but transparent) predicate, returning `true` if,\nsupplied data satisfy the check, and `false` otherwise.\n\n```java\nboolean result = Check.that(data, isNull());\n// Result of the check is returned, and code can continue\n```\n\nThat allows to use the checks anywhere in decision logic of a test (or other program). Of course this\nis almost equal to standard `Predicate`. But you may still benefit from the `Check` in cases, where\n- Transparent evaluation providing detailed information is needed (see 2. Result representation)\n- You have already predefined checks used elsewhere, so you can re-use them.\n\n\n### 1.2 Assertion\nMore frequent usage of `Check` is in tests, where we need to make it fail whenever an\nexpectation represented by the `Check` is not met by the data under test.\n\nIn that case no return value is needed, but instead `AssertionFailure` is thrown.\nMethods implementing such assertion are in the class `Assert`.\n\n```java\nAssert.that(data, isNull());\n// AssertionFailure is thrown, which should terminate test execution.\n```\n\n### 1.3 Check factories\nThis library provides ready to use factories, allowing to build simple or complex checks.\nThe list of the methods here is not complete, as this is the main area of active development.\nBut it should provide comprehensive sample to get the idea. For complete list refer to static\nmethods of each of the factory classes.\n\n#### 1.3.1 Simple basic checks ([`BasicChecks`](src/main/java/fluent/validation/BasicChecks.java))\nBasic checks for objects in general are in the [`BasicChecks`](src/main/java/fluent/validation/BasicChecks.java) factory method. There are couple very common ones like:\n\n- `equalTo(D)` - Check will compare supplied data to expected value. It has alias `is(D expectedValue)`.\n- `sameInstance(D)` - checks, if reference is exactly the same object. It doesn't use `equals()` for comparison, but really `==`.\n- `anything()` - always returns true\n- `notNull()` - true for non null reference\n- `isNull()` - true for null reference\n- `instanceOf(Class)` - checks, if object is instance of provided class. Aliases: `isA(Class)`, `isAn(Class)`, `a(Class)`, `an(Class)`\n- `sameClass(Class` - checks, if object's class is exactly the same as the expected class (it fails, if object is subclass of the expected).\n\nFor more see the [`BasicChecks`](src/main/java/fluent/validation/BasicChecks.java) class static public methods.\n\n#### 1.3.2 Basic composition of checks\nThe whole idea of `Check` framework is to be able to apply complex validation. For that the improtant feature is composition.\nThere are different ways of composition. Here are the basic ones (available still in `BasicChecks`).\n\n##### 1.3.2.1 Horizontal composition (boolean logic)\nBy horizontal composition I mean composition, where we compose multiple checks by some function, but where all\nthe checks are still applied on the same object.\n\nBasic boolean binary operator composition is available directly as method of a `Check\u003cD\u003e`:\n- `check.and(otherCheck)` - Resulting check returns true only if both `check` and `otherCheck` are true.\n- `check.or(otherCheck)` - Resulting check returns true is any of `check` and `otherCheck` is true.\n\nThe chaining properly implements operator precedence. So `and` has higher priority than `or` even in following example:\n\n```java\ncheck1.or(check2).and(check3);\n```\nThe resulting check will first do `check2 \u0026\u0026 check3` and only then `check1 || result`.\n\nChaining described above is upgrading. That means, that by chaining with more specific (but compatible) check, result is\nalso automatically more specialized. It is best described by following example:\n\n```java\nCheck\u003cNumber\u003e check1 = moreThan(number);\nCheck\u003cInteger\u003e check2 = lessThan(5);\n// Chaining on check1 (Number) will specialize automatically to Integer: \nCheck\u003cInteger\u003e check3 = check1.and(check2);\n```\n\n- `not(D)`, `not(Check\u003cD\u003e)` - Negation of a check\n- `anyOf(Check\u003cD\u003e...)` - True if any of provided checks is satisfied by tested data\n- `allOf(Check\u003cD\u003e...)` - True only if all provided checks are satisfied by tested data.\n- `requireNonNull(Check\u003cD\u003e)` - Will fail if provided rererence is null, otherwise it will apply the provided check.\n\nFor more see the `basicChecks` class public methods.\n\n##### 1.3.2.3 Vertical composition (transformation)\nBy vertical composition I mean composition, where original object is transformed, and provided \"partial\" check is applied on the result, instead of the original object.\n\n- `compose(String name, Transformation\u003c? super D, V\u003e transformation, Check\u003c? super V\u003e check)` - is the basic method to compose transformed check. However it may fail to property check types, so more convenient way is builder\n- `has(String name, Transformation\u003c? super D, V\u003e transformation).matching(Check\u003c? super V\u003e)` - Same composition. Keep in mind, that it requires original object not to be null, so it can safely run e.g. method references.\n- `nullableHas(String name, Transformation\u003c? super D, V\u003e transformation).matching(Check\u003c? super V)` - The same, but allows oritinal object to be null, assuming, that transformation will properly handle it.\n- `has(String name, Transformation\u003c? super D, V\u003e transformation).equalTo(V)` - builder shortcut for simple test using expected value.\n\n#### 1.3.3 String checks ([`StringChecks`](src/main/java/fluent/validation/StringChecks.java))\n\n#### 1.3.4 Comparison checks ([`ComparisonChecks`](src/main/java/fluent/validation/ComparisonChecks.java))\nChecks, that use comparison\n\n- `moreThan(x)`, `lessThan(x)`, `equalOrMoreThan(x)`, `equalOrLessThan()` - Check with simple comparison, where `x` implements `Comparable`\n- `moreThan(x, comparator)`, `lessThan(x, comparator)` etc. - Checks with externally provided comparator\n- `between(a, b)`, `between(a, b, comparator)` - Range check excluding boundaries\n- `betweenInclude(a, b)` etc. - Range check including boundaries\n\n#### 1.3.5 Numeric checks ([`NumericChecks`](src/main/java/fluent/validation/NumericChecks.java))\n\n#### 1.3.6 Collection checks ([`CollectionChecks`](src/main/java/fluent/validation/CollectionChecks.java))\nCollection checks is a very important category of checks.\n\n- Quantifiers\n  - `exists(elementName, check)` - Validate that in provided `Iterable\u003cT\u003e` exists element, that matches provided `check`. It's\n   fast pass, so it returns true on first match, and doesn't test the rest of the iterable.\n  - `every(elementName, check)` - Validate that in provided `Iterable\u003cT\u003e` every element matches procided `check`. It's\n   fast fail, so on first item, that doesn't match the check, it returns false, and do not excercise the remaining items.\n\n- Exact match\n\n- Exact match in any order\n\n- Collection contains items matching provided checks\n\n- Collection starts with exact sequence of provided checks\n\n- Collection starts with items matching provided checks, but in any order\n\n##### 1.3.6.1 Retrying\n\nTry max 5 times to check result of query to external service with default delay between attempts\nof 1 second:\n```java\nAssert.that(service, repeatMax(has(\"result\", Service::get).equalTo(\"Response\"), 5));\n```\n\nTry the same with custom delay 100ms\n```java\nAssert.that(service, repeatMax(has(\"result\", Service::get).equalTo(\"Response\"), 5, Duration.ofMillis(100)));\n```\n\n##### 1.3.6.2 Check asynchronous events\nTo validate asynchronous events we need to handle (store) them in separate thread, and verify in the\nvalidation (test) thread. Principle is very similar to other collection checks, but this time\nwe have to limit it by timeout, and synchronize the thread. Let's use `BlockingQueue\u003cT\u003e` for it and\nblocking checks:\n\nExample of validating of exact match in any order of asynchronous events:\n```java\nBlockingQueue\u003cString\u003e queue = new LinkedBlockingQueue\u003c\u003e();\nnew Thread(() -\u003e {\n    try {\n        Thread.sleep(200);\n        queue.add(\"A\");\n        Thread.sleep(200);\n        queue.add(\"B\");\n        Thread.sleep(200);\n        queue.add(\"C\");\n    } catch (InterruptedException e) {\n        e.printStackTrace();\n    }\n}).start();\nAssert.that(queue, queueEqualInAnyOrderTo(items(\"A\", \"C\", \"B\"), Duration.ofSeconds(1)));\n```\nThe main thread verification will successfully wait for all the items, and pass.\n\nKeep in mind, that e.g. exact match of whole collection means, that no additional item was received within\nthe given timeout, so such verification finishes after additional timeout reached after receiving the last item.\n\n#### 1.3.7 Database checks ([`DatabaseChecks`](src/main/java/fluent/validation/DatabaseChecks.java))\n\n### 1.4 Fluent check builders\n\nGeneral fluent check builder allows to simply compose a check of individual fields / features of tested object.\nThat allows the example from the beginning (but now split it a bit more):\n\n```java\nPerson johnDoe = new Person(\"John Doe\", 45, MALE);\nCheckDsl\u003cPerson\u003e check = BasicCheck.dsl();\nAssert.that(johnDoe, check\n        .withField(\"name\", Person::getName).equalTo(\"John Doe\")\n        .withField(\"age\", Person::getAge).matching(moreThan(20))\n        .withField(\"gender\", Person::getGender).matching(oneOf(FEMALE)));\n```\nIn fact it's implemented by `allOf()` partial checks, that may be created using vertical composition.\n\n`CheckDsl.Final\u003cT\u003e` provides set these methods:\n- `with(Check\u003c? super T\u003e check)` - simply adds check of the object.\n- `withField(String name, Transformation\u003cT, V\u003e).matching(Check\u003c? super V\u003e check)` - adds check of a feature (field).\n\nAlthough it's named builder, it's not really a builder. Every such chaining in fact creates new immutable check.\nSo following may result in 3 different checks:\n\n```java\nCheckDsl\u003cPerson\u003e entry = BasicCheck.dsl();\nCheckDsl\u003cPerson\u003e nameCheck = entry.withField(\"name\", Person::getName).equalTo(\"John Doe\");\nCheckDsl\u003cPerson\u003e ageCheck = entry.withField(\"age\", Person::getAge).matching(moreThan(20));\n```\nEach check is different object, `entry` doesn't check anything (always returns true), `nameCheck` does only check name,\nand `ageCheck` does only check age.\n\n### 1.5 Domain specific fluent check builder (DSL)\n\nFluent building of checks is very powerful, but the general form from previous chapter is not\nvery convenient for repetitive usage. Therefore it's useful to implement custom DSL.\nFor that simply extend the class `AbstractCheckDsl`:\n\n```java\npublic class PersonCheck extends AbstractCheckDsl\u003cPerson\u003e {\n\n    // Need to provide factory to both constructors in order to create immutable checks.\n    PersonCheck(Check\u003c? super D\u003e check) {\n        super(check, PersonCheck::new);\n    }\n\n    PersonCheck() {\n        super(PersonCheck::new);\n    }\n\n    // Convenience factory method\n    public static PersonCheck personWith() {\n        return new PersonCheck();\n    }\n\n    public PersonCheck name(Check\u003c? super String\u003e check) {\n        return with(\"name\", Person::getName).matching(check);\n    }\n\n    public PersonCheck name(String expectedValue) {\n        return name(equalTo(expectedValue));\n    }\n\n    public PersonCheck age(Check\u003c? super Integer\u003e check) {\n        return with(\"age\", Person::getAge).matching(check);\n    }\n\n    public PersonCheck age(int expectedValue) {\n        return name(equalTo(expectedValue));\n    }\n\n    // ...\n}\n```\n\nSuch custom DSL allows to use following:\n\n```java\nAssert.that(johnDoe, personWith().name\"John Doe\").age(moreThan(20));\n```\n\n## 2. Result representation\n\n```java\nCheck\u003cObject\u003e check = BasicChecks.anything();\n```\n\n## 3. Custom check development\nOf course no library is able to cover specific cases, that need to be tested in product specific\ntests. Therefore this `Check` framework's main goal is not to limit anybody from extending it.\n\n### 3.1 Simple implementation using `Predicate\u003cT\u003e`\nThe simplest way of implementing custom `Check` is simply by providing a lambda implementation\nof the logic, and string description:\n\n```java\nCheck\u003cString\u003e myCheck = BasicChecks.check(s -\u003e s.split(\",\")[2].equals(\"A\"), \"comma separated list's item #2 is A\")\n```\n\nThe logic is used to evaluate if supplied data satisfies the check, and the string is used to describe\nit in the transparent evaluation.\n\nSimple usage like this:\n\n```java\nAssert.that(\"A,C,D\", myCheck);\n```\nwill result in following error:\n```java\nExpected: \u003ccomma separated list's item #2 is A\u003e but was: \u003cA,C,D\u003e\n```\n\nSuch check can be used in composition of any type mentioned above.\n\n### 3.2 Extending `Check\u003cT\u003e` abstract class\nThe custom implementation of a specific check metnioned above is very simple, but in turn limited in the transparency\ncapabilities. In cases, that you also need/want to better handle evaluation detail description, you may need to\ngo and extend the abstract class `Check\u003cD\u003e` itself. By doing that you need to implement the abstract method `Result test(D data, ResultFactory resultFactory)`.\nThat gives you full control over the evaluation detail, that you want to provide.\n\n#### 3.2.1 `ResultFactory` API\n\n## 4. Custom validation result representation\n\n### 4.1 `ResultVisitor` API\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc0stra%2Ffluent-validation-support","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc0stra%2Ffluent-validation-support","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc0stra%2Ffluent-validation-support/lists"}