ConformanceAdapter
in package
This implementation's adapter for the Cooper Conformance Suite: a JSON request in, a JSON answer out (`php bin/cooper-conformance REQUEST ANSWER`). The PHP twin of the Elixir reference's `Cooper.Conformance`, with the same behaviour and the same JSON.
The suite (libs_prx/cooper_conformance in the Praxis repository,
run by its assize library) holds every CASC implementation to the
same cases. Each case asks for one operation, load, on a document
file; this answers with the loaded configuration in the suite's typed
JSON, or with the category (CASC.md ยง9.5) of the error that refused
it -- the CooperError's stage.
What every adapter loads with
The suite fixes the environment and the extension points, so a case
can use them and every implementation sees the same: the variables in
ENV, no .env file, the document's own directory as the root, the
resolvers echo (answers its payload) and fail (refuses), the tag
twice (doubles an integer, repeats a string) and the import scheme
mem (mem://base, mem://vars). !module is the built-in one,
with no modules mapping, its result marked so the answer can name
the module (see ConformanceModule).
Values
Every value travels in the suite's typed JSON (see encode()):
{"integer": "42"}, {"float": "2.0"}, {"keyword": "info"},
{"tuple": [...]}, {"map": [[key, value], ...]}, arrays, and
strings, booleans and null as they are; a float's text is the one
Elixir's Float.to_string/1 writes (2.0, 1.0e3), and a map's
pairs are sorted by key. What CASC has and Praxis
notation has no literal for is a tuple tagged with a namespaced keyword
-- casc/duration, casc/bytes, casc/secret, casc/module,
casc/float, casc/ipv4, casc/ipv6, casc/date, casc/time,
casc/local-date-time, casc/date-time -- which no CASC atom can be,
so a tag never reads as a tuple a document wrote.
Table of Contents
Constants
- ENV : mixed = ['REGION' => 'eu-west', 'PORT' => '9090', 'DEBU...
- The environment every case is loaded with, the suite's fixed standard.
- MEM : mixed = ['base' => "#@version = 1\nfrom_mem = true\nsha...
- What the `mem` import scheme serves, by the rest of its URI.
Methods
-
answer()
: array{results: list
} - The answer to a decoded request: one result per case, in order.
- encode() : mixed
- `$value`, as this implementation holds a loaded configuration, in the suite's typed JSON (as a PHP value `json_encode()` writes as that JSON).
- load() : mixed
- Loads `$document` with the suite's standard options, through the whole pipeline but without a cache, and gives back the resolved value as the pipeline holds it -- a map still a `MapNode`, so an empty map stays distinguishable from an empty list -- with every module a `ConformanceModule`.
- run() : void
- Reads the request at `$requestPath` and writes the answer to `$answerPath`. A case that fails is an answer, not a failure of the run.
- __construct() : mixed
- inspect() : string
- A request's value as an error message shows it, the way Elixir's `inspect/1` does for what a JSON request can hold: `"dance"`, `nil`.
- options() : array<string, mixed>
- The load options every case runs with (see the class documentation).
- pairs() : array<int, array{0: string, 1: mixed}>
- A map's pairs, `[key, typed value]`, sorted by key byte for byte.
- result() : array{id: mixed, result?: mixed, error?: string}
- One case's result. A `CooperError` is its stage; anything else thrown is this implementation's failure on this case, answered as a crash: the suite reports it as a disagreement rather than losing every other case's answer with it.
- tagged() : array{tuple: list}
- A tuple tagged with a namespaced keyword: `{"tuple": [{"keyword": tag}, ...items]}`.
Constants
ENV
The environment every case is loaded with, the suite's fixed standard.
public
mixed
ENV
= ['REGION' => 'eu-west', 'PORT' => '9090', 'DEBUG' => 'true', 'EMPTY' => '', 'LIST' => 'a, b;c', 'SCHEME' => 'HTTPS://', 'PADDED' => ' padded ', 'TOKEN_1' => 'tok-one', 'TOKEN_2' => 'tok-two', 'FEATURE' => 'on', 'WHICH' => 'dev', 'FORMATTER' => 'Foo.Bar', 'COOPER_ENV' => 'conformance']
MEM
What the `mem` import scheme serves, by the rest of its URI.
private
mixed
MEM
= ['base' => "#@version = 1\nfrom_mem = true\nshared = \"mem\"\n", 'vars' => "#@version = 1\n@mem_var = \"from-mem\"\n"]
Methods
answer()
The answer to a decoded request: one result per case, in order.
public
static answer(array<string|int, mixed> $request) : array{results: list}
A result is ["id" => ..., "result" => typed value] or
["id" => ..., "error" => category]; an unknown operation and a
crash are errors too ("unknown operation: ...",
"crash: <message>"), so one case never costs the others theirs.
Parameters
- $request : array<string|int, mixed>
-
the decoded request, its cases under the key
cases
Tags
Return values
array{results: listthe answer
encode()
`$value`, as this implementation holds a loaded configuration, in the suite's typed JSON (as a PHP value `json_encode()` writes as that JSON).
public
static encode(mixed $value) : mixed
Parameters
- $value : mixed
-
a value
load()gives back, or any part of one
Tags
Return values
mixed —the typed JSON
load()
Loads `$document` with the suite's standard options, through the whole pipeline but without a cache, and gives back the resolved value as the pipeline holds it -- a map still a `MapNode`, so an empty map stays distinguishable from an empty list -- with every module a `ConformanceModule`.
public
static load(string $document) : mixed
Parameters
- $document : string
-
the CASC file
Tags
Return values
mixed —the resolved configuration, a MapNode
run()
Reads the request at `$requestPath` and writes the answer to `$answerPath`. A case that fails is an answer, not a failure of the run.
public
static run(string $requestPath, string $answerPath) : void
Parameters
- $requestPath : string
-
the JSON request file
- $answerPath : string
-
where the JSON answer is written
Tags
__construct()
private
__construct() : mixed
inspect()
A request's value as an error message shows it, the way Elixir's `inspect/1` does for what a JSON request can hold: `"dance"`, `nil`.
private
static inspect(mixed $value) : string
Parameters
- $value : mixed
Return values
stringoptions()
The load options every case runs with (see the class documentation).
private
static options(string $root) : array<string, mixed>
Parameters
- $root : string
Return values
array<string, mixed>pairs()
A map's pairs, `[key, typed value]`, sorted by key byte for byte.
private
static pairs(MapNode $map) : array<int, array{0: string, 1: mixed}>
Parameters
- $map : MapNode
Return values
array<int, array{0: string, 1: mixed}>result()
One case's result. A `CooperError` is its stage; anything else thrown is this implementation's failure on this case, answered as a crash: the suite reports it as a disagreement rather than losing every other case's answer with it.
private
static result(mixed $case) : array{id: mixed, result?: mixed, error?: string}
Parameters
- $case : mixed
Return values
array{id: mixed, result?: mixed, error?: string}tagged()
A tuple tagged with a namespaced keyword: `{"tuple": [{"keyword": tag}, ...items]}`.
private
static tagged(string $tag, mixed ...$items) : array{tuple: list}
Parameters
- $tag : string
- $items : mixed