Skip to content
Search

OpenAPI Specification Bridge

This package converts Flow PHP schemas to OpenAPI 3.0 specifications, and back. You can generate OpenAPI documentation from Flow schemas, and Flow schemas from OpenAPI documentation. This helps API-first development and documentation workflows.

Installation

For the installation instructions, read the installation page.

Usage

With this bridge, you can convert Flow PHP schemas to the OpenAPI specification format, and back.

Converting Flow Schema to OpenAPI

<?php

use function Flow\ETL\DSL\{schema, int_schema, str_schema, bool_schema};
use function Flow\Bridge\OpenAPI\Specification\DSL\schema_to_openapi_specification;
use Flow\Bridge\OpenAPI\Specification\OpenAPIMetadata;

$userSchema = schema(
    int_schema('id', false, 
        OpenAPIMetadata::description('Unique user identifier')
            ->merge(OpenAPIMetadata::example(123))
    ),
    str_schema('name', true, 
        OpenAPIMetadata::description('User full name')
            ->merge(OpenAPIMetadata::example('John Doe'))
    ),
    bool_schema('active', false, 
        OpenAPIMetadata::description('Account status')
    )
);

$openApiSpec = schema_to_openapi_specification($userSchema);

// Output:
// [
//     'type' => 'object',
//     'properties' => [
//         'id' => [
//             'type' => 'integer', 
//             'nullable' => false,
//             'description' => 'Unique user identifier',
//             'example' => 123
//         ],
//         'name' => [
//             'type' => 'string', 
//             'nullable' => true,
//             'description' => 'User full name', 
//             'example' => 'John Doe'
//         ],
//         'active' => [
//             'type' => 'boolean', 
//             'nullable' => false,
//             'description' => 'Account status'
//         ]
//     ]
// ]

Converting OpenAPI to Flow Schema

<?php

use function Flow\Bridge\OpenAPI\Specification\DSL\schema_from_openapi_specification;

$openApiSpec = [
    'type' => 'object',
    'properties' => [
        'id' => ['type' => 'integer', 'nullable' => false],
        'email' => [
            'type' => 'string', 
            'format' => 'email', 
            'nullable' => false,
            'description' => 'User email address'
        ],
        'profile' => [
            'type' => 'object',
            'properties' => [
                'name' => ['type' => 'string', 'nullable' => false],
                'bio' => ['type' => 'string', 'nullable' => true]
            ],
            'nullable' => false
        ],
        'tags' => [
            'type' => 'array',
            'items' => ['type' => 'string'],
            'nullable' => true
        ]
    ]
];

$flowSchema = schema_from_openapi_specification($openApiSpec);

// Now you can use $flowSchema in Flow ETL pipelines

OpenAPI Metadata

The OpenAPI Specification Bridge handles special metadata through the OpenAPIMetadata enum. With it, you can add OpenAPI-specific properties to your Flow schema definitions. The schema conversion uses this metadata to generate detailed OpenAPI specifications.

Available OpenAPI Metadata

The OpenAPIMetadata enum has these metadata types:

  • DESCRIPTION: human-readable description of the property
  • FORMAT: openAPI format specification (for example 'email', 'date-time', 'uuid')
  • EXAMPLE: example value for the property
  • EXAMPLES: multiple named examples
  • DEPRECATED: mark property as deprecated
  • TITLE: short title for the property
  • DEFAULT: default value for the property
  • READ_ONLY: mark property as read-only
  • WRITE_ONLY: mark property as write-only
  • NULLABLE: override schema nullability

Using OpenAPI Metadata

<?php

use function Flow\ETL\DSL\{schema, int_schema, str_schema};
use Flow\Bridge\OpenAPI\Specification\{OpenAPIConverter, OpenAPIMetadata};

$userSchema = schema(
    int_schema('id', false, 
        OpenAPIMetadata::description('Unique user identifier')
            ->merge(OpenAPIMetadata::format('int64'))
            ->merge(OpenAPIMetadata::example(12345))
            ->merge(OpenAPIMetadata::readOnly())
    ),
    str_schema('email', false,
        OpenAPIMetadata::description('User email address')
            ->merge(OpenAPIMetadata::format('email'))
            ->merge(OpenAPIMetadata::example('user@example.com'))
            ->merge(OpenAPIMetadata::title('Email Address'))
    ),
    str_schema('password', false,
        OpenAPIMetadata::description('User password')
            ->merge(OpenAPIMetadata::format('password'))
            ->merge(OpenAPIMetadata::writeOnly())
    ),
    str_schema('status', false,
        OpenAPIMetadata::description('Account status')
            ->merge(OpenAPIMetadata::default('active'))
            ->merge(OpenAPIMetadata::examples([
                'active' => 'active',
                'inactive' => 'inactive',
                'suspended' => 'suspended'
            ]))
    )
);

$converter = new OpenAPIConverter();
$openApiSpec = $converter->toOpenAPI($userSchema);

