Skip to content

Commit

Permalink
feat: add endpoint for maps plugin to get info about custom map (#898)
Browse files Browse the repository at this point in the history
  • Loading branch information
achou11 authored Oct 9, 2024
1 parent f21a675 commit 642ba88
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 1 deletion.
59 changes: 58 additions & 1 deletion src/fastify-plugins/maps.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { fetch } from 'undici'
import { Server as SMPServerPlugin } from 'styled-map-package'

import { noop } from '../utils.js'
import { NotFoundError, ENOENTError } from './utils.js'

/** @import { FastifyPluginAsync } from 'fastify' */
/** @import { Stats } from 'node:fs' */

export const CUSTOM_MAP_PREFIX = 'custom'
export const FALLBACK_MAP_PREFIX = 'fallback'
Expand All @@ -19,9 +23,62 @@ export const FALLBACK_MAP_PREFIX = 'fallback'
/** @type {FastifyPluginAsync<MapsPluginOpts>} */
export async function plugin(fastify, opts) {
if (opts.customMapPath) {
const { customMapPath } = opts

fastify.get(`/${CUSTOM_MAP_PREFIX}/info`, async () => {
const baseUrl = new URL(fastify.prefix, fastify.listeningOrigin)

if (!baseUrl.href.endsWith('/')) {
baseUrl.href += '/'
}

const customStyleJsonUrl = new URL(
`${CUSTOM_MAP_PREFIX}/style.json`,
baseUrl
)
const response = await fetch(customStyleJsonUrl)

if (response.status === 404) {
throw new NotFoundError(customStyleJsonUrl.href)
}

if (!response.ok) {
throw new Error(`Failed to get style from ${customStyleJsonUrl.href}`)
}

/** @type {Stats | undefined} */
let stats

try {
stats = await fs.stat(customMapPath)
} catch (err) {
if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
throw new ENOENTError(customMapPath)
}

throw err
}

const style = await response.json()

const styleJsonName =
typeof style === 'object' &&
style &&
'name' in style &&
typeof style.name === 'string'
? style.name
: undefined

return {
created: stats.ctime,
size: stats.size,
name: styleJsonName || path.parse(customMapPath).name,
}
})

fastify.register(SMPServerPlugin, {
prefix: CUSTOM_MAP_PREFIX,
filepath: opts.customMapPath,
filepath: customMapPath,
})
}

Expand Down
6 changes: 6 additions & 0 deletions src/fastify-plugins/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export const NotFoundError = createError(
404
)

export const ENOENTError = createError(
'FST_ENOENT',
"ENOENT: no such file or directory '%s'",
404
)

/**
* @param {import('node:http').Server} server
* @param {{ timeout?: number }} [options]
Expand Down
102 changes: 102 additions & 0 deletions test/fastify-plugins/maps.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import path from 'node:path'
import test from 'node:test'
import Fastify from 'fastify'
import { Reader } from 'styled-map-package'
Expand Down Expand Up @@ -223,6 +225,106 @@ test('/style.json resolves style.json of fallback map when custom and online are
)
})

test('custom map info endpoint not available when custom map path not provided', async (t) => {
const server = setup(t)

server.register(MapServerPlugin, {
defaultOnlineStyleUrl: DEFAULT_ONLINE_STYLE_URL,
fallbackMapPath: DEFAULT_FALLBACK_MAP_FILE_PATH,
})

const address = await server.listen()

mockAgent.enableNetConnect(new URL(address).host)

const response = await server.inject({
method: 'GET',
url: `/${CUSTOM_MAP_PREFIX}/info`,
})

assert.equal(response.statusCode, 404)
})

test('custom map info endpoint returns not found error when custom map does not exist', async (t) => {
const server = setup(t)

const nonExistentFile =
path.parse(SAMPLE_SMP_FIXTURE_PATH).dir + '/does/not/exist.smp'

server.register(MapServerPlugin, {
customMapPath: nonExistentFile,
defaultOnlineStyleUrl: DEFAULT_ONLINE_STYLE_URL,
fallbackMapPath: DEFAULT_FALLBACK_MAP_FILE_PATH,
})

const address = await server.listen()

mockAgent.enableNetConnect(new URL(address).host)

const response = await server.inject({
method: 'GET',
url: `/${CUSTOM_MAP_PREFIX}/info`,
})

assert.equal(response.statusCode, 404)
assert.match(response.json().error, /Not Found/)
})

test('custom map info endpoint returns server error when custom map is invalid', async (t) => {
const server = setup(t)

const invalidFile = new URL(import.meta.url).pathname

server.register(MapServerPlugin, {
customMapPath: invalidFile,
defaultOnlineStyleUrl: DEFAULT_ONLINE_STYLE_URL,
fallbackMapPath: DEFAULT_FALLBACK_MAP_FILE_PATH,
})

const address = await server.listen()

mockAgent.enableNetConnect(new URL(address).host)

const response = await server.inject({
method: 'GET',
url: `/${CUSTOM_MAP_PREFIX}/info`,
})

assert.equal(response.statusCode, 500)
assert.match(response.json().error, /Internal Server Error/)
})

test('custom map info endpoint returns expected info when valid custom map is available', async (t) => {
const server = setup(t)

const smpStats = await fs.stat(SAMPLE_SMP_FIXTURE_PATH)

server.register(MapServerPlugin, {
customMapPath: SAMPLE_SMP_FIXTURE_PATH,
defaultOnlineStyleUrl: DEFAULT_ONLINE_STYLE_URL,
fallbackMapPath: DEFAULT_FALLBACK_MAP_FILE_PATH,
})

const address = await server.listen()

mockAgent.enableNetConnect(new URL(address).host)

const response = await server.inject({
method: 'GET',
url: `/${CUSTOM_MAP_PREFIX}/info`,
})

assert.equal(response.statusCode, 200)

const info = response.json()

assert.deepEqual(info, {
created: smpStats.ctime.toISOString(),
size: smpStats.size,
name: 'MapLibre',
})
})

/**
* @param {import('node:test').TestContext} t
*/
Expand Down

0 comments on commit 642ba88

Please sign in to comment.