Documentation
This commit is contained in:
parent
163957ad27
commit
1f337016fa
7 changed files with 452 additions and 1 deletions
29
build.sbt
29
build.sbt
|
|
@ -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
6
docs/README.md
Normal 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
187
docs/predicates-json.md
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
109
docs/predicates-key-value.md
Normal file
109
docs/predicates-key-value.md
Normal 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` |
|
||||||
57
docs/predicates-standard.md
Normal file
57
docs/predicates-standard.md
Normal 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
33
docs/terminology.md
Normal 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`.
|
||||||
|
|
@ -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
|
||||||
Loading…
Add table
Reference in a new issue