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
74 changes: 74 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Code Quality & Tests

on:
push:
pull_request:

jobs:
mago:
name: Mago Code Quality
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup PHP with Composer cache
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
coverage: none
tools: composer
env:
COMPOSER_ALLOW_SUPERUSER: 1

- name: Install Composer Dependencies
run: composer install --prefer-dist --no-progress

- name: Setup Mago
uses: nhedger/setup-mago@v1

- name: Run Mago
run: |
mago format --dry-run
mago lint --reporting-format=github
# mago analyze --reporting-format=github

psalm:
name: Psalm Static Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
coverage: none
tools: composer

- name: Install Composer Dependencies
run: composer install --prefer-dist --no-progress

- name: Run Psalm
run: composer psalm

tests:
name: PHPUnit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
coverage: none
tools: composer

- name: Install Composer Dependencies
run: composer install --prefer-dist --no-progress

- name: Run Tests
run: composer test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/composer.lock
/vendor/
.mago/
mago-cache/
54 changes: 54 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

This is a PHP library that provides OpenAPI validation for Symfony applications. The library validates both HTTP requests and responses against OpenAPI specifications using PSR-7 HTTP message interfaces.

## Architecture

The codebase consists of two main components:

### Core Classes

- **`OpenApiValidator`** (`src/OpenApiValidator.php:1`): The main validator class that handles validation logic
- Parses OpenAPI YAML specifications
- Converts Symfony requests/responses to PSR-7 format
- Validates requests and responses against the spec
- Generates test cases from OpenAPI specifications

- **`OpenApiValidationTrait`** (`src/OpenApiValidationTrait.php:1`): PHPUnit test trait for integration testing
- Provides a ready-to-use test method for endpoint validation
- Automatically generates test cases from OpenAPI spec using data providers
- Requires implementing classes to provide `client()`, `openApiPath()`, and `apiMap()` methods

### Key Dependencies

The library integrates several components:
- `league/openapi-psr7-validator` for OpenAPI validation
- `nyholm/psr7` for PSR-7 HTTP message factories
- `symfony/psr-http-message-bridge` to convert Symfony HTTP messages to PSR-7
- `symfony/yaml` for parsing OpenAPI YAML specifications

### Path Mapping System

The validator uses a path mapping system (`$pathMappings` in constructor) to translate between:
- OpenAPI specification paths (e.g., `/api/users/{id}`)
- Actual application paths (e.g., `/users/{id}`)

This allows the library to work with applications where the actual route structure differs from the OpenAPI specification.

## Usage Pattern

1. Create an `OpenApiValidator` instance with the path to your OpenAPI spec and path mappings
2. Use the trait in PHPUnit tests by implementing the required abstract methods
3. The trait automatically generates test cases for all endpoints defined in the OpenAPI spec
4. Each endpoint is tested with both full parameters (from examples/defaults) and minimal parameters

## Development Notes

