mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 18:37:09 +02:00
* feat(api): add OpenAPI v1 spec skeleton, lint ruleset and bundle tooling vacuum v0.30.6's `bundle --composed` mangles component names for this spec's multi-file layout (duplicates Problem as Problem__schemas etc.), so api-bundle uses the Redocly CLI (npx @redocly/cli bundle) instead. * fix(api): pin the Redocly CLI version Tried moving components out of the root document (per libopenapi's nested_files example) so vacuum's own bundler could produce clean names, but any component declared via $ref inside components.* still gets a __<parent>-suffixed twin regardless of collisions elsewhere, so vacuum's --composed bundler can't cleanly bundle this spec. Pin the already-working Redocly fallback to an exact version instead of @latest. * fix(api): bundle the OpenAPI spec with vacuum vacuum's --composed bundler suffixes any component reached via a $ref written directly inside the root document's own components.* block, regardless of collisions elsewhere. Dropping the root-level schemas/ parameters/responses declarations (keeping only securitySchemes, and leaving every component file under api/openapi/components/ untouched) lets vacuum bundle cleanly with no __ suffixes, going back to Go-only tooling. Components nothing references yet (ListMeta, offset, limit, BadRequest, Unauthorized, Forbidden, NotFound) are absent from the bundle until a later task's operation references them. * fix(api): make spec lint rules cover all schemas and error codes nd-schema-property-descriptions targeted $.components.schemas, but our schemas live in path/response files, not the root document, so it was dead code; switched to $..properties[*] to walk every resolved schema wherever it ends up. nd-error-responses-are-problems only checked a hardcoded status-code list; switched to a patternProperties schema matching the full 4xx/5xx range. Also: api-diff now diffs against the merge-base with API_DIFF_BASE (falling back to its tip with a notice if no merge-base exists), gen no longer depends on api-gen until Task 3 wires up oapi-codegen, and api-lint suppresses vacuum's banner. * feat(api): embed the bundled OpenAPI spec and expose its version * feat(api): generate the v1 server interface with oapi-codegen * feat(api): add RFC 9457 problem responses for API v1 * feat(api): add API v1 router with /server discovery and spec routes * fix(api): serve the OpenAPI document without range support * feat(api): mount API v1 behind the DevAPIv1 flag * chore(ci): lint, regenerate and diff the OpenAPI v1 spec * refactor(api): tighten spec version access, lint rules and test naming * refactor(api): simplify spec routes, tests and OpenAPI tooling Share one If-None-Match parser (utils/req) between the image and spec routes, declare the YAML spec response as an object so tests need no decoder override, and reuse ETag/304 spec components. Install the OpenAPI tools only when missing or at a different version, fail api-diff when its base ref does not exist, and in CI cache the tools, fold regeneration into the go generate check, and fetch only the PR base commit for the breaking-change gate. * refactor(api): raise the list limit maximum to 2000 and drop the flag test * feat(api): treat added enum values as non-breaking Enums in API v1 are open: clients must accept unknown values. api-diff now downgrades response-property-enum-value-added to INFO, while removing a value from a request enum stays breaking. * feat(api): gate breaking changes on x-stability-level Every operation declares x-stability-level (alpha, beta, stable). oasdiff ignores breaking changes to alpha operations and rejects lowering a level, so unreleased endpoints can evolve while beta and stable ones stay additive. All current operations start as alpha. * feat(api): declare loginMethods as an enum Prefix generated enum constants with their type name so enums sharing a value (for example password) cannot collide in package apiv1. * feat(api): send Allow on 405 and answer HEAD wherever GET is routed chi only sets Allow in its default 405 handler, so the problem-format handler now builds it by matching each method against the v1 router. HEAD requests fall back to the GET route, as RFC 9110 expects. * refactor(api): hash the spec ETag with xxh3 The bytes are compiled in, and the digest was truncated to 64 bits anyway, so this matches the artwork ETags instead of paying for cryptographic strength we discard. * docs(api): explain the about:blank problem type * feat(api): make code the problem identifier and omit a blank type RFC 9457 says clients switch on the type URI, but no adopter surveyed ships both a populated type and a separate code. Declare code as an enum, and send type only once a problem has semantics of its own. * fix(api): advertise the configured base path in the served OpenAPI spec With BaseURL=/music the API is mounted at /music/api/v1, but the spec told clients to call /api/v1 at the host root. The server now rewrites servers[0].url to BasePath + /api/v1 when it serves the document. Relative server URLs were tested first: "." and "../v1" work in openapi-generator, Swagger UI and Redoc, but Scalar resolves them against the page origin, so it breaks even without a base path. The committed bundle keeps /api/v1, and a test pins that it appears exactly once, which the rewrite relies on.
262 lines
No EOL
8.1 KiB
JSON
262 lines
No EOL
8.1 KiB
JSON
{
|
|
"openapi": "3.0.3",
|
|
"info": {
|
|
"title": "Navidrome API",
|
|
"version": "1.0.0",
|
|
"description": "Navidrome API v1. Spec-first, additive within v1. Clients discover implemented\ncapability modules through `GET /server` and never sniff versions.\n\nEnums are open: new values may be added to any enum within v1. Clients must\naccept values they do not recognise instead of failing.\n\nEvery operation declares `x-stability-level`: `alpha` operations may change or\ndisappear without notice, `beta` and `stable` operations only change additively.\nA level is only ever raised, never lowered.\n\n`HEAD` is accepted wherever `GET` is. A `405` response lists the allowed methods\nin its `Allow` header.\n",
|
|
"license": {
|
|
"name": "GPL-3.0",
|
|
"url": "https://www.gnu.org/licenses/gpl-3.0.html"
|
|
}
|
|
},
|
|
"servers": [
|
|
{
|
|
"url": "/api/v1"
|
|
}
|
|
],
|
|
"tags": [
|
|
{
|
|
"name": "server",
|
|
"description": "Server discovery and the published OpenAPI document."
|
|
}
|
|
],
|
|
"paths": {
|
|
"/server": {
|
|
"get": {
|
|
"operationId": "getServerInfo",
|
|
"x-module": "core",
|
|
"x-stability-level": "alpha",
|
|
"tags": [
|
|
"server"
|
|
],
|
|
"summary": "Describe the server",
|
|
"description": "Returns the public server description. No authentication required.\nAuthenticated requests will additionally receive the implemented capability modules\nonce authentication is available.\n",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Server description.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ServerInfo"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"$ref": "#/components/responses/InternalError"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/openapi.json": {
|
|
"get": {
|
|
"operationId": "getOpenAPISpecJSON",
|
|
"x-module": "core",
|
|
"x-stability-level": "alpha",
|
|
"tags": [
|
|
"server"
|
|
],
|
|
"summary": "Get the OpenAPI document (JSON)",
|
|
"description": "The bundled OpenAPI document of the running server version. Supports ETag revalidation.",
|
|
"responses": {
|
|
"200": {
|
|
"description": "The OpenAPI document.",
|
|
"headers": {
|
|
"ETag": {
|
|
"$ref": "#/components/headers/ETag"
|
|
}
|
|
},
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"description": "OpenAPI 3.0 document."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"304": {
|
|
"$ref": "#/components/responses/NotModified"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/openapi.yaml": {
|
|
"get": {
|
|
"operationId": "getOpenAPISpecYAML",
|
|
"x-module": "core",
|
|
"x-stability-level": "alpha",
|
|
"tags": [
|
|
"server"
|
|
],
|
|
"summary": "Get the OpenAPI document (YAML)",
|
|
"description": "The bundled OpenAPI document of the running server version. Supports ETag revalidation.",
|
|
"responses": {
|
|
"200": {
|
|
"description": "The OpenAPI document.",
|
|
"headers": {
|
|
"ETag": {
|
|
"$ref": "#/components/headers/ETag"
|
|
}
|
|
},
|
|
"content": {
|
|
"application/yaml": {
|
|
"schema": {
|
|
"type": "object",
|
|
"description": "OpenAPI 3.0 document."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"304": {
|
|
"$ref": "#/components/responses/NotModified"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"securitySchemes": {
|
|
"bearerAuth": {
|
|
"type": "http",
|
|
"scheme": "bearer",
|
|
"bearerFormat": "JWT",
|
|
"description": "Short-lived access token minted from a device grant. Not yet applied to any operation."
|
|
}
|
|
},
|
|
"schemas": {
|
|
"ServerInfo": {
|
|
"type": "object",
|
|
"description": "Public server description. Everything an add-server screen needs before login.",
|
|
"required": [
|
|
"name",
|
|
"serverVersion",
|
|
"specVersion",
|
|
"setupRequired",
|
|
"loginMethods"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Human-readable server product name."
|
|
},
|
|
"serverVersion": {
|
|
"type": "string",
|
|
"description": "Version of the running server build."
|
|
},
|
|
"specVersion": {
|
|
"type": "string",
|
|
"description": "Version of the OpenAPI document this server implements."
|
|
},
|
|
"setupRequired": {
|
|
"type": "boolean",
|
|
"description": "True until the first admin user has been created."
|
|
},
|
|
"loginMethods": {
|
|
"type": "array",
|
|
"description": "Login methods this server accepts. New methods may be added; clients ignore values they do not recognise.",
|
|
"items": {
|
|
"type": "string",
|
|
"enum": [
|
|
"password"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"Problem": {
|
|
"type": "object",
|
|
"description": "RFC 9457 problem details, returned for every 4xx and 5xx response.",
|
|
"required": [
|
|
"title",
|
|
"status",
|
|
"code"
|
|
],
|
|
"properties": {
|
|
"type": {
|
|
"type": "string",
|
|
"description": "URI reference identifying the problem type. Omitted while the problem carries no semantics\nbeyond its HTTP status code, which RFC 9457 defines as `about:blank`. Problems with their\nown semantics get their own URI; switch on `code` instead.\n"
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Short human-readable summary, the same for all occurrences of this problem type."
|
|
},
|
|
"status": {
|
|
"type": "integer",
|
|
"description": "HTTP status code of this response."
|
|
},
|
|
"detail": {
|
|
"type": "string",
|
|
"description": "Human-readable explanation specific to this occurrence. Omitted for internal errors."
|
|
},
|
|
"code": {
|
|
"type": "string",
|
|
"description": "Machine-readable error code, and the value clients switch on. New codes may be added.",
|
|
"enum": [
|
|
"validation",
|
|
"unauthorized",
|
|
"forbidden",
|
|
"not_found",
|
|
"method_not_allowed",
|
|
"unavailable",
|
|
"internal"
|
|
]
|
|
},
|
|
"errors": {
|
|
"type": "array",
|
|
"description": "Per-field failures. Present only when `code` is `validation`.",
|
|
"items": {
|
|
"$ref": "#/components/schemas/ValidationError"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ValidationError": {
|
|
"type": "object",
|
|
"description": "One field-level validation failure.",
|
|
"required": [
|
|
"field",
|
|
"message"
|
|
],
|
|
"properties": {
|
|
"field": {
|
|
"type": "string",
|
|
"description": "Name of the offending query parameter, path parameter, or body field (dotted for nested)."
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"description": "Why the value was rejected."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"InternalError": {
|
|
"description": "Unexpected server failure. Details are in the server log.",
|
|
"content": {
|
|
"application/problem+json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/Problem"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"NotModified": {
|
|
"description": "Not modified.",
|
|
"headers": {
|
|
"ETag": {
|
|
"$ref": "#/components/headers/ETag"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"headers": {
|
|
"ETag": {
|
|
"description": "Entity tag for `If-None-Match` revalidation.",
|
|
"schema": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |