diff --git a/src/render/index.js b/src/render/index.js index 7ab1b20..3ae5221 100644 --- a/src/render/index.js +++ b/src/render/index.js @@ -64,6 +64,24 @@ const getFormat = (input) => { } }; +/** + * Validate MathML string. + * + * @param {string} math - The input MathML string to validate. + * @param {Promise} conversionPromise - The MathJax conversion function to use for validation. + * @returns {Promise} The MathJax conversion promise + * @throws {Error} Throws an error if input string is not valid MathML. + */ +const validateMathML = async (math, conversionPromise) => { + const p = conversionPromise(math); + try { + await p; + } catch(err) { + throw new Error(`Invalid MathML: ${err.message}`); + } + return p; +} + /** * Typeset math. * @@ -85,7 +103,10 @@ const typeset = async (data) => { throw new Error(`Supported output formats for ${data.format} input are: MathML, SVG`); case 'MathML': if (data.svg) { - return MathJax.mathml2svgPromise(data.math); + return await validateMathML(data.math, MathJax.mathml2svgPromise); + } + if (data.mml) { + return await validateMathML(data.math, MathJax.mathml2mmlPromise); } throw new Error(`Supported output formats for ${data.format} input are: SVG`); default: @@ -116,11 +137,6 @@ const typeset = async (data) => { * @returns {Promise} */ exports.render = async (event) => { - if (event.input === 'mathml' && event.output === 'mathml') { - // No-op conversion MathML-to-MathML. - return { contentType: RESPONSE_TYPES.mathml, data: event.source }; - } - const format = getFormat(event); const math = event.source; switch (event.output) { diff --git a/test/app.js b/test/app.js index 1a52629..62ee31e 100644 --- a/test/app.js +++ b/test/app.js @@ -90,6 +90,26 @@ describe('GET /render', function () { .expect(400, { message: 'Invalid output: INVALID' }) .expect('Content-Type', /^application\/json(?:;|$)/); }); + + it('should return "400 Bad Request" with invalid MathML source (xml validation error)', function () { + const search = { input: 'mathml', output: 'mathml', source: 'x^2' }; + const url = `/render?${querystring.stringify(search)}`; + + return testApp + .get(url) + .expect(400, { message: 'Invalid MathML: MathML must be formed by a element, not <#text>' }) + .expect('Content-Type', /^application\/json(?:;|$)/); + }); + + it('should return "400 Bad Request" with invalid MathML source (MathML validation error)', function () { + const search = { input: 'mathml', output: 'mathml', source: 'x^2' }; + const url = `/render?${querystring.stringify(search)}`; + + return testApp + .get(url) + .expect(400, { message: 'Invalid MathML: Unexpected text node "x^2"' }) + .expect('Content-Type', /^application\/json(?:;|$)/); + }); }); describe('POST /render', function () { @@ -157,4 +177,26 @@ describe('POST /render', function () { .expect(400, { message: 'Invalid output: INVALID' }) .expect('Content-Type', /^application\/json(?:;|$)/); }); + + it('should return "400 Bad Request" with invalid MathML source (xml validation error)', function () { + const body = { input: 'mathml', output: 'mathml', source: 'x^2' }; + + return testApp + .post('/render') + .set('Content-Type', 'application/json') + .send(body) + .expect(400, { message: 'Invalid MathML: MathML must be formed by a element, not <#text>' }) + .expect('Content-Type', /^application\/json(?:;|$)/); + }); + + it('should return "400 Bad Request" with invalid MathML source (MathML validation error)', function () { + const body = { input: 'mathml', output: 'mathml', source: 'x^2' }; + + return testApp + .post('/render') + .set('Content-Type', 'application/json') + .send(body) + .expect(400, { message: 'Invalid MathML: Unexpected text node "x^2"' }) + .expect('Content-Type', /^application\/json(?:;|$)/); + }); }); diff --git a/test/render/index.js b/test/render/index.js index e0616e4..fdc280c 100644 --- a/test/render/index.js +++ b/test/render/index.js @@ -90,7 +90,7 @@ describe('index#render', function () { } }); - it('should throw with invalid source', async function () { + it('should throw with invalid xml source', async function () { const event = { input: 'mathml', output: 'svg', source: 'x^2' }; try { @@ -99,7 +99,21 @@ describe('index#render', function () { } catch (err) { expect(err).to.be.an.instanceOf(Error) .that.has.property('message') - .that.contains('MathML must be formed by a element, not <#text>') + .that.contains('Invalid MathML: MathML must be formed by a element, not <#text>') + .and.that.does.not.contain('\n'); + } + }); + + it('should throw with valid xml but invalid MathML source', async function () { + const event = { input: 'mathml', output: 'svg', source: 'x^2' }; + + try { + await render(event); + expect.fail('Did not throw'); + } catch (err) { + expect(err).to.be.an.instanceOf(Error) + .that.has.property('message') + .that.contains('Invalid MathML: Unexpected text node "x^2"') .and.that.does.not.contain('\n'); } });