Skip to content

Commit 49f1370

Browse files
committed
Use StringError exception and errors
Implement Milan's idea for abstract StringEncodingError Changes per Milan's review: generalerror -> iconv_error Removal of iconv: from most error messages Addition of more specific exceptions: IConvError, OutputBufferError, InvalidBytesError Eliminate GeneralError, per review Remove iconv_error, modulename, clean up show, per review
1 parent 4cb98a3 commit 49f1370

File tree

2 files changed

+78
-25
lines changed

2 files changed

+78
-25
lines changed

src/iconv.jl

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,52 @@
11
# This file is a part of Julia. License is MIT: http://julialang.org/license
22

33
module iconv
4-
import Base: close, eof, flush, read, readall, write
4+
import Base: close, eof, flush, read, readall, write, show
55
import Base.Libc: errno, strerror, E2BIG, EINVAL, EILSEQ
66
export StringEncoder, StringDecoder, encode, decode
7+
export StringEncodingError, OutputBufferError, IConvError
8+
export InvalidEncodingError, InvalidSequenceError, IncompleteSequenceError
9+
10+
11+
abstract StringEncodingError
12+
13+
# Specified encodings or the combination are not supported by iconv
14+
type InvalidEncodingError <: StringEncodingError
15+
args::Tuple{ASCIIString, ASCIIString}
16+
end
17+
InvalidEncodingError(from, to) = InvalidEncodingError((from, to))
18+
message(::Type{InvalidEncodingError}) = "Conversion from <<1>> to <<2>> not supported by iconv implementation, check that specified encodings are correct"
19+
20+
# Encountered invalid byte sequence
21+
type InvalidSequenceError <: StringEncodingError
22+
args::Tuple{ASCIIString}
23+
end
24+
InvalidSequenceError(seq::Vector{UInt8}) = InvalidSequenceError((bytes2hex(seq),))
25+
message(::Type{InvalidSequenceError}) = "Byte sequence 0x<<1>> is invalid in source encoding or cannot be represented in target encoding"
26+
27+
type IConvError <: StringEncodingError
28+
args::Tuple{ASCIIString, Int, ASCIIString}
29+
end
30+
IConvError(func) = IConvError((func, errno(), strerror(errno())))
31+
message(::Type{IConvError}) = "<<1>>: <<2>> (<<3>>)"
32+
33+
# Input ended with incomplete byte sequence
34+
type IncompleteSequenceError <: StringEncodingError ; end
35+
message(::Type{IncompleteSequenceError}) = "Incomplete byte sequence at end of input"
36+
37+
type OutputBufferError <: StringEncodingError ; end
38+
message(::Type{OutputBufferError}) = "Ran out of space in the output buffer"
39+
40+
function show(io::IO, exc::StringEncodingError)
41+
str = message(typeof(exc))
42+
for i = 1:length(exc.args)
43+
str = replace(str, "<<$i>>", exc.args[i])
44+
end
45+
print(io, str)
46+
end
47+
48+
show{T<:Union{IncompleteSequenceError,OutputBufferError}}(io::IO, exc::T) =
49+
print(io, message(T))
750

