From 9bbc4e21421137805399482afb10b973997cfd3b Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Fri, 26 Jun 2026 18:09:32 -0300 Subject: [PATCH] feat: Add PSR-15 error-handling middleware. --- .claude/hooks/php-ordering-conformance.py | 607 +++++++++++ .../php-prose-punctuation-conformance.py | 176 ++++ .claude/rules/php-library-architecture.md | 147 +++ .claude/rules/php-library-code-style.md | 960 ++++++++++++++++++ .claude/rules/php-library-documentation.md | 203 ++++ .claude/rules/php-library-github-workflows.md | 104 ++ .claude/rules/php-library-modeling.md | 314 ++++++ .claude/rules/php-library-testing.md | 372 +++++++ .claude/rules/php-library-tooling.md | 138 +++ .claude/settings.json | 249 +++++ .claude/skills/commit-message/SKILL.md | 119 +++ .claude/skills/tiny-blocks-consume/SKILL.md | 68 ++ .../tiny-blocks-consume/references/catalog.md | 32 + .../scripts/refresh-catalog.py | 102 ++ .claude/skills/tiny-blocks-create/SKILL.md | 158 +++ .../assets/config/.editorconfig | 19 + .../assets/config/.gitattributes | 19 + .../assets/config/.gitignore | 28 + .../tiny-blocks-create/assets/config/Makefile | 74 ++ .../assets/config/composer.json | 70 ++ .../assets/config/infection.json.dist | 23 + .../assets/config/phpcs.xml | 7 + .../assets/config/phpstan.neon.dist | 6 + .../assets/config/phpunit.xml | 39 + .../assets/docs/SECURITY.md | 12 + .../github/ISSUE_TEMPLATE/bug_report.md | 29 + .../github/ISSUE_TEMPLATE/feature_request.md | 17 + .../assets/github/PULL_REQUEST_TEMPLATE.md | 16 + .../assets/github/workflows/ci.yml | 105 ++ .editorconfig | 19 + .gitattributes | 19 + .github/ISSUE_TEMPLATE/bug_report.md | 29 + .github/ISSUE_TEMPLATE/feature_request.md | 17 + .github/PULL_REQUEST_TEMPLATE.md | 16 + .github/copilot-instructions.md | 12 + .github/dependabot.yml | 31 + .github/workflows/auto-assign.yml | 32 + .github/workflows/ci.yml | 105 ++ .github/workflows/codeql.yml | 39 + .gitignore | 28 + CLAUDE.md | 61 ++ Makefile | 74 ++ README.md | 205 ++++ SECURITY.md | 12 + composer.json | 81 ++ infection.json.dist | 23 + phpcs.xml | 7 + phpstan.neon.dist | 34 + phpunit.xml | 39 + src/ErrorHandlingSettings.php | 53 + src/ErrorMiddleware.php | 71 ++ src/ExceptionMapping.php | 22 + src/ExceptionMappingRule.php | 69 ++ src/ExceptionMappingTable.php | 121 +++ src/Exceptions/HttpStatusOutOfRange.php | 14 + src/Internal/AnyExactClassMatcher.php | 19 + src/Internal/DynamicMappedErrorResolver.php | 21 + src/Internal/ErrorLogger.php | 43 + src/Internal/ErrorMiddlewareBuilder.php | 79 ++ src/Internal/ErrorOutcome.php | 37 + src/Internal/ExactClassMatcher.php | 19 + src/Internal/ExceptionMatcher.php | 21 + .../Exceptions/MappingNotConfigured.php | 11 + src/Internal/FallbackResponse.php | 45 + src/Internal/FixedMappedErrorResolver.php | 20 + src/Internal/MappedErrorResolver.php | 22 + src/Internal/MappedResponse.php | 36 + src/Internal/MappingEntry.php | 24 + src/Internal/SubclassMatcher.php | 19 + src/MappedError.php | 31 + tests/Unit/CapturingHandler.php | 30 + tests/Unit/ErrorMiddlewareTest.php | 640 ++++++++++++ tests/Unit/ExceptionMappingRuleTest.php | 103 ++ tests/Unit/ExceptionMappingTableTest.php | 255 +++++ .../Internal/AnyExactClassMatcherTest.php | 62 ++ .../DynamicMappedErrorResolverTest.php | 58 ++ tests/Unit/Internal/ExactClassMatcherTest.php | 63 ++ .../Internal/FixedMappedErrorResolverTest.php | 44 + tests/Unit/Internal/MappingEntryTest.php | 77 ++ tests/Unit/Internal/SubclassMatcherTest.php | 50 + tests/Unit/MappedErrorTest.php | 94 ++ tests/Unit/UnmappedExceptions.php | 16 + 82 files changed, 7385 insertions(+) create mode 100644 .claude/hooks/php-ordering-conformance.py create mode 100644 .claude/hooks/php-prose-punctuation-conformance.py create mode 100644 .claude/rules/php-library-architecture.md create mode 100644 .claude/rules/php-library-code-style.md create mode 100644 .claude/rules/php-library-documentation.md create mode 100644 .claude/rules/php-library-github-workflows.md create mode 100644 .claude/rules/php-library-modeling.md create mode 100644 .claude/rules/php-library-testing.md create mode 100644 .claude/rules/php-library-tooling.md create mode 100644 .claude/settings.json create mode 100644 .claude/skills/commit-message/SKILL.md create mode 100644 .claude/skills/tiny-blocks-consume/SKILL.md create mode 100644 .claude/skills/tiny-blocks-consume/references/catalog.md create mode 100644 .claude/skills/tiny-blocks-consume/scripts/refresh-catalog.py create mode 100644 .claude/skills/tiny-blocks-create/SKILL.md create mode 100644 .claude/skills/tiny-blocks-create/assets/config/.editorconfig create mode 100644 .claude/skills/tiny-blocks-create/assets/config/.gitattributes create mode 100644 .claude/skills/tiny-blocks-create/assets/config/.gitignore create mode 100644 .claude/skills/tiny-blocks-create/assets/config/Makefile create mode 100644 .claude/skills/tiny-blocks-create/assets/config/composer.json create mode 100644 .claude/skills/tiny-blocks-create/assets/config/infection.json.dist create mode 100644 .claude/skills/tiny-blocks-create/assets/config/phpcs.xml create mode 100644 .claude/skills/tiny-blocks-create/assets/config/phpstan.neon.dist create mode 100644 .claude/skills/tiny-blocks-create/assets/config/phpunit.xml create mode 100644 .claude/skills/tiny-blocks-create/assets/docs/SECURITY.md create mode 100644 .claude/skills/tiny-blocks-create/assets/github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .claude/skills/tiny-blocks-create/assets/github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .claude/skills/tiny-blocks-create/assets/github/PULL_REQUEST_TEMPLATE.md create mode 100644 .claude/skills/tiny-blocks-create/assets/github/workflows/ci.yml create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/auto-assign.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 composer.json create mode 100644 infection.json.dist create mode 100644 phpcs.xml create mode 100644 phpstan.neon.dist create mode 100644 phpunit.xml create mode 100644 src/ErrorHandlingSettings.php create mode 100644 src/ErrorMiddleware.php create mode 100644 src/ExceptionMapping.php create mode 100644 src/ExceptionMappingRule.php create mode 100644 src/ExceptionMappingTable.php create mode 100644 src/Exceptions/HttpStatusOutOfRange.php create mode 100644 src/Internal/AnyExactClassMatcher.php create mode 100644 src/Internal/DynamicMappedErrorResolver.php create mode 100644 src/Internal/ErrorLogger.php create mode 100644 src/Internal/ErrorMiddlewareBuilder.php create mode 100644 src/Internal/ErrorOutcome.php create mode 100644 src/Internal/ExactClassMatcher.php create mode 100644 src/Internal/ExceptionMatcher.php create mode 100644 src/Internal/Exceptions/MappingNotConfigured.php create mode 100644 src/Internal/FallbackResponse.php create mode 100644 src/Internal/FixedMappedErrorResolver.php create mode 100644 src/Internal/MappedErrorResolver.php create mode 100644 src/Internal/MappedResponse.php create mode 100644 src/Internal/MappingEntry.php create mode 100644 src/Internal/SubclassMatcher.php create mode 100644 src/MappedError.php create mode 100644 tests/Unit/CapturingHandler.php create mode 100644 tests/Unit/ErrorMiddlewareTest.php create mode 100644 tests/Unit/ExceptionMappingRuleTest.php create mode 100644 tests/Unit/ExceptionMappingTableTest.php create mode 100644 tests/Unit/Internal/AnyExactClassMatcherTest.php create mode 100644 tests/Unit/Internal/DynamicMappedErrorResolverTest.php create mode 100644 tests/Unit/Internal/ExactClassMatcherTest.php create mode 100644 tests/Unit/Internal/FixedMappedErrorResolverTest.php create mode 100644 tests/Unit/Internal/MappingEntryTest.php create mode 100644 tests/Unit/Internal/SubclassMatcherTest.php create mode 100644 tests/Unit/MappedErrorTest.php create mode 100644 tests/Unit/UnmappedExceptions.php diff --git a/.claude/hooks/php-ordering-conformance.py b/.claude/hooks/php-ordering-conformance.py new file mode 100644 index 0000000..21a3ec0 --- /dev/null +++ b/.claude/hooks/php-ordering-conformance.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python3 +"""PHP ordering conformance hook for tiny-blocks PHP libraries. + +Self-contained PostToolUse hook on Edit|Write|MultiEdit. Verifies the deterministic +ordering conventions for PHP declarations: + +- Parameter ordering: declaration parameters (constructors, factories, methods, + property promotion) in three tiers, required parameters first, then defaulted + parameters, then a variadic, each tier by identifier length ascending, + alphabetical tie-breaker, semantic pairs preserved. A PHPUnit test method fed by + a data provider is exempt, its parameters are the columns of its data set. +- Member ordering: constants, enum cases, constructor, static methods, instance + methods, in that group order, each group length-ascending with alphabetical + tie-breaker. PHPUnit test classes instead order methods as lifecycle hooks (in + execution order), then other methods, then data providers. + +The analysis is pure (FileUnit in, Violation out) and runs in three passes over +well-formed PHP: a lexical pass blanks every comment, string, and heredoc/nowdoc +body (LITERALS), a structural pass maps every bracket to its pair (bracket_spans); +extraction assigns tokens of interest to their containers by flat walks. Control +flow uses guard clauses only and nesting never exceeds two levels. Reports +violations to stderr and exits 2 to prompt Claude, exits 0 silently if no violations +or the file is out of scope. +""" + +import json +import re +import sys +from dataclasses import dataclass +from enum import Enum +from functools import cached_property +from pathlib import Path +from typing import Final + +# --- Configuration ---------------------------------------------------------- + +# In-scope files: PHP sources under src/ or tests/. +SCOPE_PATTERN: Final = re.compile(r"(^|/)(src|tests)/.+\.php$") + +# Semantic pairs (exhaustive). Natural order wins between +# the two members when both appear in the same parameter list. +SEMANTIC_PAIRS: Final = ( + ("start", "end"), + ("from", "to"), + ("startAt", "endAt"), + ("createdAt", "updatedAt"), + ("before", "after"), + ("min", "max"), +) + +# Each member maps to (first, second, position). Both members keep their natural +# order only when both are present, sorting as a unit at the lead member's key. +PAIR_MEMBER: Final = { + member: (first, second, position) + for first, second in SEMANTIC_PAIRS + for position, member in enumerate((first, second)) +} + +MODIFIERS: Final = ("abstract", "final", "private", "protected", "public", "static") + +# The lexical grammar: every PHP construct that must not be scanned as code. +# Alternatives are ordered, the heredoc label closes via backreference. +LITERALS: Final = re.compile( + r""" + /\*.*?\*/ # block comment + | //[^\n]* # line comment + | \#(?!\[)[^\n]* # hash comment, never a #[ attribute + | <<<[ \t]*(?P['"]?)(?P