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
152 changes: 143 additions & 9 deletions mintlify-codegen/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,38 @@ function kindSuffix(hasErrors: boolean, hasWarnings: boolean): string {
return hasErrors ? 'Errors' : 'Warnings'
}

/** Render the full standalone errors/warnings page (frontmatter + sections). */
/**
* Render a note pointing readers to the errors/warnings shared by all devices.
* Device sub-category pages (locks/thermostats/phones) are subsets that omit the
* device-level codes (`device_offline`, `device_disconnected`, …) applying to
* every device, so a reader troubleshooting connectivity there needs a pointer
* to the aggregate device page. `kinds` is the lowercased sections phrase
* (`errors and warnings` / `errors` / `warnings`) so the prose matches the
* page's actual content.
*/
function renderCommonDeviceErrorsNote(
noun: string,
kinds: string,
route: string,
): string {
const phrase = kinds.toLowerCase()
return [
'<Note>',
` These are ${noun}-specific ${phrase}. For ${phrase} common to all devices, see [Device Errors and Warnings](${route}).`,
'</Note>',
].join('\n')
}

/**
* Render the full standalone errors/warnings page (frontmatter + sections). When
* `commonDeviceErrorsRoute` is set, a note linking to the shared device
* errors/warnings page is prepended — used on device sub-category pages.
*/
function renderPage(
noun: string,
errorSection: string,
warningSection: string,
commonDeviceErrorsRoute?: string,
): string {
const title = `${noun} ${kindSuffix(Boolean(errorSection), Boolean(warningSection))}`
const kinds =
Expand All @@ -302,7 +329,10 @@ function renderPage(
`description: '${description.replace(/'/g, "\\'")}'`,
'---',
].join('\n')
const body = [errorSection, warningSection].filter(Boolean).join('\n\n')
const note = commonDeviceErrorsRoute
? renderCommonDeviceErrorsNote(noun, kinds, commonDeviceErrorsRoute)
: ''
const body = [note, errorSection, warningSection].filter(Boolean).join('\n\n')
return `${frontmatter}\n\n${body}\n`
}

Expand All @@ -314,6 +344,13 @@ async function readFileOrNull(path: string): Promise<string | null> {
}
}

interface ErrorPageOptions {
// When set, prepend a note linking to the errors/warnings shared by all
// devices. Used only for device sub-category pages (locks/thermostats/phones),
// which are subsets that omit the device-level codes.
commonDeviceErrorsRoute?: string
}