// Results in:
// [
//     'type' => 'object',
//     'properties' => [
//         'id' => [
//             'type' => 'integer',
//             'nullable' => false,
//             'description' => 'Unique user identifier',
//             'format' => 'int64',
//             'example' => 12345,
//             'readOnly' => true
//         ],
//         'email' => [
//             'type' => 'string',
//             'nullable' => false,
//             'description' => 'User email address',
//             'format' => 'email',
//             'example' => 'user@example.com',
//             'title' => 'Email Address'
//         ],
//         'password' => [
//             'type' => 'string',
//             'nullable' => false,
//             'description' => 'User password',
//             'format' => 'password',
//             'writeOnly' => true
//         ],
//         'status' => [
//             'type' => 'string',
//             'nullable' => false,
//             'description' => 'Account status',
//             'default' => 'active',
//             'examples' => [
//                 'active' => 'active',
//                 'inactive' => 'inactive',
//                 'suspended' => 'suspended'
//             ]
//         ]
//     ]
// ]

Metadata Priority System

The OpenAPI bridge uses a priority system for metadata:

  1. OpenAPI-specific metadata (from OpenAPIMetadata) has the highest priority.
  2. Without OpenAPI metadata, the bridge uses the legacy metadata keys.
<?php

use Flow\ETL\Values\Schema\Metadata;
use Flow\Bridge\OpenAPI\Specification\OpenAPIMetadata;

// OpenAPI metadata takes priority over legacy metadata
$metadata = Metadata::with('description', 'Legacy description')
    ->merge(OpenAPIMetadata::description('OpenAPI description')) // This wins
    ->merge(Metadata::with('example', 'Legacy example'))
    ->merge(OpenAPIMetadata::example('OpenAPI example')); // This wins

$schema = schema(
    str_schema('field', false, $metadata)
);

// Result will use OpenAPI values: "OpenAPI description" and "OpenAPI example"

Common OpenAPI Formats

These are common OpenAPI formats for OpenAPIMetadata::format():

String formats:

  • email: email address
  • uri: URI/URL
  • uuid: UUID string
  • date: date (YYYY-MM-DD)
  • date-time: date and time (RFC 3339)
  • password: password field
  • byte: base64 encoded string
  • binary: binary data

Integer formats:

  • int32: 32-bit integer
  • int64: 64-bit integer

Number formats:

  • float: floating point number
  • double: double precision number

Usage Example

The properties object of a structure keeps the declared field order of the structure type. An element declared with structure_element(..., optional: true) emits nullable: true in its property.

<?php

use function Flow\ETL\DSL\{schema, int_schema, str_schema, list_schema, structure_schema};
use function Flow\Types\DSL\{type_list, type_string, type_structure};
use function Flow\Bridge\OpenAPI\Specification\DSL\schema_to_openapi_specification;
use Flow\Bridge\OpenAPI\Specification\OpenAPIMetadata;

// Define your data schema with rich OpenAPI metadata
$productSchema = schema(
    int_schema('id', false, 
        OpenAPIMetadata::description('Product identifier')
            ->merge(OpenAPIMetadata::format('int64'))
            ->merge(OpenAPIMetadata::example(123))
            ->merge(OpenAPIMetadata::readOnly())
    ),
    str_schema('name', false,
        OpenAPIMetadata::description('Product name')
            ->merge(OpenAPIMetadata::title('Name'))
            ->merge(OpenAPIMetadata::example('iPhone 15'))
    ),
    str_schema('description', true,
        OpenAPIMetadata::description('Detailed product description')
            ->merge(OpenAPIMetadata::example('Latest smartphone with advanced features'))
    ),
    structure_schema('category', type_structure([
        'id' => type_string(),
        'name' => type_string()
    ]), false, 
        OpenAPIMetadata::description('Product category information')
    ),
    list_schema('tags', type_list(type_string()), true,
        OpenAPIMetadata::description('Product tags for categorization')
            ->merge(OpenAPIMetadata::examples([
                'electronics' => 'electronics',
                'smartphone' => 'smartphone',
                'apple' => 'apple'
            ]))
    )
);

$apiSpec = schema_to_openapi_specification($productSchema);

// Use in your API documentation
$fullApiSpec = [
    'openapi' => '3.0.0',
    'info' => [
        'title' => 'Product API',
        'version' => '1.0.0'
    ],
    'paths' => [
        '/products' => [
            'get' => [
                'summary' => 'List products',
                'responses' => [
                    '200' => [
                        'description' => 'A list of products',
                        'content' => [
                            'application/json' => [
                                'schema' => [
                                    'type' => 'array',
                                    'items' => $apiSpec
                                ]
                            ]
                        ]
                    ]
                ]
            ]
        ]
    ]
];

Found a typo or an outdated section? Edit this page on GitHub


Contributors

Built in the open.

Join us on GitHub
scroll back to top