Skip to content

Commit

Permalink
better error handling with unit tests!
Browse files Browse the repository at this point in the history
  • Loading branch information
FourierTransformer committed Nov 6, 2016
1 parent e218fb0 commit 20f4dd3
Show file tree
Hide file tree
Showing 12 changed files with 125 additions and 20 deletions.
15 changes: 15 additions & 0 deletions ERRORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#Error Handling
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/



##Parsing
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.

| Error Message | Detailed Explanation |
| ------------- | ------------- |
| ftcsv: Cannot parse an empty file | The file passed in contains no information. It is an empty file. |
| 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`) |
| 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) |
| 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. |
| ftcsv: File not found at [path] | When loading, lua can't open the file at [path] |
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,14 @@ I did some basic testing and found that in lua, if you want to iterate over a st



## Error Handling
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)



## Contributing
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:
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!
1. Fork the repo
2. Create a new branch
3. Push your changes to the branch
Expand Down
4 changes: 2 additions & 2 deletions ftcsv-1.1.1-1.rockspec → ftcsv-1.1.2-1.rockspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package = "ftcsv"
version = "1.1.1-1"
version = "1.1.2-1"

source = {
url = "git://github.com/FourierTransformer/ftcsv.git",
tag = "1.1.1"
tag = "1.1.2"
}

description = {
Expand Down
59 changes: 46 additions & 13 deletions ftcsv.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
local ftcsv = {
_VERSION = 'ftcsv 1.1.1',
_VERSION = 'ftcsv 1.1.2',
_DESCRIPTION = 'CSV library for Lua',
_URL = 'https://github.com/FourierTransformer/ftcsv',
_LICENSE = [[
Expand Down Expand Up @@ -97,7 +97,7 @@ end
-- load an entire file into memory
local function loadFile(textFile)
local file = io.open(textFile, "r")
if not file then error("File not found at " .. textFile) end
if not file then error("ftcsv: File not found at " .. textFile) end
local allLines = file:read("*all")
file:close()
return allLines
Expand Down Expand Up @@ -156,7 +156,21 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
outResults = {}
outResults[1] = {}
assignValue = function()
outResults[lineNum][headerField[fieldNum]] = field
if not pcall(function()
outResults[lineNum][headerField[fieldNum]] = field
end) then
error('ftcsv: too many columns in row ' .. lineNum)
end
end
end

-- calculate the initial line count (note: this can include duplicates)
local headerFieldsExist = {}
local initialLineCount = 0
for _, value in pairs(headerField) do
if not headerFieldsExist[value] and (fieldsToKeep == nil or fieldsToKeep[value]) then
headerFieldsExist[value] = true
initialLineCount = initialLineCount + 1
end
end

Expand Down Expand Up @@ -225,6 +239,9 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
end

-- incrememnt for new line
if fieldNum < initialLineCount then
error('ftcsv: too few columns in row ' .. lineNum)
end
lineNum = lineNum + 1
outResults[lineNum] = {}
fieldNum = 1
Expand All @@ -251,16 +268,20 @@ local function parseString(inputString, inputLength, delimiter, i, headerField,
-- clean up last line if it's weird (this happens when there is a CRLF newline at end of file)
-- doing a count gets it to pick up the oddballs
local finalLineCount = 0
for _, _ in pairs(outResults[lineNum]) do
local lastValue = nil
for k, v in pairs(outResults[lineNum]) do
finalLineCount = finalLineCount + 1
lastValue = v
end
local initialLineCount = 0
for _, _ in pairs(outResults[1]) do
initialLineCount = initialLineCount + 1
end

-- this indicates a CRLF
-- print("Final/Initial", finalLineCount, initialLineCount)
if finalLineCount ~= initialLineCount then
if finalLineCount == 1 and lastValue == "" then
outResults[lineNum] = nil

-- otherwise there might not be enough line
elseif finalLineCount < initialLineCount then
error('ftcsv: too few columns in row ' .. lineNum)
end

return outResults
Expand Down Expand Up @@ -295,8 +316,8 @@ function ftcsv.parse(inputFile, delimiter, options)
fieldsToKeep[ofieldsToKeep[j]] = true
end
end
if header == false then
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.")
if header == false and options.rename == nil then
error("ftcsv: fieldsToKeep only works with header-less files when using the 'rename' functionality")
end
end
if options.loadFromString ~= nil then
Expand All @@ -318,10 +339,22 @@ function ftcsv.parse(inputFile, delimiter, options)
end
local inputLength = #inputString

-- if they sent in an empty file...
if inputLength == 0 then
error('ftcsv: Cannot parse an empty file')
end

-- parse through the headers!
local headerField, i = parseString(inputString, inputLength, delimiter, 0)
i = i + 1 -- start at the next char

-- make sure a header isn't empty
for _, header in ipairs(headerField) do
if #header == 0 then
error('ftcsv: Cannot parse a file which contains empty headers')
end
end

-- for files where there aren't headers!
if header == false then
i = 0
Expand All @@ -347,7 +380,7 @@ function ftcsv.parse(inputFile, delimiter, options)
end
end

-- apply some sweet header manuipulation
-- apply some sweet header manipulation
if headerFunc then
for j = 1, #headerField do
headerField[j] = headerFunc(headerField[j])
Expand All @@ -374,7 +407,7 @@ local function writer(inputTable, dilimeter, headers)
-- they came in
for i = 1, #headers do
if inputTable[1][headers[i]] == nil then
error("the field '" .. headers[i] .. "' doesn't exist in the table")
error("ftcsv: the field '" .. headers[i] .. "' doesn't exist in the inputTable")
end
if headers[i]:find('"') then
headers[i] = headers[i]:gsub('"', '\\"')
Expand Down
Empty file added spec/bad_csvs/empty_file.csv
Empty file.
1 change: 1 addition & 0 deletions spec/bad_csvs/empty_file_newline.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 2 additions & 0 deletions spec/bad_csvs/empty_header.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a,b,
herp,derp
3 changes: 3 additions & 0 deletions spec/bad_csvs/too_few_cols.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a,b,c
failing,hard
man,oh,well...
3 changes: 3 additions & 0 deletions spec/bad_csvs/too_few_cols_end.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a,b,c
man,oh,well...
failing,hard
3 changes: 3 additions & 0 deletions spec/bad_csvs/too_many_cols.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
a,b,c
no,one,knows
what,am,i,doing?
44 changes: 44 additions & 0 deletions spec/error_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
local ftcsv = require('ftcsv')

local files = {
{"empty_file", "ftcsv: Cannot parse an empty file"},
{"empty_file_newline", "ftcsv: Cannot parse a file which contains empty headers"},
{"empty_header", "ftcsv: Cannot parse a file which contains empty headers"},
{"too_few_cols", "ftcsv: too few columns in row 1"},
{"too_few_cols_end", "ftcsv: too few columns in row 2"},
{"too_many_cols", "ftcsv: too many columns in row 2"},
{"dne", "ftcsv: File not found at spec/bad_csvs/dne.csv"}
}

describe("csv decode error", function()
for _, value in ipairs(files) do
it("should error out " .. value[1], function()
local test = function()
ftcsv.parse("spec/bad_csvs/" .. value[1] .. ".csv", ",")
end
assert.has_error(test, value[2])
end)
end
end)

it("should error out for fieldsToKeep if no headers and no renaming takes place", function()
local test = function()
local options = {loadFromString=true, headers=false, fieldsToKeep={1, 2}}
ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", ">", options)
end
assert.has_error(test, "ftcsv: fieldsToKeep only works with header-less files when using the 'rename' functionality")
end)

it("should error out when you want to encode a table and specify a field that doesn't exist", function()
local encodeThis = {
{a = 'herp1', b = 'derp1'},
{a = 'herp2', b = 'derp2'},
{a = 'herp3', b = 'derp3'},
}

local test = function()
ftcsv.encode(encodeThis, ">", {fieldsToKeep={"c"}})
end

assert.has_error(test, "ftcsv: the field 'c' doesn't exist in the inputTable")
end)
5 changes: 0 additions & 5 deletions spec/feature_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,6 @@ describe("csv features", function()
assert.are.same(expected, actual)
end)

it("should error out for fieldsToKeep if no headers and no renaming", function()
local options = {loadFromString=true, headers=false, fieldsToKeep={1, 2}}
assert.has.errors(function() ftcsv.parse("apple>banana>carrot\ndiamond>emerald>pearl", ">", options) end)
end)

it("should handle only renaming fields from files without headers", function()
local expected = {}
expected[1] = {}
Expand Down

0 comments on commit 20f4dd3

Please sign in to comment.