Skip to content

Commit

Permalink
error if parent selectors given when resources are fetched by ID
Browse files Browse the repository at this point in the history
  • Loading branch information
david-crespo committed Aug 24, 2024
1 parent 7712765 commit a42d51f
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 27 deletions.
76 changes: 60 additions & 16 deletions mock-api/msw/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,25 @@ export const notFoundErr = (msg: string) => {
return json({ error_code: 'ObjectNotFound', message } as const, { status: 404 })
}

export const badSelectorErr = (resource: string, parents: string[]) => {
const message = `when ${resource} is specified by ID, ${commaSeries(parents, 'and')} should not be specified`
return json({ error_code: 'InvalidRequest', message }, { status: 400 })
}

export const lookupById = <T extends { id: string }>(table: T[], id: string) => {
const item = table.find((i) => i.id === id)
if (!item) throw notFoundErr(`by id ${id}`)
return item
}

function ensureNoParentSelector(
resource: string,
selector: Record<string, string | undefined>
) {
const keysWithValues = Object.entries(selector)
.filter(([_, v]) => v)
.map(([k]) => k)
if (keysWithValues.length > 0) {
const message = `when ${resource} is specified by ID, ${commaSeries(keysWithValues, 'and')} should not be specified`
throw json({ error_code: 'InvalidRequest', message }, { status: 400 })
}
}

