Skip to content

Commit 70af8d4

Browse files
committed
Add safe conversion to HexString
1 parent 5c065f6 commit 70af8d4

File tree

2 files changed

+32
-7
lines changed

2 files changed

+32
-7
lines changed

src/Data/HexString.hs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
module Data.HexString ( HexString
22
, hexString
3+
, hexString'
34
, fromBinary
5+
, fromBinary'
46
, toBinary
57
, fromBytes
8+
, fromBytes'
69
, toBytes
710
, toText ) where
811

@@ -27,33 +30,51 @@ data HexString =
2730
deriving ( Show, Eq, Ord )
2831

2932
instance FromJSON HexString where
30-
parseJSON = withText "HexString" $ pure . hexString . TE.encodeUtf8
33+
parseJSON = withText "HexString" $ toParser . TE.encodeUtf8
34+
where
35+
toParser input = case hexString' input of
36+
(Just value) -> pure value
37+
Nothing -> fail ("Not a valid hex string: " ++ show input)
3138

3239
instance ToJSON HexString where
3340
toJSON = String . toText
3441

3542
-- | Smart constructor which validates that all the text are actually
3643
-- hexadecimal characters.
37-
hexString :: BS.ByteString -> HexString
38-
hexString bs =
44+
hexString' :: BS.ByteString -> Maybe HexString
45+
hexString' bs =
3946
let isValidHex :: Word8 -> Bool
4047
isValidHex c
4148
| (48 <= c) && (c < 58) = True
4249
| (97 <= c) && (c < 103) = True
4350
| otherwise = False
44-
4551
in if BS.all isValidHex bs
46-
then HexString bs
47-
else error ("Not a valid hex string: " ++ show bs)
52+
then Just (HexString bs)
53+
else Nothing
54+
55+
hexString :: BS.ByteString -> HexString
56+
hexString bs = case hexString' bs of
57+
Just hex -> hex
58+
Nothing -> error ("Not a valid hex string: " ++ show bs)
59+
60+
-- | Converts a 'B.Binary' to a 'Maybe HexString' value
61+
fromBinary' :: B.Binary a => a -> Maybe HexString
62+
fromBinary' = hexString' . BS16.encode . BSL.toStrict . B.encode
4863

4964
-- | Converts a 'B.Binary' to a 'HexString' value
50-
fromBinary :: B.Binary a => a -> HexString
65+
fromBinary :: B.Binary a => a -> HexString
5166
fromBinary = hexString . BS16.encode . BSL.toStrict . B.encode
5267

5368
-- | Converts a 'HexString' to a 'B.Binary' value
5469
toBinary :: B.Binary a => HexString -> a
5570
toBinary (HexString bs) = B.decode . BSL.fromStrict . fst . BS16.decode $ bs
5671

72+
-- | Reads a 'BS.ByteString' as raw bytes and converts to hex representation. We
73+
-- cannot use the instance Binary of 'BS.ByteString' because it provides
74+
-- a leading length, which is not what we want when dealing with raw bytes.
75+
fromBytes' :: BS.ByteString -> Maybe HexString
76+
fromBytes' = hexString' . BS16.encode
77+
5778
-- | Reads a 'BS.ByteString' as raw bytes and converts to hex representation. We
5879
-- cannot use the instance Binary of 'BS.ByteString' because it provides
5980
-- a leading length, which is not what we want when dealing with raw bytes.

test/Data/HexStringSpec.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module Data.HexStringSpec where
22

33
import Data.HexString ( hexString
4+
, hexString'
45
, fromBytes
56
, toBytes )
67

@@ -20,6 +21,9 @@ spec = do
2021
putStrLn (show (hexString (BS8.pack "`"))) `shouldThrow` anyErrorCall
2122
putStrLn (show (hexString (BS8.pack "g"))) `shouldThrow` anyErrorCall
2223

24+
it "should return nothing when rejecting" $
25+
(hexString' (BS8.pack "/")) `shouldBe` Nothing
26+
2327
describe "when interpreting a hex string" $ do
2428
it "should convert the hex string properly when interpreting as bytes" $
2529
toBytes (hexString (BS8.pack "ffff")) `shouldBe` BS8.pack "\255\255"

0 commit comments

Comments
 (0)