diff --git a/src/core/components/files.js b/src/core/components/files.js index 4c66d954e0..57f4393d1b 100644 --- a/src/core/components/files.js +++ b/src/core/components/files.js @@ -131,22 +131,53 @@ module.exports = function files (self) { throw new Error('You must supply an ipfsPath') } + let bestMatch = { depth: -1 } + ipfsPath = normalizePath(ipfsPath) const pathComponents = ipfsPath.split('/') + const prePath = normalizePath(pathComponents.slice(0, 1).join('/')) const restPath = normalizePath(pathComponents.slice(1).join('/')) - const filterFile = (file) => (restPath && file.path === restPath) || (file.path === ipfsPath) + const filterFile = (file) => { + // Save off best match to provide a better error message if file + // isn't found. + if (file.path === ipfsPath.substring(0, file.path.length)) { + if (file.depth > bestMatch.depth) { + bestMatch = file + } + } + + return (restPath && file.path === restPath) || (file.path === ipfsPath) + } const d = deferred.source() pull( - exporter(ipfsPath, self._ipld), + exporter(prePath, self._ipld), pull.collect((err, files) => { if (err) { return d.abort(err) } - if (files && files.length > 1) { + if (files) { files = files.filter(filterFile) } if (!files || !files.length) { - return d.abort(new Error('No such file')) + // File used as directory + if (bestMatch.type === 'file') { + const path = bestMatch.path.substring(0, bestMatch.path.length - bestMatch.name.length - 1) + return d.abort(new Error( + `"${bestMatch.name}" is a file not a directory under ${path}` + )) + } + + const link = pathComponents[bestMatch.depth + 1] + + // Missing directory + if (bestMatch.depth < pathComponents.length - 2) { + return d.abort(new Error( + `no directory named "${link}" under ${bestMatch.path}` + )) + } + + // Missing file + return d.abort(new Error(`no file named "${link}" under ${bestMatch.path}`)) } const file = files[0] diff --git a/test/cli/files.js b/test/cli/files.js index 2e9fec39b5..0c1e2a8af1 100644 --- a/test/cli/files.js +++ b/test/cli/files.js @@ -319,10 +319,47 @@ describe('files', () => runOnAndOff((thing) => { }) it('cat non-existent file', () => { - return ipfs('cat QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB/dummy') + return ipfs('cat QmUhUuiTKkkK8J6JZ9zmj8iNHPuNfGYcszgRumzhHBxEEU/dummy') .then(() => expect.fail(0, 1, 'Should have thrown an error')) .catch((err) => { + const message = err.stderr.match(/^Error:(?: Failed to cat file: Error:)? (.*)$/m)[1] expect(err).to.exist() + expect(message).to.eql( + 'no file named "dummy" under QmUhUuiTKkkK8J6JZ9zmj8iNHPuNfGYcszgRumzhHBxEEU' + ) + }) + }) + + it('cat specifies missing directory in a nested link', () => { + return ipfs('cat QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs/wrong-dir/dummy') + .then(() => expect.fail(0, 1, 'Should have thrown an error')) + .catch((err) => { + const message = err.stderr.match(/^Error:(?: Failed to cat file: Error:)? (.*)$/m)[1] + expect(message).to.eql( + 'no directory named "wrong-dir" under QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs' + ) + }) + }) + + it('cat specifies missing file in a nested link', () => { + return ipfs('cat QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs/dummy') + .then(() => expect.fail(0, 1, 'Should have thrown an error')) + .catch((err) => { + const message = err.stderr.match(/^Error:(?: Failed to cat file: Error:)? (.*)$/m)[1] + expect(message).to.eql( + 'no file named "dummy" under QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs' + ) + }) + }) + + it('cat specifies a link is not a directory', () => { + return ipfs('cat QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs/index/dummy') + .then(() => expect.fail(0, 1, 'Should have thrown an error')) + .catch((err) => { + const message = err.stderr.match(/^Error:(?: Failed to cat file: Error:)? (.*)$/m)[1] + expect(message).to.eql( + '"index" is a file not a directory under QmYmW4HiZhotsoSqnv2o1oUusvkRM8b9RweBoH7ao5nki2/init-docs/docs' + ) }) })