- This is a library package (no application-specific build/test commands)
- No composer.json found - likely managed by a parent project
- Uses PHP 8+ features like `declare(strict_types=1)` and constructor property promotion
- Follows PSR-12 coding standards with proper type declarations
54 changes: 54 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "gpht/openapi-symfony-validator",
"description": "OpenAPI validation library for Symfony applications with PHPUnit integration",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Sergei Baikin",
"email": "sergei.baikin@fotograf.de"
}
],
"require": {
"php": "^8.4",
"league/openapi-psr7-validator": "^0.20",
"nyholm/psr7": "^1.8",
"symfony/psr-http-message-bridge": "^6.0|^7.0",
"symfony/yaml": "^6.0|^7.0",
"symfony/framework-bundle": "^6.0|^7.0",
"symfony/browser-kit": "^6.0|^7.0"
},
"require-dev": {
"carthage-software/mago": "1.0.0-beta.15",
"phpunit/phpunit": "^10.0|^11.0",
"vimeo/psalm": "^7.0.0-beta11"
},
"autoload": {
"psr-4": {
"Gpht\\OpenapiSymfonyValidator\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Gpht\\OpenapiSymfonyValidator\\Tests\\": "tests/"
}
},
"scripts": {
"analyze": "./vendor/bin/mago analyze",
"format": "./vendor/bin/mago format",
"format-check": "./vendor/bin/mago format --check",
"lint": "./vendor/bin/mago lint",
"psalm": "psalm",
"psalm-baseline": "psalm --set-baseline=psalm-baseline.xml",
"test": "phpunit"
},
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"allow-plugins": {
"carthage-software/mago": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
35 changes: 35 additions & 0 deletions mago.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Welcome to Mago!
# For full documentation, see https://mago.carthage.software/tools/overview
php-version = "8.4.0"

[source]
paths = ["src/", "tests/"]
excludes = ["vendor/"]

[formatter]
print-width = 120
tab-width = 4
use-tabs = false

[linter]
integrations = ["symfony", "phpunit"]

[linter.rules]
ambiguous-function-call = { enabled = false }
halstead = { effort-threshold = 8000 } # Allow slightly higher effort for complex methods
literal-named-argument = { enabled = false } # Too noisy for test files
no-literal-password = { enabled = false } # Test environment allows test secrets
class-name = { enabled = false } # Allow flexible naming in tests
cyclomatic-complexity = { threshold = 20 } # Allow higher complexity for main classes
kan-defect = { threshold = 2.0 } # Allow higher kan defect for main classes
excessive-parameter-list = { threshold = 7 } # Allow more parameters for validation methods
no-else-clause = { enabled = false } # Allow elseif for parameter parsing logic
property-type = { enabled = false } # Allow properties without type hints for callable compatibility

[analyzer]
find-unused-definitions = false # Disabled for test environment
find-unused-expressions = false # Disabled for test environment
analyze-dead-code = false
check-throws = false # Too strict for test files
allow-possibly-undefined-array-keys = true
perform-heuristic-checks = false # Too strict for test environment
21 changes: 21 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
executionOrder="depends,defects"
failOnRisky="true"
failOnWarning="true"
stopOnFailure="false"
cacheDirectory=".ci-cache/phpunit">
<testsuites>
<testsuite name="Unit">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>
26 changes: 26 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<psalm
xmlns="https://getpsalm.org/schema/config"
errorLevel="1"
useDocblockPropertyTypes="true"
ignoreInternalFunctionFalseReturn="true"
ignoreInternalFunctionNullReturn="false"
ensureArrayIntOffsetsExist="true"
findUnusedPsalmSuppress="true"
findUnusedCode="false"
findUnusedVariablesAndParams="true"
findUnusedBaselineEntry="true"
strictBinaryOperands="true"
reportMixedIssues="true"
ensureArrayStringOffsetsExist="true"
cacheDirectory=".ci-cache/psalm"
>
<projectFiles>
<directory name="src"/>
<directory name="tests"/>
<ignoreFiles>
<directory name="vendor"/>
</ignoreFiles>
</projectFiles>

</psalm>
60 changes: 60 additions & 0 deletions src/OpenApiValidationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Gpht\OpenapiSymfonyValidator;

use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;

trait OpenApiValidationTrait
{
private OpenApiValidator $validator;

abstract public function client(): KernelBrowser;

abstract public static function openApiPath(): string;

abstract public static function apiMap(): array;

protected function setUp(): void
{
parent::setUp();

$this->validator = new OpenApiValidator(self::openApiPath(), self::apiMap());
}

#[DataProvider('endpointProvider')]
public function testEndpointCompliesWithOpenApiSpec(
string $method,
string $actualPath,
string $openApiPath,
array $queryParams = [],
array $headers = [],
null|string $body = null,
): void {
$this->validator->validateRequestResponse(
$this->client(),
$method,
$actualPath,
$openApiPath,
$queryParams,
$headers,
$body,
);

static::assertTrue(true);
}

/**
* Data provider that returns all endpoints to test using examples from OpenAPI spec.
*
* @return array<non-empty-string, list{string, array<array-key, string>|mixed|string, string, array<array-key, mixed>, array{Accept: 'application/json'}, null}>
*/
public static function endpointProvider(): array
{
$validator = new OpenApiValidator(self::openApiPath(), self::apiMap());

return $validator->generateTestCases();
}
}
Loading