851
depsjl = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl")
952
isfile(depsjl) ? include(depsjl) : error("libiconv not properly installed. Please run\nPkg.build(\"iconv\")")
@@ -14,7 +57,7 @@ isfile(depsjl) ? include(depsjl) : error("libiconv not properly installed. Pleas
1457
function iconv_close(cd::Ptr{Void})
1558
if cd != C_NULL
1659
ccall((:iconv_close, libiconv), Cint, (Ptr{Void},), cd) == 0 ||
17-
error("failed to call iconv_close: error $(errno()) ($(strerror(errno())))")
60+
throw(IConvError("iconv_close"))
1861
end
1962
end
2063

@@ -23,9 +66,9 @@ function iconv_open(tocode, fromcode)
2366
if p != Ptr{Void}(-1)
2467
return p
2568
elseif errno() == EINVAL
26-
error("conversion from $fromcode to $tocode not supported by iconv implementation, check that specified encodings are correct")
69+
throw(InvalidEncodingError(fromcode, tocode))
2770
else
28-
error("iconv_open error $(errno()): $(strerror(errno()))")
71+
throw(IConvError("iconv_open"))
2972
end
3073
end
3174

@@ -84,16 +127,16 @@ function iconv!(cd::Ptr{Void}, inbuf::Vector{UInt8}, outbuf::Vector{UInt8},
84127

85128
# Should never happen unless a very small buffer is used
86129
if err == E2BIG && outbytesleft[] == BUFSIZE
87-
error("iconv error: ran out of space in the output buffer")
130+
throw(OutputBufferError())
88131
# Output buffer is full, or sequence is incomplete:
89132
# copy remaining bytes to the start of the input buffer for next time
90133
elseif err == E2BIG || err == EINVAL
91134
copy!(inbuf, 1, inbuf, inbytesleft_orig-inbytesleft[]+1, inbytesleft[])
92135
elseif err == EILSEQ
93-
b = inbuf[(inbytesleft_orig-inbytesleft[]+1):inbytesleft_orig]
94-
error("iconv error: byte sequence 0x$(bytes2hex(b)) is invalid in source encoding or cannot be represented in target encoding")
136+
seq = inbuf[(inbytesleft_orig-inbytesleft[]+1):inbytesleft_orig]
137+
throw(InvalidSequenceError(seq))
95138
else
96-
error("iconv error $(errno()): $(strerror(errno()))")
139+
throw(IConvError("iconv"))
97140
end
98141
end
99142

@@ -114,13 +157,11 @@ function iconv_reset!(s::Union{StringEncoder, StringDecoder})
114157
if ret == -1 % Csize_t
115158
err = errno()
116159
if err == EINVAL
117-
error("iconv error: incomplete byte sequence at end of input")
160+
throw(IncompleteSequenceError())
118161
elseif err == E2BIG
119-
error("iconv error: ran out of space in the output buffer")
120-
elseif err == EILSEQ
121-
error("iconv error: invalid byte sequence in input")
162+
throw(OutputBufferError())
122163
else
123-
error("iconv error $(errno()): $(strerror(errno()))")
164+
throw(IConvError("iconv"))
124165
end
125166
end
126167

@@ -171,7 +212,7 @@ function close(s::StringEncoder)
171212
# Make sure C memory/resources are returned
172213
finalize(s)
173214
# flush() wasn't able to empty input buffer, which cannot happen with correct data
174-
s.inbytesleft[] == 0 || error("iconv error: incomplete byte sequence at end of input")
215+
s.inbytesleft[] == 0 || throw(IncompleteSequenceError())
175216
end
176217

177218
function write(s::StringEncoder, x::UInt8)
@@ -236,7 +277,7 @@ function close(s::StringDecoder)
236277
# Make sure C memory/resources are returned
237278
finalize(s)
238279
# iconv_reset!() wasn't able to empty input buffer, which cannot happen with correct data
239-
s.inbytesleft[] == 0 || error("iconv error: incomplete byte sequence at end of input")
280+
s.inbytesleft[] == 0 || throw(IncompleteSequenceError())
240281
end
241282

242283
function read(s::StringDecoder, ::Type{UInt8})

test/runtests.jl

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,23 @@ end
2929
let s = "a string チャネルパートナーの選択"
3030
p = StringEncoder(IOBuffer(), "UTF-16LE")
3131
write(p, s.data[1:10])
32-
@test_throws ErrorException close(p)
32+
@test_throws IncompleteSequenceError close(p)
33+
34+
# This time, call show
35+
p = StringEncoder(IOBuffer(), "UTF-16LE")
36+
write(p, s.data[1:10])
37+
try
38+
close(p)
39+
catch err
40+
@test isa(err, IncompleteSequenceError)
41+
io = IOBuffer()
42+
showerror(io, err)
43+
@test takebuf_string(io) == "Incomplete byte sequence at end of input"
44+
end
3345

3446
p = StringDecoder(IOBuffer(encode(s, "UTF-16LE")[1:19]), "UTF-16LE")
3547
@test readall(p) == s[1:9]
36-
@test_throws ErrorException close(p)
48+
@test_throws IncompleteSequenceError close(p)
3749

3850
# Test stateful encoding, which output some bytes on final reset
3951
# with strings containing different scripts
@@ -48,39 +60,39 @@ let s = "a string チャネルパートナーの選択"
4860
close(p)
4961
end
5062

51-
@test_throws ErrorException encode("qwertyé€", "ASCII")
63+
@test_throws InvalidSequenceError encode("qwertyé€", "ASCII")
5264
try
5365
encode("qwertyé€", "ASCII")
5466
catch err
5567
io = IOBuffer()
5668
showerror(io, err)
5769
@test takebuf_string(io) ==
58-
"iconv error: byte sequence 0xc3a9e282ac is invalid in source encoding or cannot be represented in target encoding"
70+
"Byte sequence 0xc3a9e282ac is invalid in source encoding or cannot be represented in target encoding"
5971
end
6072

6173
# win_iconv currently does not throw an error on bytes >= 0x80 in ASCII sources
6274
# https://github.com/win-iconv/win-iconv/pull/26
6375
if OS_NAME != :Windows
64-
@test_throws ErrorException decode(b"qwertyé€", "ASCII")
76+
@test_throws InvalidSequenceError decode(b"qwertyé€", "ASCII")
6577
try
6678
decode(b"qwertyé€", "ASCII")
6779
catch err
6880
io = IOBuffer()
6981
showerror(io, err)
7082
@test takebuf_string(io) ==
71-
"iconv error: byte sequence 0xc3a9e282ac is invalid in source encoding or cannot be represented in target encoding"
83+
"Byte sequence 0xc3a9e282ac is invalid in source encoding or cannot be represented in target encoding"
7284
end
7385
end
7486

7587
let x = encode("ÄÆä", "ISO-8859-1")
76-
@test_throws ErrorException decode(x, "UTF-8")
88+
@test_throws InvalidSequenceError decode(x, "UTF-8")
7789
try
7890
decode(x, "UTF-8")
7991
catch err
8092
io = IOBuffer()
8193
showerror(io, err)
8294
@test takebuf_string(io) ==
83-
"iconv error: byte sequence 0xc4c6e4 is invalid in source encoding or cannot be represented in target encoding"
95+
"Byte sequence 0xc4c6e4 is invalid in source encoding or cannot be represented in target encoding"
8496
end
8597
end
8698

@@ -91,7 +103,7 @@ mktemp() do p, io
91103
@test readall(p, "CP1252") == s
92104
end
93105

94-
@test_throws ErrorException p = StringEncoder(IOBuffer(), "nonexistent_encoding")
95-
@test_throws ErrorException p = StringDecoder(IOBuffer(), "nonexistent_encoding")
106+
@test_throws InvalidEncodingError p = StringEncoder(IOBuffer(), "nonexistent_encoding")
107+
@test_throws InvalidEncodingError p = StringDecoder(IOBuffer(), "nonexistent_encoding")
96108

97109
nothing

0 commit comments

Comments
 (0)