Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/Changed-20261004-020848.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Changed
body: Implement spec 0.3.0. It adds the postcode field and its messages, and results stay the same.
time: 2026-10-04T02:08:48.403352+01:00
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This repo is part of Gatepost, unofficial open-source developer tools for Nigeri

## Rules that no config file shows

- If the task needs a public symbol that the Interface section of `spec/grammar.md` does not list, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8).
- If the task needs a public symbol that no Interface section of the spec lists, in `spec/grammar.md`, `spec/client.md` or `spec/field.md`, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8).
- Call only the documented NIPOST gateway endpoints.
- Commit only NIPOST's published test codes and synthetic values, such as `FC-01-Z99-ZZ-01`.
- Sign off each commit with `git commit -s`. The person who opens the pull request is the only author. Leave out co-author lines for AI tools.
Expand Down
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ _Avoid_: input, widget, picker
To show the user what a postcode points to before the app stores it.
_Avoid_: verify, validate (for this step)

**Check status**:
What the WooCommerce plugin learned about the postcode of an order: `valid`, `invalid`, `unchecked` or `error`. The order meta `_gatepost_postcode_check` holds it.
_Avoid_: verification status, validation result

**Form value**:
The text that a field gives its form when the form is sent: the canonical form of a postcode, the 6 digits of a legacy postcode, or the text without white space at the start and the end.
_Avoid_: submitted value, output

**Message catalogue**:
A data file that holds the text of a UI in one language, one message for each key.
_Avoid_: strings file, translations, locale file

**Tell**:
A habit that makes code look machine-written, such as very long lines.
_Avoid_: smell (smells are design problems), anti-pattern
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ The example uses `FC-01-Z99-ZZ-01`, a synthetic postcode, and shows the answer o
|---|---|
| PHP | 8.1 or later |
| HTTP, for the client | a PSR-18 client and a PSR-17 request factory |
| Gatepost spec | 0.2.0 |
| Gatepost spec | 0.3.0 |

## Docs

Expand Down
2 changes: 1 addition & 1 deletion docs/api-dump.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ final class ParseResult

final class Postcode
{
public const SPEC_VERSION = '0.2.0';
public const SPEC_VERSION = '0.3.0';
public readonly string $compact;
public readonly string $canonical;
public readonly string $display;
Expand Down
2 changes: 2 additions & 0 deletions docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ composer check

The BC check tool lists ext-intl as a requirement. Its check runs without that extension, so the install line ignores the requirement. The line also works on a machine that has ext-intl.

`composer api:check` compares the public API with the newest release tag, so run `git fetch --tags` first. It ignores one finding: the value of `Postcode::SPEC_VERSION` changes with each spec version by design, so that change is not a break of the API.

`composer run` lists the other scripts. `composer test` runs the tests without coverage. `composer mutation` runs the mutation tests, and it needs Xdebug.

## Contract scenarios
Expand Down
34 changes: 33 additions & 1 deletion scripts/check-api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# SPDX-License-Identifier: Apache-2.0
# Compares the public API with the newest release tag (GIT-7, API-13). Before the first tag,
# no released API exists, so the script says so and stops.
#
# The value of Postcode::SPEC_VERSION changes with each spec version by design. That change is
# not a break of the API, so the script drops that one finding. It keeps every other finding,
# and it fails on every other failure of the tool.
set -euo pipefail

# Git sorts v0.1.0-alpha.0 above v0.1.0 unless the config names the suffix. Without the option,
Expand All @@ -12,4 +16,32 @@ if [ -z "$tag" ]; then
echo "No release tag yet, so there is no released API to compare."
exit 0
fi
exec tools/bc-check/vendor/bin/roave-backward-compatibility-check --from="$tag"

spec_version_change='[BC] CHANGED: Value of constant '
spec_version_change+='Gatepost\Postcode\Postcode::SPEC_VERSION changed'
status=0
tool=tools/bc-check/vendor/bin/roave-backward-compatibility-check
output="$("$tool" --from="$tag" 2>&1)" || status=$?

dropped=0
breaks=0
while IFS= read -r line; do
if [[ $line == "$spec_version_change"* ]]; then
dropped=1
elif [[ $line =~ ^[0-9]+\ backwards-incompatible\ changes\ detected$ ]]; then
: # The tool counts the dropped finding, so the script prints its own count.
else
echo "$line"
if [[ $line == '[BC]'* ]]; then breaks=$((breaks + 1)); fi
fi
done <<<"$output"

if [ "$breaks" -gt 0 ]; then
echo "$breaks backwards-incompatible changes detected"
exit 3
fi
# Code 3 is the code of the tool for a break. Another code, or code 3 with no finding to
# explain it, is a failure of the tool itself.
if [ "$status" -ne 0 ] && { [ "$status" -ne 3 ] || [ "$dropped" -eq 0 ]; }; then
exit "$status"
fi
2 changes: 1 addition & 1 deletion src/Postcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class Postcode
/**
* The version of the Gatepost spec that this package implements.
*/
public const SPEC_VERSION = '0.2.0';
public const SPEC_VERSION = '0.3.0';

// In UTF-8, a code point takes at most 4 bytes. Longer input is over the limit without a
// count, so a 10 MB string costs no more than a short one.
Expand Down
55 changes: 54 additions & 1 deletion tests/Unit/CheckApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* scripts/check-api.sh starts the BC check from the newest release tag. These tests run the
* script in a temporary repo with the tags of one case. A stub takes the place of the BC check
* tool and prints the tag that the script gave it.
* tool. It prints the tag that the script gave it, or the output of the case.
*/
final class CheckApiTest extends TestCase
{
Expand Down Expand Up @@ -83,6 +83,59 @@ public function stopsBeforeTheFirstTag(): void
);
}

/**
* @return array<string, array{string, int, int, string}>
*/
public static function toolOutputs(): array
{
$constant = '[BC] CHANGED: Value of constant Gatepost\\Postcode\\Postcode::SPEC_VERSION '
. "changed from '0.2.0' to '0.3.0'";
$method = '[BC] REMOVED: Method Gatepost\\Postcode\\Postcode::check() was removed';
$summary = static fn(int $count): string
=> "{$count} backwards-incompatible changes detected";

return [
'the constant alone' => [
"{$constant}\n{$summary(1)}",
3,
0,
'',
],
'the constant and another break' => [
"{$constant}\n{$method}\n{$summary(2)}",
3,
3,
"{$method}\n{$summary(1)}",
],
'another break alone' => [
"{$method}\n{$summary(1)}",
3,
3,
"{$method}\n{$summary(1)}",
],
'a failure of the tool' => ['Could not read the tag', 1, 1, 'Could not read the tag'],
'a break code with no finding' => ['Odd output', 3, 3, 'Odd output'],
];
}

#[Test]
#[DataProvider('toolOutputs')]
public function dropsOnlyTheChangeOfTheSpecVersion(
string $toolOutput,
int $toolCode,
int $exitCode,
string $output,
): void {
$this->startRepo('v0.1.0');
$stub = "{$this->repo}/tools/bc-check/vendor/bin/roave-backward-compatibility-check";
\file_put_contents($stub, "#!/bin/sh\ncat <<'END'\n{$toolOutput}\nEND\nexit {$toolCode}\n");

$run = $this->checkApi();

self::assertSame($exitCode, $run->exitCode, $run->output);
self::assertSame($output, $run->output);
}

// The repo starts in each test, so that tearDown() removes it even when a git command fails.
private function startRepo(string ...$tags): void
{
Expand Down