packages feed

conferer-source-json (empty) → 0.4.0.0

raw patch · 7 files changed

+271/−0 lines, 7 filesdep +aesondep +aeson-qqdep +basesetup-changed

Dependencies added: aeson, aeson-qq, base, bytestring, conferer, conferer-source-json, directory, hspec, text, unordered-containers, vector

Files

+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2019 Lucas David Traverso++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,1 @@+# conferer-source-json
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ conferer-source-json.cabal view
@@ -0,0 +1,66 @@+cabal-version: 1.18++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 75e351874fa593dd3d42bba9fbbe98f40ceb07b84a807ac0f020472cc07901d2++name:           conferer-source-json+version:        0.4.0.0+synopsis:       conferer's source for reading json files++description:    Library to abstract the parsing of many haskell config values from different config sources+category:       Configuration+homepage:       https://github.com/ludat/conferer#readme+author:         Lucas David Traverso+maintainer:     lucas6246@gmail.com+copyright:      MIT+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-doc-files:+    README.md+    LICENSE++library+  exposed-modules:+      Conferer.Source.JSON+  other-modules:+      Paths_conferer_source_json+  hs-source-dirs:+      src+  default-extensions: OverloadedStrings LambdaCase QuasiQuotes ScopedTypeVariables+  build-depends:+      aeson >=0.10 && <2.0+    , base >=4.3 && <5+    , bytestring >=0.10 && <0.11+    , conferer >=0.4.0.0 && <0.5.0.0+    , directory >=1.2 && <2.0+    , text >=1.1 && <1.3+    , unordered-containers+    , vector+  default-language: Haskell2010++test-suite specs+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Conferer.Source.JSONSpec+      Paths_conferer_source_json+  hs-source-dirs:+      test+  default-extensions: OverloadedStrings LambdaCase QuasiQuotes ScopedTypeVariables+  build-depends:+      aeson >=0.10 && <2.0+    , aeson-qq+    , base >=4.3 && <5+    , bytestring >=0.10 && <0.11+    , conferer+    , conferer-source-json+    , directory >=1.2 && <2.0+    , hspec+    , text >=1.1 && <1.3+    , unordered-containers+    , vector+  default-language: Haskell2010
+ src/Conferer/Source/JSON.hs view
@@ -0,0 +1,121 @@+module Conferer.Source.JSON+  (+  -- * How to use this source+  -- | As any other source you can add it to a config using the 'addSource'+  -- function. There are a couple of oddities that come from supporting many+  -- different sources which do not support numbers, arrays or objects+  -- nativelly+  --+  -- @+  -- import 'Conferer'+  -- import Conferer.Source.JSON ('mkJsonSource')+  --+  -- main = do+  --   config <-+  --     'defaultConfig' \"awesomeapp\"+  --     & 'addSource' 'mkJsonSource'+  --   warpSettings <- 'getFromConfig' \"warp\" config+  --   runSettings warpSettings application+  -- @+  --+  -- This will result on a source that upon starting looks the file+  -- @config/{.env}.json@ in the current directory and uses it to provide config+  -- keys.+  --+  -- TODO Describe how we transform json into key value strings+  mkJsonSource+  , mkJsonSource'++  -- * Internal utility functions+  -- | These may be useful for someone but are subject to change at any point so+  -- use with care+  , traverseJSON+  , resultToMaybe+  , valueToText+  , boolToString+  )+where++import           Data.Aeson+import qualified Data.HashMap.Strict as HashMap+import           Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import           Data.Vector+import           Text.Read (readMaybe)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import           System.Directory (doesFileExist)+import           Data.Monoid+++import Conferer.Source.Files+import Conferer.Source.Null+import Conferer.Types++-- | Default 'SourceCreator' which usese files with @config/{env}.json@+-- template, if the file is not present it will behave like the null source+-- (it has no keys) but if the file doesn't have valid json it will throw an+-- error+mkJsonSource :: SourceCreator+mkJsonSource config = do+  fileToParse <- getFilePathFromEnv config "json"+  fileExists <- doesFileExist fileToParse+  if fileExists+    then do+      value <- decodeStrict' <$> B.readFile fileToParse+      case value of+        Nothing ->+          error $ "Failed to decode file '" <> fileToParse <> "'"+        Just v -> do+          mkJsonSource' v config+    else do+      mkNullSource config++-- | Just like 'mkJsonSource' but accepts the json value as a parameter+mkJsonSource' :: Value -> SourceCreator+mkJsonSource' v = \_config ->+  return $ Source+  { getKeyInSource = \k -> do+      return $ traverseJSON k v+  }++-- | Traverse a 'Value' using a 'Key' to get a value for conferer ('Text').+--+-- This function can nest objects and arrays when keys are nested+--+-- @+-- 'traverseJSON' "a.b" {a: {b: 12}} == Just "12"+-- 'traverseJSON' "a.b" {a: {b: false}} == Just "false"+-- 'traverseJSON' "a" {a: {b: false}} == Nothing+-- 'traverseJSON' "1" [false, true] == Just "true"+-- 'traverseJSON' "0.a" [{a: "hi"}] == Just "hi"+-- 'traverseJSON' "0" [] == Nothing+-- @+traverseJSON :: Key -> Value -> Maybe Text+traverseJSON (Path []) v = valueToText v+traverseJSON (Path (k:ks)) (Object o) =+  HashMap.lookup k o >>= traverseJSON (Path ks)+traverseJSON (Path (k:ks)) (Array vs) = do+  n :: Int <- readMaybe $ Text.unpack k+  value <- vs !? n+  traverseJSON (Path ks) value+traverseJSON (Path _) _ = Nothing++valueToText :: Value -> Maybe Text+valueToText (String t) = Just t+valueToText (Object _o) = Nothing+valueToText (Array _as) = Nothing+valueToText (Number n) = Just $ Text.decodeUtf8 $ L.toStrict $ encode $ Number n+valueToText (Bool b) = Just $ boolToString b+valueToText (Null) = Nothing++boolToString :: Bool -> Text+boolToString True = "true"+boolToString False = "false"++-- | Because we use an old version of 'aeson'+resultToMaybe :: Result a -> Maybe a+resultToMaybe (Error _) = Nothing+resultToMaybe (Success a) = Just a+
+ test/Conferer/Source/JSONSpec.hs view
@@ -0,0 +1,59 @@+module Conferer.Source.JSONSpec where++import Data.Aeson.QQ+import Test.Hspec++import Conferer+import Conferer.Source.JSON++spec :: Spec+spec = do+  describe "json source" $ do+    it "getting an existing path returns the right value" $ do+      c <- emptyConfig+           & addSource (mkJsonSource' [aesonQQ| {"postgres": {"url": "some url", "ssl": true}} |])+      res <- getKey "postgres.url" c+      res `shouldBe` Just "some url"++    it "getting an non existing path returns nothing" $ do+      c <- emptyConfig+           & addSource (mkJsonSource' [aesonQQ| {"postgres": {"url": "some url", "ssl": true}} |])+      res <- getKey "some.path" c+      res `shouldBe` Nothing++    describe "with an array" $ do+      it "getting a path with number gets the right value" $ do+        c <- emptyConfig+             & addSource (mkJsonSource'+                          [aesonQQ| {"key": ["value"]} |])+        res <- getKey "key.0" c+        res `shouldBe` Just "value"++    describe "with an object" $ do+      it "getting an existing path returns nothing" $ do+        c <- emptyConfig+           & addSource (mkJsonSource'+            [aesonQQ| {"key": { "path": "value"}} |])+        res <- getKey "key" c+        res `shouldBe` Nothing++    describe "with an int" $ do+      it "getting an existing path returns the right value" $ do+        c <- emptyConfig+           & addSource (mkJsonSource' [aesonQQ| {"key": 1} |])+        res <- getKey "key" c+        res `shouldBe` Just "1"++    describe "with a float" $ do+      it "getting an existing path returns the right value" $ do+        c <- emptyConfig+           & addSource (mkJsonSource' [aesonQQ| {"key": 1.2} |])+        res <- getKey "key" c+        res `shouldBe` Just "1.2"++    describe "with a boolean" $ do+      it "getting an existing path returns the right value" $ do+        c <- emptyConfig+           & addSource (mkJsonSource' [aesonQQ| {"key": false} |])+        res <- getKey "key" c+        res `shouldBe` Just "false"
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}