php-cooper

ConformanceAdapter
in package

FinalYes

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
throws
InvalidArgumentException

when the request holds no list of cases

Return values
array{results: list} —

the 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
throws
InvalidArgumentException

for a value the suite has no type for

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
throws
CooperError

when the document is refused

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
throws
RuntimeException

when the request cannot be read or decoded, or the answer cannot be written

InvalidArgumentException

when the request holds no list of cases

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
string

options()

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
Return values
array{tuple: list}
On this page

Search results