diff --git a/packages/apib-serializer/CHANGELOG.md b/packages/apib-serializer/CHANGELOG.md index 63c2a4d2d..69d4ffbac 100644 --- a/packages/apib-serializer/CHANGELOG.md +++ b/packages/apib-serializer/CHANGELOG.md @@ -1,5 +1,11 @@ # API Elements: API Blueprint Serializer Changelog +## 0.16.2 (2020-08-31) + +### Enhancements + +- Enable synchronous serialization. + ## 0.16.1 (2020-08-06) Adds compatibility for @apielements/core 0.2.0. diff --git a/packages/apib-serializer/README.md b/packages/apib-serializer/README.md index 66ae062cb..50c65c06f 100644 --- a/packages/apib-serializer/README.md +++ b/packages/apib-serializer/README.md @@ -13,6 +13,8 @@ $ npm install @apielements/apib-serializer ## Usage +### Async + ```js import fury from 'fury'; import apibSerializer from '@apielements/apib-serializer'; @@ -20,7 +22,24 @@ import apibSerializer from '@apielements/apib-serializer'; fury.use(apibSerializer); // Assume `api` is a Minim element instance, e.g. from `fury.parse(...)` -fury.serialize({api}, (err, content) => { +fury.serialize({ api }, (err, content) => { fs.write('serialized.apib', content, 'utf8'); }); ``` + +### Sync + +```js +import fury from 'fury'; +import apibSerializer from '@apielements/apib-serializer'; + +fury.use(apibSerializer); + +try { + // Assume `api` is a Minim element instance, e.g. from `fury.parse(...)` + const content = fury.serializeSync({ api }); + fs.write('serialized.apib', content, 'utf8'); +} catch (error) { + console.log(error); +} +``` diff --git a/packages/apib-serializer/lib/adapter.js b/packages/apib-serializer/lib/adapter.js index 48a178076..f7627e1cf 100644 --- a/packages/apib-serializer/lib/adapter.js +++ b/packages/apib-serializer/lib/adapter.js @@ -29,6 +29,12 @@ const mediaTypes = [ /* * Serialize an API into API Blueprint. */ +function filterExtraSpacing(apib) { + const result = apib.trim().replace(/\n\s*\n\s*\n/g, '\n\n'); + + return `${result}\n`; +} + function serialize({ api }) { return new Promise((resolve, reject) => { nunjucks.render('template.nunjucks', { api }, (err, apib) => { @@ -36,11 +42,17 @@ function serialize({ api }) { return reject(err); } - // Attempt to filter out extra spacing - const result = apib.trim().replace(/\n\s*\n\s*\n/g, '\n\n'); - return resolve(`${result}\n`); + return resolve(filterExtraSpacing(apib)); }); }); } -module.exports = { name, mediaTypes, serialize }; +function serializeSync({ api }) { + const apib = nunjucks.render('template.nunjucks', { api }); + + return filterExtraSpacing(apib); +} + +module.exports = { + name, mediaTypes, serialize, serializeSync, +}; diff --git a/packages/apib-serializer/package.json b/packages/apib-serializer/package.json index f41d51754..3d2138b31 100644 --- a/packages/apib-serializer/package.json +++ b/packages/apib-serializer/package.json @@ -1,6 +1,6 @@ { "name": "@apielements/apib-serializer", - "version": "0.16.1", + "version": "0.16.2", "description": "API Blueprint serializer for API Elements", "author": "Apiary.io ", "license": "MIT", diff --git a/packages/apib-serializer/test/adapter-test.js b/packages/apib-serializer/test/adapter-test.js index 775f14445..cef2bfb48 100644 --- a/packages/apib-serializer/test/adapter-test.js +++ b/packages/apib-serializer/test/adapter-test.js @@ -23,7 +23,7 @@ describe('API Blueprint serializer adapter', () => { files.forEach((file) => { const apib = `${file.substr(0, file.length - 4)}apib`; - it(`serializes ${path.basename(file)}`, (done) => { + it(`serializes ${path.basename(file)} asynchronously`, (done) => { let serializedRefract; let expectedBlueprint; let api; @@ -47,6 +47,17 @@ describe('API Blueprint serializer adapter', () => { return done(); }); }); + + it(`serializes ${path.basename(file)} synchronously`, () => { + const serializedRefract = require(file); + const expectedBlueprint = fs.readFileSync(apib, 'utf-8'); + + const parseResult = fury.load(serializedRefract); + const { api } = parseResult; + + const serialized = fury.serializeSync({ api }); + expect(serialized).to.deep.equal(expectedBlueprint); + }); }); }); diff --git a/packages/cli/package.json b/packages/cli/package.json index c0e2daee9..9c9a4b84a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,7 +24,7 @@ "dependencies": { "@apielements/apiaryb-parser": "^0.2.1", "@apielements/apib-parser": "^0.20.1", - "@apielements/apib-serializer": "^0.16.1", + "@apielements/apib-serializer": "^0.16.2", "@apielements/core": ">=0.1.0 <0.3.0", "@apielements/openapi2-parser": "^0.32.3", "@apielements/openapi3-parser": "^0.15.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index dc2f1e307..a9c9a2a4f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # API Elements: Core +## 0.2.1 (2020-08-27) + +### Enhancements + +Added `serializeSync` method to Fury. + ## 0.2.0 (2020-08-05) This package updates the version of `api-elements` being used. See diff --git a/packages/core/README.md b/packages/core/README.md index 2f718c5ad..0126907f7 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -137,7 +137,7 @@ export const mediaTypes = { ``` -Adapters are made up of a name, a list of media types, and up to three optional public functions: `detect`, `parse`, and `serialize`. A simple example might look like this: +Adapters are made up of a name, a list of media types, and up to four optional public functions: `detect`, `parse`, `serialize` and `serializeSync`. A simple example might look like this: ```js export const name = 'my-adapter'; @@ -176,7 +176,14 @@ export async function serialize({api, mediaType, minim}) { return outputString; } -export default {name, mediaTypes, detect, parse, serialize}; +export function serializeSync({api, mediaType, minim}) { + // Here you convert `api` from javascript element objects to the serialized + // source format. + // ... + return outputString; +} + +export default {name, mediaTypes, detect, parse, serialize, serializeSync}; ``` Now you can register your adapter with Fury.js: diff --git a/packages/core/lib/fury.js b/packages/core/lib/fury.js index d3bb3b5b3..f4086065e 100644 --- a/packages/core/lib/fury.js +++ b/packages/core/lib/fury.js @@ -4,7 +4,7 @@ const minim = new Namespace(); /* * Find an adapter by a given media type and method name, which should be - * either `parse` or `serialize`. If no adapter is found, then + * either `parse`, `serialize` or `serializeSync`. If no adapter is found, then * undefined is returned. */ const findAdapter = (adapters, mediaType, method) => { @@ -80,6 +80,19 @@ const findAdapter = (adapters, mediaType, method) => { * @memberof FuryAdapter */ +/** + * @function serializeSync + * + * @param {Object} options + * @param {Category} options.api + * @param {Namespace} options.namespace + * + * @returns {String} + * @throws {Error} error + * + * @memberof FuryAdapter + */ + /** */ class Fury { @@ -244,6 +257,28 @@ class Fury { return promise; } + /** + * Synchronously serialize an API Description into the given output format. + * + * @param {Object} options + * @param {Category} options.api + * @param {string} [options.mediaType] + */ + serializeSync({ api, mediaType = 'text/vnd.apiblueprint' }) { + const adapter = findAdapter(this.adapters, mediaType, 'serializeSync'); + + if (!adapter) { + throw new Error('Media type did not match any registered serializer!'); + } + + if (!api) { + // eslint-disable-next-line no-param-reassign + api = new this.minim.elements.Category(); + } + + return adapter.serializeSync({ api, namespace: this.minim, mediaType }); + } + /** * @callback SerializeCallback * @@ -252,7 +287,7 @@ class Fury { */ /** - * Serialize an API Description into the given output format. + * Asynchronously serialize an API Description into the given output format. * * @param {Object} options * @param {Category} options.api diff --git a/packages/core/package.json b/packages/core/package.json index 0ab12f172..3bfb1ce77 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@apielements/core", - "version": "0.2.0", + "version": "0.2.1", "description": "API Description SDK", "author": "Apiary.io ", "license": "MIT", diff --git a/packages/core/test/serialize-test.js b/packages/core/test/serialize-test.js index 4444e01aa..3ab9b3b11 100644 --- a/packages/core/test/serialize-test.js +++ b/packages/core/test/serialize-test.js @@ -108,4 +108,42 @@ describe('Serialize', () => { expect(result).to.equal('{"element":"category"}'); }); }); + + describe('using serializeSync', () => { + it('errors with unknown mediaType', () => { + const fury = new Fury(); + const api = new fury.minim.elements.Category(); + + expect(() => { + fury.serializeSync({ api, mediaType: 'application/unregistered' }); + }).to.throw('Media type did not match any registered serializer!'); + }); + + it('can serialize undefined `api` by creating default category', async () => { + const fury = new Fury(); + fury.use({ + name: 'json', + mediaTypes: ['application/json'], + serializeSync: ({ api, namespace }) => JSON.stringify(namespace.serialiser.serialise(api)), + }); + + expect( + fury.serializeSync({ api: undefined, mediaType: 'application/json' }) + ).to.equal('{"element":"category"}'); + }); + + it('can serialize with matching adapter', async () => { + const fury = new Fury(); + fury.use({ + name: 'json', + mediaTypes: ['application/json'], + serializeSync: ({ api, namespace }) => JSON.stringify(namespace.serialiser.serialise(api)), + }); + + const api = new fury.minim.elements.Category(); + + const result = fury.serializeSync({ api, mediaType: 'application/json' }); + expect(result).to.equal('{"element":"category"}'); + }); + }); }); diff --git a/packages/form-serializer/CHANGELOG.md b/packages/form-serializer/CHANGELOG.md index 9d6bfea33..783294460 100644 --- a/packages/form-serializer/CHANGELOG.md +++ b/packages/form-serializer/CHANGELOG.md @@ -1,5 +1,11 @@ # Form Serializer Changelog +## 0.1.3 (2020-08-27) + +### Enhancements + +- Enable synchronous serialization. + ## 0.1.2 (2020-08-19) ### Enhancements diff --git a/packages/form-serializer/README.md b/packages/form-serializer/README.md index 80ef8c0c0..bcd8ec99c 100644 --- a/packages/form-serializer/README.md +++ b/packages/form-serializer/README.md @@ -2,12 +2,44 @@ ## Usage +### Async + ```js +const { Fury } = require('@apielements/core'); const formSerializer = require('@apielements/form-serializer'); + +const fury = new Fury(); fury.use(formSerializer); +const api = new namespace.elements.DataStructure( + new namespace.elements.String('Hello world') +); const mediaType = 'multipart/form-data'; fury.serialize({ api, mediatype }, (error, body) => { console.log(body); + // --BOUNDARY\r\nContent-Disposition: form-data; name="undefined"\r\n\r\nHello world\r\n--BOUNDARY--\r\n }); ``` + +### Sync + +```js +const { Fury } = require('@apielements/core'); +const formSerializer = require('@apielements/form-serializer'); + +const fury = new Fury(); +fury.use(formSerializer); + +const api = new namespace.elements.DataStructure( + new namespace.elements.String('Hello world') +); +const mediaType = 'multipart/form-data'; +try { + const body = fury.serializeSync({ api, mediatype }); + console.log(body); + // --BOUNDARY\r\nContent-Disposition: form-data; name="undefined"\r\n\r\nHello world\r\n--BOUNDARY--\r\n +} catch (error) { + console.log(error); + // Media type did not match any registered serializer! +} +``` diff --git a/packages/form-serializer/lib/adapter.js b/packages/form-serializer/lib/adapter.js index 67d52a79a..9ba540255 100644 --- a/packages/form-serializer/lib/adapter.js +++ b/packages/form-serializer/lib/adapter.js @@ -7,4 +7,10 @@ function serialize({ api, mediaType }) { return new Promise(resolve => resolve(serializeForm({ api, mediaType }))); } -module.exports = { name, mediaTypes, serialize }; +function serializeSync({ api, mediaType }) { + return serializeForm({ api, mediaType }); +} + +module.exports = { + name, mediaTypes, serialize, serializeSync, +}; diff --git a/packages/form-serializer/package.json b/packages/form-serializer/package.json index 17e617e24..e36de9ff4 100644 --- a/packages/form-serializer/package.json +++ b/packages/form-serializer/package.json @@ -1,6 +1,6 @@ { "name": "@apielements/form-serializer", - "version": "0.1.2", + "version": "0.1.3", "description": "Multipart/form-data serializer for API Elements", "author": "Apiary.io ", "license": "MIT", diff --git a/packages/form-serializer/test/adapter-test.js b/packages/form-serializer/test/adapter-test.js index 22ed112a2..00e8c010f 100644 --- a/packages/form-serializer/test/adapter-test.js +++ b/packages/form-serializer/test/adapter-test.js @@ -19,7 +19,7 @@ describe('Form Serializer Adapter', () => { expect(adapter.mediaTypes).to.include('multipart/form-data'); }); - it('can serialize an object element', (done) => { + it('can serialize an object element asynchronously', (done) => { const element = new fury.minim.elements.Object({ message: 'Hello world' }); fury.serialize({ api: element, mediaType: 'multipart/form-data' }, (error, result) => { @@ -29,7 +29,14 @@ describe('Form Serializer Adapter', () => { }); }); - it('can serialize a string element', (done) => { + it('can serialize an object element synchronously', () => { + const element = new fury.minim.elements.Object({ message: 'Hello world' }); + const result = fury.serializeSync({ api: element, mediaType: 'multipart/form-data' }); + + expect(result).to.equal('--BOUNDARY\r\nContent-Disposition: form-data; name="message"\r\n\r\nHello world\r\n--BOUNDARY--\r\n'); + }); + + it('can serialize a string element asynchronously', (done) => { const element = new fury.minim.elements.String('Hello world'); fury.serialize({ api: element, mediaType: 'multipart/form-data' }, (error, result) => { @@ -38,6 +45,13 @@ describe('Form Serializer Adapter', () => { done(); }); }); + + it('can serialize a string element synchronously', () => { + const element = new fury.minim.elements.String('Hello world'); + const result = fury.serializeSync({ api: element, mediaType: 'multipart/form-data' }); + + expect(result).to.equal('--BOUNDARY\r\nContent-Disposition: form-data; name="undefined"\r\n\r\nHello world\r\n--BOUNDARY--\r\n'); + }); }); describe('Application/x-www-form-urlencoded mediaType', () => { @@ -49,7 +63,7 @@ describe('Form Serializer Adapter', () => { expect(adapter.mediaTypes).to.include('application/x-www-form-urlencoded'); }); - it('can serialize an object element', (done) => { + it('can serialize an object element asynchronoulsy', (done) => { const element = new fury.minim.elements.Object({ message: 'Hello world' }); fury.serialize({ api: element, mediaType: 'application/x-www-form-urlencoded' }, (error, result) => { @@ -59,7 +73,14 @@ describe('Form Serializer Adapter', () => { }); }); - it('can serialize a string element', (done) => { + it('can serialize an object element synchronoulsy', () => { + const element = new fury.minim.elements.Object({ message: 'Hello world' }); + const result = fury.serializeSync({ api: element, mediaType: 'application/x-www-form-urlencoded' }); + + expect(result).to.equal('message=Hello%20world'); + }); + + it('can serialize a string element asynchronously', (done) => { const element = new fury.minim.elements.String('Hello world'); fury.serialize({ api: element, mediaType: 'application/x-www-form-urlencoded' }, (error, result) => { @@ -68,5 +89,12 @@ describe('Form Serializer Adapter', () => { done(); }); }); + + it('can serialize a string element synchronously', () => { + const element = new fury.minim.elements.String('Hello world'); + const result = fury.serializeSync({ api: element, mediaType: 'application/x-www-form-urlencoded' }); + + expect(result).to.equal('undefined=Hello%20world'); + }); }); }); diff --git a/packages/json-serializer/CHANGELOG.md b/packages/json-serializer/CHANGELOG.md index 9212bf858..ee1602861 100644 --- a/packages/json-serializer/CHANGELOG.md +++ b/packages/json-serializer/CHANGELOG.md @@ -1,5 +1,11 @@ # JSON Serializer Changelog +## 0.1.4 (2020-08-27) + +### Enhancements + +- Enable synchronous serialization. + ## 0.1.3 (2020-08-20) ### Enhancements diff --git a/packages/json-serializer/README.md b/packages/json-serializer/README.md index 8ee0b29bf..9b08c6b50 100644 --- a/packages/json-serializer/README.md +++ b/packages/json-serializer/README.md @@ -5,6 +5,8 @@ Takes an API Element data structure, and returns JSON serialized data structures, for example: +### Async + ```javascript const { Fury } = require('@apielements/core'); const jsonSerializer = require('@apielements/json-serializer'); @@ -24,3 +26,30 @@ fury.serialize({ api, mediaType }, (error, body) => { // } }); ``` + + +### Sync + +```javascript +const { Fury } = require('@apielements/core'); +const jsonSerializer = require('@apielements/json-serializer'); + +const fury = new Fury(); +fury.use(jsonSerializer); + +const name = new fury.minim.elements.String(); +name.attributes.set('default', 'Doe'); + +const api = new fury.minim.elements.Object({ name }); +const mediaType = 'application/json'; +try { + const body = fury.serialize({ api, mediaType }); + console.log(body); + // { + // "name": "Doe" + // } +} catch (error) { + console.log(error); + // Media type did not match any registered serializer! +} +``` \ No newline at end of file diff --git a/packages/json-serializer/lib/adapter.js b/packages/json-serializer/lib/adapter.js index fbf78b850..251845a96 100644 --- a/packages/json-serializer/lib/adapter.js +++ b/packages/json-serializer/lib/adapter.js @@ -9,4 +9,10 @@ function serialize({ api }) { return new Promise(resolve => resolve(serializeJSON(api))); } -module.exports = { name, mediaTypes, serialize }; +function serializeSync({ api }) { + return serializeJSON(api); +} + +module.exports = { + name, mediaTypes, serialize, serializeSync, +}; diff --git a/packages/json-serializer/package.json b/packages/json-serializer/package.json index b384b565c..7d75f1c26 100644 --- a/packages/json-serializer/package.json +++ b/packages/json-serializer/package.json @@ -1,6 +1,6 @@ { "name": "@apielements/json-serializer", - "version": "0.1.3", + "version": "0.1.4", "description": "JSON serializer for API Elements", "author": "Apiary.io ", "license": "MIT", diff --git a/packages/json-serializer/test/adapter-test.js b/packages/json-serializer/test/adapter-test.js index d7022fedd..183825101 100644 --- a/packages/json-serializer/test/adapter-test.js +++ b/packages/json-serializer/test/adapter-test.js @@ -18,7 +18,7 @@ describe('JSON Serialiser Adapter', () => { expect(adapter.mediaTypes).to.deep.equal(['application/json']); }); - it('can serialize an element', (done) => { + it('can serialize an element asynchronously', (done) => { const element = new fury.minim.elements.String('hello world'); fury.serialize({ api: element, mediaType: 'application/json' }, (error, result) => { @@ -27,4 +27,11 @@ describe('JSON Serialiser Adapter', () => { done(); }); }); + + it('can serialize an element synchronously', () => { + const element = new fury.minim.elements.String('hello world'); + const result = fury.serializeSync({ api: element, mediaType: 'application/json' }); + + expect(result).to.equal('"hello world"'); + }); });