From d0ad9c3aec7c159d40206cbfba26c031638b29da Mon Sep 17 00:00:00 2001 From: valadaptive Date: Fri, 2 Feb 2024 21:07:29 -0500 Subject: [PATCH] Move `read` convenience function to files.js --- doc | 2 +- etc/browser/avsc-services.js | 3 +- etc/browser/lib/files.js | 4 +-- lib/files.js | 70 ++++++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/doc b/doc index 5ddd9df9..bebd0480 160000 --- a/doc +++ b/doc @@ -1 +1 @@ -Subproject commit 5ddd9df9cff57d5d61fde6dd39f13f74e1f0a590 +Subproject commit bebd0480b9201978ee6c0f8eb787bdd8325e1b5e diff --git a/etc/browser/avsc-services.js b/etc/browser/avsc-services.js index e2a02d89..a73717a2 100644 --- a/etc/browser/avsc-services.js +++ b/etc/browser/avsc-services.js @@ -7,6 +7,7 @@ */ let avroTypes = require('./avsc-types'), + files = require('../../lib/files'), services = require('../../lib/services'), specs = require('../../lib/specs'), utils = require('../../lib/utils'); @@ -14,7 +15,7 @@ let avroTypes = require('./avsc-types'), /** Slightly enhanced parsing, supporting IDL declarations. */ function parse(any, opts) { - let schemaOrProtocol = specs.read(any); + let schemaOrProtocol = files.readSchemaFromPathOrString(any); return schemaOrProtocol.protocol ? services.Service.forProtocol(schemaOrProtocol, opts) : avroTypes.Type.forSchema(schemaOrProtocol, opts); diff --git a/etc/browser/lib/files.js b/etc/browser/lib/files.js index a170467d..7cbb99f8 100644 --- a/etc/browser/lib/files.js +++ b/etc/browser/lib/files.js @@ -12,10 +12,10 @@ function createSyncImportHook() { return function () { throw createError(); }; } - module.exports = { createImportHook, createSyncImportHook, existsSync: function () { return false; }, - readFileSync: function () { throw createError(); } + readFileSync: function () { throw createError(); }, + readSchemaFromPathOrString: function () { throw createError(); } }; diff --git a/lib/files.js b/lib/files.js index 0ba09d16..4500e8df 100644 --- a/lib/files.js +++ b/lib/files.js @@ -8,7 +8,8 @@ */ let fs = require('fs'), - path = require('path'); + path = require('path'), + specs = require('./specs'); /** Default (asynchronous) file loading function for assembling IDLs. */ function createImportHook() { @@ -52,11 +53,76 @@ function createSyncImportHook() { }; } +/** + * Convenience function to parse multiple inputs into protocols and schemas. + * + * It should cover most basic use-cases but has a few limitations: + * + * + It doesn't allow passing options to the parsing step. + * + The protocol/type inference logic can be deceived. + * + * The parsing logic is as follows: + * + * + If `str` contains `path.sep` (on windows `\`, otherwise `/`) and is a path + * to an existing file, it will first be read as JSON, then as an IDL + * specification if JSON parsing failed. If either succeeds, the result is + * returned, otherwise the next steps are run using the file's content + * instead of the input path. + * + If `str` is a valid JSON string, it is parsed then returned. + * + If `str` is a valid IDL protocol specification, it is parsed and returned + * if no imports are present (and an error is thrown if there are any + * imports). + * + If `str` is a valid IDL type specification, it is parsed and returned. + * + If neither of the above cases apply, `str` is returned. + */ +function readSchemaFromPathOrString(str) { + let schema; + if ( + typeof str == 'string' && + str.indexOf('/') !== -1 && + fs.existsSync(str) + ) { + // Try interpreting `str` as path to a file contain a JSON schema or an IDL + // protocol. Note that we add the second check to skip primitive references + // (e.g. `"int"`, the most common use-case for `avro.parse`). + let contents = fs.readFileSync(str, {encoding: 'utf8'}); + try { + return JSON.parse(contents); + } catch (err) { + let opts = {importHook: createSyncImportHook()}; + specs.assembleProtocol(str, opts, (err, protocolSchema) => { + schema = err ? contents : protocolSchema; + }); + } + } else { + schema = str; + } + if (typeof schema != 'string' || schema === 'null') { + // This last predicate is to allow `read('null')` to work similarly to + // `read('int')` and other primitives (null needs to be handled separately + // since it is also a valid JSON identifier). + return schema; + } + try { + return JSON.parse(schema); + } catch (err) { + try { + return specs.readProtocol(schema); + } catch (err) { + try { + return specs.readSchema(schema); + } catch (err) { + return schema; + } + } + } +} module.exports = { createImportHook, createSyncImportHook, // Proxy a few methods to better shim them for browserify. existsSync: fs.existsSync, - readFileSync: fs.readFileSync + readFileSync: fs.readFileSync, + readSchemaFromPathOrString };