diff --git a/packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js b/packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js index 1e254bfd2a..4ba85ee23d 100644 --- a/packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js +++ b/packages/ipfs-core-utils/src/files/normalise-candidate-multiple.js @@ -29,8 +29,10 @@ export async function * normaliseCandidateMultiple (input, normaliseContent) { // String // Uint8Array|ArrayBuffer|TypedArray // Blob|File - if (typeof input === 'string' || input instanceof String || isBytes(input) || isBlob(input)) { - throw errCode(new Error('Unexpected input: single item passed'), 'ERR_UNEXPECTED_INPUT') + // fs.ReadStream + // @ts-expect-error _readableState is a property of a node fs.ReadStream + if (typeof input === 'string' || input instanceof String || isBytes(input) || isBlob(input) || input._readableState) { + throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT') } // Browser ReadableStream @@ -57,8 +59,8 @@ export async function * normaliseCandidateMultiple (input, normaliseContent) { // (Async)Iterable // (Async)Iterable - if (Number.isInteger(value) || isBytes(value)) { - throw errCode(new Error('Unexpected input: single item passed'), 'ERR_UNEXPECTED_INPUT') + if (Number.isInteger(value)) { + throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT') } // (Async)Iterable @@ -68,11 +70,16 @@ export async function * normaliseCandidateMultiple (input, normaliseContent) { return } + if (isBytes(value)) { + yield toFileObject({ content: peekable }, normaliseContent) + return + } + // (Async)Iterable<(Async)Iterable> // (Async)Iterable> // ReadableStream<(Async)Iterable> // ReadableStream> - if (isFileObject(value) || value[Symbol.iterator] || value[Symbol.asyncIterator] || isReadableStream(value)) { + if (isFileObject(value) || value[Symbol.iterator] || value[Symbol.asyncIterator] || isReadableStream(value) || isBlob(value)) { yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject(value, normaliseContent)) return } @@ -82,7 +89,7 @@ export async function * normaliseCandidateMultiple (input, normaliseContent) { // Note: Detected _after_ (Async)Iterable because Node.js fs.ReadStreams have a // `path` property that passes this check. if (isFileObject(input)) { - throw errCode(new Error('Unexpected input: single item passed'), 'ERR_UNEXPECTED_INPUT') + throw errCode(new Error('Unexpected input: single item passed - if you are using ipfs.allAll, please use ipfs.add instead'), 'ERR_UNEXPECTED_INPUT') } throw errCode(new Error('Unexpected input: ' + typeof input), 'ERR_UNEXPECTED_INPUT') diff --git a/packages/ipfs-core-utils/src/files/normalise-candidate-single.js b/packages/ipfs-core-utils/src/files/normalise-candidate-single.js index 760a1df226..f66afefe00 100644 --- a/packages/ipfs-core-utils/src/files/normalise-candidate-single.js +++ b/packages/ipfs-core-utils/src/files/normalise-candidate-single.js @@ -1,7 +1,6 @@ import errCode from 'err-code' import browserStreamToIt from 'browser-readablestream-to-it' import itPeekable from 'it-peekable' -import map from 'it-map' import { isBytes, isBlob, @@ -66,19 +65,13 @@ export async function * normaliseCandidateSingle (input, normaliseContent) { // (Async)Iterable // (Async)Iterable - if (Number.isInteger(value) || isBytes(value)) { + // (Async)Iterable + if (Number.isInteger(value) || isBytes(value) || typeof value === 'string' || value instanceof String) { yield toFileObject(peekable, normaliseContent) return } - // (Async)Iterable - if (value._readableState) { - // @ts-ignore Node fs.ReadStreams have a `.path` property so we need to pass it as the content - yield * map(peekable, (/** @type {ImportCandidate} */ value) => toFileObject({ content: value }, normaliseContent)) - return - } - - throw errCode(new Error('Unexpected input: multiple items passed'), 'ERR_UNEXPECTED_INPUT') + throw errCode(new Error('Unexpected input: multiple items passed - if you are using ipfs.add, please use ipfs.addAll instead'), 'ERR_UNEXPECTED_INPUT') } // { path, content: ? } diff --git a/packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js b/packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js index 8f49a833f5..21614201e4 100644 --- a/packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js +++ b/packages/ipfs-core-utils/test/files/normalise-input-multiple.spec.js @@ -16,6 +16,7 @@ const NEWSTRING = () => new String('hello world') // eslint-disable-line no-new- const BUFFER = () => uint8ArrayFromString(STRING()) const ARRAY = () => Array.from(BUFFER()) const TYPEDARRAY = () => Uint8Array.from(ARRAY()) +const FILE = () => new File([BUFFER()], 'test-file.txt') /** @type {() => Blob} */ let BLOB @@ -54,6 +55,14 @@ async function testContent (input) { await verifyNormalisation(result) } +/** + * @param {*} input + * @param {RegExp} message + */ +async function testFailure (input, message) { + await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message) +} + /** * @template T * @param {T} thing @@ -90,14 +99,14 @@ describe('normalise-input-multiple', function () { /** * @param {() => any} content * @param {string} name - * @param {boolean} isBytes + * @param {{ acceptStream: boolean, acceptContentStream: boolean }} options */ - function testInputType (content, name, isBytes) { - it(name, async function () { - await testContent(content()) + function testInputType (content, name, { acceptStream, acceptContentStream }) { + it(`Failure ${name}`, async function () { + await testFailure(content(), /single item passed/) }) - if (isBytes) { + if (acceptStream) { if (ReadableStream) { it(`ReadableStream<${name}>`, async function () { await testContent(browserReadableStreamOf(content())) @@ -111,43 +120,35 @@ describe('normalise-input-multiple', function () { it(`AsyncIterable<${name}>`, async function () { await testContent(asyncIterableOf(content())) }) - } - - it(`{ path: '', content: ${name} }`, async function () { - await testContent({ path: '', content: content() }) - }) - - if (isBytes) { + } else { if (ReadableStream) { - it(`{ path: '', content: ReadableStream<${name}> }`, async function () { - await testContent({ path: '', content: browserReadableStreamOf(content()) }) + it(`Failure ReadableStream<${name}>`, async function () { + await testFailure(browserReadableStreamOf(content()), /single item passed/) }) } - it(`{ path: '', content: Iterable<${name}> }`, async function () { - await testContent({ path: '', content: iterableOf(content()) }) + it(`Failure Iterable<${name}>`, async function () { + await testFailure(iterableOf(content()), /single item passed/) }) - it(`{ path: '', content: AsyncIterable<${name}> }`, async function () { - await testContent({ path: '', content: asyncIterableOf(content()) }) + it(`Failure AsyncIterable<${name}>`, async function () { + await testFailure(asyncIterableOf(content()), /single item passed/) }) } - if (ReadableStream) { - it(`ReadableStream<${name}>`, async function () { - await testContent(browserReadableStreamOf(content())) - }) - } + it(`Failure { path: '', content: ${name} }`, async function () { + await testFailure({ path: '', content: content() }, /single item passed/) + }) - it(`Iterable<{ path: '', content: ${name} }`, async function () { + it(`Iterable<{ path: '', content: ${name} }>`, async function () { await testContent(iterableOf({ path: '', content: content() })) }) - it(`AsyncIterable<{ path: '', content: ${name} }`, async function () { + it(`AsyncIterable<{ path: '', content: ${name} }>`, async function () { await testContent(asyncIterableOf({ path: '', content: content() })) }) - if (isBytes) { + if (acceptContentStream) { if (ReadableStream) { it(`Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { await testContent(iterableOf({ path: '', content: browserReadableStreamOf(content()) })) @@ -175,16 +176,53 @@ describe('normalise-input-multiple', function () { it(`AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { await testContent(asyncIterableOf({ path: '', content: asyncIterableOf(content()) })) }) + } else { + if (ReadableStream) { + it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/) + }) + } + + it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/) + }) + + it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/) + }) + + if (ReadableStream) { + it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /Unexpected input/) + }) + } + + it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /Unexpected input/) + }) + + it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /Unexpected input/) + }) } } describe('String', () => { - testInputType(STRING, 'String', true) - testInputType(NEWSTRING, 'new String()', true) + testInputType(STRING, 'String', { + acceptStream: true, + acceptContentStream: true + }) + testInputType(NEWSTRING, 'new String()', { + acceptStream: true, + acceptContentStream: true + }) }) describe('Buffer', () => { - testInputType(BUFFER, 'Buffer', true) + testInputType(BUFFER, 'Buffer', { + acceptStream: true, + acceptContentStream: true + }) }) describe('Blob', () => { @@ -192,23 +230,31 @@ describe('normalise-input-multiple', function () { return } - testInputType(BLOB, 'Blob', false) + testInputType(BLOB, 'Blob', { + acceptStream: true, + acceptContentStream: false + }) }) describe('@web-std/file', () => { - it('normalizes File input', async () => { - const FILE = new File([BUFFER()], 'test-file.txt') - - await testContent(FILE) + testInputType(FILE, 'File', { + acceptStream: true, + acceptContentStream: false }) }) describe('Iterable', () => { - testInputType(ARRAY, 'Iterable', false) + testInputType(ARRAY, 'Iterable', { + acceptStream: true, + acceptContentStream: false + }) }) describe('TypedArray', () => { - testInputType(TYPEDARRAY, 'TypedArray', true) + testInputType(TYPEDARRAY, 'TypedArray', { + acceptStream: true, + acceptContentStream: true + }) }) if (isNode) { @@ -226,14 +272,9 @@ describe('normalise-input-multiple', function () { return fs.createReadStream(path) } - testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', false) - - it('Iterable', async function () { - await testContent(iterableOf(NODEFSREADSTREAM())) - }) - - it('AsyncIterable', async function () { - await testContent(asyncIterableOf(NODEFSREADSTREAM())) + testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', { + acceptStream: true, + acceptContentStream: false }) }) } diff --git a/packages/ipfs-core-utils/test/files/normalise-input-single.spec.js b/packages/ipfs-core-utils/test/files/normalise-input-single.spec.js index f44ba17012..6113e1db67 100644 --- a/packages/ipfs-core-utils/test/files/normalise-input-single.spec.js +++ b/packages/ipfs-core-utils/test/files/normalise-input-single.spec.js @@ -16,6 +16,7 @@ const NEWSTRING = () => new String('hello world') // eslint-disable-line no-new- const BUFFER = () => uint8ArrayFromString(STRING()) const ARRAY = () => Array.from(BUFFER()) const TYPEDARRAY = () => Uint8Array.from(ARRAY()) +const FILE = () => new File([BUFFER()], 'test-file.txt') /** @type {() => Blob} */ let BLOB @@ -54,6 +55,14 @@ async function testContent (input) { await verifyNormalisation(result) } +/** + * @param {*} input + * @param {RegExp} message + */ +async function testFailure (input, message) { + await expect(all(normaliseInput(input))).to.eventually.be.rejectedWith(message) +} + /** * @template T * @param {T} thing @@ -90,14 +99,14 @@ describe('normalise-input-single', function () { /** * @param {() => any} content * @param {string} name - * @param {boolean} isBytes + * @param {{ acceptStream: boolean }} options */ - function testInputType (content, name, isBytes) { + function testInputType (content, name, { acceptStream }) { it(name, async function () { await testContent(content()) }) - if (isBytes) { + if (acceptStream) { if (ReadableStream) { it(`ReadableStream<${name}>`, async function () { await testContent(browserReadableStreamOf(content())) @@ -111,13 +120,27 @@ describe('normalise-input-single', function () { it(`AsyncIterable<${name}>`, async function () { await testContent(asyncIterableOf(content())) }) + } else { + if (ReadableStream) { + it(`Failure ReadableStream<${name}>`, async function () { + await testFailure(browserReadableStreamOf(content()), /Unexpected input/) + }) + } + + it(`Failure Iterable<${name}>`, async function () { + await testFailure(iterableOf(content()), /Unexpected input/) + }) + + it(`Failure AsyncIterable<${name}>`, async function () { + await testFailure(asyncIterableOf(content()), /Unexpected input/) + }) } it(`{ path: '', content: ${name} }`, async function () { await testContent({ path: '', content: content() }) }) - if (isBytes) { + if (acceptStream) { if (ReadableStream) { it(`{ path: '', content: ReadableStream<${name}> }`, async function () { await testContent({ path: '', content: browserReadableStreamOf(content()) }) @@ -134,57 +157,69 @@ describe('normalise-input-single', function () { } if (ReadableStream) { - it(`ReadableStream<${name}>`, async function () { - await testContent(browserReadableStreamOf(content())) - }) + if (acceptStream) { + it(`ReadableStream<${name}>`, async function () { + await testContent(browserReadableStreamOf(content())) + }) + } else { + it(`Failure ReadableStream<${name}>`, async function () { + await testFailure(browserReadableStreamOf(content()), /multiple items passed/) + }) + } } - it(`Iterable<{ path: '', content: ${name} }`, async function () { - await testContent(iterableOf({ path: '', content: content() })) + it(`Failure Iterable<{ path: '', content: ${name} }>`, async function () { + await testFailure(iterableOf({ path: '', content: content() }), /multiple items passed/) }) - it(`AsyncIterable<{ path: '', content: ${name} }`, async function () { - await testContent(asyncIterableOf({ path: '', content: content() })) + it(`Failure AsyncIterable<{ path: '', content: ${name} }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: content() }), /multiple items passed/) }) - if (isBytes) { + if (acceptStream) { if (ReadableStream) { - it(`Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { - await testContent(iterableOf({ path: '', content: browserReadableStreamOf(content()) })) + it(`Failure Iterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/) }) } - it(`Iterable<{ path: '', content: Iterable<${name}> }>`, async function () { - await testContent(iterableOf({ path: '', content: iterableOf(content()) })) + it(`Failure Iterable<{ path: '', content: Iterable<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/) }) - it(`Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { - await testContent(iterableOf({ path: '', content: asyncIterableOf(content()) })) + it(`Failure Iterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { + await testFailure(iterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/) }) if (ReadableStream) { - it(`AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { - await testContent(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) })) + it(`Failure AsyncIterable<{ path: '', content: ReadableStream<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: browserReadableStreamOf(content()) }), /multiple items passed/) }) } - it(`AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () { - await testContent(asyncIterableOf({ path: '', content: iterableOf(content()) })) + it(`Failure AsyncIterable<{ path: '', content: Iterable<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: iterableOf(content()) }), /multiple items passed/) }) - it(`AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { - await testContent(asyncIterableOf({ path: '', content: asyncIterableOf(content()) })) + it(`Failure AsyncIterable<{ path: '', content: AsyncIterable<${name}> }>`, async function () { + await testFailure(asyncIterableOf({ path: '', content: asyncIterableOf(content()) }), /multiple items passed/) }) } } describe('String', () => { - testInputType(STRING, 'String', true) - testInputType(NEWSTRING, 'new String()', true) + testInputType(STRING, 'String', { + acceptStream: true + }) + testInputType(NEWSTRING, 'new String()', { + acceptStream: true + }) }) describe('Buffer', () => { - testInputType(BUFFER, 'Buffer', true) + testInputType(BUFFER, 'Buffer', { + acceptStream: true + }) }) describe('Blob', () => { @@ -192,23 +227,27 @@ describe('normalise-input-single', function () { return } - testInputType(BLOB, 'Blob', false) + testInputType(BLOB, 'Blob', { + acceptStream: false + }) }) describe('@web-std/file', () => { - it('normalizes File input', async () => { - const FILE = new File([BUFFER()], 'test-file.txt') - - await testContent(FILE) + testInputType(FILE, 'File', { + acceptStream: false }) }) describe('Iterable', () => { - testInputType(ARRAY, 'Iterable', false) + testInputType(ARRAY, 'Iterable', { + acceptStream: false + }) }) describe('TypedArray', () => { - testInputType(TYPEDARRAY, 'TypedArray', true) + testInputType(TYPEDARRAY, 'TypedArray', { + acceptStream: true + }) }) if (isNode) { @@ -226,14 +265,8 @@ describe('normalise-input-single', function () { return fs.createReadStream(path) } - testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', false) - - it('Iterable', async function () { - await testContent(iterableOf(NODEFSREADSTREAM())) - }) - - it('AsyncIterable', async function () { - await testContent(asyncIterableOf(NODEFSREADSTREAM())) + testInputType(NODEFSREADSTREAM, 'Node fs.ReadStream', { + acceptStream: false }) }) } diff --git a/packages/ipfs-core-utils/test/tests.spec.js b/packages/ipfs-core-utils/test/tests.spec.js index 39abcf45fe..cc8a3db62d 100644 --- a/packages/ipfs-core-utils/test/tests.spec.js +++ b/packages/ipfs-core-utils/test/tests.spec.js @@ -1,5 +1,6 @@ import './files/format-mode.spec.js' import './files/format-mtime.spec.js' -import './files/normalise-input.spec.js' +import './files/normalise-input-multiple.spec.js' +import './files/normalise-input-single.spec.js' import './pins/normalise-input.spec.js'