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
2 changes: 2 additions & 0 deletions src/RaftDeviceTypeCatalogue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const site = {
{ label: "main", raftCore: "1.62.0", count: 2 }, { label: "v1.61.0", raftCore: "1.61.0", count: 2 },
{ label: "v1.59.0", raftCore: "1.59.0", count: 1 }] }),
[`${SITE}/v1.61.0/catalogue.json`]: JSON.stringify({ formatVersion: 1, label: "v1.61.0", raftCore: "1.61.0", recordFormat: 1, count: 2,
sets: { common: ["MCP9808"] },
records: [{ key: "LSM6DS3", file: "records/LSM6DS3.json", sha256: "x", summary: { name: "IMU", hasData: true, customCode: false, initCode: false } },
{ key: "MCP9808", file: "records/MCP9808.json", sha256: "y", summary: { name: "Temp", hasData: true, customCode: false, initCode: false } }] }),
[`${SITE}/v1.61.0/DeviceTypeRecords.json`]: '{"devTypes":{"LSM6DS3":{"addresses":"0x6a","m":{"12.5":1,"26":2}},"MCP9808":{"addresses":"0x18"}}}',
Expand All @@ -39,6 +40,7 @@ describe("RaftCore's published catalogue", () => {
const records = await catalogue.records();
expect(writeDevTypeJson(records.records.get("LSM6DS3") ?? null)).toBe('{"addresses":"0x6a","m":{"12.5":1,"26":2}}');
expect(records.raftCore).toBe("1.61.0");
expect(Array.from(records.sets?.entries() ?? [])).toEqual([["common", ["MCP9808"]]]);
expect((await catalogue.recordFile("MCP9808")).text).toContain("0x18");
await expect(catalogue.recordFile("Nope")).rejects.toThrow("no record");
await expect(RaftCoreCatalogue.open(fetchFn, "v9.9.9", SITE)).rejects.toThrow("HTTP 404");
Expand Down
3 changes: 2 additions & 1 deletion src/RaftDeviceTypeCatalogue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export interface DeviceTypeCatalogueInfo {
recordFormat?: number;
count: number;
records: DeviceTypeCatalogueEntry[];
sets?: Record<string, string[]>; // named sets of records ("raftcore:@<set>"), from RaftCore 1.63.0
}

export class RaftCoreCatalogue {
Expand Down Expand Up @@ -238,7 +239,7 @@ export class RaftCoreCatalogue {
throw new DeviceTypeSourceError("RaftCore catalogue: DeviceTypeRecords.json has no devTypes");
this._records = devTypes;
}
return { records: this._records, raftCore: this.raftCore };
return { records: this._records, raftCore: this.raftCore, sets: new Map(Object.entries(this.info.sets ?? {})) };
}

// One record's file, as it is in RaftCore
Expand Down
3 changes: 2 additions & 1 deletion src/RaftDeviceTypeSources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe("selection vectors", () => {
const merged = mergeDeviceTypeSources(Object.entries(vec.files).map(([name, content]) => ({ name, content: content as string })));
const sources = new Map<string, DeviceTypeSourceRecords>();
for (const [name, source] of Object.entries<any>(vec.sources))
sources.set(name, { records: recordsOf(source.records), raftCore: source.kind === "bundled" ? vectorsFile.raftCore : undefined });
sources.set(name, { records: recordsOf(source.records), raftCore: source.kind === "bundled" ? vectorsFile.raftCore : undefined,
sets: source.sets ? new Map(Object.entries<string[]>(source.sets)) : undefined });
return resolveDeviceTypeSelection(merged, vec.section as DeviceTypeSection, sources);
};
if (vec.error !== undefined) {
Expand Down
29 changes: 25 additions & 4 deletions src/RaftDeviceTypeSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface DeviceTypeSourceRecords {
records: Map<string, DevTypeJson>;
commit?: string; // a git source: the commit its ref resolved to
raftCore?: string; // RaftCore's records: its version
sets?: Map<string, string[]>; // RaftCore's records: its named sets (devtypes/sets), selected as "<source>:@<set>"
}

export interface DeviceTypeSelectionResult {
Expand Down Expand Up @@ -188,7 +189,25 @@ export function resolveDeviceTypeSelection(merged: MergedDeviceTypeSources, sect
sources.set(name, { records: loaded.records, isLocal: kind === "path" });
}
const names = Array.from(sources.keys());
const bundled = names.filter((n) => deviceTypeSourceKind(merged.sources.get(n) as DevTypeJsonObject) === "bundled");
const chosen = merged.sections[section];

// The keys of a named set ("<source>:@<set>", RaftCore's records only)
const setKeys = (pattern: string, source: string | undefined, key: string, listName: string): string[] => {
if (source === undefined || !bundled.includes(source))
throw new DeviceTypeSelectionError(`${section}.${listName} "${pattern}": named sets (@...) are only in RaftCore's records - ` +
`use "<the bundled source>:${key}"`);
const sets = sourceRecords.get(source)?.sets ?? new Map<string, string[]>();
const keys = sets.get(key.slice(1));
if (keys === undefined)
throw new DeviceTypeSelectionError(`${section}.${listName} "${pattern}": no set "${key.slice(1)}" in RaftCore's records ` +
`(sets: ${Array.from(sets.keys()).sort().join(", ") || "none"})`);
const records = (sources.get(source) as { records: Map<string, DevTypeJson> }).records;
const missing = keys.filter((k) => !records.has(k));
if (missing.length > 0)
throw new DeviceTypeSelectionError(`set "${key.slice(1)}" names ${missing.join(", ")}, not in RaftCore's records`);
return keys;
};
// builtin includes everything unless it says otherwise; a type file includes nothing unless it says otherwise
const includes = chosen.hasInclude ? chosen.include : (section === "builtin" ? ["*"] : []);

Expand All @@ -200,24 +219,26 @@ export function resolveDeviceTypeSelection(merged: MergedDeviceTypeSources, sect
let matched = false;
for (const name of source ? [source] : names) {
const records = (sources.get(name) as { records: Map<string, DevTypeJson> }).records;
const keys = key === "*" ? Array.from(records.keys()) : (records.has(key) ? [key] : []);
const keys = key.startsWith("@") ? setKeys(pattern, source, key, "include") :
(key === "*" ? Array.from(records.keys()) : (records.has(key) ? [key] : []));
for (const k of keys) {
if (!candidates.has(k))
candidates.set(k, { named: [], any: [] });
const entry = candidates.get(k) as { named: string[]; any: string[] };
addTo(source && key !== "*" ? entry.named : entry.any, name);
addTo(source && key !== "*" && !key.startsWith("@") ? entry.named : entry.any, name);
matched = true;
}
}
if (!matched && key !== "*") {
if (!matched && key !== "*" && !key.startsWith("@")) {
const where = source ? `source "${source}"` : "any source";
throw new DeviceTypeSelectionError(`${section}.include "${pattern}": no record "${key}" in ${where}`);
}
}
for (const pattern of chosen.exclude) {
const [source, key] = parsePattern(pattern, names);
const excluded = key.startsWith("@") ? new Set(setKeys(pattern, source, key, "exclude")) : undefined;
for (const k of Array.from(candidates.keys())) {
if (key === "*" || k === key) {
if (key === "*" || k === key || (excluded !== undefined && excluded.has(k))) {
const entry = candidates.get(k) as { named: string[]; any: string[] };
if (source === undefined) {
entry.named = [];
Expand Down
242 changes: 242 additions & 0 deletions testdata/selection_vectors.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,248 @@
"policyOnly": {},
"messages": [],
"provenance": "{\"formatVersion\":1,\"sources\":{\"raftcore\":{\"bundled\":true,\"raftCore\":\"1.62.0\"}},\"typeFile\":{\"include\":[\"raftcore:Alpha\"],\"exclude\":[\"local:*\"]}}"
},
{
"name": "a named set",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}}, \"builtin\": {\"include\": [\"raftcore:@small\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
}
},
"section": "builtin",
"records": {
"Alpha": {
"source": "raftcore",
"record": "{\"addresses\":\"0x11\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Test\"}}"
},
"Charlie": {
"source": "raftcore",
"record": "{\"addresses\":\"0x12\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Test\"}}"
}
},
"replaced": {},
"policyOnly": {},
"messages": [],
"provenance": "{\"formatVersion\":1,\"sources\":{\"raftcore\":{\"bundled\":true,\"raftCore\":\"1.62.0\"}},\"builtin\":{\"include\":[\"raftcore:@small\"]}}"
},
{
"name": "excluding a named set",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}}, \"builtin\": {\"include\": [\"raftcore:*\"], \"exclude\": [\"raftcore:@one\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
}
},
"section": "builtin",
"records": {
"Alpha": {
"source": "raftcore",
"record": "{\"addresses\":\"0x11\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Test\"}}"
},
"Bravo": {
"source": "raftcore",
"record": "{\"addresses\":\"0x10\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Bravo core\"}}"
}
},
"replaced": {},
"policyOnly": {},
"messages": [],
"provenance": "{\"formatVersion\":1,\"sources\":{\"raftcore\":{\"bundled\":true,\"raftCore\":\"1.62.0\"}},\"builtin\":{\"include\":[\"raftcore:*\"],\"exclude\":[\"raftcore:@one\"]}}"
},
{
"name": "a named set in a type file, with a local copy replacing one record",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}, \"local\": {\"path\": \"../../local\"}}, \"typeFile\": {\"include\": [\"raftcore:@small\", \"local:*\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
},
"local": {
"kind": "path",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Alpha local\"}}}}"
}
},
"section": "typeFile",
"records": {
"Alpha": {
"source": "local",
"record": "{\"addresses\":\"0x11\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Alpha local\"}}"
},
"Charlie": {
"source": "raftcore",
"record": "{\"addresses\":\"0x12\",\"detectionValues\":\"0x00=0x12\",\"devInfoJson\":{\"name\":\"Test\"}}"
}
},
"replaced": {
"Alpha": [
"raftcore"
]
},
"policyOnly": {},
"messages": [
"\"Alpha\" from local replaces the record from raftcore"
],
"provenance": "{\"formatVersion\":1,\"sources\":{\"raftcore\":{\"bundled\":true,\"raftCore\":\"1.62.0\"},\"local\":{\"path\":\"../../local\"}},\"typeFile\":{\"include\":[\"raftcore:@small\",\"local:*\"]}}"
},
{
"name": "error: no such set",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}}, \"builtin\": {\"include\": [\"raftcore:@nope\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
}
},
"section": "builtin",
"error": "builtin.include \"raftcore:@nope\": no set \"nope\" in RaftCore's records (sets: one, small, stale)"
},
{
"name": "error: a set naming a record the source hasn't",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}}, \"builtin\": {\"include\": [\"raftcore:@stale\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
}
},
"section": "builtin",
"error": "set \"stale\" names Zulu, not in RaftCore's records"
},
{
"name": "error: a set without a source",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}}, \"builtin\": {\"include\": [\"@small\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
}
},
"section": "builtin",
"error": "builtin.include \"@small\": named sets (@...) are only in RaftCore's records - use \"<the bundled source>:@small\""
},
{
"name": "error: a set from a path source",
"files": {
"systypes/Common/DeviceTypeSources.json": "{\"sources\": {\"raftcore\": {\"bundled\": true}, \"local\": {\"path\": \"../../local\"}}, \"builtin\": {\"include\": [\"local:@small\"]}}"
},
"sources": {
"raftcore": {
"kind": "bundled",
"records": "{\"devTypes\": {\"Alpha\": {\"addresses\": \"0x11\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Bravo\": {\"addresses\": \"0x10\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Bravo core\"}}, \"Charlie\": {\"addresses\": \"0x12\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}",
"sets": {
"small": [
"Alpha",
"Charlie"
],
"one": [
"Charlie"
],
"stale": [
"Alpha",
"Zulu"
]
}
},
"local": {
"kind": "path",
"records": "{\"devTypes\": {\"Delta\": {\"addresses\": \"0x13\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}, \"Echo\": {\"addresses\": \"0x14\", \"detectionValues\": \"0x00=0x12\", \"devInfoJson\": {\"name\": \"Test\"}}}}"
}
},
"section": "builtin",
"error": "builtin.include \"local:@small\": named sets (@...) are only in RaftCore's records - use \"<the bundled source>:@small\""
}
]
}
Loading