/**
* Write one resource's `errors.mdx` (an `## Errors` and/or `## Warnings`
* section) and record its route in `routes`. No-ops when the resource has no
Expand All @@ -325,7 +362,16 @@ async function writeErrorPage(
errorsProp: Property | undefined,
warningsProp: Property | undefined,
routes: string[],
options: ErrorPageOptions = {},
): Promise<void> {
// Defensive against a variant-group key ever coinciding with a real resource
// route: the resource loop writes first, so skip a route already emitted
// rather than clobbering it from the sub-category loop.
if (routes.includes(routePath)) {
console.log(` Errors page for ${routePath} already written, skipping`)
return
}

const errorGroups = groupCodes(errorsProp)
const warningGroups = groupCodes(warningsProp)
if (errorGroups.length === 0 && warningGroups.length === 0) return
Expand All @@ -342,11 +388,24 @@ async function writeErrorPage(
noun,
renderSection('Errors', 'error', errorsProp, errorGroups),
renderSection('Warnings', 'warning', warningsProp, warningGroups),
options.commonDeviceErrorsRoute,
)
await writeFile(join(resourceDir, 'errors.mdx'), page)
routes.push(routePath)
}

interface DeviceSubcategory {
// Route that carries the sub-category's dedicated errors page.
routePath: string
// Whether the sub-category shares the device-level errors/warnings documented
// on /api/devices/errors, and so should cross-link to them. The shared codes
// are physical-device connectivity/subscription errors (`device_offline`,
// `hub_disconnected`, `salto_ks_subscription_limit_exceeded`, …), relevant to
// locks and thermostats but not to phones (mobile devices used for
// credentials), so the phones page omits the cross-link.
linkCommonDeviceErrors: boolean
}

// Some device sub-categories have their own docs route but no blueprint
// resource of their own — the Seam API models a lock, thermostat, or phone as a
// `device`. Their error/warning codes therefore live on the `device` resource
Expand All @@ -359,12 +418,17 @@ async function writeErrorPage(
// but have no standalone sub-category route. In particular /access_codes
// already documents the `access_code` resource's own, distinct errors — routing
// the device `access_codes` group there would clobber it.
const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record<string, string> = {
locks: '/locks',
phones: '/phones',
thermostats: '/thermostats',
const DEVICE_SUBCATEGORY_ERROR_ROUTES: Record<string, DeviceSubcategory> = {
locks: { routePath: '/locks', linkCommonDeviceErrors: true },
phones: { routePath: '/phones', linkCommonDeviceErrors: false },
thermostats: { routePath: '/thermostats', linkCommonDeviceErrors: true },
}

// Route of the aggregate device errors/warnings page. Sub-categories that share
// the device-level codes link back to it (see writeErrorPage's
// `commonDeviceErrorsRoute` option).
const COMMON_DEVICE_ERRORS_ROUTE = '/api/devices/errors'

/**
* Narrow a device `errors`/`warnings` property to a single variant group,
* flattening the kept variants to ungrouped (`variantGroupKey` cleared) so their
Expand All @@ -384,6 +448,58 @@ function variantGroupProp(
return { ...prop, variants, variantGroups: [] }
}

/**
* Warn when a device errors/warnings variant group looks like it should have its
* own sub-category errors page but is missing from DEVICE_SUBCATEGORY_ERROR_ROUTES.
*
* That allowlist is maintained by hand, so a newly added device sub-category
* (its own object + events pages plus an error/warning variant group) would
* silently produce no errors page until someone edits the map. This surfaces
* that inconsistency at generate time.
*
* A group is flagged only when it (a) has documented codes, (b) has both an
* `object.mdx` and an `events.mdx` under `/api/<key>` — the shape a real
* sub-category route has — and (c) is neither already mapped nor itself a
* documented blueprint resource. Condition (c) keeps real resources
* (`access_codes`, which owns its own distinct errors) and annotation-only
* groups (`hardware`, `provider_metadata` — no such route) from tripping it.
*/
async function warnUnmappedDeviceSubcategoryGroups(
docsDir: string,
errorsProp: Property | undefined,
warningsProp: Property | undefined,
documentedResourceRoutes: Set<string>,
): Promise<void> {
const groupKeys = new Set<string>()
for (const prop of [errorsProp, warningsProp]) {
if (!isDiscriminatedListProperty(prop)) continue
for (const group of prop.variantGroups) groupKeys.add(group.variantGroupKey)
}

for (const groupKey of groupKeys) {
if (groupKey in DEVICE_SUBCATEGORY_ERROR_ROUTES) continue
if (documentedResourceRoutes.has(`/${groupKey}`)) continue

const hasCodes =
groupCodes(variantGroupProp(errorsProp, groupKey)).length > 0 ||
groupCodes(variantGroupProp(warningsProp, groupKey)).length > 0
if (!hasCodes) continue

const resourceDir = join(docsDir, 'api', groupKey)
const hasObject =
(await readFileOrNull(join(resourceDir, 'object.mdx'))) != null
const hasEvents =
(await readFileOrNull(join(resourceDir, 'events.mdx'))) != null
if (!hasObject || !hasEvents) continue

console.log(
` WARNING: device errors/warnings variant group "${groupKey}" has codes ` +
`and an object+events page (/api/${groupKey}) but no dedicated errors ` +
`page. Add it to DEVICE_SUBCATEGORY_ERROR_ROUTES in mintlify-codegen/errors.ts.`,
)
}
}

/**
* Generate the per-resource `errors.mdx` pages. Returns the route paths that
* received a page (e.g. `/devices`) so the caller can wire them into the
Expand Down Expand Up @@ -413,17 +529,35 @@ export async function updateErrorPages(
if (device != null) {
const errorsProp = device.properties.find((p) => p.name === 'errors')
const warningsProp = device.properties.find((p) => p.name === 'warnings')
for (const [groupKey, routePath] of Object.entries(
DEVICE_SUBCATEGORY_ERROR_ROUTES,
)) {
for (const [
groupKey,
{ routePath, linkCommonDeviceErrors },
] of Object.entries(DEVICE_SUBCATEGORY_ERROR_ROUTES)) {
await writeErrorPage(
docsDir,
routePath,
variantGroupProp(errorsProp, groupKey),
variantGroupProp(warningsProp, groupKey),
routes,
linkCommonDeviceErrors
? { commonDeviceErrorsRoute: COMMON_DEVICE_ERRORS_ROUTE }
: {},
)
}

// Flag any device sub-category that has its own object+events pages and
// error codes but was never added to the allowlist above.
const documentedResourceRoutes = new Set(
blueprint.resources
.filter((r) => !r.isUndocumented)
.map((r) => r.routePath),
)
await warnUnmappedDeviceSubcategoryGroups(
docsDir,
errorsProp,
warningsProp,
documentedResourceRoutes,
)
}

return routes
Expand Down
4 changes: 4 additions & 0 deletions mintlify-docs/api/locks/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ title: 'Lock Errors and Warnings'
description: 'Errors and warnings that Seam reports on the Lock resource, each with its code and meaning.'
---

<Note>
These are Lock-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors).
</Note>

## Errors

Each error is an object with the following shape:
Expand Down
4 changes: 4 additions & 0 deletions mintlify-docs/api/thermostats/errors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ title: 'Thermostat Errors and Warnings'
description: 'Errors and warnings that Seam reports on the Thermostat resource, each with its code and meaning.'
---

<Note>
These are Thermostat-specific errors and warnings. For errors and warnings common to all devices, see [Device Errors and Warnings](/api/devices/errors).
</Note>

## Errors

Each error is an object with the following shape:
Expand Down
Loading