packages feed

network-arbitrary-1.0.0.1: src/Network/URI/Arbitrary.hs

{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

{-# HLINT ignore "Evaluate" #-}

-- |
-- Module      : Network.URI.Arbitrary
-- Description : Arbitrary Instances for Network.URI
-- Copyright   : (c) Alex Brandt, 2018
-- License     : MIT
--
-- Arbitrary instances for "Network.URI".
module Network.URI.Arbitrary () where

import Control.Monad
  ( replicateM,
  )
import Data.List
  ( intercalate,
  )
import Network.URI
  ( URI (..),
    URIAuth (..),
    parseURIReference,
    uriToString,
  )
import Test.QuickCheck
  ( Arbitrary (arbitrary, shrink),
    Gen,
    choose,
    elements,
    listOf,
    listOf1,
    oneof,
    suchThat,
  )

instance Arbitrary URI where
  arbitrary = do
    uriScheme <- oneof [return "", scheme]
    uriAuthority <- arbitrary :: Gen (Maybe URIAuth)
    uriPath <- path (null uriScheme) $ maybe True emptyAuthority uriAuthority
    uriQuery <- oneof [query, return ""]
    uriFragment <- oneof [fragment, return ""]

    return URI {..}
    where
      emptyAuthority URIAuth {..} = all null [uriUserInfo, uriRegName, uriPort]

  shrink URI {..} =
    filter
      isURI
      [ URI uriScheme' uriAuthority' uriPath' uriQuery' uriFragment'
        | (uriScheme', uriAuthority', uriPath', uriQuery', uriFragment') <-
            shrink
              (uriScheme, uriAuthority, uriPath, uriQuery, uriFragment)
      ]
    where
      isURI u = case parseURIReference (uriToString id u "") of
        Just u' -> u' == u
        Nothing -> False

instance Arbitrary URIAuth where
  arbitrary = URIAuth <$> userinfo <*> host `suchThat` (not . null) <*> port

  shrink URIAuth {..} =
    [ URIAuth uriUserInfo' uriRegName' uriPort'
      | (uriUserInfo', uriRegName', uriPort') <-
          shrink
            (uriUserInfo, uriRegName, uriPort)
    ]

-- * RFC 3986 Generators

--
--   Some generators are handled by the 'Arbitrary' instances above, and
--   others are folded into symbols that are preceded or followed by
--   identifying tokens.

scheme :: Gen String
scheme = do
  a <- alpha
  r <- listOf $ oneof [alpha, digit, elements ['+', '-', '.']]
  return $ a : (r ++ ":")

userinfo :: Gen String
userinfo = do
  u <- concat <$> userinfo'
  if null u then return "" else return $ u ++ "@"
  where
    userinfo' =
      listOf $
        oneof
          [replicateM 1 $ oneof [unreserved, subDelims, return ':'], percentEncoded]

host :: Gen String
host = oneof [ipLiteral, ipv4Address, regName]

port :: Gen String
port = do
  p <- listOf digit
  if null p then return "" else return $ ':' : p

ipLiteral :: Gen String
ipLiteral = do
  x <-
    oneof
      [ ipv6Address
      -- , ipvFuture
      ]
  return $ "[" ++ x ++ "]"

{- TODO Check that "Network.URI" implements this correctly.
ipvFuture :: Gen String
ipvFuture =
  do h <- hexdig
     o <- oneof [ unreserved, subDelims, return ':' ]
     return ['v', h, '.', o]
-}

ipv6Address :: Gen String
ipv6Address =
  concat
    <$> oneof
      [ sequence [b 6, ls32],
        sequence [return "::", b 5, ls32],
        sequence [h16, return "::", b 4, ls32],
        sequence [b 1, h16, return "::", b 3, ls32],
        sequence [b 2, h16, return "::", b 2, ls32],
        sequence [b 3, h16, return "::", b 1, ls32],
        sequence [b 4, h16, return "::", ls32],
        sequence [b 5, h16, return "::", h16],
        sequence [b 6, h16, return "::"]
      ]
  where
    b n = fmap concat $ replicateM n $ fmap (++ ":") h16 :: Gen String

h16 :: Gen String
h16 = replicateM 4 hexdig

ls32 :: Gen String
ls32 = oneof [intercalate ":" <$> replicateM 2 h16, ipv4Address]

ipv4Address :: Gen String
ipv4Address = intercalate "." <$> replicateM 4 decOctet

decOctet :: Gen String
decOctet = (show :: Int -> String) <$> choose (0, 255)

regName :: Gen String
regName =
  fmap concat $
    listOf $
      oneof
        [replicateM 1 unreserved, percentEncoded, replicateM 1 subDelims]

path :: Bool -> Bool -> Gen String
path emptyScheme emptyURIAuth =
  if emptyURIAuth
    then
      oneof
        [ pathAbsolute,
          if emptyScheme then pathNoScheme else pathRootless,
          return ""
        ]
    else pathAbEmpty

pathAbEmpty :: Gen String
pathAbEmpty = concat <$> listOf (('/' :) . concat <$> listOf pchar)

pathAbsolute :: Gen String
pathAbsolute = ('/' :) <$> oneof [return "", pathRootless]

pathNoScheme :: Gen String
pathNoScheme = concat <$> sequence [segment1nc, pathAbEmpty]

pathRootless :: Gen String
pathRootless = concat <$> sequence [concat <$> listOf1 pchar, pathAbEmpty]

segment1nc :: Gen String
segment1nc =
  oneof
    [ replicateM 1 unreserved,
      percentEncoded,
      replicateM 1 subDelims,
      replicateM 1 $ return '@'
    ]

pchar :: Gen String
pchar =
  oneof
    [ replicateM 1 unreserved,
      percentEncoded,
      replicateM 1 subDelims,
      replicateM 1 $ return ':',
      replicateM 1 $ return '@'
    ]

query :: Gen String
query =
  fmap (('?' :) . concat) $ listOf $ oneof [pchar, return "/", return "?"]

fragment :: Gen String
fragment =
  fmap (('#' :) . concat) $ listOf $ oneof [pchar, return "/", return "?"]

percentEncoded :: Gen String
percentEncoded = ('%' :) <$> replicateM 2 hexdig

unreserved :: Gen Char
unreserved = oneof [alpha, digit, elements ['-', '.', '_', '~']]

subDelims :: Gen Char
subDelims = elements ['!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=']

-- * RFC 2234 Generators

alpha :: Gen Char
alpha = elements $ ['a' .. 'z'] ++ ['A' .. 'Z']

digit :: Gen Char
digit = elements ['0' .. '9']

hexdig :: Gen Char
hexdig = oneof [digit, elements ['A' .. 'F']]