Skip to content

Commit aa52187

Browse files
authored
Merge pull request #15 from hed-standard/write-schema-loader-as-class
Write schema loader as class
2 parents bec4f51 + 70e783a commit aa52187

File tree

8 files changed

+307
-320
lines changed

8 files changed

+307
-320
lines changed

browser/src/schema/init.js

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,7 @@
44
* This file is a copy of the original `src/schema/init.js` with the import path for the schema loader modified
55
* to point to the browser-specific version.
66
*/
7-
import zip from 'lodash/zip'
8-
9-
import { loadSchema } from './loader'
10-
import SchemaParser from '../../../src/schema/parser'
11-
import PartneredSchemaMerger from '../../../src/schema/schemaMerger'
12-
import { Schema, HedSchemas } from '../../../src/schema/containers'
13-
import { IssueError } from '../../../src/issues/issues'
14-
import { splitStringTrimAndRemoveBlanks } from '../../../src/utils/string'
15-
import { SchemasSpec } from '../../../src/schema/specs'
16-
17-
/**
18-
* Build a single schema container object from an XML file.
19-
*
20-
* @param {Object} xmlData The schema's XML data
21-
* @returns {HedSchema} The HED schema object.
22-
*/
23-
const buildSchemaObject = function (xmlData) {
24-
const schemaEntries = new SchemaParser(xmlData.HED).parse()
25-
return new Schema(xmlData, schemaEntries)
26-
}
27-
28-
/**
29-
* Build a single merged schema container object from one or more XML files.
30-
*
31-
* @param {Object[]} xmlData The schemas' XML data.
32-
* @returns {HedSchema} The HED schema object.
33-
*/
34-
const buildSchemaObjects = function (xmlData) {
35-
const schemas = xmlData.map((data) => buildSchemaObject(data))
36-
if (schemas.length === 1) {
37-
return schemas[0]
38-
}
39-
const partneredSchemaMerger = new PartneredSchemaMerger(schemas)
40-
return partneredSchemaMerger.mergeSchemas()
41-
}
42-
43-
/**
44-
* Build a schema collection object from a schema specification.
45-
*
46-
* @param {SchemasSpec} schemaSpecs The description of which schemas to use.
47-
* @returns {Promise<HedSchemas>} The schema container object and any issues found.
48-
*/
49-
export async function buildSchemas(schemaSpecs) {
50-
const schemaPrefixes = Array.from(schemaSpecs.data.keys())
51-
/* Data format example:
52-
* [[xmlData, ...], [xmlData, xmlData, ...], ...] */
53-
const schemaXmlData = await Promise.all(
54-
schemaPrefixes.map((prefix) => {
55-
const specs = schemaSpecs.data.get(prefix)
56-
return Promise.all(specs.map((spec) => loadSchema(spec)))
57-
}),
58-
)
59-
const schemaObjects = schemaXmlData.map(buildSchemaObjects)
60-
const schemas = new Map(zip(schemaPrefixes, schemaObjects))
61-
return new HedSchemas(schemas)
62-
}
7+
import HedSchemaLoader from './loader'
638

649
/**
6510
* Build HED schemas from a version specification string.
@@ -69,8 +14,5 @@ export async function buildSchemas(schemaSpecs) {
6914
* @throws {IssueError} If the schema specification is invalid or schemas cannot be built.
7015
*/
7116
export async function buildSchemasFromVersion(hedVersionString) {
72-
hedVersionString ??= ''
73-
const hedVersionSpecStrings = splitStringTrimAndRemoveBlanks(hedVersionString, ',')
74-
const hedVersionSpecs = SchemasSpec.parseVersionSpecs(hedVersionSpecStrings)
75-
return buildSchemas(hedVersionSpecs)
17+
return new HedSchemaLoader().buildSchemasFromVersion(hedVersionString)
7618
}

browser/src/schema/loader.js

Lines changed: 46 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,66 @@
11
/** HED schema loading functions. */
22

33
/* Imports */
4-
import { IssueError } from '../../../src/issues/issues'
5-
import parseSchemaXML from '../../../src/utils/xml.js'
64
import { schemaData } from './vite-importer'
5+
import { IssueError } from '../../../src/issues/issues'
6+
import AbstractHedSchemaLoader from '../../../src/schema/abstractLoader'
77

