Skip to content

Commit 49baa6d

Browse files
Merge pull request #5 from FourierTransformer/better_testing
better error handling with unit tests!
2 parents e218fb0 + 20f4dd3 commit 49baa6d

12 files changed

+125
-20
lines changed

ERRORS.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#Error Handling
2+
Below you can find a more detailed explanation of some of the errors that can be encountered while using ftcsv. For parsing, examples of these files can be found in /spec/bad_csvs/
3+
4+
5+
6+
##Parsing
7+
Note: `[row_number]` indicates the row number of the parsed lua table. As such, it will be one off from the line number in the csv. However, for header-less files, the row returned *will* match the csv line number.
8+
9+
| Error Message | Detailed Explanation |
10+
| ------------- | ------------- |
11+
| ftcsv: Cannot parse an empty file | The file passed in contains no information. It is an empty file. |
12+
| ftcsv: Cannot parse a file which contains empty headers | If a header field contains no information, then it can't be parsed <br> (ex: `Name,City,,Zipcode`) |
13+
| ftcsv: too few columns in row [row_number] | The number of columns is less than the amount in the header after transformations (renaming, keeping certain fields, etc) |
14+
| ftcsv: too many columns in row [row_number] | The number of columns is greater than the amount in the header after transformations. It can't map the field's count with an existing header. |
15+
| ftcsv: File not found at [path] | When loading, lua can't open the file at [path] |

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,14 @@ I did some basic testing and found that in lua, if you want to iterate over a st
117117

118118

119119

120+
## Error Handling
121+
ftcsv returns a litany of errors when passed a bad csv file or incorrect parameters. You can find a more detailed explanation of the more cryptic errors in [ERRORS.md](ERRORS.md)
122+
123+
124+
120125
## Contributing
121126
Feel free to create a new issue for any bugs you've found or help you need. If you want to contribute back to the project please do the following:
127+
0. If it's a major change (aka more than a quick little < 5 line bugfix), please create an issue so we can discuss it!
122128
1. Fork the repo
123129
2. Create a new branch
124130
3. Push your changes to the branch

ftcsv-1.1.1-1.rockspec ftcsv-1.1.2-1.rockspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package = "ftcsv"
2-
version = "1.1.1-1"
2+
version = "1.1.2-1"
33

44
source = {
55
url = "git://github.com/FourierTransformer/ftcsv.git",
6-
tag = "1.1.1"
6+
tag = "1.1.2"
77
}
88