export const getIpFromPool = (poolName: string | undefined) => {
const pool = lookup.ipPool({ pool: poolName })
const ipPoolRange = db.ipPoolRanges.find((range) => range.ip_pool_id === pool.id)
Expand All @@ -63,7 +71,10 @@ export const lookup = {
instance({ instance: id, ...projectSelector }: PP.Instance): Json<Api.Instance> {
if (!id) throw notFoundErr('no instance specified')

if (isUuid(id)) return lookupById(db.instances, id)
if (isUuid(id)) {
ensureNoParentSelector('instance', projectSelector)
return lookupById(db.instances, id)
}

const project = lookup.project(projectSelector)
const instance = db.instances.find((i) => i.project_id === project.id && i.name === id)
Expand All @@ -77,7 +88,10 @@ export const lookup = {
}: PP.NetworkInterface): Json<Api.InstanceNetworkInterface> {
if (!id) throw notFoundErr('no NIC specified')

if (isUuid(id)) return lookupById(db.networkInterfaces, id)
if (isUuid(id)) {
ensureNoParentSelector('network interface', instanceSelector)
return lookupById(db.networkInterfaces, id)
}

const instance = lookup.instance(instanceSelector)

Expand All @@ -91,7 +105,10 @@ export const lookup = {
disk({ disk: id, ...projectSelector }: PP.Disk): Json<Api.Disk> {
if (!id) throw notFoundErr('no disk specified')

if (isUuid(id)) return lookupById(db.disks, id)
if (isUuid(id)) {
ensureNoParentSelector('disk', projectSelector)
return lookupById(db.disks, id)
}

const project = lookup.project(projectSelector)

Expand All @@ -104,7 +121,7 @@ export const lookup = {
if (!id) throw notFoundErr('no floating IP specified')

if (isUuid(id)) {
if (projectSelector.project) throw badSelectorErr('floating IP', ['project'])
ensureNoParentSelector('floating IP', projectSelector)
return lookupById(db.floatingIps, id)
}

Expand All @@ -119,7 +136,10 @@ export const lookup = {
snapshot({ snapshot: id, ...projectSelector }: PP.Snapshot): Json<Api.Snapshot> {
if (!id) throw notFoundErr('no snapshot specified')

if (isUuid(id)) return lookupById(db.snapshots, id)
if (isUuid(id)) {
ensureNoParentSelector('snapshot', projectSelector)
return lookupById(db.snapshots, id)
}

const project = lookup.project(projectSelector)
const snapshot = db.snapshots.find((i) => i.project_id === project.id && i.name === id)
Expand All @@ -130,7 +150,10 @@ export const lookup = {
vpc({ vpc: id, ...projectSelector }: PP.Vpc): Json<Api.Vpc> {
if (!id) throw notFoundErr('no VPC specified')

if (isUuid(id)) return lookupById(db.vpcs, id)
if (isUuid(id)) {
ensureNoParentSelector('vpc', projectSelector)
return lookupById(db.vpcs, id)
}

const project = lookup.project(projectSelector)
const vpc = db.vpcs.find((v) => v.project_id === project.id && v.name === id)
Expand All @@ -141,7 +164,10 @@ export const lookup = {
vpcRouter({ router: id, ...vpcSelector }: PP.VpcRouter): Json<Api.VpcRouter> {
if (!id) throw notFoundErr('no router specified')

if (isUuid(id)) return lookupById(db.vpcRouters, id)
if (isUuid(id)) {
ensureNoParentSelector('router', vpcSelector)
return lookupById(db.vpcRouters, id)
}

const vpc = lookup.vpc(vpcSelector)
const router = db.vpcRouters.find((r) => r.vpc_id === vpc.id && r.name === id)
Expand All @@ -155,7 +181,10 @@ export const lookup = {
}: PP.VpcRouterRoute): Json<Api.RouterRoute> {
if (!id) throw notFoundErr('no route specified')

if (isUuid(id)) return lookupById(db.vpcRouterRoutes, id)
if (isUuid(id)) {
ensureNoParentSelector('route', routerSelector)
return lookupById(db.vpcRouterRoutes, id)
}

const router = lookup.vpcRouter(routerSelector)
const route = db.vpcRouterRoutes.find(
Expand All @@ -168,7 +197,10 @@ export const lookup = {
vpcSubnet({ subnet: id, ...vpcSelector }: PP.VpcSubnet): Json<Api.VpcSubnet> {
if (!id) throw notFoundErr('no subnet specified')

if (isUuid(id)) return lookupById(db.vpcSubnets, id)
if (isUuid(id)) {
ensureNoParentSelector('subnet', vpcSelector)
return lookupById(db.vpcSubnets, id)
}

const vpc = lookup.vpc(vpcSelector)
const subnet = db.vpcSubnets.find((s) => s.vpc_id === vpc.id && s.name === id)
Expand All @@ -179,8 +211,13 @@ export const lookup = {
image({ image: id, project: projectId }: PP.Image): Json<Api.Image> {
if (!id) throw notFoundErr('no image specified')

if (isUuid(id)) return lookupById(db.images, id)
if (isUuid(id)) {
ensureNoParentSelector('image', { project: projectId })
return lookupById(db.images, id)
}

// TODO: is this logic right? seems kinda weird. you should be able to look
// up either kind by ID. we should probably have two lookup functions
let image: Json<Api.Image> | undefined
if (projectId === undefined) {
// silo image
Expand Down Expand Up @@ -264,8 +301,15 @@ export const lookup = {
samlIdp({ provider: id, silo }: PP.IdentityProvider): Json<Api.SamlIdentityProvider> {
if (!id) throw notFoundErr('no IdP specified')

const dbSilo = lookup.silo({ silo })
if (isUuid(id)) {
ensureNoParentSelector('identity provider', { silo })
return lookupById(
db.identityProviders.filter((o) => o.type === 'saml').map((o) => o.provider),
id
)
}

const dbSilo = lookup.silo({ silo })
const dbIdp = db.identityProviders.find(
({ type, siloId, provider }) =>
type === 'saml' && siloId === dbSilo.id && provider.name === id
Expand Down
26 changes: 15 additions & 11 deletions mock-api/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,17 @@ export const handlers = makeHandlers({

return 204
},
floatingIpAttach({ path, query, body }) {
const floatingIp = lookup.floatingIp({ ...path, ...query })
if (floatingIp.instance_id) {
floatingIpAttach({ path: { floatingIp }, query: { project }, body }) {
const dbFloatingIp = lookup.floatingIp({ floatingIp, project })
if (dbFloatingIp.instance_id) {
throw 'floating IP cannot be attached to one instance while still attached to another'
}
const instance = lookup.instance({ ...path, ...query, instance: body.parent })
floatingIp.instance_id = instance.id
// TODO: make sure the logic around when project is passed matches
// the API
const dbInstance = lookup.instance({ instance: body.parent })
dbFloatingIp.instance_id = dbInstance.id

return floatingIp
return dbFloatingIp
},
floatingIpDetach({ path, query }) {
const floatingIp = lookup.floatingIp({ ...path, ...query })
Expand Down Expand Up @@ -359,13 +361,15 @@ export const handlers = makeHandlers({

return json(image, { status: 202 })
},
imageDemote({ path, query }) {
const image = lookup.image({ ...path, ...query })
const project = lookup.project({ ...path, ...query })
imageDemote({ path: { image }, query: { project } }) {
// TODO: change this to lookup.siloImage when I add that. or at least
// check API logic to make sure we match it
const dbImage = lookup.image({ image })
const dbProject = lookup.project({ project })

image.project_id = project.id
dbImage.project_id = dbProject.id

return json(image, { status: 202 })
return json(dbImage, { status: 202 })
},
instanceList({ query }) {
const project = lookup.project(query)
Expand Down

0 comments on commit a42d51f

Please sign in to comment.