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
1 change: 1 addition & 0 deletions data/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ below is also machine-enforced by [`places.schema.json`](places.schema.json), wh
**Fields:**
- `id`: `<city-prefix>-<type>-<number>`. Prefix is a short slug for the city (e.g. `mum`, `thane`, `navi`, `ldn`, `jkt`). Increment from the highest existing number in the file for that prefix.
- `city`: lowercase, underscore-separated slug (e.g. `mumbai`, `navi_mumbai`, `jakarta`). Any city worldwide is welcome, not just Mumbai/Thane/Navi Mumbai.
- `country`: optional for now. The place's ISO 3166-1 alpha-2 country code in capitals (e.g. `IN`, `GB`, `KE`), the same codes competitions use. Lowercase or a country name fails `npm run validate`. Existing records are being backfilled, after which it becomes required (#288).
- `type`: must match the filename exactly
- `lat`/`lng`: from Google Maps (right-click pin, "What's here?"). Any real-world coordinate works: `places.schema.json` checks latitude is between -90 and 90 and longitude between -180 and 180.
- `address`: optional, short, human-readable
Expand Down
5 changes: 5 additions & 0 deletions data/places.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
"pattern": "^[a-z0-9]+(_[a-z0-9]+)*$",
"description": "Lowercase, underscore-separated slug, e.g. \"mumbai\", \"navi_mumbai\". Any city worldwide is welcome, not a fixed enum."
},
"country": {
"type": "string",
"pattern": "^[A-Z]{2}$",
"description": "Optional for now. ISO 3166-1 alpha-2 country code in capitals, e.g. \"IN\", \"GB\". Competitions already use the same codes."
},
"lat": {
"type": "number",
"minimum": -90,
Expand Down
5 changes: 5 additions & 0 deletions scripts/lib/data-validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,8 @@ export class Reporter {
this.totalWarnings++;
}
}

/** True when `value` is an ISO 3166-1 alpha-2 style country code: exactly two capital letters. */
export function isCountryCode(value) {
return typeof value === "string" && /^[A-Z]{2}$/.test(value);
}
10 changes: 10 additions & 0 deletions scripts/validate-places.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* - type is one of the schema's enum values
* - type matches the filename it lives in
* - city is a lowercase, underscore-separated slug
* - country, when present, is a two-letter upper-case ISO 3166-1 code
* - id matches the <city-prefix>-<type>-<number> convention, or is a bare
* numeric id (the College Board SAT centre code convention)
* - lat/lng are numbers within the schema's min/max bounds
Expand All @@ -29,6 +30,7 @@ import { fileURLToPath } from "url";
import {
Reporter,
hasEmDash,
isCountryCode,
isLatLngPlaceholderGmapsLink,
isRealIsoDate,
} from "./lib/data-validation.mjs";
Expand Down Expand Up @@ -173,6 +175,14 @@ for (const file of files) {
);
}

// country, when present, is an upper-case ISO 3166-1 alpha-2 code (see #288)
if (r.country !== undefined && !isCountryCode(r.country)) {
err(
loc,
`country must be a two-letter upper-case ISO 3166-1 alpha-2 code like "IN", got ${JSON.stringify(r.country)}`,
);
}

// valid_till format, when present
if (r.valid_till !== undefined && !isRealIsoDate(r.valid_till)) {
err(loc, `valid_till must be a real ISO date (YYYY-MM-DD), got "${r.valid_till}"`);
Expand Down
27 changes: 27 additions & 0 deletions src/lib/country-code.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";

import { isCountryCode } from "../../scripts/lib/data-validation.mjs";

describe("isCountryCode", () => {
it("accepts two capital letters", () => {
for (const code of ["IN", "GB", "KE", "US"]) expect(isCountryCode(code)).toBe(true);
});

it("rejects names, lower case, wrong length and non-strings", () => {
for (const value of [
"india",
"in",
"In",
"IND",
"I",
"",
"I1",
"I N",
undefined,
null,
7,
]) {
expect(isCountryCode(value), String(value)).toBe(false);
}
});
});
2 changes: 2 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ export interface Place {
name: string;
type: PlaceType;
city: City;
/** ISO 3166-1 alpha-2 country code in capitals (e.g. "IN"). Optional while records are backfilled (#288). */
country?: string;
lat: number;
lng: number;
address?: string;
Expand Down
Loading