Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Doctests #11

Merged
merged 18 commits into from

2 participants

@orlitzky

I've moved most of the simple examples into the haddock docs, and used doctest to check them. The hSpecs are still useful, though, for testing expected behavior that users won't care to see in the docs.

I also snuck in an extra feature, lookupRDNS, while documenting lookupPTR.

Fashionably late but better than never!

orlitzky added some commits
@orlitzky orlitzky Add a doctest suite which does nothing at the moment. 060e874
@orlitzky orlitzky Remove non-haddock example from the header of Network.DNS.
Mention thread-safety and purity in the Cabal description field.
Document the re-exported modules in Network.DNS.
d7feac5
@orlitzky orlitzky Add top-level documentation for Network.DNS.Lookup.
Remove the (now redundant, see Network.DNS.Lookup) TimeoutExpired hSpec.
5dfec76
@orlitzky orlitzky Move hSpec tests for 'lookupA' into the (tested) docs for 'lookupA'.
Refer to Data.ByteString.Char8 directly in doctests to save space.
db3f67e
@orlitzky orlitzky Move hSpec tests for 'lookupAAAA' into the (tested) docs for 'lookupA…
…AAA'.
6e4cfbb
@orlitzky orlitzky Add a note mentioning that "no records" is a successful result. 4e069e9
@orlitzky orlitzky Add documentation and tests for lookupMX. 6c23288
@orlitzky orlitzky Move hSpec tests for 'lookupAviaMX' into the (tested) docs for 'looku…
…pAviaMX'.

Add a note to Lookup.hs about sorting the results.
3240c6d
@orlitzky orlitzky Move hSpec tests for 'lookupNS' into the (tested) docs for 'lookupNS'. 4634eab
@orlitzky orlitzky Remove extra space that somehow crept into all doctests. 2bb99a9
@orlitzky orlitzky Move hSpec tests for 'lookupNSAuth' into the (tested) docs for 'looku…
…pNSAuth'.

Remove unused imports from LookupSpec.hs.
ecaa45a
@orlitzky orlitzky Move hSpec tests for 'lookupTXT' into the (tested) docs for 'lookupTXT' e2af434
@orlitzky orlitzky Move hSpec tests for 'lookupPTR' into the (tested) docs for 'lookupPTR'. 6468fad
@orlitzky orlitzky Remove unused import from LookupSpec.hs. afd6be4
@orlitzky orlitzky Add the 'lookupRDNS' function to perform reverse lookups on IP addres…
…ses.
1d5415f
@orlitzky orlitzky Move hSpec tests for 'lookupSRC' into the (tested) docs for 'lookupSRV'. 407360e
@orlitzky orlitzky Add miscellaneous documentation to Resolver.hs and remove the (now re…
…dundant) code block near the beginning of its docs.
f564e7c
@orlitzky orlitzky Add docs for lookupAAAAviaMX. 8be25fc
@kazu-yamamoto kazu-yamamoto merged commit caa33fa into from
@kazu-yamamoto

Very good! I have merged your patches. Do you think we are ready to release a new version?

@orlitzky

Yup! If I get time to add anything else it can go in a later release.

@kazu-yamamoto

I changed your Doctests.hs to use the standard way to use doctest. Please review. If you like it, I will release.

https://github.com/kazu-yamamoto/unit-test-example/blob/master/markdown/en/tutorial.md

@orlitzky

Looks good, all of the tests are still run. I didn't realize that doctest will recurse into imported modules, I'll make this change in my other programs as well.

@kazu-yamamoto

Released.

@orlitzky

Thanks again for all your help with this.

@kazu-yamamoto