99
description = {

ftcsv.lua

+46-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
local ftcsv = {
2-
_VERSION = 'ftcsv 1.1.1',
2+
_VERSION = 'ftcsv 1.1.2',
33
_DESCRIPTION = 'CSV library for Lua',
44
_URL = 'https://github.com/FourierTransformer/ftcsv',
55
_LICENSE = [[
@@ -97,7 +97,7 @@ end
9797
-- load an entire file into memory
9898
local function loadFile(textFile)
9999
local file = io.open(textFile, "r")
100-
if not file then error("File not found at " .. textFile) end
100+
if not file then error("ftcsv: File not found at " .. textFile) end
101101
local allLines = file:read("*all")
102102
file:close()
103103
return allLines
@@ -156,7 +156,21 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
156156
outResults = {}
157157
outResults[1] = {}
158158
assignValue = function()
159-
outResults[lineNum][headerField[fieldNum]] = field
159+
if not pcall(function()
160+
outResults[lineNum][headerField[fieldNum]] = field
161+
end) then
162+
error('ftcsv: too many columns in row ' .. lineNum)
163+
end
164+
end
165+
end
166+
167+
-- calculate the initial line count (note: this can include duplicates)
168+
local headerFieldsExist = {}
169+
local initialLineCount = 0
170+
for _, value in pairs(headerField) do
171+
if not headerFieldsExist[value] and (fieldsToKeep == nil or fieldsToKeep[value]) then
172+
headerFieldsExist[value] = true
173+
initialLineCount = initialLineCount + 1
160174
end
161175
end
162176

@@ -225,6 +239,9 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
225239
end
226240

227241
-- incrememnt for new line
242+
if fieldNum < initialLineCount then
243+
error('ftcsv: too few columns in row ' .. lineNum)
244+
end
228245
lineNum = lineNum + 1
229246
outResults[lineNum] = {}
230247
fieldNum = 1
@@ -251,16 +268,20 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
251268
-- clean up last line if it's weird (this happens when there is a CRLF newline at end of file)
252269
-- doing a count gets it to pick up the oddballs
253270
local finalLineCount = 0
254-
for _, _ in pairs(outResults[lineNum]) do
271+
local lastValue = nil
272+
for k, v in pairs(outResults[lineNum]) do
255273
finalLineCount = finalLineCount + 1
274+
lastValue = v
256275
end
257-
local initialLineCount = 0
258-
for _, _ in pairs(outResults[1]) do
259-
initialLineCount = initialLineCount + 1
260-
end
276+
277+
-- this indicates a CRLF
261278
-- print("Final/Initial", finalLineCount, initialLineCount)
262-
if finalLineCount ~= initialLineCount then
279+
if finalLineCount == 1 and lastValue == "" then
263280
outResults[lineNum] = nil
281+
282+
-- otherwise there might not be enough line
283+
elseif finalLineCount < initialLineCount then
284+
error('ftcsv: too few columns in row ' .. lineNum)
264285
end
265286

266287
return outResults
@@ -295,8 +316,8 @@ function ftcsv.parse(inputFile, delimiter, options)
295316
fieldsToKeep[ofieldsToKeep[j]] = true
296317
end
297318
end
298-
if header == false then
299-
assert(next(rename) ~= nil, "ftcsv can only have fieldsToKeep for header-less files when they have been renamed. Please add the 'rename' option and try again.")
319+
if header == false and options.rename == nil then
320+
error("ftcsv: fieldsToKeep only works with header-less files when using the 'rename' functionality")
300321
end
301322
end
302323
if options.loadFromString ~= nil then
@@ -318,10 +339,22 @@ function ftcsv.parse(inputFile, delimiter, options)
318339
end
319340
local inputLength = #inputString
320341

342+
-- if they sent in an empty file...
343+
if inputLength == 0 then
344+
error('ftcsv: Cannot parse an empty file')
345+
end
346+
321347
-- parse through the headers!
322348
local headerField, i = parseString(inputString, inputLength, delimiter, 0)
323349
i = i + 1 -- start at the next char
324350

351+
-- make sure a header isn't empty
352+
for _, header in ipairs(headerField) do
353+
if #header == 0 then
354+
error('ftcsv: Cannot parse a file which contains empty headers')
355+
end
356+
end
357+
325358
-- for files where there aren't headers!
326359
if header == false then
327360
i = 0
@@ -347,7 +380,7 @@ function ftcsv.parse(inputFile, delimiter, options)
347380
end
348381
end
349382

350-
-- apply some sweet header manuipulation
383+
-- apply some sweet header manipulation
351384
if headerFunc then
352385
for j = 1, #headerField do
353386
headerField[j] = headerFunc(headerField[j])
@@ -374,7 +407,7 @@ local function writer(inputTable, dilimeter, headers)
374407
-- they came in
375408
for i = 1, #headers do
376409
if inputTable[1][headers[i]] == nil then
377-
error("the field '" .. headers[i] .. "' doesn't exist in the table")
410+
error("ftcsv: the field '" .. headers[i] .. "' doesn't exist in the inputTable")
378411
end
379412
if headers[i]:find('"') then
380413
headers[i] = headers[i]:gsub('"', '\\"')

spec/bad_csvs/empty_file.csv

Whitespace-only changes.

spec/bad_csvs/empty_file_newline.csv

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

spec/bad_csvs/empty_header.csv

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
a,b,
2+
herp,derp

spec/bad_csvs/too_few_cols.csv

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a,b,c
2+
failing,hard
3+
man,oh,well...

spec/bad_csvs/too_few_cols_end.csv

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a,b,c
2+
man,oh,well...
3+
failing,hard

spec/bad_csvs/too_many_cols.csv

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
a,b,c
2+
no,one,knows
3+
what,am,i,doing?

spec/error_spec.lua

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
local ftcsv = require('ftcsv')
2+
3+
local files = {
4+
{"empty_file", "ftcsv: Cannot parse an empty file"},
5+
{"empty_file_newline", "ftcsv: Cannot parse a file which contains empty headers"},
6+
{"empty_header", "ftcsv: Cannot parse a file which contains empty headers"},
7+
{"too_few_cols", "ftcsv: too few columns in row 1"},
8+
{"too_few_cols_end", "ftcsv: too few columns in row 2"},
9+
{"too_many_cols", "ftcsv: too many columns in row 2"},
10+
{"dne", "ftcsv: File not found at spec/bad_csvs/dne.csv"}
11+
}
12+
13+
describe("csv decode error", function()
14+
for _, value in ipairs(files) do
15+
it("should error out " .. value[1], function()
16+
local test = function()
17+
ftcsv.parse("spec/bad_csvs/" .. value[1] .. ".csv", ",")
18+
end
19+
assert.has_error(test, value[2])
20+
end)
21+
end
22+
end)
23+
24+
it("should error out for fieldsToKeep if no headers and no renaming takes place", function()
25+
local test = function()
26+
local options = {loadFromString=true, headers=false, fieldsToKeep={1, 2}}
27+
ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", ">", options)
28+
end
29+
assert.has_error(test, "ftcsv: fieldsToKeep only works with header-less files when using the 'rename' functionality")
30+
end)
31+
32+
it("should error out when you want to encode a table and specify a field that doesn't exist", function()
33+
local encodeThis = {
34+
{a = 'herp1', b = 'derp1'},
35+
{a = 'herp2', b = 'derp2'},
36+
{a = 'herp3', b = 'derp3'},
37+
}
38+
39+
local test = function()
40+
ftcsv.encode(encodeThis, ">", {fieldsToKeep={"c"}})
41+
end
42+
43+
assert.has_error(test, "ftcsv: the field 'c' doesn't exist in the inputTable")
44+
end)

spec/feature_spec.lua

-5
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,6 @@ describe("csv features", function()
107107
assert.are.same(expected, actual)
108108
end)
109109

110-
it("should error out for fieldsToKeep if no headers and no renaming", function()
111-
local options = {loadFromString=true, headers=false, fieldsToKeep={1, 2}}
112-
assert.has.errors(function() ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", ">", options) end)
113-
end)
114-
115110
it("should handle only renaming fields from files without headers", function()
116111
local expected = {}
117112
expected[1] = {}

0 commit comments

Comments
 (0)