1 {-# LANGUAGE OverloadedStrings #-}
3 module NmcDom ( NmcDom(..)
11 import Prelude hiding (length)
12 import Control.Applicative ((<$>), (<*>), empty, pure)
14 import Data.Text (Text, unpack)
15 import Data.List (union)
16 import Data.List.Split
17 import Data.Vector ((!), length, singleton)
18 import Data.Map (Map, unionWith)
19 import qualified Data.HashMap.Strict as H (lookup)
21 import Data.Aeson.Types
22 import Data.Default.Class
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 :: Maybe String
85 , i2pName :: Maybe String
86 , i2pB32 :: Maybe 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 , domTlsa :: Maybe [String] -- Synthetic
159 } deriving (Show, Eq)
161 instance Default NmcDom where
162 def = NmcDom Nothing Nothing Nothing Nothing Nothing Nothing Nothing
163 Nothing Nothing Nothing Nothing Nothing Nothing Nothing
164 Nothing Nothing Nothing Nothing Nothing Nothing Nothing
166 instance FromJSON NmcDom where
167 -- Wherever we expect a domain object, there may be a string
168 -- containing IPv4 address. Interpret it as such.
169 -- Question: shall we try to recognize IPv6 addresses too?
170 parseJSON (String s) =
171 return $ if isIPv4 s'
172 then def { domIp = Just [s'] }
176 isIPv4 x = all isNibble $ splitOn "." x
178 if all isDigit x then (read x :: Int) < 256
180 parseJSON (Object o) = NmcDom
188 <*> o .:? "translate"
196 <*> o .:/ "fingerprint"
199 <*> return Nothing -- domMx not parsed
200 <*> return Nothing -- domSrv not parsed
201 <*> return Nothing -- domTlsa not parsed
204 instance Mergeable NmcDom where
205 merge sub dom = dom { domService = mergelm domService
206 , domIp = mergelm domIp
207 , domIp6 = mergelm domIp6
208 , domTor = choose domTor
209 , domI2p = mergelm domI2p
210 , domFreenet = choose domFreenet
211 , domAlias = choose domAlias
212 , domTranslate = choose domTranslate
213 , domEmail = choose domEmail
214 , domLoc = choose domLoc
215 , domInfo = mergelm domInfo
216 , domNs = mergelm domNs
217 , domDelegate = mergelm domDelegate
218 , domImport = mergelm domImport
219 , domMap = mergelm domMap
220 , domFingerprint = mergelm domFingerprint
221 , domTls = mergelm domTls
222 , domDs = mergelm domDs
223 , domMx = mergelm domMx
224 , domSrv = mergelm domSrv
225 , domTlsa = mergelm domTlsa
228 mergelm x = merge (x sub) (x dom)
229 -- Because it is not possible to define instance of merge for Strings,
230 -- we have to treat string elements separately, otherwise strings are
231 -- 'unioned' along with the rest of lists. Ugly, but alternatives are worse.
232 choose field = case field dom of
236 mergeNmcDom :: NmcDom -> NmcDom -> NmcDom