Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
417 changes: 400 additions & 17 deletions src/lifecycle.md

Large diffs are not rendered by default.

69 changes: 58 additions & 11 deletions src/schemas/validation/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,63 @@ $id: 'https://spec.openapis.org/lifecycle/1.0/schema/WORK-IN-PROGRESS'
title: Lifecycle Schema
description: Validates a Lifecycle Object.

# $dynamicAnchor marks Schema Object positions as an integration point.
# When this schema is used standalone, $dynamicRef resolves to the standard
# JSON Schema 2020-12 definition immediately below.
# When composed with an OAS metaschema (integration mechanism TBD), the OAS
# dialect fills in this anchor with the full OAS Schema Object definition.
$ref: '#/$defs/lifecycle'

$defs:
schema-object:
$dynamicAnchor: schema-object
$ref: 'https://json-schema.org/draft/2020-12/schema'
lifecycle:
description: Deprecation and sunset status of a single operation.
type: object
required:
- date
properties:
date:
type: string
format: date-time
reason:
type: string
sunset:
$ref: '#/$defs/sunset'
externalLinks:
type: array
items:
$ref: '#/$defs/lifecycle-link'
unevaluatedProperties: false

sunset:
description: When an operation is expected to stop accepting requests.
type: object
required:
- date
properties:
date:
type: string
format: date-time
unevaluatedProperties: false

# An External Documentation Object in lifecycle.externalLinks, where kind is required.
lifecycle-link:
$ref: '#/$defs/external-documentation'
required:
- kind

# External Documentation Object as extended by the OAS Docs Links proposal.
external-documentation:
type: object
required:
- url
properties:
summary:
type: string
description:
type: string
url:
type: string
format: uri-reference
kind:
type: string
$ref: '#/$defs/specification-extensions'
unevaluatedProperties: false

