use Data.Default.Class for empty NmcDom
[pdns-pipe-nmc.git] / NmcDom.hs
index 11b77ac4a01905140f19161f0451d9f6501f7ecc..79402e7da1b8f185421842552195c06a8b895812 100644 (file)
--- a/NmcDom.hs
+++ b/NmcDom.hs
@@ -2,24 +2,24 @@
 
 module NmcDom   ( NmcDom(..)
                 , NmcRRService(..)
-                , emptyNmcDom
-                , seedNmcDom
-                , descendNmcDom
+                , NmcRRI2p(..)
+                , NmcRRTls(..)
+                , NmcRRDs(..)
+                , mergeNmcDom
                 ) where
 
 import Prelude hiding (length)
-import Data.ByteString.Lazy (ByteString)
+import Control.Applicative ((<$>), (<*>), empty, pure)
+import Data.Char
 import Data.Text (Text, unpack)
-import Data.List as L (union)
+import Data.List (union)
 import Data.List.Split
-import Data.Char
-import Data.Map as M (Map, lookup, delete, size, unionWith)
-import Data.Vector (toList,(!),length, singleton)
-import Control.Applicative ((<$>), (<*>), empty, pure)
+import Data.Vector ((!), length, singleton)
+import Data.Map (Map, unionWith)
+import qualified Data.HashMap.Strict as H (lookup)
 import Data.Aeson
-
-import qualified Data.HashMap.Strict as H
 import Data.Aeson.Types
+import Data.Default.Class
 
 -- Variant of Aeson's `.:?` that interprets a String as a
 -- single-element list, so it is possible to have either
@@ -38,8 +38,9 @@ class Mergeable a where
         merge :: a -> a -> a -- bias towads second arg
 
 instance (Ord k, Mergeable a) => Mergeable (Map k a) where
-        merge mx my = M.unionWith merge my mx
+        merge mx my = unionWith merge my mx
 