Thank you, too. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 12, 2013
  1. @orlitzky
  2. @orlitzky

    Remove non-haddock example from the header of Network.DNS.

    orlitzky authored
    Mention thread-safety and purity in the Cabal description field.
    Document the re-exported modules in Network.DNS.
  3. @orlitzky

    Add top-level documentation for Network.DNS.Lookup.

    orlitzky authored
    Remove the (now redundant, see Network.DNS.Lookup) TimeoutExpired hSpec.
  4. @orlitzky

    Move hSpec tests for 'lookupA' into the (tested) docs for 'lookupA'.

    orlitzky authored
    Refer to Data.ByteString.Char8 directly in doctests to save space.
  5. @orlitzky
  6. @orlitzky
  7. @orlitzky
  8. @orlitzky

    Move hSpec tests for 'lookupAviaMX' into the (tested) docs for 'looku…

    orlitzky authored
    …pAviaMX'.
    
    Add a note to Lookup.hs about sorting the results.
  9. @orlitzky
  10. @orlitzky
  11. @orlitzky

    Move hSpec tests for 'lookupNSAuth' into the (tested) docs for 'looku…

    orlitzky authored
    …pNSAuth'.
    
    Remove unused imports from LookupSpec.hs.
  12. @orlitzky
  13. @orlitzky
  14. @orlitzky
  15. @orlitzky
  16. @orlitzky
  17. @orlitzky

    Add miscellaneous documentation to Resolver.hs and remove the (now re…

    orlitzky authored
    …dundant) code block near the beginning of its docs.
  18. @orlitzky
