Merge pull request #1 from Coffey-Labs/drop-the-no-game-shim
Stop guessing which board a request means
This commit is contained in:
@@ -79,14 +79,17 @@ accounts has ever been able to offer.
|
||||
Every entry in a post is validated before any of them is written: a party that
|
||||
half-posts is worse than one that does not post at all.
|
||||
|
||||
### The missing-game shim
|
||||
### Every request names its game
|
||||
|
||||
A request with no `game` is treated as `lemonade`. That is a compatibility
|
||||
shim, not a default worth keeping — the deployed lemonade bundle posts no game
|
||||
at all, because when it was built there was only one board, and copies of it
|
||||
are sitting in people's browsers. It can go once that bundle has been rebuilt
|
||||
and redeployed, and not before, or every score set from a cached page lands
|
||||
nowhere.
|
||||
There is no default. A request that does not say which board it wants is
|
||||
refused with the list of boards there are.
|
||||
|
||||
There was briefly a shim that read a missing `game` as `lemonade`, because the
|
||||
deployed lemonade bundle predated this service and posted no game at all. It
|
||||
was removed once that bundle had been rebuilt and was what the site served —
|
||||
verified by reading the asset the public URL actually points at, not the one
|
||||
the origin holds, because there is a CDN in between and the two are not the
|
||||
same claim.
|
||||
|
||||
## The migration
|
||||
|
||||
|
||||
+16
-12
@@ -113,10 +113,10 @@ function readBody(req: IncomingMessage): Promise<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rows go back in the game's own field names, flattened, exactly as its
|
||||
* client already expects them. That is not politeness -- the lemonade stand's
|
||||
* bundle is sitting in people's browsers right now expecting `assets` and
|
||||
* `days`, and it will keep expecting them until it is rebuilt.
|
||||
* Rows go back in the game's own field names, flattened, exactly as its client
|
||||
* expects them. That is the bargain that makes a shared service invisible: a
|
||||
* game asks for its board and gets `assets` and `days`, or `bagged` and
|
||||
* `arrows`, and never has to know it is sharing a table with anybody.
|
||||
*/
|
||||
const shape = (rows: Row[]) =>
|
||||
rows.map((r) => ({ id: r.id, name: r.name, at: r.at, ...r.fields }))
|
||||
@@ -124,14 +124,18 @@ const shape = (rows: Row[]) =>
|
||||
/**
|
||||
* Which game a request is about.
|
||||
*
|
||||
* A missing game means lemonade, and that is a compatibility shim rather than
|
||||
* a default worth keeping: the deployed lemonade client posts no game at all,
|
||||
* because when it was built there was only one board. It can go once that
|
||||
* bundle has been rebuilt and redeployed -- and not before, or every score set
|
||||
* from a cached page lands nowhere.
|
||||
* There was a shim here that read a missing game as lemonade, because the
|
||||
* deployed lemonade bundle predated this service and posted no game at all.
|
||||
* That bundle has been rebuilt and is what the site serves now -- both games
|
||||
* name themselves on every call -- so the shim has been removed and a request
|
||||
* that does not say which board it wants is refused rather than guessed at.
|
||||
*
|
||||
* Guessing was the right thing while there was something to guess for. It is
|
||||
* the wrong thing now: silently filing an unlabelled score under whichever
|
||||
* game happened to be first is the sort of default that is invisible until it
|
||||
* is wrong, and the caller always knows which game it is.
|
||||
*/
|
||||
function gameFor(explicit: unknown): Game | undefined {
|
||||
if (explicit === undefined || explicit === null || explicit === '') return lookup('lemonade')
|
||||
return lookup(explicit)
|
||||
}
|
||||
|
||||
@@ -153,7 +157,7 @@ const server = createServer(async (req, res) => {
|
||||
|
||||
if (path === '/api/scores' && req.method === 'GET') {
|
||||
const game = gameFor(url.searchParams.get('game') ?? undefined)
|
||||
if (!game) return send(res, 404, { error: 'unknown game' })
|
||||
if (!game) return send(res, 404, { error: `unknown game; try one of: ${known().join(', ')}` })
|
||||
return send(res, 200, { game: game.id, scores: shape(store.board(game, BOARD_LIMIT)) })
|
||||
}
|
||||
|
||||
@@ -170,7 +174,7 @@ const server = createServer(async (req, res) => {
|
||||
|
||||
const body = (parsed ?? {}) as { game?: unknown; entries?: unknown }
|
||||
const game = gameFor(body.game)
|
||||
if (!game) return send(res, 404, { error: 'unknown game' })
|
||||
if (!game) return send(res, 404, { error: `unknown game; try one of: ${known().join(', ')}` })
|
||||
|
||||
const list = Array.isArray(parsed) ? parsed : body.entries
|
||||
if (!Array.isArray(list)) return send(res, 400, { error: 'expected an array of entries' })
|
||||
|
||||
@@ -15,6 +15,21 @@ describe('the registry', () => {
|
||||
expect(lookup('doom')).toBeUndefined()
|
||||
expect(lookup(42)).toBeUndefined()
|
||||
})
|
||||
|
||||
/**
|
||||
* There is no default game, and there must not be one.
|
||||
*
|
||||
* A shim used to read a missing game as lemonade, for bundles that predated
|
||||
* this service. Now that nothing posts unlabelled, guessing would be worse
|
||||
* than refusing: it would file somebody's score under whichever game
|
||||
* happened to be first, silently, and nobody would find out until the board
|
||||
* looked wrong. These are the shapes a missing game arrives in.
|
||||
*/
|
||||
it('refuses to guess when nothing says which board', () => {
|
||||
for (const missing of [undefined, null, '', 0, false, {}, []]) {
|
||||
expect(lookup(missing)).toBeUndefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user