NAME

Mojolicious::Plugin::Fondation::OpenAPI::Command::openapi - Generate OpenAPI specification from DBIx::Class sources

VERSION

version 0.02

SYNOPSIS

$ myapp.pl openapi generate
$ myapp.pl openapi generate -y
$ myapp.pl openapi generate --output custom.json

DESCRIPTION

Command-line interface for generating an OpenAPI 3.0.3 specification from DBIx::Class sources discovered via Fondation::Model::DBIx::Async.

No database connection is required -- sources are read from the schema class metadata via $schema_class->sources.

NAME

Mojolicious::Plugin::Fondation::OpenAPI::Command::openapi - Generate OpenAPI specification from DBIx::Class sources

This module is more than experimental !

DESIGN

Two layers

The generator works with two conceptual layers:

DBIx source

The raw database structure: column types, constraints, defaults.

API Base

The canonical OpenAPI representation of the resource. Built from the DBIx source with implicit rules applied, then overridden by extra->{openapi} annotations and plugin configuration.

Implicit rules

The following rules are derived automatically from DBIx structure and require no extra->{openapi} annotations:

DBIx                          -> OpenAPI
─────────────────────────────   ───────
is_auto_increment = 1          readOnly: true
column named created_at        readOnly: true
column named updated_at        readOnly: true
data_type =~ /^date$/i         format: "date"
data_type =~ /datetime/i       format: "date-time"
data_type =~ /timestamp/i      format: "date-time"
data_type =~ /^float$/i        format: "float"
data_type =~ /^double$/i       format: "double"
is_nullable = 0                required (unless writeOnly/readOnly)
size = N (string type)         maxLength: N
data_type =~ /int|serial/i     type: "integer"
data_type =~ /float|decimal/i  type: "number"
data_type =~ /boolean/i        type: "boolean"
default_value                  default
is_nullable = 1                nullable: true

extra->{openapi} annotations

Semantic choices that cannot be derived from DBIx are declared in the Result class via extra->{openapi}:

username => {
    data_type => 'varchar', size => 100, is_nullable => 0,
    extra => {
        openapi => {
            minLength => 4,
        },
    },
},

Flat keys (apply to all contexts)

format      -- "email", "password", "uri", etc.
minLength   -- minimum string length
maxLength   -- maximum string length (overrides DBIx size)
minimum     -- minimum numeric value
maximum     -- maximum numeric value
enum        -- arrayref of allowed values
writeOnly   -- boolean, field only sent in requests
readOnly    -- boolean, field only sent in responses (overrides implicit)
description -- human-readable description
type        -- override the inferred OpenAPI type
nullable    -- override the inferred nullability
default     -- override the inferred default value

Contextual keys (per-operation overrides)

create => { required => 1 }   # force required on POST
create => { required => 0 }   # force optional on POST
update => { required => 1 }   # force required on PUT
update => { required => 0 }   # force optional on PUT
read   => { required => 1 }   # force required on GET item
read   => { required => 0 }   # force optional on GET item
list   => { required => 1 }   # force required on GET collection
list   => { required => 0 }   # force optional on GET collection

Conditional schema generation

Contextual schemas (UserCreate, UserUpdate, UserRead, UserList) are generated only when they differ from the API Base. Comparison considers both property names and the required array.

A simple source like Group with no contextual rules produces a single canonical schema used everywhere. A complex source like User with password having create.required => 1 and update.required => 0 produces User, UserCreate, and UserUpdate.

writeOnly handling

Fields marked writeOnly are:

  • Excluded from the API Base required array

  • Excluded from the API Base properties hash

  • Added back into create and update projection properties

This means GET responses never contain writeOnly fields and the OpenAPI validator does not expect them.

Config override

Any column property can be overridden via the plugin configuration without modifying DBIx classes:

'Fondation::OpenAPI' => {
    backend => 'main',
    schemas => {
        User => {
            columns => {
                name => {
                    maxLength => 100,       # override DBIx size
                },
                password => {
                    writeOnly => 1,
                    create    => { required => 1 },
                    update    => { required => 0 },
                },
            },
        },
    },
},

Config keys follow the same flat + contextual structure as extra->{openapi} and take the highest priority in the cascade:

1. Structure DBIx (implicit)
2. extra->{openapi} flat keys (Result class)
3. Config flat keys (myapp.conf)
4. extra->{openapi} contextual (Result class)
5. Config contextual (myapp.conf)

SUBCOMMANDS

generate

myapp.pl openapi generate
myapp.pl openapi generate -y
myapp.pl openapi generate --output custom.json

Iterates all DBIx sources (monikers), builds the API Base for each, applies contextual projections, and writes two files:

share/openapi.json

OpenAPI 3.0.3 specification with schemas and CRUD paths. Loaded at runtime by Mojolicious::Plugin::OpenAPI for request validation.

public/js/validators.js

Client-side validation (FondationValidators.validate()) consumed by Fondation::Asset bundles.

Options:

--output FILE   Output path relative to $app->home (default: share/openapi.json)
-y              Overwrite without confirmation prompt

CRUD PATHS

Each source generates five endpoints with x-mojo-to routing and automatic x-auth permission annotations:

GET    /{moniker}       -> {Moniker}#list    x-auth: {moniker_lc}_list
POST   /{moniker}       -> {Moniker}#create  x-auth: {moniker_lc}_create
GET    /{moniker}/{id}  -> {Moniker}#read    x-auth: {moniker_lc}_read
PUT    /{moniker}/{id}  -> {Moniker}#update  x-auth: {moniker_lc}_update
DELETE /{moniker}/{id}  -> {Moniker}#delete  x-auth: {moniker_lc}_delete

The x-auth default can be overridden via the plugin config (schemas.{Source}.x_auth.{operation}). See Mojolicious::Plugin::Fondation::OpenAPI for details. Enforcement is handled at runtime by Mojolicious::Plugin::Fondation::OpenAPI::Security.

SEE ALSO

Mojolicious::Plugin::Fondation::OpenAPI, Fondation::Model::DBIx::Async, Mojolicious::Plugin::OpenAPI

AUTHOR

Daniel Brosseau <dab@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Daniel Brosseau.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.