1 {-# LANGUAGE OverloadedStrings #-}
3 module NmcDom ( NmcDom(..)
12 import Prelude hiding (length)
13 import Control.Applicative ((<$>), (<*>), empty, pure)
15 import Data.Text (Text, unpack)
16 import Data.List (union)
17 import Data.List.Split
18 import Data.Vector ((!), length, singleton)
19 import Data.Map (Map, unionWith)
20 import qualified Data.HashMap.Strict as H (lookup)
22 import Data.Aeson.Types
24 -- Variant of Aeson's `.:?` that interprets a String as a
25 -- single-element list, so it is possible to have either
29 -- with the same result.
30 (.:/) :: (FromJSON a) => Object -> Text -> Parser (Maybe a)
31 obj .:/ key = case H.lookup key obj of
32 Nothing -> pure Nothing
34 String s -> parseJSON $ Array (singleton v)
37 class Mergeable a where
38 merge :: a -> a -> a -- bias towads second arg
40 instance (Ord k, Mergeable a) => Mergeable (Map k a) where
41 merge mx my = unionWith merge my mx
43 -- Alas, the following is not possible in Haskell :-(
44 -- instance Mergeable String where
47 instance Mergeable Value where
50 instance Mergeable a => Mergeable (Maybe a) where
51 merge (Just x) (Just y) = Just (merge x y)
52 merge Nothing (Just y) = Just y
53 merge (Just x) Nothing = Just x
54 merge Nothing Nothing = Nothing
56 instance Eq a => Mergeable [a] where
57 merge xs ys = union xs ys
59 data NmcRRService = NmcRRService
68 instance FromJSON NmcRRService where
70 if length a == 6 then NmcRRService
80 instance Mergeable NmcRRService where
83 data NmcRRI2p = NmcRRI2p
84 { i2pDestination :: String
89 instance FromJSON NmcRRI2p where
90 parseJSON (Object o) = NmcRRI2p
91 <$> o .: "destination"
96 instance Mergeable NmcRRI2p where
99 data NmcRRTls = NmcRRTls
100 { tlsMatchType :: Int -- 0:exact 1:sha256 2:sha512
101 , tlsMatchValue :: String
102 , tlsIncSubdoms :: Int -- 1:enforce on subdoms 0:no
103 } deriving (Show, Eq)
105 instance FromJSON NmcRRTls where
106 parseJSON (Array a) =
107 if length a == 3 then NmcRRTls
108 <$> parseJSON (a ! 0)
109 <*> parseJSON (a ! 1)
110 <*> parseJSON (a ! 2)
114 instance Mergeable NmcRRTls where
117 data NmcRRDs = NmcRRDs
121 , dsHashValue :: String
122 } deriving (Show, Eq)
124 instance FromJSON NmcRRDs where
125 parseJSON (Array a) =
126 if length a == 4 then NmcRRDs
127 <$> parseJSON (a ! 0)
128 <*> parseJSON (a ! 1)
129 <*> parseJSON (a ! 2)
130 <*> parseJSON (a ! 3)
134 instance Mergeable NmcRRDs where
137 data NmcDom = NmcDom { domService :: Maybe [NmcRRService]
138 , domIp :: Maybe [String]
139 , domIp6 :: Maybe [String]
140 , domTor :: Maybe String
141 , domI2p :: Maybe NmcRRI2p
142 , domFreenet :: Maybe String
143 , domAlias :: Maybe String
144 , domTranslate :: Maybe String
145 , domEmail :: Maybe String
146 , domLoc :: Maybe String
147 , domInfo :: Maybe Value
148 , domNs :: Maybe [String]
149 , domDelegate :: Maybe String
150 , domImport :: Maybe [String]
151 , domMap :: Maybe (Map String NmcDom)
152 , domFingerprint :: Maybe [String]
153 , domTls :: Maybe (Map String
154 (Map String [NmcRRTls]))
155 , domDs :: Maybe [NmcRRDs]
156 , domMx :: Maybe [String] -- Synthetic
157 , domSrv :: Maybe [String] -- Synthetic
158 } deriving (Show, Eq)
160 instance FromJSON NmcDom where
161 -- Wherever we expect a domain object, there may be a string
162 -- containing IPv4 address. Interpret it as such.
163 -- Question: shall we try to recognize IPv6 addresses too?
164 parseJSON (String s) =
165 return $ if isIPv4 s'
166 then emptyNmcDom { domIp = Just [s'] }
170 isIPv4 x = all isNibble $ splitOn "." x
172 if all isDigit x then (read x :: Int) < 256
174 parseJSON (Object o) = NmcDom
182 <*> o .:? "translate"
190 <*> o .:/ "fingerprint"
193 <*> return Nothing -- domMx not parsed
194 <*> return Nothing -- domSrv not parsed
197 instance Mergeable NmcDom where
198 merge sub dom = dom { domService = mergelm domService
199 , domIp = mergelm domIp
200 , domIp6 = mergelm domIp6
201 , domTor = choose domTor
202 , domI2p = mergelm domI2p
203 , domFreenet = choose domFreenet
204 , domAlias = choose domAlias
205 , domTranslate = choose domTranslate
206 , domEmail = choose domEmail
207 , domLoc = choose domLoc
208 , domInfo = mergelm domInfo
209 , domNs = mergelm domNs
210 , domDelegate = mergelm domDelegate
211 , domImport = mergelm domImport
212 , domMap = mergelm domMap
213 , domFingerprint = mergelm domFingerprint
214 , domTls = mergelm domTls
215 , domDs = mergelm domDs
216 , domMx = mergelm domMx
217 , domSrv = mergelm domSrv
220 mergelm x = merge (x sub) (x dom)
221 -- Because it is not possible to define instance of merge for Strings,
222 -- we have to treat string elements separately, otherwise strings are
223 -- 'unioned' along with the rest of lists. Ugly, but alternatives are worse.
224 choose field = case field dom of
228 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom
231 emptyNmcDom = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing
232 Nothing Nothing Nothing Nothing Nothing Nothing
233 Nothing Nothing Nothing Nothing Nothing Nothing