Documentation

This commit is contained in:
Pat Garrity 2025-11-11 22:47:12 -06:00
parent 163957ad27
commit 1f337016fa
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
7 changed files with 452 additions and 1 deletions

View file

@ -30,6 +30,11 @@ val Deps = new {
val Effect: ModuleID = "org.typelevel" %% "cats-effect" % "3.6.3" val Effect: ModuleID = "org.typelevel" %% "cats-effect" % "3.6.3"
} }
val Circe = new {
val Core: ModuleID = "io.circe" %% "circe-core" % "0.14.15"
val Parser: ModuleID = "io.circe" %% "circe-parser" % "0.14.15"
}
val Gs = new { val Gs = new {
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.3.3" val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.3.3"
val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.4.1" val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.4.1"
@ -50,7 +55,8 @@ lazy val `gs-predicate` = project
.aggregate( .aggregate(
`test-support`, `test-support`,
api, api,
keyValue keyValue,
json
) )
.settings(noPublishSettings) .settings(noPublishSettings)
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}") .settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
@ -107,3 +113,24 @@ lazy val keyValue = project
Deps.Gs.Uuid Deps.Gs.Uuid
) )
) )
/** JSON - Defines predicates that can match on JSON values.
*/
lazy val json = project
.in(file("modules/json"))
.dependsOn(`test-support` % "test->test")
.dependsOn(api)
.settings(sharedSettings)
.settings(testSettings)
.settings(
name := s"${gsProjectName.value}-json-v${semVerMajor.value}"
)
.settings(
libraryDependencies ++= Seq(
Deps.Cats.Core,
Deps.Cats.Effect,
Deps.Circe.Core,
Deps.Circe.Parser,
Deps.Gs.Uuid
)
)

6
docs/README.md Normal file
View file