This page is out of date. Refresh to see the latest.
View
11 Doctests.hs
@@ -0,0 +1,11 @@
+module Main
+where
+
+import Test.DocTest
+import System.FilePath.Find ((==?), always, extension, find)
+
+find_sources :: IO [FilePath]
+find_sources = find always (extension ==? ".hs") "Network"
+
+main :: IO ()
+main = find_sources >>= doctest
View
42 Network/DNS.hs
@@ -1,29 +1,33 @@
-{-|
- Thread-safe DNS library written in Haskell.
-
- This code is written in Haskell, not using FFI.
-
- Sample code for DNS lookup:
-
-@
- import qualified Network.DNS as DNS (lookup)
- import Network.DNS hiding (lookup)
- main :: IO ()
- main = do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \\resolver -> do
- DNS.lookup resolver \"www.example.com\" A >>= print
-@
--}
-
+-- | The Network.DNS module re-exports all other exposed modules for
+-- convenience.
+--
+-- Applications will most likely use the high-level interface, while
+-- library/daemon authors may need to use the lower-level one.
+--
module Network.DNS (
-- * High level
module Network.DNS.Lookup
+ -- | The "Network.DNS.Lookup" module contains simple functions to
+ -- perform various DNS lookups. If you simply want to resolve a
+ -- hostname ('lookupA'), or find a domain's MX record
+ -- ('lookupMX'), this is the easiest way to do it.
+
, module Network.DNS.Resolver
+ -- | The "Network.DNS.Resolver" module is slightly more low-level
+ -- than "Network.DNS.Lookup". If you need to do something unusual,
+ -- you may need to use the 'lookup', 'lookupAuth', or 'lookupRaw'
+ -- functions.
+
, module Network.DNS.Types
+ -- | All of the types that the other modules use.
+
-- * Low level
, module Network.DNS.Decode
+ -- | Decoding a response.
+
, module Network.DNS.Encode
+ -- | Encoding a query.
+
) where
import Network.DNS.Lookup
@@ -31,5 +35,3 @@ import Network.DNS.Resolver
import Network.DNS.Types
import Network.DNS.Decode
import Network.DNS.Encode
-
-
View
293 Network/DNS/Lookup.hs
@@ -1,7 +1,62 @@
-{-|
- Upper level DNS lookup functions.
--}
-
+-- | Simple, high-level DNS lookup functions.
+--
+-- All of the lookup functions necessary run in IO, since they
+-- interact with the network. The return types are similar, but
+-- differ in what can be returned from a successful lookup.
+--
+-- We can think of the return type as \"either what I asked for, or
+-- an error\". For example, the 'lookupA' function, if successful,
+-- will return a list of 'IPv4'. The 'lookupMX' function will
+-- instead return a list of @('Domain',Int)@ pairs, where each pair
+-- represents a hostname and its associated priority.
+--
+-- The order of multiple results may not be consistent between
+-- lookups. If you require consistent results, apply
+-- 'Data.List.sort' to the returned list.
+--
+-- The errors that can occur are the same for all lookups. Namely:
+--
+-- * Timeout
+--
+-- * Wrong sequence number (foul play?)
+--
+-- * Unexpected data in the response
+--
+-- If an error occurs, you should be able to pattern match on the
+-- 'DNSError' constructor to determine which of these is the case.
+--
+-- /Note/: A result of \"no records\" is not considered an
+-- error. If you perform, say, an \'AAAA\' lookup for a domain with
+-- no such records, the \"success\" result would be @Right []@.
+--
+-- We perform a successful lookup of \"www.example.com\":
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.example.com"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupA resolver hostname
+-- Right [93.184.216.119]
+--
+-- The only error that we can easily cause is a timeout. We do this
+-- by creating and utilizing a 'ResolvConf' which has a timeout of
+-- one millisecond:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.example.com"
+-- >>> let badrc = defaultResolvConf { resolvTimeout = 1 }
+-- >>>
+-- >>> rs <- makeResolvSeed badrc
+-- >>> withResolver rs $ \resolver -> lookupA resolver hostname
+-- Left TimeoutExpired
+--
+-- As is the convention, successful results will always be wrapped
+-- in a 'Right', while errors will be wrapped in a 'Left'.
+--
+-- For convenience, you may wish to enable GHC's OverloadedStrings
+-- extension. This will allow you to avoid calling
+-- 'Data.ByteString.Char8.pack' on each domain name. See
+-- <http://www.haskell.org/ghc/docs/7.6.3/html/users_guide/type-class-extensions.html#overloaded-strings>
+-- for more information.
+--
module Network.DNS.Lookup (
lookupA, lookupAAAA
, lookupMX, lookupAviaMX, lookupAAAAviaMX
@@ -9,19 +64,37 @@ module Network.DNS.Lookup (
, lookupNSAuth
, lookupTXT
, lookupPTR
+ , lookupRDNS
, lookupSRV
) where
import Data.ByteString (ByteString)
+import qualified Data.ByteString.Char8 as BS (append, intercalate, pack, split)
import Data.IP
import Network.DNS.Resolver as DNS
import Network.DNS.Types
----------------------------------------------------------------
-{-|
- Resolving 'IPv4' by 'A'.
--}
+-- | Look up all \'A\' records for the given hostname.
+--
+-- A straightforward example:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.mew.org"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupA resolver hostname
+-- Right [202.232.15.101]
+--
+-- This function will also follow a CNAME and resolve its target if
+-- one exists for the queries hostname:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.kame.net"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupA resolver hostname
+-- Right [203.178.141.194]
+--
lookupA :: Resolver -> Domain -> IO (Either DNSError [IPv4])
lookupA rlv dom = do
erds <- DNS.lookup rlv dom A
@@ -34,9 +107,17 @@ lookupA rlv dom = do
unTag (RD_A x) = Right x
unTag _ = Left UnexpectedRDATA
-{-|
- Resolving 'IPv6' by 'AAAA'.
--}
+
+-- | Look up all (IPv6) \'AAAA\' records for the given hostname.
+--
+-- Examples:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.mew.org"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupAAAA resolver hostname
+-- Right [2001:240:11e:c00:00:00:00:101]
+--
lookupAAAA :: Resolver -> Domain -> IO (Either DNSError [IPv6])
lookupAAAA rlv dom = do
erds <- DNS.lookup rlv dom AAAA
@@ -51,9 +132,32 @@ lookupAAAA rlv dom = do
----------------------------------------------------------------
-{-|
- Resolving 'Domain' and its preference by 'MX'.
--}
+-- | Look up all \'MX\' records for the given hostname. Two parts
+-- constitute an MX record: a hostname , and an integer priority. We
+-- therefore return each record as a @('Domain', Int)@.
+--
+-- In this first example, we look up the MX for the domain
+-- \"example.com\". It has no MX (to prevent a deluge of spam from
+-- examples posted on the internet). But remember, \"no results\" is
+-- still a successful result.
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "example.com"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupMX resolver hostname
+-- Right []
+--
+-- The domain \"mew.org\" does however have a single MX:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "mew.org"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupMX resolver hostname
+-- Right [("mail.mew.org.",10)]
+--
+-- Also note that all hostnames are returned with a trailing dot to
+-- indicate the DNS root.
+--
lookupMX :: Resolver -> Domain -> IO (Either DNSError [(Domain,Int)])
lookupMX rlv dom = do
erds <- DNS.lookup rlv dom MX
@@ -66,15 +170,30 @@ lookupMX rlv dom = do
unTag (RD_MX pr dm) = Right (dm,pr)
unTag _ = Left UnexpectedRDATA
-{-|
- Resolving 'IPv4' by 'A' via 'MX'.
--}
+-- | Look up all \'MX\' records for the given hostname, and then
+-- resolve their hostnames to IPv4 addresses by calling
+-- 'lookupA'. The priorities are not retained.
+--
+-- Examples:
+--
+-- >>> import Data.List (sort)
+-- >>> let hostname = Data.ByteString.Char8.pack "mixi.jp"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> ips <- withResolver rs $ \resolver -> lookupAviaMX resolver hostname
+-- >>> fmap sort ips
+-- Right [202.32.29.4,202.32.29.5]
+--
+-- Since there is more than one result, it is necessary to sort the
+-- list in order to check for equality.
+--
lookupAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv4])
lookupAviaMX rlv dom = lookupXviaMX rlv dom (lookupA rlv)
-{-|
- Resolving 'IPv6' by 'AAAA' via 'MX'.
--}
+-- | Look up all \'MX\' records for the given hostname, and then
+-- resolve their hostnames to IPv6 addresses by calling
+-- 'lookupAAAA'. The priorities are not retained.
+--
lookupAAAAviaMX :: Resolver -> Domain -> IO (Either DNSError [IPv6])
lookupAAAAviaMX rlv dom = lookupXviaMX rlv dom (lookupAAAA rlv)
@@ -123,26 +242,71 @@ lookupNSImpl lookup_function rlv dom = do
unTag (RD_NS dm) = Right dm
unTag _ = Left UnexpectedRDATA
-{-|
- Resolving 'Domain' by 'NS'. Results taken from the ANSWER section of
- the response.
--}
+-- | Look up all \'NS\' records for the given hostname. The results
+-- are taken from the ANSWER section of the response (as opposed to
+-- AUTHORITY). For details, see e.g.
+-- <http://www.zytrax.com/books/dns/ch15/>.
+--
+-- There will typically be more than one name server for a
+-- domain. It is therefore extra important to sort the results if
+-- you prefer them to be at all deterministic.
+--
+-- Examples:
+--
+-- >>> import Data.List (sort)
+-- >>> let hostname = Data.ByteString.Char8.pack "mew.org"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> ns <- withResolver rs $ \resolver -> lookupNS resolver hostname
+-- >>> fmap sort ns
+-- Right ["ns1.mew.org.","ns2.mew.org."]
+--
lookupNS :: Resolver -> Domain -> IO (Either DNSError [Domain])
lookupNS = lookupNSImpl DNS.lookup
-{-|
- Resolving 'Domain' by 'NS'. Results taken from the AUTHORITY section
- of the response.
--}
+-- | Look up all \'NS\' records for the given hostname. The results
+-- are taken from the AUTHORITY section of the response and not the
+-- usual ANSWER (use 'lookupNS' for that). For details, see e.g.
+-- <http://www.zytrax.com/books/dns/ch15/>.
+--
+-- There will typically be more than one name server for a
+-- domain. It is therefore extra important to sort the results if
+-- you prefer them to be at all deterministic.
+--
+-- For an example, we can look up the nameservers for
+-- \"example.com\" from one of the root servers, a.gtld-servers.net,
+-- the IP address of which was found beforehand:
+--
+-- >>> import Data.List (sort)
+-- >>> let hostname = Data.ByteString.Char8.pack "example.com"
+-- >>>
+-- >>> let ri = RCHostName "192.5.6.30" -- a.gtld-servers.net
+-- >>> let rc = defaultResolvConf { resolvInfo = ri }
+-- >>> rs <- makeResolvSeed rc
+-- >>> ns <- withResolver rs $ \resolver -> lookupNSAuth resolver hostname
+-- >>> fmap sort ns
+-- Right ["a.iana-servers.net.","b.iana-servers.net."]
+--
lookupNSAuth :: Resolver -> Domain -> IO (Either DNSError [Domain])
lookupNSAuth = lookupNSImpl DNS.lookupAuth
----------------------------------------------------------------
-{-|
- Resolving 'String' by 'TXT'.
--}
+-- | Look up all \'TXT\' records for the given hostname. The results
+-- are free-form 'ByteString's.
+--
+-- Two common uses for \'TXT\' records are
+-- <http://en.wikipedia.org/wiki/Sender_Policy_Framework> and
+-- <http://en.wikipedia.org/wiki/DomainKeys_Identified_Mail>. As an
+-- example, we find the SPF record for \"mew.org\":
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "mew.org"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupTXT resolver hostname
+-- Right ["v=spf1 +mx -all"]
+--
lookupTXT :: Resolver -> Domain -> IO (Either DNSError [ByteString])
lookupTXT rlv dom = do
erds <- DNS.lookup rlv dom TXT
@@ -157,9 +321,21 @@ lookupTXT rlv dom = do
----------------------------------------------------------------
-{-|
- Resolving 'Domain' and its preference by 'PTR'.
--}
+-- | Look up all \'PTR\' records for the given hostname. To perform a
+-- reverse lookup on an IP address, you must first reverse its
+-- octets and then append the suffix \".in-addr.arpa.\"
+--
+-- We look up the PTR associated with the IP address
+-- 210.130.137.80, i.e., 80.137.130.210.in-addr.arpa:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "80.137.130.210.in-addr.arpa"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupPTR resolver hostname
+-- Right ["www-v4.iij.ad.jp."]
+--
+-- The 'lookupRDNS' function is more suited to this particular task.
+--
lookupPTR :: Resolver -> Domain -> IO (Either DNSError [Domain])
lookupPTR rlv dom = do
erds <- DNS.lookup rlv dom PTR
@@ -172,11 +348,56 @@ lookupPTR rlv dom = do
unTag (RD_PTR dm) = Right dm
unTag _ = Left UnexpectedRDATA
+
+-- | Convenient wrapper around 'lookupPTR' to perform a reverse lookup
+-- on a single IP address.
+--
+-- We repeat the example from 'lookupPTR', except now we pass the IP
+-- address directly:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "210.130.137.80"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupRDNS resolver hostname
+-- Right ["www-v4.iij.ad.jp."]
+--
+lookupRDNS :: Resolver -> Domain -> IO (Either DNSError [Domain])
+lookupRDNS rlv ip = lookupPTR rlv dom
+ where
+ -- ByteString constants.
+ dot = BS.pack "."
+ suffix = BS.pack ".in-addr.arpa"
+
+ octets = BS.split '.' ip
+ reverse_ip = BS.intercalate dot (reverse octets)
+ dom = reverse_ip `BS.append` suffix
+
----------------------------------------------------------------
-{-|
- Resolving 'Domain' and its preference by 'SRV'.
--}
+-- | Look up all \'SRV\' records for the given hostname. A SRV record
+-- comprises four fields,
+--
+-- * Priority (lower is more-preferred)
+--
+-- * Weight (relative frequency with which to use this record
+-- amongst all results with the same priority)
+--
+-- * Port (the port on which the service is offered)
+--
+-- * Target (the hostname on which the service is offered)
+--
+-- The first three are integral, and the target is another DNS
+-- hostname. We therefore return a four-tuple
+-- @(Int,Int,Int,'Domain')@.
+--
+-- Examples:
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "_sip._tcp.cisco.com"
+-- >>>
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookupSRV resolver hostname
+-- Right [(1,0,5060,"vcsgw.cisco.com.")]
+--
lookupSRV :: Resolver -> Domain -> IO (Either DNSError [(Int,Int,Int,Domain)])
lookupSRV rlv dom = do
erds <- DNS.lookup rlv dom SRV
View
127 Network/DNS/Resolver.hs
@@ -1,20 +1,5 @@
{-# LANGUAGE CPP #-}
-{-|
- DNS Resolver and lookup functions.
-
- Sample code:
-
-@
- import qualified Network.DNS as DNS (lookup)
- import Network.DNS hiding (lookup)
- main :: IO ()
- main = do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \\resolver -> do
- DNS.lookup resolver \"www.example.com\" A >>= print
-@
--}
-
+-- | DNS Resolver and generic (lower-level) lookup functions.
module Network.DNS.Resolver (
-- * Documentation
-- ** Configuration for resolver
@@ -49,15 +34,21 @@ import Control.Monad (when)
#endif
----------------------------------------------------------------
-{-|
- Union type for 'FilePath' and 'HostName'. Specify 'FilePath' to
- \"resolv.conf\" or numeric IP address in 'String' form.
--}
+
+-- | Union type for 'FilePath' and 'HostName'. Specify 'FilePath' to
+-- \"resolv.conf\" or numeric IP address in 'String' form.
+--
+-- /Warning/: Only numeric IP addresses are valid @RCHostName@s.
+--
+-- Example (using Google's public DNS cache):
+--
+-- >>> let cache = RCHostName "8.8.8.8"
+--
data FileOrNumericHost = RCFilePath FilePath | RCHostName HostName
-{-|
- Type for resolver configuration
--}
+
+-- | Type for resolver configuration. The easiest way to construct a
+-- @ResolvConf@ object is to modify the 'defaultResolvConf'.
data ResolvConf = ResolvConf {
resolvInfo :: FileOrNumericHost
, resolvTimeout :: Int
@@ -65,12 +56,20 @@ data ResolvConf = ResolvConf {
, resolvBufsize :: Integer
}
-{-|
- Default 'ResolvConf'.
- 'resolvInfo' is 'RCFilePath' \"\/etc\/resolv.conf\".
- 'resolvTimeout' is 3,000,000 micro seconds.
- 'resolvBufsize' is 512. (obsoleted)
--}
+
+-- | Return a default 'ResolvConf':
+--
+-- * 'resolvInfo' is 'RCFilePath' \"\/etc\/resolv.conf\".
+--
+-- * 'resolvTimeout' is 3,000,000 micro seconds.
+--
+-- * 'resolvBufsize' is 512. (obsoleted)
+--
+-- Example (use Google's public DNS cache instead of resolv.conf):
+--
+-- >>> let cache = RCHostName "8.8.8.8"
+-- >>> let rc = defaultResolvConf { resolvInfo = cache }
+--
defaultResolvConf :: ResolvConf
defaultResolvConf = ResolvConf {
resolvInfo = RCFilePath "/etc/resolv.conf"
@@ -101,9 +100,13 @@ data Resolver = Resolver {
----------------------------------------------------------------
-{-|
- Making 'ResolvSeed' from an IP address of a DNS cache server.
--}
+
+-- | Make a 'ResolvSeed' from a 'ResolvConf'.
+--
+-- Examples:
+--
+-- >>> rs <- makeResolvSeed defaultResolvConf
+--
makeResolvSeed :: ResolvConf -> IO ResolvSeed
makeResolvSeed conf = ResolvSeed <$> addr
<*> pure (resolvTimeout conf)
@@ -129,11 +132,10 @@ makeAddrInfo addr = do
----------------------------------------------------------------
-{-|
- Giving a thread-safe 'Resolver' to the function of the second
- argument. 'withResolver' should be passed to 'forkIO'.
--}
+-- | Giving a thread-safe 'Resolver' to the function of the second
+-- argument. 'withResolver' should be passed to 'forkIO'. For
+-- examples, see "Network.DNS.Lookup".
withResolver :: ResolvSeed -> (Resolver -> IO a) -> IO a
withResolver seed func = do
let ai = addrInfo seed
@@ -174,6 +176,14 @@ lookupSection section rlv dom typ = (>>= toRDATA) <$> lookupRaw rlv dom typ
-- | Look up resource records for a domain, collecting the results
-- from the ANSWER section of the response.
+--
+-- We repeat an example from "Network.DNS.Lookup":
+--
+-- >>> let hostname = Data.ByteString.Char8.pack "www.example.com"
+-- >>> rs <- makeResolvSeed defaultResolvConf
+-- >>> withResolver rs $ \resolver -> lookup resolver hostname A
+-- Right [93.184.216.119]
+--
lookup :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])
lookup = lookupSection answer
@@ -182,9 +192,48 @@ lookup = lookupSection answer
lookupAuth :: Resolver -> Domain -> TYPE -> IO (Either DNSError [RDATA])
lookupAuth = lookupSection authority
-{-|
- Looking up a domain and returning an entire DNS Response.
--}
+
+-- | Look up a name and return the entire DNS Response. Sample output
+-- is included below, however it is /not/ tested -- the sequence
+-- number is unpredictable (it has to be!).
+--
+-- The example code:
+--
+-- @
+-- let hostname = Data.ByteString.Char8.pack \"www.example.com\"
+-- rs <- makeResolvSeed defaultResolvConf
+-- withResolver rs $ \resolver -> lookupRaw resolver hostname A
+-- @
+--
+-- And the (formatted) expected output:
+--
+-- @
+-- Right (DNSFormat
+-- { header = DNSHeader
+-- { identifier = 1,
+-- flags = DNSFlags
+-- { qOrR = QR_Response,
+-- opcode = OP_STD,
+-- authAnswer = False,
+-- trunCation = False,
+-- recDesired = True,
+-- recAvailable = True,
+-- rcode = NoErr },
+-- qdCount = 1,
+-- anCount = 1,
+-- nsCount = 0,
+-- arCount = 0},
+-- question = [Question { qname = \"www.example.com.\",
+-- qtype = A}],
+-- answer = [ResourceRecord {rrname = \"www.example.com.\",
+-- rrtype = A,
+-- rrttl = 800,
+-- rdlen = 4,
+-- rdata = 93.184.216.119}],
+-- authority = [],
+-- additional = []})
+-- @
+--
lookupRaw :: Resolver -> Domain -> TYPE -> IO (Either DNSError DNSFormat)
lookupRaw rlv dom typ = do
seqno <- genId rlv
View
32 dns.cabal
@@ -5,7 +5,9 @@ Maintainer: Kazu Yamamoto <kazu@iij.ad.jp>
License: BSD3
License-File: LICENSE
Synopsis: DNS library in Haskell
-Description: DNS library for clients and servers.
+Description:
+ A thread-safe DNS library for both clients and servers written
+ in pure Haskell.
Category: Network
Cabal-Version: >= 1.10
Build-Type: Simple
@@ -86,6 +88,34 @@ Test-Suite spec
, network-conduit
, random
+-- The only dependencies we need to /build/ the doctest suite are
+-- base, doctest, and filemanip. If you want to be able to /run/ them,
+-- however, you're going to need the rest of the deps that might be
+-- used in the examples.
+Test-Suite doctests
+ Type: exitcode-stdio-1.0
+ Default-Language: Haskell2010
+ Hs-Source-Dirs: .
+ Ghc-Options: -Wall
+ Main-Is: Doctests.hs
+ Build-Depends: base
+ , attoparsec
+ , attoparsec-conduit
+ , binary
+ , blaze-builder
+ , bytestring
+ , conduit >= 0.5
+ , containers
+ , dns
+ , doctest >= 0.9
+ , filemanip == 0.3.*
+ , hspec
+ , iproute >= 1.2.4
+ , mtl
+ , network >= 2.3
+ , network-conduit
+ , random
+
Source-Repository head
Type: git
Location: git://github.com/kazu-yamamoto/dns.git
View
88 test2/LookupSpec.hs
@@ -2,37 +2,11 @@
module LookupSpec where
-import Control.Applicative
-import qualified Data.ByteString.Char8 as BS
-import Data.List
import Network.DNS as DNS
import Test.Hspec
spec :: Spec
spec = do
- describe "lookupA" $ do
- it "gets IPv4 addresses" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver ->
- DNS.lookupA resolver "www.mew.org"
- `shouldReturn`
- Right ["202.232.15.101"]
-
- it "gets IPv4 addresses via CNAME" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver ->
- DNS.lookupA resolver "www.kame.net"
- `shouldReturn`
- Right ["203.178.141.194"]
-
- it "returns TimeoutExpired on timeout" $ do
- -- Use a timeout of one millisecond.
- let badrc = defaultResolvConf { resolvTimeout = 1 }
- rs <- makeResolvSeed badrc
- withResolver rs $ \resolver ->
- DNS.lookupA resolver "www.example.com"
- `shouldReturn`
- Left TimeoutExpired
describe "lookupAAAA" $ do
it "gets IPv6 addresses" $ do
@@ -42,65 +16,3 @@ spec = do
`shouldReturn`
Right []
- DNS.lookupAAAA resolver "www.mew.org"
- `shouldReturn`
- Right ["2001:240:11e:c00::101"]
-
- describe "lookupNS" $ do
- it "gets NS" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver -> do
- actual <- DNS.lookupNS resolver "mew.org"
- let expected = Right ["ns1.mew.org.", "ns2.mew.org."]
- -- The order of NS records is variable, so we sort the
- -- result.
- sort <$> actual `shouldBe` expected
-
- describe "lookupNSAuth" $ do
- it "gets NS" $ do
- -- We expect the GTLD servers to return the NS in the
- -- AUTHORITY section of the response.
- let ri = RCHostName "192.5.6.30" -- a.gtld-servers.net
- let rc = defaultResolvConf { resolvInfo = ri }
- rs <- makeResolvSeed rc
- withResolver rs $ \resolver -> do
- actual <- DNS.lookupNSAuth resolver "example.com"
- let expected = Right ["a.iana-servers.net.",
- "b.iana-servers.net."]
- -- The order of NS records is variable, so we sort the
- -- result.
- sort <$> actual `shouldBe` expected
-
- describe "lookupTXT" $ do
- it "gets TXT" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver ->
- DNS.lookupTXT resolver "mew.org"
- `shouldReturn`
- Right ["v=spf1 +mx -all"]
-
- describe "lookupAviaMX" $ do
- it "gets IPv4 addresses via MX" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver -> do
- as <- DNS.lookupAviaMX resolver "mixi.jp"
- sort <$> as `shouldBe` Right ["202.32.29.4", "202.32.29.5"]
-
- describe "lookupPTR" $ do
- it "gets PTR" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver -> do
- let target = "210.130.137.80"
- rev = BS.intercalate "." (reverse (BS.split '.' target))
- `BS.append` ".in-addr.arpa"
- DNS.lookupPTR resolver rev
- `shouldReturn`
- Right ["www-v4.iij.ad.jp."]
-
- describe "lookupSRV" $ do
- it "gets SRV" $ do
- rs <- makeResolvSeed defaultResolvConf
- withResolver rs $ \resolver ->
- DNS.lookupSRV resolver "_sip._tcp.cisco.com"
- `shouldReturn`
- Right [(1,0,5060,"vcsgw.cisco.com.")]
Something went wrong with that request. Please try again.