8-
/**
9-
* Load schema XML data from a schema version or path description.
10-
*
11-
* @param {SchemaSpec} schemaDef The description of which schema to use.
12-
* @returns {Promise<object>} The schema XML data.
13-
* @throws {IssueError} If the schema could not be loaded.
14-
*/
15-
export async function loadSchema(schemaDef = null) {
16-
const xmlData = await loadPromise(schemaDef)
17-
if (xmlData === null) {
18-
IssueError.generateAndThrow('invalidSchemaSpecification', { spec: JSON.stringify(schemaDef) })
8+
export default class HedSchemaLoader extends AbstractHedSchemaLoader {
9+
/**
10+
* Load schema XML data from a local file.
11+
*
12+
* @param {string} path - The path to the schema XML data.
13+
* @returns {Promise<never>} The schema XML data.
14+
* @throws {IssueError} If the schema could not be loaded.
15+
* @override
16+
*/
17+
async loadLocalSchema(path) {
18+
IssueError.generateAndThrow('localSchemaLoadFailed', {
19+
path,
20+
error: 'Local schema loading is not supported in the browser.',
21+
})
1922
}
20-
return xmlData
21-
}
2223

23-
/**
24-
* Choose the schema Promise from a schema version or path description.
25-
*
26-
* @param {SchemaSpec} schemaDef The description of which schema to use.
27-
* @returns {Promise<object>} The schema XML data.
28-
* @throws {IssueError} If the schema could not be loaded.
29-
*/
30-
async function loadPromise(schemaDef) {
31-
if (schemaDef === null) {
32-
return null
33-
} else if (schemaDef.localPath) {
34-
return loadLocalSchema(schemaDef.localPath)
35-
} else if (schemaDef.localName) {
36-
return loadBundledSchema(schemaDef)
37-
} else {
38-
return loadRemoteSchema(schemaDef)
24+
/**
25+
* Determine whether this validator bundles a particular schema.
26+
*
27+
* @param {SchemaSpec} schemaDef - The description of which schema to use.
28+
* @returns {boolean} Whether this validator bundles a particular schema.
29+
* @override
30+
*/
31+
hasBundledSchema(schemaDef) {
32+
const localPath = `../../../src/data/schemas/${schemaDef.localName}.xml`
33+
return localPath in schemaData
3934
}
40-
}
4135

42-
/**
43-
* Load schema XML data from the HED GitHub repository.
44-
*
45-
* @param {SchemaSpec} schemaDef The standard schema version to load.
46-
* @returns {Promise<object>} The schema XML data.
47-
* @throws {IssueError} If the schema could not be loaded.
48-
*/
49-
function loadRemoteSchema(schemaDef) {
50-
let url
51-
if (schemaDef.library) {
52-
url = `https://raw.githubusercontent.com/hed-standard/hed-schemas/refs/heads/main/library_schemas/${schemaDef.library}/hedxml/HED_${schemaDef.library}_${schemaDef.version}.xml`
53-
} else {
54-
url = `https://raw.githubusercontent.com/hed-standard/hed-schemas/refs/heads/main/standard_schema/hedxml/HED${schemaDef.version}.xml`
36+
/**
37+
* Retrieve the contents of a bundled schema.
38+
*
39+
* @param {SchemaSpec} schemaDef - The description of which schema to use.
40+
* @returns {Promise<string>} The raw schema XML data.
41+
* @throws {IssueError} If the schema could not be loaded.
42+
* @override
43+
*/
44+
async getBundledSchema(schemaDef) {
45+
const localPath = `../../../src/data/schemas/${schemaDef.localName}.xml`
46+
const schemaLoader = schemaData[localPath]
47+
if (!schemaLoader) {
48+
// We've already verified this exists, so this is a consistency error.
49+
IssueError.generateAndThrowInternalError('Schema loader has disappeared after already being checked')
50+
}
51+
return await schemaLoader()
5552
}
56-
return loadSchemaFile(
57-
fetch(url).then((res) => res.text()),
58-
'remoteSchemaLoadFailed',
59-
{ spec: JSON.stringify(schemaDef) },
60-
)
6153
}
6254

6355
/**
64-
* Load schema XML data from a local file.
56+
* Load schema XML data from a schema version or path description.
6557
*
66-
* @param {string} path The path to the schema XML data.
67-
* @returns {Promise<object>} The schema XML data.
68-
* @throws {IssueError} If the schema could not be loaded.
69-
*/
70-
function loadLocalSchema(path) {
71-
throw new Error('Local schema loading is not supported in the browser.')
72-
}
73-
74-
/**
75-
* Load schema XML data from a bundled file.
58+
* @deprecated
7659
*
7760
* @param {SchemaSpec} schemaDef The description of which schema to use.
7861
* @returns {Promise<object>} The schema XML data.
7962
* @throws {IssueError} If the schema could not be loaded.
8063
*/
81-
async function loadBundledSchema(schemaDef) {
82-
const localPath = `../../../src/data/schemas/${schemaDef.localName}.xml`
83-
const schemaLoader = schemaData[localPath]
84-
if (!schemaLoader) {
85-
// This occurs in the test environment or if the schema is not found.
86-
return
87-
}
88-
try {
89-
const data = await schemaLoader()
90-
return parseSchemaXML(data)
91-
} catch (error) {
92-
const issueArgs = { spec: JSON.stringify(schemaDef), error: error.message }
93-
IssueError.generateAndThrow('bundledSchemaLoadFailed', issueArgs)
94-
}
95-
}
96-
97-
/**
98-
* Actually load the schema XML file.
99-
*
100-
* @param {Promise<string>} xmlDataPromise The Promise containing the unparsed XML data.
101-
* @param {string} issueCode The issue code.
102-
* @param {Object<string, string>} issueArgs The issue arguments passed from the calling function.
103-
*returns {Promise<object>} The parsed schema XML data.
104-
* @throws {IssueError} If the schema could not be loaded.
105-
*/
106-
async function loadSchemaFile(xmlDataPromise, issueCode, issueArgs) {
107-
try {
108-
const data = await xmlDataPromise
109-
return parseSchemaXML(data)
110-
} catch (error) {
111-
issueArgs.error = error.message
112-
IssueError.generateAndThrow(issueCode, issueArgs)
113-
}
64+
export async function loadSchema(schemaDef = null) {
65+
return new HedSchemaLoader().loadSchema(schemaDef)
11466
}

browser/src/schema/loader.spec.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
import { describe, it, expect } from 'vitest'
1+
import { describe, it, expect, vi } from 'vitest'
22
import { loadSchema } from './loader'
33
import { SchemaSpec } from '../../../src/schema/specs.ts'
44

5+
vi.mock(import('./vite-importer.js'), () => {
6+
return {
7+
schemaData: {
8+
'../../../src/data/schemas/HED_HED8.0.0_8.0.0.xml': () =>
9+
Promise.resolve('<?xml version="1.0" ?>\n<HED><schema></schema></HED>\n'),
10+
},
11+
}
12+
})
13+
514
describe('Browser Schema Loader', () => {
6-
it('should return undefined when loading a bundled schema in test environment', async () => {
15+
it('should return a parsed XML object when loading a dummy bundled schema in test environment', async () => {
716
// Create a spec with localName to trigger bundled schema loading
817
const spec = new SchemaSpec('', '8.0.0', 'HED8.0.0', '')
9-
// In a test environment, this will return undefined since schemaData is empty
1018
const schema = await loadSchema(spec)
11-
expect(schema).toBeUndefined()
19+
expect(schema).toHaveProperty('HED')
1220
})
1321

1422
it('should throw an error for local file loading', async () => {

src/issues/issues.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ export class IssueError extends Error {
5858
}
5959

6060
/**
61-
* Generate and re-throw an error according to {@code generateFn}.
61+
* Generate and re-throw an error according to `generateFn`.
6262
*
6363
* @remarks
64-
* This method will pass {@code error} to {@code generateFn}, unless it is not an {@link Error} object (in which case,
65-
* it will generate an internal error via {@link IssueError.generateAndThrowInternalError}). {@code generateFn} is
64+
* This method will pass `error` to `generateFn`, unless it is not an {@link Error} object (in which case,
65+
* it will generate an internal error via {@link IssueError.generateAndThrowInternalError}). generateFn` is
6666
* expected to return an internal issue code and parameter object, which will be passed to
6767
* {@link IssueError.generateAndThrow}. Failure to provide an internal issue code will also result in an internal error.
6868
*
69-
* @param error This is expected to be an object of {@code Error} or a subclass.
69+
* @param error This is expected to be an object of {@link Error} or a subclass.
7070
* @param generateFn A function which is passed the screened error object and returns an internal error code and parameter object.
7171
* @param illegalErrorTypeMessage A message to be used for an illegal error type.
7272
*/

0 commit comments

Comments
 (0)