@ -0,0 +1,6 @@
# Library Documentation
- [Terminology](./terminology.md)
- [Predicates: Standard](./predicates-standard.md)
- [Predicates: Key-Value](./predicates-key-value.md)
- [Predicates: JSON](#)

187
docs/predicates-json.md Normal file
View file

@ -0,0 +1,187 @@
# Predicates: JSON
Refer to [Terminology](./terminology.md) for information about specific terms.
[[_TOC_]]
## Description
These predicates are focused on reasoning about JSON values.
## Input
All key-value predicates accept a single input: some JSON value.
## JSON Query Strings
This library may query JSON values using query strings. These are deliberately
_not_ powerful and are not intended for `jq`-like functionality.
These query strings directly interact with the query system.
### Empty Query
An empty query string indicates that the predicate should address the value
verbatim.
#### Example: Plain String
**Input**:
This example will match.
```json
"some string"
```
**Predicate**:
```json
{
"predicate": "json-value-equals",
"parameters": {
"queryString": "",
"value": "some string"
}
}
```
### Value (`.`)
The `.` operator accesses a named value by key within the current object. This
will not match if the current value is not an object.
#### Example: Mismatched Types
**Input**:
This example will not match, because the value at `x` is a string, so no key can
possibly be accessed through it.
```json
{ "x": "some string" }
```
**Predicate**:
```json
{
"predicate": "json-value-equals",
"parameters": {
"queryString": "x.y",
"value": "some string"
}
}
```
### JSON Key
Any text aside from `.` and the contents of `[]` is considered a key. These keys
refer to named elements of the JSON structure.
```json
{
"x": { "y": { "z": 100 } }
}
```
`x`, `y`, and `z` are all keys.
### Array Operator
The `[any|all|<index>]` syntax is used to reason about arrays:
- `[any]` indicates that the predicate will match if _any_ of the selected
values based on traversing the array match.
- `[all]` indicates that the predicate will match only if _all_ of the selected
values based on traversing the array match.
- `[<index>]` indicates that the predicate will match only if the specified
index exists within the array and the selected value at that index matches.
#### Example: Value Equals (Any)
The following scenario will _match_:
**Input**:
```json
{
"data": {
"things": [
{ "x": 100, "y": 200 }
]
}
}
```
**Predicate**:
```json
{
"predicate": "json-value-equals",
"parameters": {
"queryString": "data.things[any].x",
"value": 100
}
}
```
#### Example: Value Equals (All)
The following scenario will _match_:
**Input**:
```json
{
"data": {
"things": [
{ "x": 100, "y": 200 },
{ "x": 100, "y": 300 },
{ "x": 100, "y": 400 }
]
}
}
```
**Predicate**:
```json
{
"predicate": "json-value-equals",
"parameters": {
"queryString": "data.things[all].x",
"value": 100
}
}
```
#### Example: Value Equals (Index)
The following scenario will _match_:
**Input**:
```json
{
"data": {
"things": [
{ "x": 100, "y": 200 },
{ "x": 999, "y": 300 },
{ "x": 100, "y": 400 }
]
}
}
```
**Predicate**:
```json
{
"predicate": "json-value-equals",
"parameters": {
"queryString": "data.things[1].x",
"value": 999
}
}
```

View file

@ -0,0 +1,109 @@
# Predicates: Key-Value
Refer to [Terminology](./terminology.md) for information about specific terms.
[[_TOC_]]
## Description
These predicates are focused on reasoning about keys and their associated
(or not associated) values.
## Input
All key-value predicates accept a single input: some key-value provider. This
is the state referenced by the predicates.
This provider can answer two questions:
- Does the given key exist?
- What is the value associated with a given key?
## Predicate: Key Exists
Matches if the key-value provider contains the given key.
## Predicate: Value Equals
Given some key `K` and expected value `EV`, this predicate matches if:
- There is some value `V` associated with `K`.
- `V` is equal to `EV`.
## Predicate: Value Not Equals
Given some key `K` and unexpected value `UV`, this predicate matches if:
- There is some value `V` associated with `K`.
- `V` is not equal to `UV`.
## Predicate: String Contains
> [!important]
> This predicate is only supported for string values.
Given some key `K` and string `S`, this predicate matches if:
- There is some value `V` associated with `K`.
- `V` contains the substring `S`.
### Examples
| `S` | `V` | Result |
| ------- | ------- | ------- |
| `""` | `""` | `match` |
| `""` | `"a"` | `match` |
| `"a"` | `""` | `miss` |
| `"a"` | `"abc"` | `match` |
| `"ab"` | `"abc"` | `match` |
| `"b"` | `"abc"` | `match` |
| `"bc"` | `"abc"` | `match` |
| `"c"` | `"abc"` | `match` |
| `"d"` | `"abc"` | `miss` |
| `"abc"` | `"abc"` | `match` |
## Predicate: String Starts With
> [!important]
> This predicate is only supported for string values.
Given some key `K` and prefix `P`, this predicate matches if:
- There is some value `V` associated with `K`.
- `V` starts with the prefix `P`.
### Examples
| `P` | `V` | Result |
| ------- | ------- | ------- |
| `""` | `""` | `match` |
| `""` | `"a"` | `match` |
| `"a"` | `""` | `miss` |
| `"a"` | `"abc"` | `match` |
| `"ab"` | `"abc"` | `match` |
| `"b"` | `"abc"` | `miss` |
| `"d"` | `"abc"` | `miss` |
| `"abc"` | `"abc"` | `match` |
## Predicate: String Ends With
> [!important]
> This predicate is only supported for string values.
Given some key `K` and suffix `S`, this predicate matches if:
- There is some value `V` associated with `K`.
- `V` ends with the suffix `S`.
### Examples
| `S` | `V` | Result |
| ------- | ------- | ------- |
| `""` | `""` | `match` |
| `""` | `"a"` | `match` |
| `"a"` | `""` | `miss` |
| `"a"` | `"abc"` | `miss` |
| `"bc"` | `"abc"` | `match` |
| `"c"` | `"abc"` | `match` |
| `"d"` | `"abc"` | `miss` |
| `"abc"` | `"abc"` | `match` |

View file

@ -0,0 +1,57 @@
# Predicates: Standard
Refer to [Terminology](./terminology.md) for information about specific terms.
[[_TOC_]]
## And
The `and` predicate calculates the logical AND of some list of predicates. For
this predicate, a `match` corresponds to `true` and a `miss` corresponds to
`false`.
> Only match if all contained predicates match.
### Case: Empty List
The predicate always misses.
Since there are no predicates, nothing can possibly match.
### Case: Single Predicate
Identiical to that single predicate.
### Case: Multiple Predicates
The logical AND of the predicate results.
## Or
The `or` predicate calculates the logical OR of some list of predicates. For
this predicate, a `match` corresponds to `true` and a `miss` corresponds to
`false`.
> Only match if any contained predicate matches.
### Case: Empty List
The predicate always misses.
Since there are no predicates, nothing can possibly match.
### Case: Single Predicate
Identiical to that single predicate.
### Case: Multiple Predicates
The logical OR of the predicate results.
## True
This predicate always matches.
## False
This predicate always misses.

33
docs/terminology.md Normal file
View file

@ -0,0 +1,33 @@
# Predicate Terminology
[[_TOC_]]
## Definition: Predicate
Any function that accepts exactly one input and produces a
[Predicate Result](#definition-predicate-result) (aka a Boolean output).
Predicates are ways to express logical conditions.
## Definition: Predicate Result
The result of evaluating a [Predicate](#definition-predicate). Predicates may:
- [Match](#definition-match)
- [Miss](#definition-miss)
## Definition: Match
The term **match** is a specific [Result](#definition-predicate-result).
Semantically, it means that the predicate ran successfully against the given
input. The conditions within were met.
If the predicate were expressed as a Boolean, this would correspond to `true`.
## Definition: Miss
The term **miss** is a specific [Result](#definition-predicate-result).
Semantically, it means that the predicate did not run successfully against the
given input. The conditions within were not met.
If the predicate were expressed as a Boolean, this would correspond to `false`.

View file

@ -0,0 +1,32 @@
package gs.predicate.v0.json
import cats.Applicative
import gs.predicate.v0.api.Predicate
import gs.uuid.v0.UUID
import io.circe.Json
/** Predicate that matches if JSON blob is an object that contains the given
* key.
*
* @param id
* The unique identifier of this [[Predicate]].
* @param key
* The key that should exist.
*/
final class JsonKeyExists[F[_]: Applicative](
val id: UUID,
val key: String
) extends Predicate[F, Json]:
/** @inheritDocs
*/
override def eval(input: Json): F[Predicate.Result] =
// \\ is recursive, we need a path-based lookup or something
Applicative[F].pure(Predicate.Result(input.\\(key).nonEmpty))
object JsonKeyExists:
def apply[F[_]: Applicative](key: String): JsonKeyExists[F] =
new JsonKeyExists[F](UUID.v7(), key)
end JsonKeyExists