Skip to content

Commit

Permalink
Use openapi spec to find return resource
Browse files Browse the repository at this point in the history
  • Loading branch information
razor-x committed Sep 28, 2023
1 parent e0651c8 commit 64cbf3e
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 71 deletions.
77 changes: 34 additions & 43 deletions generate-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { resolve } from 'node:path'
import { openapi } from '@seamapi/types/connect'
import { camelCase, paramCase, pascalCase, snakeCase } from 'change-case'
import { ESLint } from 'eslint'
import pluralize from 'pluralize'
import { format, resolveConfig } from 'prettier'

const rootClassPath = resolve('src', 'lib', 'seam', 'connect', 'client.ts')
Expand Down Expand Up @@ -53,24 +52,14 @@ const endpointResources: Partial<
| null
>
> = {
'/access_codes/generate_code': 'generated_code',
'/access_codes/create_multiple': 'access_codes',
'/access_codes/pull_backup_access_code': 'backup_access_code',
'/access_codes/unmanaged/convert_to_managed': null,
'/acs/access_groups/list_users': 'acs_users',
'/acs/access_groups/remove_user': null,
'/acs/users/add_to_access_group': null,
'/acs/users/remove_from_access_group': null,
'/access_codes/delete': null,
'/access_codes/unmanaged/delete': null,
'/access_codes/update': null,
'/connect_webviews/view': null,
'/devices/list_device_providers': 'device_providers',
'/locks/lock_door': 'action_attempt',
'/locks/unlock_door': 'action_attempt',
'/noise_sensors/noise_thresholds/create': null, // could return action action_attempt
'/thermostats/cool': null,
'/thermostats/heat': null,
'/thermostats/heat_cool': null,
'/thermostats/off': null,
'/thermostats/set_fan_mode': null,
'/noise_sensors/noise_thresholds/create': null,
'/noise_sensors/noise_thresholds/delete': null,
'/noise_sensors/noise_thresholds/update': null,
'/thermostats/climate_setting_schedules/update': null,
'/workspaces/reset_sandbox': null,
}

Expand All @@ -88,7 +77,7 @@ interface Endpoint {
requestFormat: 'params' | 'body'
}

type Method = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH'
type Method = 'GET' | 'POST'

interface ClassMeta {
constructors: string
Expand Down Expand Up @@ -150,46 +139,48 @@ const createEndpoint = (
namespace,
path: endpointPath,
method,
resource: deriveResource(endpointPath, routePath, name, method),
resource: deriveResource(endpointPath, method),
requestFormat: ['GET', 'DELETE'].includes(method) ? 'params' : 'body',
}
}

const deriveResource = (
endpointPath: string,
routePath: string,
name: string,
method: Method,
): string | null => {
if (isEndpointResource(endpointPath)) {
return endpointResources[endpointPath] ?? null
}
if (['DELETE', 'PATCH', 'PUT'].includes(method)) return null
if (['update', 'delete'].includes(name)) return null
const group = deriveGroupFromRoutePath(routePath)
if (group == null) throw new Error(`Could not parse group from ${routePath}`)
if (name === 'list') return group
return pluralize.singular(group)
}

const deriveGroupFromRoutePath = (routePath: string): string | undefined => {
const parts = routePath.split('/').slice(1)

if (routePath.endsWith('/unmanaged')) {
return parts[0]
}

if (routePath.startsWith('/acs')) {
return [parts[0], parts[1]].join('_')
}

if (parts.length === 2) {
return parts[1]
if (isOpenApiPath(endpointPath)) {
const spec = openapi.paths[endpointPath]
const methodKey = method.toLowerCase()

if (methodKey === 'post' && 'post' in spec) {
const response = spec.post.responses[200]
if (!('content' in response)) return null
return deriveResourceFromSchema(
response.content['application/json']?.schema?.properties ?? {},
)
}

if (methodKey === 'get' && 'get' in spec) {
const response = spec.get.responses[200]
if (!('content' in response)) {
throw new Error(`Missing resource for ${method} ${endpointPath}`)
}
return deriveResourceFromSchema(
response.content['application/json']?.schema?.properties ?? {},
)
}
}

return parts[0]
throw new Error(`Could not derive resource for ${method} ${endpointPath}`)
}

const deriveResourceFromSchema = (properties: object): string | null =>
Object.keys(properties).filter((key) => key !== 'ok')[0] ?? null

const deriveSemanticMethod = (methods: string[]): Method => {
if (methods.includes('get')) return 'GET'
if (methods.includes('post')) return 'POST'
Expand Down
17 changes: 0 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
"@seamapi/types": "^1.14.0",
"@types/eslint": "^8.44.2",
"@types/node": "^18.11.18",
"@types/pluralize": "^0.0.31",
"ava": "^5.0.1",
"c8": "^8.0.0",
"change-case": "^4.1.2",
Expand All @@ -103,7 +102,6 @@
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^3.0.0",
"landlubber": "^1.0.0",
"pluralize": "^8.0.0",
"prettier": "^3.0.0",
"tsc-alias": "^1.8.2",
"tsup": "^7.2.0",
Expand Down
7 changes: 2 additions & 5 deletions src/lib/seam/connect/routes/acs-access-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,12 @@ export class SeamHttpAcsAccessGroups {
return new SeamHttpAcsAccessGroups(opts)
}

async addUser(
body: AcsAccessGroupsAddUserBody,
): Promise<AcsAccessGroupsAddUserResponse['acs_access_group']> {
const { data } = await this.client.request<AcsAccessGroupsAddUserResponse>({
async addUser(body: AcsAccessGroupsAddUserBody): Promise<void> {
await this.client.request<AcsAccessGroupsAddUserResponse>({
url: '/acs/access_groups/add_user',
method: 'post',
data: body,
})
return data.acs_access_group
}

async create(
Expand Down
8 changes: 4 additions & 4 deletions src/lib/seam/connect/routes/locks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,22 @@ export class SeamHttpLocks {
return new SeamHttpLocks(opts)
}

async get(body: LocksGetBody): Promise<LocksGetResponse['lock']> {
async get(body: LocksGetBody): Promise<LocksGetResponse['device']> {
const { data } = await this.client.request<LocksGetResponse>({
url: '/locks/get',
method: 'post',
data: body,
})
return data.lock
return data.device
}

async list(body: LocksListBody): Promise<LocksListResponse['locks']> {
async list(body: LocksListBody): Promise<LocksListResponse['devices']> {
const { data } = await this.client.request<LocksListResponse>({
url: '/locks/list',
method: 'post',
data: body,
})
return data.locks
return data.devices
}

async lockDoor(
Expand Down

0 comments on commit 64cbf3e

Please sign in to comment.