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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ go.work.sum

# Editor/IDE
.idea/
.vscode/
.vscode/
.claude/
30 changes: 20 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,39 @@ go mod tidy

## Architecture

`spec-ui` is a Go library that serves OpenAPI documentation UIs as HTTP handlers. It supports five UI providers: SwaggerUI, StoplightElements, ReDoc, Scalar, and RapiDoc.
`spec-ui` is a Go library that serves OpenAPI documentation UIs as HTTP handlers. It supports five UI providers: SwaggerUI, StoplightElements, ReDoc, Scalar, and RapiDoc. Each provider comes in two variants: CDN mode and embedded mode.

### Data flow

1. The user calls `specui.NewHandler(opts...)` with functional options from `option.go`
1. The user calls `specui.NewHandler(opts...)` with functional options from `option.go` and a provider-specific `WithUI()` option
2. Options populate a `config.SpecUI` struct (defined in `config/config.go`)
3. `Handler.Docs()` dispatches to the appropriate provider package under `internal/` based on `cfg.Provider`
4. `Handler.Spec()` always dispatches to `internal/spec`, which serves the raw OpenAPI file
3. `Handler.Docs()` calls `cfg.DocsHandlerFactory` — set by the provider's `WithUI()` — to create the handler once (via `sync.Once`)
4. `Handler.Assets()` calls `cfg.AssetsHandlerFactory` for embedded asset serving; returns `nil` in CDN mode
5. `Handler.Spec()` dispatches to `internal/spec`, which serves the raw OpenAPI file

### Provider packages

Each provider lives in two top-level packages:

- **`<provider>/`** (e.g., `swaggerui/`, `rapidoc/`): CDN mode — loads JS/CSS from pinned CDN URLs in `internal/constant/constant.go`. Contains `handler.go`, `index.tpl.go`, and `option.go`.
- **`<provider>emb/`** (e.g., `swaggeruiemb/`, `rapidocemb/`): Embedded mode — sets `cfg.EmbedAssets = true` and delegates to the CDN handler, but also registers `AssetsHandlerFactory` to serve files from the package's `assets/` directory via `//go:embed assets`.

Each provider package exposes `WithUI(cfg ...config.<Provider>) specui.Option`. Only the imported provider's code is linked into the binary (tree-shaking via factory pattern).

### Key structural patterns

- **Provider packages** (`internal/swaggerui`, `internal/stoplightelements`, `internal/redoc`, `internal/scalar`, `internal/rapidoc`): each has a `handler.go` that renders an HTML template, and an `index.tpl.go` that holds the template string. All UI assets are loaded from CDN (URLs pinned in `internal/constant/constant.go`).
- **`option.go` (root)**: Shared options only — `WithTitle`, `WithDocsPath`, `WithSpecPath`, `WithAssetsPath`, `WithSpecFile`, `WithSpecEmbedFS`, `WithSpecIOFS`, `WithSpecGenerator`, `WithCacheAge`. Provider selection is done by importing the provider package.

- **Spec serving** (`internal/spec/spec.go`): uses `sync.Once` to read the spec file once and cache it in memory. Supports four source modes: `SpecGenerator` interface, `embed.FS`, `fs.FS`, or plain OS file path.
- **`config/config.go`**: A single `SpecUI` struct holds all configuration. `DocsHandlerFactory` and `AssetsHandlerFactory` are function fields set by `WithUI()`. Each provider has its own typed config struct with enum-like constants (e.g., `SwaggerLayout`, `RapiDocTheme`).

- **Config** (`config/config.go`): a single `SpecUI` struct holds all configuration. Each UI provider has its own nested config struct with typed constants for enum-like fields (e.g., `ElementLayout`, `RapiDocTheme`).
- **`internal/spec/spec.go`**: Uses `sync.Once` to read the spec file once and cache it. Supports four source modes: `SpecGenerator` interface, `embed.FS`, `fs.FS`, or plain OS file path.

- **Default provider** is `ProviderStoplightElements`; default paths are `/docs` (docs) and `/docs/openapi.json` (spec).
- **`handler.go`**: `Docs()`, `Spec()`, `Assets()` — the three HTTP handlers. Call `handler.AssetsEnabled()` to check if embedded mode is active; if so, register `handler.Assets()` at `handler.AssetsPath() + "/"`.

### Adding a new UI provider