+-- Alas, the following is not possible in Haskell :-(
 -- instance Mergeable String where
 --         merge _ b = b
 
@@ -53,7 +54,7 @@ instance Mergeable a => Mergeable (Maybe a) where
         merge Nothing  Nothing  = Nothing
 
 instance Eq a => Mergeable [a] where
-        merge xs ys = L.union xs ys
+        merge xs ys = union xs ys
 
 data NmcRRService = NmcRRService
                         { srvName       :: String
@@ -80,16 +81,16 @@ instance Mergeable NmcRRService where
         merge _ b = b
 
 data NmcRRI2p = NmcRRI2p
-                        { i2pDestination :: String
-                        , i2pName        :: String
-                        , i2pB32         :: String
+                        { i2pDestination :: Maybe String
+                        , i2pName        :: Maybe String
+                        , i2pB32         :: Maybe String
                         } deriving (Show, Eq)
 
 instance FromJSON NmcRRI2p where
         parseJSON (Object o) = NmcRRI2p
-                <$> o .: "destination"
-                <*> o .: "name"
-                <*> o .: "b32"
+                <$> o .:? "destination"
+                <*> o .:? "name"
+                <*> o .:? "b32"
         parseJSON _ = empty
 
 instance Mergeable NmcRRI2p where
@@ -146,23 +147,29 @@ data NmcDom = NmcDom    { domService     :: Maybe [NmcRRService]
                         , domInfo        :: Maybe Value
                         , domNs          :: Maybe [String]
                         , domDelegate    :: Maybe String
-                        , domImport      :: Maybe String
+                        , domImport      :: Maybe [String]
                         , domMap         :: Maybe (Map String NmcDom)
                         , domFingerprint :: Maybe [String]
                         , domTls         :: Maybe (Map String
                                                     (Map String [NmcRRTls]))
                         , domDs          :: Maybe [NmcRRDs]
                         , domMx          :: Maybe [String] -- Synthetic
+                        , domSrv         :: Maybe [String] -- Synthetic
                         } deriving (Show, Eq)
 
+instance Default NmcDom where
+  def = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing Nothing
+               Nothing Nothing Nothing Nothing Nothing Nothing Nothing
+               Nothing Nothing Nothing Nothing Nothing Nothing
+
 instance FromJSON NmcDom where
         -- Wherever we expect a domain object, there may be a string
         -- containing IPv4 address. Interpret it as such.
         -- Question: shall we try to recognize IPv6 addresses too?
         parseJSON (String s) =
                  return $ if isIPv4 s'
-                            then emptyNmcDom { domIp = Just [s'] }
-                            else emptyNmcDom
+                            then def { domIp = Just [s'] }
+                            else def
                           where
                             s' = unpack s
                             isIPv4 x = all isNibble $ splitOn "." x
@@ -183,12 +190,13 @@ instance FromJSON NmcDom where
                 <*> o .:? "info"
                 <*> o .:/ "ns"
                 <*> o .:? "delegate"
-                <*> o .:? "import"
+                <*> o .:/ "import"
                 <*> o .:? "map"
                 <*> o .:/ "fingerprint"
                 <*> o .:? "tls"
                 <*> o .:? "ds"
                 <*> return Nothing -- domMx not parsed
+                <*> return Nothing -- domSrv not parsed
         parseJSON _ = empty
 
 instance Mergeable NmcDom where
@@ -205,12 +213,13 @@ instance Mergeable NmcDom where
                                 , domInfo =        mergelm domInfo
                                 , domNs =          mergelm domNs
                                 , domDelegate =    mergelm domDelegate
-                                , domImport =      choose  domImport
+                                , domImport =      mergelm domImport
                                 , domMap =         mergelm domMap
                                 , domFingerprint = mergelm domFingerprint
                                 , domTls =         mergelm domTls
                                 , domDs =          mergelm domDs
                                 , domMx =          mergelm domMx
+                                , domSrv =         mergelm domSrv
                                 }
           where
                 mergelm x = merge (x sub) (x dom)
@@ -221,122 +230,5 @@ instance Mergeable NmcDom where
                         Nothing -> field sub
                         Just x  -> Just x
 
-
-emptyNmcDom = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing
-                     Nothing Nothing Nothing Nothing Nothing Nothing
-                     Nothing Nothing Nothing Nothing Nothing Nothing
-                     Nothing
-
--- | Perform query and return error string or parsed domain object
-queryNmcDom ::
-  (String -> IO (Either String ByteString)) -- ^ query operation action
-  -> String                                 -- ^ key
-  -> IO (Either String NmcDom)              -- ^ error string or domain
-queryNmcDom queryOp key = do
-  l <- queryOp key
-  case l of
-    Left estr -> return $ Left estr
-    Right str -> case decode str :: Maybe NmcDom of
-      Nothing  -> return $ Left $ "Unparseable value: " ++ (show str)
-      Just dom -> return $ Right dom
-
--- | Try to fetch "import" object and merge it into the base domain
---   Original "import" element is removed, but new imports from the
---   imported objects are processed recursively until there are none.
-mergeImport ::
-  (String -> IO (Either String ByteString)) -- ^ query operation action
-  -> Int                                    -- ^ recursion counter
-  -> NmcDom                                 -- ^ base domain
-  -> IO (Either String NmcDom)              -- ^ result with merged import
-mergeImport queryOp depth base = do
-  let
-    mbase = mergeSelf base
-    base' = mbase {domImport = Nothing}
-  -- print base
-  if depth <= 0 then return $ Left "Nesting of imports is too deep"
-  else case domImport mbase of
-    Nothing  -> return $ Right base'
-    Just key -> do
-      sub <- queryNmcDom queryOp key
-      case sub of
-        Left  e    -> return $ Left e
-        Right sub' -> mergeImport queryOp (depth - 1) $ sub' `merge` base'
-
--- | If there is an element in the map with key "", merge the contents
---   and remove this element. Do this recursively.
-mergeSelf :: NmcDom -> NmcDom
-mergeSelf base =
-  let
-    map   = domMap base
-    base' = base {domMap = removeSelf map}
-    removeSelf Nothing    = Nothing
-    removeSelf (Just map) = if size map' == 0 then Nothing else Just map'
-      where map' = M.delete "" map
-  in
-    case map of
-      Nothing   -> base'
-      Just map' ->
-        case M.lookup "" map' of
-          Nothing  -> base'
-          Just sub -> (mergeSelf sub) `merge` base'
-        -- recursion depth limited by the size of the record
-
--- | SRV case - remove everyting and filter SRV records
-normalizeSrv :: String -> String -> NmcDom -> NmcDom
-normalizeSrv serv proto dom =
-  emptyNmcDom {domService = fmap (filter needed) (domService dom)}
-    where
-      needed r = srvName r == serv && srvProto r == proto
-
--- | Presence of some elements require removal of some others
-normalizeDom :: NmcDom -> NmcDom
-normalizeDom dom = foldr id dom [ srvNormalizer
-                                , translateNormalizer
-                                , nsNormalizer
-                                ]
-  where
-    nsNormalizer dom = case domNs dom of
-      Nothing  -> dom
-      Just ns  -> emptyNmcDom { domNs = domNs dom, domEmail = domEmail dom }
-    translateNormalizer dom = case domTranslate dom of
-      Nothing  -> dom
-      Just tr  -> dom { domMap = Nothing }
-    srvNormalizer dom = dom { domService = Nothing, domMx = makemx }
-      where
-        makemx = case domService dom of
-          Nothing  -> Nothing
-          Just svl -> Just $ map makerec (filter needed svl)
-            where
-              needed sr = srvName sr == "smtp"
-                        && srvProto sr == "tcp"
-                        && srvPort sr == 25
-              makerec sr = (show (srvPrio sr)) ++ " " ++ (srvHost sr)
-
--- | Merge imports and Selfs and follow the maps tree to get dom
-descendNmcDom ::
-  (String -> IO (Either String ByteString)) -- ^ query operation action
-  -> [String]                               -- ^ subdomain chain
-  -> NmcDom                                 -- ^ base domain
-  -> IO (Either String NmcDom)              -- ^ fully processed result
-descendNmcDom queryOp subdom base = do
-  base' <- mergeImport queryOp 10 base
-  case subdom of
-    []   -> return $ fmap normalizeDom base'
-    -- A hack to handle SRV records: don't descend if ["_prot","_serv"]
-    [('_':p),('_':s)] -> return $ fmap (normalizeSrv s p) base'
-    d:ds ->
-      case base' of
-        Left err     -> return base'
-        Right base'' ->
-          case domMap base'' of
-            Nothing  -> return $ Right emptyNmcDom
-            Just map ->
-              case M.lookup d map of
-                Nothing  -> return $ Right emptyNmcDom
-                Just sub -> descendNmcDom queryOp ds sub
-
--- | Initial NmcDom populated with "import" only, suitable for "descend"
-seedNmcDom ::
-  String        -- ^ domain key (without namespace prefix)
-  -> NmcDom     -- ^ resulting seed domain
-seedNmcDom dn = emptyNmcDom { domImport = Just ("d/" ++ dn)}
+mergeNmcDom :: NmcDom -> NmcDom -> NmcDom
+mergeNmcDom = merge