jekyll-openapi

A (mostly) spec-compliant OpenAPI 3.1 toolkit for Liquid-based static site generators (Jekyll in particular).

It deliberately does not render HTML. The gem provides:

  • a navigable object model over a parsed OpenAPI document — dereferencing, tag and operation enumeration, parameter merging;
  • schema-to-text renderers (TypeScript-like and simplified XML) exposed as opt-in Liquid filters;

…and your site provides the templates. See examples/ for complete, copy-paste-able templates.

The gem works as a plain Ruby library too — without Jekyll or Liquid. That usage, the object model, and the renderer API are documented in examples/ruby/README.md.

Jekyll integration

Add the gem to your Gemfile and list it as a plugin — nothing else to do, the filters register themselves and warnings are routed to Jekyll's logger:

# Gemfile
group :plugins do
  gem "jekyll-openapi"
end
# _config.yml
plugins:
  - jekyll-openapi

Walkthrough: a minimal API page

Four steps take you from a spec file to a rendered reference page.

1. Enable the plugin as shown above (Gemfile + plugins: entry). No _plugins/ file is needed.

2. Drop your spec into _data/ so Jekyll parses it into site.data:

_data/openapi.yml     # YAML or JSON; becomes site.data.openapi

3. Write a page that walks the tags and operations with the filters:

---
title: API Reference
---
{% assign api = site.data.openapi %}

<h1>{{ api.info.title }}</h1>

{% assign tags = api | oapi_tags %}
{% for tag in tags %}
  <h2>{{ tag.name }}</h2>
  {{ tag.description | markdownify }}

  {% assign ops = api | oapi_operations: tag.name %}
  {% for op in ops %}
    <h3>{{ op.method | upcase }} {{ op.path }}</h3>
    {{ op.operation.description | markdownify }}
  {% endfor %}
{% endfor %}

4. Build (bundle exec jekyll serve) and you have a page listing every operation, grouped by tag. That's a complete, working integration.

oapi_operations already does the tedious parts for you: it skips non-operation path-item keys, handles x- extension methods, filters by tag, and merges path-level parameters into each operation.

Each op in the loop is a plain Hash you can traverse further:

key content
"path" "/pets/{petId}"
"method" normalized method, x- prefix stripped ("mkcol")
"raw_method" the literal key ("x-mkcol")
"operation" the Operation object, dereferenced
"parameters" path-level + operation-level parameters, merged (operation wins on same name/location)
"grouped_parameters" same, grouped: {"query" => [...], "path" => [...], "header" => [...], "cookie" => [...]}

Going further

The walkthrough above is intentionally bare. For a production-quality page — tag index/navigation, parameter tables, request/response bodies with rendered schemas — start from the complete examples and restyle them:

Liquid filters

Registered automatically in Jekyll. (In a plain Liquid setup you register them yourself — see examples/ruby/README.md.)

filter input → output
oapi_dereference document → document with local $refs resolved
oapi_tags document → tag list
oapi_operations document (+ optional tag argument) → operation list
oapi_parameters parameter list → grouped by location
oapi_merge_parameters path params, operation params → merged list
oapi_schema JSON-Schema → TypeScript-like string
oapi_xml_schema JSON-Schema → simplified XML string

The two schema filters turn a JSON-Schema into readable text, e.g. {{ content.schema | oapi_schema }} produces:

{
  name: "string"
  age?: integer
}

See examples/jekyll/_includes/oapi/schema.html for choosing the renderer by content type.

Error handling

The library never raises on malformed input: it warns through JekyllOpenAPI.logger (in Jekyll, routed to Jekyll's own logger) and degrades gracefully — unresolvable, external, and circular $refs are left in place; invalid schemas render as "". Your build never breaks on a bad spec.

Compliance notes

Intentional simplifications, in the spirit of "readable docs over exhaustive fidelity":

  • Only local (#/...) references are resolved; external files/URLs are left as-is.
  • The TypeScript renderer supports oneOf/anyOf (|), allOf (&), 3.1 type arrays (type: [string, "null"]), enums, additionalProperties, and infers object/array when type is omitted — but does not merge allOf members into a single object literal.
  • The XML renderer honors xml.name, xml.prefix, xml.namespace, xml.attribute and xml.wrapped, and renders example values where provided.

Development

rake test          # or: ruby -Ilib -Itest test/test_<name>.rb

Tests run against generic fixtures (test/fixtures/petstore.yml); when the gem sources live inside the eCorpus_doc repository, an extra integration suite runs the whole pipeline over the real eCorpus API definition.