type: object
properties: {}
additionalProperties: false
specification-extensions:
patternProperties:
'^x-': true
1 change: 1 addition & 0 deletions tests/schema/fail/date-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
date: 1736899200
1 change: 1 addition & 0 deletions tests/schema/fail/lifecycle-not-object.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- date: "2025-01-15T00:00:00Z"
5 changes: 5 additions & 0 deletions tests/schema/fail/link-description-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
description: 1
url: https://docs.example.com/migrate
4 changes: 4 additions & 0 deletions tests/schema/fail/link-kind-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: 1
url: https://docs.example.com/migrate
4 changes: 4 additions & 0 deletions tests/schema/fail/link-missing-kind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# kind is REQUIRED in lifecycle.externalLinks
date: "2025-01-15T00:00:00Z"
externalLinks:
- url: https://docs.example.com/migrate
4 changes: 4 additions & 0 deletions tests/schema/fail/link-missing-url.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
summary: Migration guide
3 changes: 3 additions & 0 deletions tests/schema/fail/link-not-object.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- https://docs.example.com/migrate
5 changes: 5 additions & 0 deletions tests/schema/fail/link-summary-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
summary: 1
url: https://docs.example.com/migrate
5 changes: 5 additions & 0 deletions tests/schema/fail/link-unknown-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
name: Migration guide
url: https://docs.example.com/migrate
4 changes: 4 additions & 0 deletions tests/schema/fail/link-url-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
url: 1
4 changes: 4 additions & 0 deletions tests/schema/fail/links-not-array.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
date: "2025-01-15T00:00:00Z"
externalLinks:
kind: deprecation
url: https://docs.example.com/migrate
1 change: 1 addition & 0 deletions tests/schema/fail/missing-date.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
reason: "Replaced by v2 API with improved performance."
2 changes: 2 additions & 0 deletions tests/schema/fail/reason-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
date: "2025-01-15T00:00:00Z"
reason: 42
4 changes: 4 additions & 0 deletions tests/schema/fail/removed-migration-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# migration was replaced by externalLinks with kind: deprecation
date: "2025-01-15T00:00:00Z"
migration:
url: https://docs.example.com/migrate
4 changes: 4 additions & 0 deletions tests/schema/fail/removed-replacement-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# replacement was replaced by externalLinks with kind: successor-version
date: "2025-01-15T00:00:00Z"
replacement:
url: https://api.example.com/v2/openapi.yaml
6 changes: 6 additions & 0 deletions tests/schema/fail/removed-sunset-policy-field.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# sunset.policy was replaced by externalLinks with kind: sunset
date: "2025-01-15T00:00:00Z"
sunset:
date: "2025-07-01T00:00:00Z"
policy:
url: https://docs.example.com/sunset-policy
3 changes: 3 additions & 0 deletions tests/schema/fail/sunset-date-not-string.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
date: "2025-01-15T00:00:00Z"
sunset:
date: 1751328000
2 changes: 2 additions & 0 deletions tests/schema/fail/sunset-missing-date.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
date: "2025-01-15T00:00:00Z"
sunset: {}
2 changes: 2 additions & 0 deletions tests/schema/fail/sunset-not-object.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
date: "2025-01-15T00:00:00Z"
sunset: "2025-07-01T00:00:00Z"
2 changes: 2 additions & 0 deletions tests/schema/pass/empty-links.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
date: "2025-01-15T00:00:00Z"
externalLinks: []
25 changes: 25 additions & 0 deletions tests/schema/pass/lifecycle-object-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Lifecycle Object from the spec's Complete OpenAPI Description Example
date: "2025-01-15T00:00:00Z"
reason: "Replaced by v2 API with improved performance."
sunset:
date: "2025-07-01T00:00:00Z"
externalLinks:
- kind: deprecation
summary: Migrating from Flights v1 to v2
description: |
Step-by-step guide covering renamed query parameters and the
new cursor-based pagination.
url: https://docs.example.com/migrate
- kind: successor-version
summary: Flights API v2
description: Use `listFlights_v2` (`GET /v2/flights`) instead.
url: https://api.example.com/v2/openapi.yaml
- kind: sunset
summary: API sunset policy
url: https://docs.example.com/sunset-policy
- kind: changelog
summary: Changelog entry announcing the v1 deprecation
url: https://docs.example.com/changelog#2025-01-15
- kind: announcement
summary: Sunset announcement
url: https://status.example.com/announcements/flights-v1-sunset
6 changes: 6 additions & 0 deletions tests/schema/pass/link-extension.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# External Documentation Objects MAY be extended with Specification Extensions
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
url: https://docs.example.com/migrate
x-audience: internal
2 changes: 2 additions & 0 deletions tests/schema/pass/minimal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Only the required field
date: "2025-01-15T00:00:00Z"
7 changes: 7 additions & 0 deletions tests/schema/pass/multiple-successors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# successor-version MAY appear more than once
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: successor-version
url: https://api.example.com/v2/openapi.yaml
- kind: successor-version
url: https://api.example.com/v3/openapi.yaml
5 changes: 5 additions & 0 deletions tests/schema/pass/relative-link-url.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# url is a URI reference, so relative references are allowed
date: "2025-01-15T00:00:00Z"
externalLinks:
- kind: deprecation
url: ../docs/migrate.html
30 changes: 24 additions & 6 deletions tests/schema/schema.test.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { describe, expect, it } from "@oai/build-infra/test";
import { registerSchema } from "@oai/build-infra/schema/vitest";
import { readdirSync, readFileSync } from "node:fs";
import YAML from "yaml";
import { registerSchema, toMatchJsonSchema } from "@oai/build-infra/schema/vitest";
import { describe, expect, test } from "@oai/build-infra/test";

expect.extend({ toMatchJsonSchema });

const schemaUri = "https://spec.openapis.org/lifecycle/1.0/schema/WORK-IN-PROGRESS";
await registerSchema("./src/schemas/validation/schema.yaml");

// Placeholder test suite - add pass/fail test cases as the schema is developed.
describe("Lifecycle schema", () => {
it("validates an empty Lifecycle Object", async () => {
await expect({}).to.matchJsonSchema("./src/schemas/validation/schema.yaml");
});
for (const entry of readdirSync("tests/schema/pass", { withFileTypes: true })) {
if (!entry.isFile() || !entry.name.endsWith(".yaml")) continue;

test(`pass/${entry.name}`, async () => {
const document = YAML.parse(readFileSync(`tests/schema/pass/${entry.name}`, "utf8"));
await expect(document).toMatchJsonSchema(schemaUri);
});
}

for (const entry of readdirSync("tests/schema/fail", { withFileTypes: true })) {
if (!entry.isFile() || !entry.name.endsWith(".yaml")) continue;

test(`fail/${entry.name}`, async () => {
const document = YAML.parse(readFileSync(`tests/schema/fail/${entry.name}`, "utf8"));
await expect(document).not.toMatchJsonSchema(schemaUri);
});
}
});
Loading