diff options
author | Sam Truzjan <pxqr.sta@gmail.com> | 2013-11-29 12:06:42 +0400 |
---|---|---|
committer | Sam Truzjan <pxqr.sta@gmail.com> | 2013-11-29 12:06:42 +0400 |
commit | 9a2ad80fbf5564295842a0833a7cc4a6f2e73b81 (patch) | |
tree | a41029abe9fd8fd96efcad318e25b4c3a387d8ba | |
parent | 68bd351026d562f4784f6095bbb3c8e8dae46009 (diff) |
Return Either instead of error
-rw-r--r-- | base32-bytestring.cabal | 2 | ||||
-rw-r--r-- | src/Data/ByteString/Base32.hs | 4 | ||||
-rw-r--r-- | src/Data/ByteString/Base32/Hex.hs | 4 | ||||
-rw-r--r-- | src/Data/ByteString/Base32/Internal.hs | 19 | ||||
-rw-r--r-- | tests/Data/ByteString/Base32/HexSpec.hs | 41 | ||||
-rw-r--r-- | tests/Data/ByteString/Base32Spec.hs | 41 |
6 files changed, 61 insertions, 50 deletions
diff --git a/base32-bytestring.cabal b/base32-bytestring.cabal index 1925b62..d314bbd 100644 --- a/base32-bytestring.cabal +++ b/base32-bytestring.cabal | |||
@@ -46,7 +46,7 @@ library | |||
46 | 46 | ||
47 | test-suite spec | 47 | test-suite spec |
48 | default-language: Haskell2010 | 48 | default-language: Haskell2010 |
49 | default-extensions: OverloadedStrings | 49 | default-extensions: |
50 | type: exitcode-stdio-1.0 | 50 | type: exitcode-stdio-1.0 |
51 | hs-source-dirs: tests | 51 | hs-source-dirs: tests |
52 | main-is: Spec.hs | 52 | main-is: Spec.hs |
diff --git a/src/Data/ByteString/Base32.hs b/src/Data/ByteString/Base32.hs index efc5951..79939d1 100644 --- a/src/Data/ByteString/Base32.hs +++ b/src/Data/ByteString/Base32.hs | |||
@@ -57,10 +57,10 @@ decTable = BS.pack $ L.map decW5 [minBound .. maxBound] | |||
57 | 57 | ||
58 | -- | Decode a base32 encoded bytestring. This functions is | 58 | -- | Decode a base32 encoded bytestring. This functions is |
59 | -- case-insensitive and do not require correct padding. | 59 | -- case-insensitive and do not require correct padding. |
60 | decode :: Base32 -> ByteString | 60 | decode :: Base32 -> Either String ByteString |
61 | decode = pack5 decTable | 61 | decode = pack5 decTable |
62 | 62 | ||
63 | -- | The same as 'decode' but with additional leniency: decodeLenient | 63 | -- | The same as 'decode' but with additional leniency: decodeLenient |
64 | -- will skip non-alphabet characters. | 64 | -- will skip non-alphabet characters. |
65 | decodeLenient :: Base32 -> ByteString | 65 | decodeLenient :: Base32 -> Either String ByteString |
66 | decodeLenient = pack5Lenient decTable \ No newline at end of file | 66 | decodeLenient = pack5Lenient decTable \ No newline at end of file |
diff --git a/src/Data/ByteString/Base32/Hex.hs b/src/Data/ByteString/Base32/Hex.hs index c38c2da..d866570 100644 --- a/src/Data/ByteString/Base32/Hex.hs +++ b/src/Data/ByteString/Base32/Hex.hs | |||
@@ -55,10 +55,10 @@ decTable = BS.pack $ L.map decW5 [minBound .. maxBound] | |||
55 | 55 | ||
56 | -- | Decode a base32hex encoded bytestring. This functions is | 56 | -- | Decode a base32hex encoded bytestring. This functions is |
57 | -- case-insensitive and do not requires correct padding. | 57 | -- case-insensitive and do not requires correct padding. |
58 | decode :: Base32Hex -> ByteString | 58 | decode :: Base32Hex -> Either String ByteString |
59 | decode = pack5 decTable | 59 | decode = pack5 decTable |
60 | 60 | ||
61 | -- | The same as 'decode' but with additional leniency: decodeLenient | 61 | -- | The same as 'decode' but with additional leniency: decodeLenient |
62 | -- will skip non-alphabet characters. | 62 | -- will skip non-alphabet characters. |
63 | decodeLenient :: Base32Hex -> ByteString | 63 | decodeLenient :: Base32Hex -> Either String ByteString |
64 | decodeLenient = pack5Lenient decTable \ No newline at end of file | 64 | decodeLenient = pack5Lenient decTable \ No newline at end of file |
diff --git a/src/Data/ByteString/Base32/Internal.hs b/src/Data/ByteString/Base32/Internal.hs index 5902f60..78b1146 100644 --- a/src/Data/ByteString/Base32/Internal.hs +++ b/src/Data/ByteString/Base32/Internal.hs | |||
@@ -22,6 +22,7 @@ module Data.ByteString.Base32.Internal | |||
22 | , invIx | 22 | , invIx |
23 | ) where | 23 | ) where |
24 | 24 | ||
25 | import Control.Exception hiding (mask) | ||
25 | import Data.Bits.Extras | 26 | import Data.Bits.Extras |
26 | import Data.ByteString as BS | 27 | import Data.ByteString as BS |
27 | import Data.ByteString.Internal as BS | 28 | import Data.ByteString.Internal as BS |
@@ -151,9 +152,17 @@ unpack5 (PS fptr off len) bs | |||
151 | invIx :: Word5 | 152 | invIx :: Word5 |
152 | invIx = 255 | 153 | invIx = 255 |
153 | 154 | ||
154 | pack5Ptr :: Ptr Word5 -> ByteString -> ByteString | 155 | type Result = Either String |
156 | |||
157 | cleanup :: IO a -> Result a | ||
158 | cleanup io = unsafePerformIO $ | ||
159 | catch (io >>= evaluate >>= return . Right) handler | ||
160 | where | ||
161 | handler (ErrorCall msg) = return (Left msg) | ||
162 | |||
163 | pack5Ptr :: Ptr Word5 -> ByteString -> Result ByteString | ||
155 | pack5Ptr !tbl bs @ (PS fptr off sz) = | 164 | pack5Ptr !tbl bs @ (PS fptr off sz) = |
156 | unsafePerformIO $ do | 165 | cleanup $ do |
157 | let packedSize = dstSize $ BS.length bs | 166 | let packedSize = dstSize $ BS.length bs |
158 | BS.createAndTrim packedSize $ \ dst -> do | 167 | BS.createAndTrim packedSize $ \ dst -> do |
159 | withForeignPtr fptr $ \ ptr -> do | 168 | withForeignPtr fptr $ \ ptr -> do |
@@ -162,7 +171,7 @@ pack5Ptr !tbl bs @ (PS fptr off sz) = | |||
162 | where | 171 | where |
163 | lookupTable :: Word8 -> Word5 | 172 | lookupTable :: Word8 -> Word5 |
164 | lookupTable ix | 173 | lookupTable ix |
165 | | x == invIx = error $ "base32: decode: invalid character" ++ show ix | 174 | | x == invIx = error $ show (w2c ix) ++ " is not base32 character" |
166 | | otherwise = x | 175 | | otherwise = x |
167 | where x = inlinePerformIO (peekByteOff tbl (fromIntegral ix)) | 176 | where x = inlinePerformIO (peekByteOff tbl (fromIntegral ix)) |
168 | {-# INLINE lookupTable #-} | 177 | {-# INLINE lookupTable #-} |
@@ -221,7 +230,7 @@ pack5Ptr !tbl bs @ (PS fptr off sz) = | |||
221 | 230 | ||
222 | type DecTable = ByteString | 231 | type DecTable = ByteString |
223 | 232 | ||
224 | pack5 :: DecTable -> ByteString -> ByteString | 233 | pack5 :: DecTable -> ByteString -> Result ByteString |
225 | pack5 (PS fptr off len) bs | 234 | pack5 (PS fptr off len) bs |
226 | | len /= 256 | 235 | | len /= 256 |
227 | = error $ "base32: pack5: invalid lookup table size " ++ show len | 236 | = error $ "base32: pack5: invalid lookup table size " ++ show len |
@@ -238,7 +247,7 @@ isInAlphabet :: Ptr Word5 -> Word8 -> Bool | |||
238 | isInAlphabet !tbl !ix = | 247 | isInAlphabet !tbl !ix = |
239 | inlinePerformIO (peekByteOff tbl (fromIntegral ix)) /= invIx | 248 | inlinePerformIO (peekByteOff tbl (fromIntegral ix)) /= invIx |
240 | 249 | ||
241 | pack5Lenient :: DecTable -> ByteString -> ByteString | 250 | pack5Lenient :: DecTable -> ByteString -> Either String ByteString |
242 | pack5Lenient tbl @ (PS fptr _ _) bs = | 251 | pack5Lenient tbl @ (PS fptr _ _) bs = |
243 | unsafePerformIO $ do | 252 | unsafePerformIO $ do |
244 | withForeignPtr fptr $ \ !tbl_ptr -> do | 253 | withForeignPtr fptr $ \ !tbl_ptr -> do |
diff --git a/tests/Data/ByteString/Base32/HexSpec.hs b/tests/Data/ByteString/Base32/HexSpec.hs index 2cdadef..7cddb69 100644 --- a/tests/Data/ByteString/Base32/HexSpec.hs +++ b/tests/Data/ByteString/Base32/HexSpec.hs | |||
@@ -1,3 +1,4 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
1 | {-# OPTIONS -fno-warn-orphans #-} | 2 | {-# OPTIONS -fno-warn-orphans #-} |
2 | module Data.ByteString.Base32.HexSpec ( spec ) where | 3 | module Data.ByteString.Base32.HexSpec ( spec ) where |
3 | 4 | ||
@@ -28,19 +29,19 @@ spec = do | |||
28 | 29 | ||
29 | describe "decode" $ do | 30 | describe "decode" $ do |
30 | it "conform rfc examples" $ do | 31 | it "conform rfc examples" $ do |
31 | decode "" `shouldBe` "" | 32 | decode "" `shouldBe` Right "" |
32 | decode "CO======" `shouldBe` "f" | 33 | decode "CO======" `shouldBe` Right "f" |
33 | decode "CPNG====" `shouldBe` "fo" | 34 | decode "CPNG====" `shouldBe` Right "fo" |
34 | decode "CPNMU===" `shouldBe` "foo" | 35 | decode "CPNMU===" `shouldBe` Right "foo" |
35 | decode "CPNMUOG=" `shouldBe` "foob" | 36 | decode "CPNMUOG=" `shouldBe` Right "foob" |
36 | decode "CPNMUOJ1" `shouldBe` "fooba" | 37 | decode "CPNMUOJ1" `shouldBe` Right "fooba" |
37 | decode "CPNMUOJ1E8======" `shouldBe` "foobar" | 38 | decode "CPNMUOJ1E8======" `shouldBe` Right "foobar" |
38 | 39 | ||
39 | it "inverse for encode" $ property $ \bs -> | 40 | it "inverse for encode" $ property $ \bs -> |
40 | decode (encode bs) == bs | 41 | decode (encode bs) == Right bs |
41 | 42 | ||
42 | it "case insensitive" $ property $ \bs -> | 43 | it "case insensitive" $ property $ \bs -> |
43 | decode (BC.map toLower (encode bs)) == bs | 44 | decode (BC.map toLower (encode bs)) == Right bs |
44 | 45 | ||
45 | it "fail gracefully if encoded data contains non alphabet chars" $ do | 46 | it "fail gracefully if encoded data contains non alphabet chars" $ do |
46 | evaluate (decode "#=======") `shouldThrow` anyErrorCall | 47 | evaluate (decode "#=======") `shouldThrow` anyErrorCall |
@@ -48,20 +49,20 @@ spec = do | |||
48 | 49 | ||
49 | describe "decodeLenient" $ do | 50 | describe "decodeLenient" $ do |
50 | it "conform RFC examples" $ do | 51 | it "conform RFC examples" $ do |
51 | decode "" `shouldBe` "" | 52 | decode "" `shouldBe` Right "" |
52 | decode "CO======" `shouldBe` "f" | 53 | decode "CO======" `shouldBe` Right "f" |
53 | decode "CPNG====" `shouldBe` "fo" | 54 | decode "CPNG====" `shouldBe` Right "fo" |
54 | decode "CPNMU===" `shouldBe` "foo" | 55 | decode "CPNMU===" `shouldBe` Right "foo" |
55 | decode "CPNMUOG=" `shouldBe` "foob" | 56 | decode "CPNMUOG=" `shouldBe` Right "foob" |
56 | decode "CPNMUOJ1" `shouldBe` "fooba" | 57 | decode "CPNMUOJ1" `shouldBe` Right "fooba" |
57 | decode "CPNMUOJ1E8======" `shouldBe` "foobar" | 58 | decode "CPNMUOJ1E8======" `shouldBe` Right "foobar" |
58 | 59 | ||
59 | it "inverse for encode" $ property $ \bs -> | 60 | it "inverse for encode" $ property $ \bs -> |
60 | decodeLenient (encode bs) == bs | 61 | decodeLenient (encode bs) == Right bs |
61 | 62 | ||
62 | it "case insensitive" $ property $ \bs -> | 63 | it "case insensitive" $ property $ \bs -> |
63 | decodeLenient (BC.map toLower (encode bs)) == bs | 64 | decodeLenient (BC.map toLower (encode bs)) == Right bs |
64 | 65 | ||
65 | it "skip non alphabet chars" $ do | 66 | it "skip non alphabet chars" $ do |
66 | decodeLenient "|" `shouldBe` "" | 67 | decodeLenient "|" `shouldBe` Right "" |
67 | decodeLenient "C|O" `shouldBe` "f" | 68 | decodeLenient "C|O" `shouldBe` Right "f" |
diff --git a/tests/Data/ByteString/Base32Spec.hs b/tests/Data/ByteString/Base32Spec.hs index 148539a..4cec8f9 100644 --- a/tests/Data/ByteString/Base32Spec.hs +++ b/tests/Data/ByteString/Base32Spec.hs | |||
@@ -1,3 +1,4 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
1 | {-# OPTIONS -fno-warn-orphans #-} | 2 | {-# OPTIONS -fno-warn-orphans #-} |
2 | module Data.ByteString.Base32Spec (spec) where | 3 | module Data.ByteString.Base32Spec (spec) where |
3 | 4 | ||
@@ -34,19 +35,19 @@ spec = do | |||
34 | 35 | ||
35 | describe "decode" $ do | 36 | describe "decode" $ do |
36 | it "conform RFC examples" $ do | 37 | it "conform RFC examples" $ do |
37 | decode "" `shouldBe` "" | 38 | decode "" `shouldBe` Right "" |
38 | decode "MY======" `shouldBe` "f" | 39 | decode "MY======" `shouldBe` Right "f" |
39 | decode "MZXQ====" `shouldBe` "fo" | 40 | decode "MZXQ====" `shouldBe` Right "fo" |
40 | decode "MZXW6===" `shouldBe` "foo" | 41 | decode "MZXW6===" `shouldBe` Right "foo" |
41 | decode "MZXW6YQ=" `shouldBe` "foob" | 42 | decode "MZXW6YQ=" `shouldBe` Right "foob" |
42 | decode "MZXW6YTB" `shouldBe` "fooba" | 43 | decode "MZXW6YTB" `shouldBe` Right "fooba" |
43 | decode "MZXW6YTBOI======" `shouldBe` "foobar" | 44 | decode "MZXW6YTBOI======" `shouldBe` Right "foobar" |
44 | 45 | ||
45 | it "inverse for encode" $ property $ \bs -> | 46 | it "inverse for encode" $ property $ \bs -> |
46 | decode (encode bs) == bs | 47 | decode (encode bs) == Right bs |
47 | 48 | ||
48 | it "case insensitive" $ property $ \bs -> | 49 | it "case insensitive" $ property $ \bs -> |
49 | decode (BC.map toLower (encode bs)) == bs | 50 | decode (BC.map toLower (encode bs)) == Right bs |
50 | 51 | ||
51 | it "fail gracefully if encoded data contains non alphabet chars" $ do | 52 | it "fail gracefully if encoded data contains non alphabet chars" $ do |
52 | evaluate (decode "0=======") `shouldThrow` anyErrorCall | 53 | evaluate (decode "0=======") `shouldThrow` anyErrorCall |
@@ -54,20 +55,20 @@ spec = do | |||
54 | 55 | ||
55 | describe "decodeLenient" $ do | 56 | describe "decodeLenient" $ do |
56 | it "conform RFC examples" $ do | 57 | it "conform RFC examples" $ do |
57 | decodeLenient "" `shouldBe` "" | 58 | decodeLenient "" `shouldBe` Right "" |
58 | decodeLenient "MY======" `shouldBe` "f" | 59 | decodeLenient "MY======" `shouldBe` Right "f" |
59 | decodeLenient "MZXQ====" `shouldBe` "fo" | 60 | decodeLenient "MZXQ====" `shouldBe` Right "fo" |
60 | decodeLenient "MZXW6===" `shouldBe` "foo" | 61 | decodeLenient "MZXW6===" `shouldBe` Right "foo" |
61 | decodeLenient "MZXW6YQ=" `shouldBe` "foob" | 62 | decodeLenient "MZXW6YQ=" `shouldBe` Right "foob" |
62 | decodeLenient "MZXW6YTB" `shouldBe` "fooba" | 63 | decodeLenient "MZXW6YTB" `shouldBe` Right "fooba" |
63 | decodeLenient "MZXW6YTBOI======" `shouldBe` "foobar" | 64 | decodeLenient "MZXW6YTBOI======" `shouldBe` Right "foobar" |
64 | 65 | ||
65 | it "inverse for encode" $ property $ \bs -> | 66 | it "inverse for encode" $ property $ \bs -> |
66 | decodeLenient (encode bs) == bs | 67 | decodeLenient (encode bs) == Right bs |
67 | 68 | ||
68 | it "case insensitive" $ property $ \bs -> | 69 | it "case insensitive" $ property $ \bs -> |
69 | decodeLenient (BC.map toLower (encode bs)) == bs | 70 | decodeLenient (BC.map toLower (encode bs)) == Right bs |
70 | 71 | ||
71 | it "skip non alphabet chars" $ do | 72 | it "skip non alphabet chars" $ do |
72 | decodeLenient "|" `shouldBe` "" | 73 | decodeLenient "|" `shouldBe` Right "" |
73 | decodeLenient "M|Y" `shouldBe` "f" | 74 | decodeLenient "M|Y" `shouldBe` Right "f" |