1. Add a new `Provider` constant to `config/config.go`
2. Add the provider config struct to `config/config.go`
3. Create `internal/<provider>/handler.go` and `internal/<provider>/index.tpl.go`
3. Create `<provider>/handler.go`, `<provider>/index.tpl.go`, `<provider>/option.go` — `option.go` sets `DocsHandlerFactory`, `AssetsHandlerFactory = nil`, and any config defaults
4. Add CDN asset URLs to `internal/constant/constant.go`
5. In `option.go`: import the new internal package, add a `WithXxx` option that sets `c.Provider`, `c.DocsHandlerFactory` (closure wrapping `<pkg>.NewHandler(c)`), and any provider-specific config defaults. `handler.go:Docs()` needs no changes — it dispatches via `DocsHandlerFactory`.
5. Create `<provider>emb/handler.go`, `<provider>emb/assets.go`, `<provider>emb/option.go` — `assets.go` uses `//go:embed assets` and serves files; `option.go` sets `EmbedAssets = true` and registers both factories
58 changes: 58 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Contributing

Thanks for your interest in contributing to spec-ui.

## Development Setup

1. Fork and clone the repository.
2. Ensure you have Go installed (the project uses Go modules).
3. Install dependencies:

```bash
go mod tidy
```

## Run Checks Locally

Before opening a pull request, run:

```bash
go test ./...
```

If you have golangci-lint installed:

```bash
golangci-lint run
```

## Embedded Assets Notes

For library users, embedded UI assets are already included in this module.

For maintainers only: if you intentionally update bundled provider assets (CSS/JS/favicon files), regenerate them with:

```bash
make download-assets
```

Then run tests again:

```bash
go test ./...
```

## Pull Requests

- Keep changes focused and scoped to one concern when possible.
- Add or update tests for behavior changes.
- Update documentation (README or this file) when behavior/config changes.
- Use clear commit messages.

## Issues and Discussions

For major changes, please open an issue first so we can discuss approach and compatibility.

## Code of Conduct

Please be respectful and constructive in all interactions.
32 changes: 31 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# Variables
PKG := ./...
COVERAGE_FILE := coverage.out
SWAGGERUI_VER := 5.32.1
STOPLIGHT_VER := 9.0.16
REDOC_VER := 2.5.2
SCALAR_VER := 1.51.0
RAPIDOC_VER := 9.3.8
CDN := https://cdn.jsdelivr.net/npm

# Default target
.PHONY: all
Expand Down Expand Up @@ -59,4 +65,28 @@ clean:
.PHONY: update
update:
@echo "Updating dependencies..."
@go get -u ./...
@go get -u ./...

.PHONY: download-assets
download-assets:
@mkdir -p swaggeruiemb/assets
@curl -fsSL $(CDN)/swagger-ui@$(SWAGGERUI_VER)/dist/swagger-ui.min.css -o swaggeruiemb/assets/swagger-ui.min.css
@curl -fsSL $(CDN)/swagger-ui@$(SWAGGERUI_VER)/dist/swagger-ui-bundle.js -o swaggeruiemb/assets/swagger-ui-bundle.js
@curl -fsSL $(CDN)/swagger-ui@$(SWAGGERUI_VER)/dist/swagger-ui-standalone-preset.js -o swaggeruiemb/assets/swagger-ui-standalone-preset.js
@curl -fsSL https://petstore.swagger.io/favicon-16x16.png -o swaggeruiemb/assets/favicon-16x16.png
@curl -fsSL https://petstore.swagger.io/favicon-32x32.png -o swaggeruiemb/assets/favicon-32x32.png
@mkdir -p stoplightemb/assets
@curl -fsSL $(CDN)/@stoplight/elements@$(STOPLIGHT_VER)/styles.min.css -o stoplightemb/assets/styles.min.css
@curl -fsSL $(CDN)/@stoplight/elements@$(STOPLIGHT_VER)/web-components.min.js -o stoplightemb/assets/web-components.min.js
@mkdir -p stoplightemb/assets/favicons
@curl -fsSL https://docs.stoplight.io/favicons/favicon.ico -o stoplightemb/assets/favicons/favicon.ico
@mkdir -p redocemb/assets
@curl -fsSL $(CDN)/redoc@$(REDOC_VER)/bundles/redoc.standalone.js -o redocemb/assets/redoc.standalone.js
@mkdir -p scalaremb/assets/browser
@curl -fsSL $(CDN)/@scalar/api-reference@$(SCALAR_VER)/dist/style.min.css -o scalaremb/assets/style.min.css
@curl -fsSL $(CDN)/@scalar/api-reference@$(SCALAR_VER)/dist/browser/standalone.min.js -o scalaremb/assets/browser/standalone.min.js
@curl -fsSL https://scalar.com/favicon.png -o scalaremb/assets/favicon.png
@mkdir -p rapidocemb/assets
@curl -fsSL $(CDN)/rapidoc@$(RAPIDOC_VER)/dist/rapidoc-min.js -o rapidocemb/assets/rapidoc-min.js
@mkdir -p rapidocemb/assets/images
@curl -fsSL https://rapidocweb.com/images/logo.png -o rapidocemb/assets/images/logo.png
Loading
Loading