From 6c6c120c8912e0af2651916ac14c3e30d7f03627 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Sat, 31 Aug 2019 09:14:10 +0100 Subject: [PATCH] feat: support iterators and async iterators This allows us to accept async interators as arguments to ipfs.add and friends. It also allows adding plain old js things like strings and arrays, so hooray for that. --- package.json | 2 + src/files/add-input-validation.js | 14 +++---- test/files/add-input-validation.spec.js | 55 ++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 85697a8..ff4f2e4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "license": "MIT", "dependencies": { "buffer": "^5.2.1", + "err-code": "^2.0.0", + "is-blob": "^2.0.1", "is-buffer": "^2.0.3", "is-electron": "^2.2.0", "is-pull-stream": "0.0.0", diff --git a/src/files/add-input-validation.js b/src/files/add-input-validation.js index cbfb952..ce00da2 100644 --- a/src/files/add-input-validation.js +++ b/src/files/add-input-validation.js @@ -1,13 +1,11 @@ 'use strict' -const kindOf = require('kind-of') -const isStream = require('is-stream') -const { isSource } = require('is-pull-stream') -const isBuffer = require('is-buffer') +const isBlob = require('is-blob') +const errcode = require('err-code') const validateAddInput = (input) => { - // Buffer|ReadableStream|PullStream|File - const isPrimitive = obj => isBuffer(obj) || isStream.readable(obj) || isSource(obj) || kindOf(obj) === 'file' + // AsyncIterator|Blob|Iterator + const isPrimitive = (obj) => obj[Symbol.asyncIterator] || isBlob(obj) || obj[Symbol.iterator] // An object like { content?, path? }, where content isBufferOrStream and path isString const isContentObject = obj => { @@ -18,13 +16,13 @@ const validateAddInput = (input) => { return Boolean(obj.path) && typeof obj.path === 'string' } - // An input atom: a buffer, stream or content object + // An input atom: an async iterable, an iterable, a blob or a content object const isInput = obj => isPrimitive(obj) || isContentObject(obj) if (isInput(input) || (Array.isArray(input) && input.every(isInput))) { return true } else { - throw new Error(`Input not supported. Expected Buffer|ReadableStream|PullStream|File|Array got ${kindOf(input)}. Check the documentation for more info https://github.com/ipfs/interface-js-ipfs-core/blob/master/SPEC/FILES.md#add`) + throw errcode(new Error(`Input not supported. Expected AsyncIterator|Blob|Iterator|{path, content}|Iterator<{path, content}>|AsyncIterator<{path, content}> got ${typeof input}. Check the documentation for more info https://github.com/ipfs/interface-js-ipfs-core/blob/master/SPEC/FILES.md#add`), 'ERR_UNSUPPORTED_INPUT') } } diff --git a/test/files/add-input-validation.spec.js b/test/files/add-input-validation.spec.js index f4ba1a7..a8f864a 100644 --- a/test/files/add-input-validation.spec.js +++ b/test/files/add-input-validation.spec.js @@ -7,16 +7,25 @@ const validate = require('../../src/files/add-input-validation') const { supportsFileReader } = require('../../src/supports') const { Buffer } = require('buffer') const { Readable } = require('readable-stream') -const empty = require('pull-stream/sources/empty') chai.use(dirtyChai) const expect = chai.expect describe('add-input-validation', function () { it('validates correct primitive input types', function () { + expect(validate('Hello world')).to.be.true() + expect(validate([0, 1, 2, 3])).to.be.true() expect(validate(Buffer.from(('test')))).to.be.true() expect(validate(new Readable())).to.be.true() - expect(validate(empty())).to.be.true() + expect(validate(async function * () {}())).to.be.true() + expect(validate({ + [Symbol.asyncIterator]: () => {}, + next: () => {} + })).to.be.true() + expect(validate({ + [Symbol.iterator]: () => {}, + next: () => {} + })).to.be.true() if (supportsFileReader) { const file = new self.File(['test'], 'test.txt', { type: 'text/plain' }) @@ -27,7 +36,15 @@ describe('add-input-validation', function () { it('validates correct array of primitive input types', function () { expect(validate([Buffer.from('test'), Buffer.from('test')])).to.be.true() expect(validate([new Readable(), new Readable()])).to.be.true() - expect(validate([empty(), empty()])).to.be.true() + expect(validate([(async function * () {}())])).to.be.true() + expect(validate([{ + [Symbol.asyncIterator]: () => {}, + next: () => {} + }])).to.be.true() + expect(validate([{ + [Symbol.iterator]: () => {}, + next: () => {} + }])).to.be.true() if (supportsFileReader) { const file = new self.File(['test'], 'test.txt', { type: 'text/plain' }) @@ -39,7 +56,35 @@ describe('add-input-validation', function () { expect(validate({ path: '/path' })).to.be.true() expect(validate({ path: '/path', content: Buffer.from('test') })).to.be.true() expect(validate({ content: new Readable() })).to.be.true() - expect(validate({ content: empty() })).to.be.true() + expect(validate({ content: (async function * () {}()) })).to.be.true() + expect(validate({ content: { + [Symbol.asyncIterator]: () => {}, + next: () => {} + } })).to.be.true() + expect(validate({ content: { + [Symbol.iterator]: () => {}, + next: () => {} + } })).to.be.true() + + if (supportsFileReader) { + expect(validate({ content: new Readable() })).to.be.true() + } + }) + + it('validates correct form array of of object input', function () { + expect(validate([{ path: '/path' }])).to.be.true() + expect(validate([{ path: '/path', content: Buffer.from('test') }])).to.be.true() + expect(validate([{ content: new Readable() }])).to.be.true() + expect(validate([{ content: (async function * () {}()) }])).to.be.true() + expect(validate([{ content: { + [Symbol.asyncIterator]: () => {}, + next: () => {} + } }])).to.be.true() + expect(validate([{ content: { + [Symbol.iterator]: () => {}, + next: () => {} + } }])).to.be.true() + if (supportsFileReader) { expect(validate({ content: new Readable() })).to.be.true() } @@ -47,10 +92,8 @@ describe('add-input-validation', function () { it('should throw with bad input', function () { const regex = /Input not supported/ - expect(() => validate('test')).throw(regex) expect(() => validate(2)).throw(regex) expect(() => validate({ path: 3 })).throw(regex) - expect(() => validate({ path: 'path', content: 'test' })).throw(regex) expect(() => validate({ path: 'path', content: 2 })).throw(regex) expect(() => validate({})).throw(regex) })