packages feed

dhall-json 1.6.4 → 1.7.0

raw patch · 24 files changed

+348/−180 lines, 24 filesdep +tasty-silverdep ~aeson-yamldep ~containersdep ~dhallPVP ok

version bump matches the API change (PVP)

Dependencies added: tasty-silver

Dependency ranges changed: aeson-yaml, containers, dhall

API changes (from Hackage documentation)

+ Dhall.JSON.Yaml: [noEdit] :: Options -> Bool
+ Dhall.JSON.Yaml: generatedCodeNotice :: ByteString
- Dhall.JSON.Yaml: Options :: Bool -> (Value -> Value) -> Bool -> Bool -> Conversion -> Maybe FilePath -> Maybe FilePath -> Options
+ Dhall.JSON.Yaml: Options :: Bool -> (Value -> Value) -> Bool -> Bool -> Conversion -> Maybe FilePath -> Maybe FilePath -> Bool -> Options
- Dhall.JSONToDhall: Mismatch :: ExprX -> Value -> CompileError
+ Dhall.JSONToDhall: Mismatch :: ExprX -> Value -> JSONPath -> CompileError
- Dhall.JSONToDhall: MissingKey :: Text -> ExprX -> Value -> CompileError
+ Dhall.JSONToDhall: MissingKey :: Text -> ExprX -> Value -> JSONPath -> CompileError
- Dhall.JSONToDhall: UnhandledKeys :: [Text] -> ExprX -> Value -> CompileError
+ Dhall.JSONToDhall: UnhandledKeys :: [Text] -> ExprX -> Value -> JSONPath -> CompileError

Files

CHANGELOG.md view
@@ -1,3 +1,18 @@+1.7.0++* BREAKING CHANGE: [Add `--generated-comment` flag for `dhall-to-yaml{-ng}`](https://github.com/dhall-lang/dhall-haskell/pull/1840)+    * You can now optionally add a comment header to the YAML output+      indicating that the file is generated and should not be hand-edited+    * This is a breaking change because this adds a new `noEdit` field to the+      options type+    * In practice this breakage won't affect most users+* [Produce output compatible with YAML 1.1](https://github.com/dhall-lang/dhall-haskell/pull/1788)+    * Special strings like `on` are now quoted in order to avoid being+      misinterpreted as boolean values by YAML 1.1 implementations+* [Show JSON/YAML path on error reporting](https://github.com/dhall-lang/dhall-haskell/pull/1799)+    * Error messages will now include the path to the error in the diagnostic+      output+ 1.6.4  * [Add `json-to-dhall` support for inferring the schema](https://github.com/dhall-lang/dhall-haskell/pull/1773)
dhall-json.cabal view
@@ -1,5 +1,5 @@ Name: dhall-json-Version: 1.6.4+Version: 1.7.0 Cabal-Version: >=1.10 Build-Type: Simple Tested-With: GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1@@ -27,6 +27,9 @@     CHANGELOG.md     tasty/data/*.dhall     tasty/data/*.json+    tasty/data/error/*.golden+    tasty/data/error/*.json+    tasty/data/error/*.dhall  Source-Repository head     Type: git@@ -36,12 +39,12 @@     Hs-Source-Dirs: src     Build-Depends:         base                      >= 4.8.0.0  && < 5   ,-        aeson                     >= 1.0.0.0  && < 1.5 ,+        aeson                     >= 1.0.0.0  && < 1.6 ,         aeson-pretty                             < 0.9 ,         aeson-yaml                >= 1.0.6    && < 1.1 ,         bytestring                               < 0.11,-        containers                                     ,-        dhall                     >= 1.31.0   && < 1.33,+        containers                >= 0.5.9    && < 0.7 ,+        dhall                     >= 1.33.0   && < 1.34,         exceptions                >= 0.8.3    && < 0.11,         filepath                                 < 1.5 ,         optparse-applicative      >= 0.14.0.0 && < 0.16,@@ -115,13 +118,14 @@     Hs-Source-Dirs: tasty     Main-Is: Main.hs     Build-Depends:-        base              ,-        aeson             ,-        bytestring        ,-        dhall             ,-        dhall-json        ,-        tasty       <  1.3,-        text              ,-        tasty-hunit >= 0.2+        base               ,+        aeson              ,+        bytestring         ,+        dhall              ,+        dhall-json         ,+        tasty        <  1.4,+        text               ,+        tasty-hunit  >= 0.2,+        tasty-silver >= 3.0     GHC-Options: -Wall     Default-Language: Haskell2010
src/Dhall/DhallToYaml/Main.hs view
@@ -6,12 +6,12 @@ module Dhall.DhallToYaml.Main (main) where  import Control.Applicative (optional, (<|>))-import Control.Exception (SomeException)-import Data.ByteString (ByteString)-import Data.Monoid ((<>))-import Data.Text (Text)-import Dhall.JSON (parsePreservationAndOmission, parseConversion)-import Dhall.JSON.Yaml (Options(..), parseDocuments, parseQuoted)+import Control.Exception   (SomeException)+import Data.ByteString     (ByteString)+import Data.Monoid         ((<>))+import Data.Text           (Text)+import Dhall.JSON          (parseConversion, parsePreservationAndOmission)+import Dhall.JSON.Yaml     (Options (..), parseDocuments, parseQuoted) import Options.Applicative (Parser, ParserInfo)  import qualified Control.Exception@@ -34,6 +34,7 @@             <*> Dhall.JSON.parseConversion             <*> optional parseFile             <*> optional parseOutput+            <*> parseNoEdit             )     <|> parseVersion   where@@ -64,6 +65,12 @@             <>  Options.metavar "FILE"             ) +    parseNoEdit =+        Options.switch+           (   Options.long "generated-comment"+           <>  Options.help "Include a comment header warning not to edit the generated file"+           )+ parserInfo :: ParserInfo (Maybe Options) parserInfo =     Options.info@@ -82,10 +89,10 @@     maybeOptions <- Options.execParser parserInfo      case maybeOptions of-        Nothing -> do+        Nothing ->             putStrLn (Data.Version.showVersion version) -        Just options@(Options {..}) -> do+        Just options@Options{..} ->             handle $ do                 contents <- case file of                     Nothing   -> Text.IO.getContents@@ -93,7 +100,7 @@                  let write =                         case output of-                            Nothing -> Data.ByteString.putStr+                            Nothing    -> Data.ByteString.putStr                             Just file_ -> Data.ByteString.writeFile file_                  write =<< dhallToYaml options file contents
src/Dhall/JSON.hs view
@@ -124,9 +124,9 @@     the same record.  For example, this code:  > let Example = < Left : { foo : Natural } | Right : { bar : Bool } >-> +> > let Nesting = < Inline | Nested : Text >-> +> > in  { field    = "name" >     , nesting  = Nesting.Inline >     , contents = Example.Left { foo = 2 }@@ -143,9 +143,9 @@     underneath a field named @nestedField@.  For example, this code:  > let Example = < Left : { foo : Natural } | Right : { bar : Bool } >-> +> > let Nesting = < Inline | Nested : Text >-> +> > in  { field    = "name" >     , nesting  = Nesting.Nested "value" >     , contents = Example.Left { foo = 2 }@@ -165,7 +165,7 @@  > $ cat ./example.dhall > let JSON = https://prelude.dhall-lang.org/JSON/package.dhall-> +> > in  JSON.object >     [ { mapKey = "foo", mapValue = JSON.null } >     , { mapKey =@@ -212,21 +212,21 @@     , CompileError(..)     ) where -import Control.Applicative (empty, (<|>))-import Control.Monad (guard)-import Control.Exception (Exception, throwIO)-import Data.Aeson (Value(..), ToJSON(..))-import Data.Maybe (fromMaybe)-import Data.Monoid ((<>), mempty)-import Data.Text (Text)+import Control.Applicative       (empty, (<|>))+import Control.Exception         (Exception, throwIO)+import Control.Monad             (guard)+import Data.Aeson                (ToJSON (..), Value (..))+import Data.Maybe                (fromMaybe)+import Data.Monoid               (mempty, (<>))+import Data.Text                 (Text) import Data.Text.Prettyprint.Doc (Pretty)-import Data.Void (Void)-import Dhall.Core (Binding(..), DhallDouble(..), Expr)-import Dhall.Import (SemanticCacheMode(..))-import Dhall.Map (Map)-import Dhall.JSON.Util (pattern V)-import Options.Applicative (Parser)-import Prelude hiding (getContents)+import Data.Void                 (Void)+import Dhall.Core                (Binding (..), DhallDouble (..), Expr)+import Dhall.Import              (SemanticCacheMode (..))+import Dhall.JSON.Util           (pattern V)+import Dhall.Map                 (Map)+import Options.Applicative       (Parser)+import Prelude                   hiding (getContents)  import qualified Data.Aeson                            as Aeson import qualified Data.Foldable                         as Foldable@@ -412,7 +412,7 @@     -> Either CompileError Value dhallToJSON e0 = loop (Core.alphaNormalize (Core.normalize e0))   where-    loop e = case e of +    loop e = case e of         Core.BoolLit a -> return (toJSON a)         Core.NaturalLit a -> return (toJSON a)         Core.IntegerLit a -> return (toJSON a)@@ -459,7 +459,7 @@                    , Just (alternativeName, mExpr) <- getContents contents -> do                        contents' <- case mExpr of                            Just expr -> loop expr-                           Nothing -> return Aeson.Null+                           Nothing   -> return Aeson.Null                         let taggedValue =                                Data.Map.fromList@@ -637,7 +637,7 @@ omitNull Null =     Null -{-| Omit record fields that are @null@, arrays and records whose transitive +{-| Omit record fields that are @null@, arrays and records whose transitive     fields are all null -} omitEmpty :: Value -> Value@@ -962,12 +962,6 @@          Core.None ->             Core.None--        Core.OptionalFold ->-            Core.OptionalFold--        Core.OptionalBuild ->-            Core.OptionalBuild          Core.Record a ->             Core.Record a'
src/Dhall/JSON/Yaml.hs view
@@ -13,12 +13,13 @@   , defaultOptions   , dhallToYaml   , jsonToYaml+  , generatedCodeNotice   ) where -import Data.ByteString (ByteString)-import Data.Monoid ((<>))-import Data.Text (Text)-import Dhall.JSON (Conversion(..), SpecialDoubleMode(..))+import Data.ByteString     (ByteString)+import Data.Monoid         ((<>))+import Data.Text           (Text)+import Dhall.JSON          (Conversion (..), SpecialDoubleMode (..)) import Options.Applicative (Parser)  import qualified Data.Aeson@@ -37,6 +38,7 @@     , conversion :: Conversion     , file       :: Maybe FilePath     , output     :: Maybe FilePath+    , noEdit     :: Bool     }  defaultOptions :: Options@@ -48,6 +50,7 @@           , conversion = Dhall.JSON.defaultConversion           , file = Nothing           , output = Nothing+          , noEdit = False           }  parseDocuments :: Parser Bool@@ -63,7 +66,13 @@             (   Options.Applicative.long "quoted"             <>  Options.Applicative.help "Prevent from generating not quoted scalars"             )-                           ++{-| The notice added to the top of a generated file when enabling the+    @--generated-comment@+-}+generatedCodeNotice :: ByteString+generatedCodeNotice = "# Code generated by dhall-to-yaml.  DO NOT EDIT.\n"+ {-| Convert a piece of Text carrying a Dhall inscription to an equivalent YAML ByteString -} dhallToYaml@@ -73,12 +82,17 @@   -> Text  -- ^ Input text.   -> IO ByteString dhallToYaml Options{..} mFilePath code = do-  +   let explaining = if explain then Dhall.detailed else id    json <- omission <$> explaining (Dhall.JSON.codeToValue conversion UseYAMLEncoding mFilePath code) -  return $ jsonToYaml json documents quoted+  let header =+          if noEdit+          then generatedCodeNotice+          else mempty++  return $ header <> jsonToYaml json documents quoted  -- | Transform json representation into yaml jsonToYaml
src/Dhall/JSONToDhall.hs view
@@ -191,7 +191,22 @@ > $ json-to-dhall --no-keyval-maps 'List { mapKey : Text, mapValue : Text }' <<< '{ "foo": "bar" }' > Error: Homogeneous JSON map objects cannot be converted to Dhall association lists under --no-keyval-arrays flag +    If your schema is a record with a `List` field and omit that field in the JSON,+    you'll get an error: +> $ json-to-dhall  '{ a : List Natural }' <<< '{}'+>+>+> Error: Key a, expected by Dhall type:+> List Natural+> is not present in JSON object:+> {}++    You can use the @--omissible-lists@ option to default to an empty list in this case++> $ json-to-dhall --omissible-lists  '{ a : List Natural }' <<< '{}'+> { a = [] : List Natural }+ == Optional values and unions      JSON @null@ values correspond to @Optional@ Dhall values:@@ -344,44 +359,45 @@     , showCompileError     ) where -import           Control.Applicative ((<|>))-import           Control.Exception (Exception, throwIO)-import           Control.Monad.Catch (throwM, MonadCatch)-import           Data.Aeson (Value)-import qualified Data.Aeson as A-import           Data.Aeson.Encode.Pretty (encodePretty)+import Control.Applicative      ((<|>))+import Control.Exception        (Exception, throwIO)+import Control.Monad.Catch      (MonadCatch, throwM)+import Data.Aeson               (Value)+import Data.Aeson.Encode.Pretty (encodePretty)+import Data.Either              (rights)+import Data.Foldable            (toList)+import Data.List                ((\\))+import Data.Monoid              (Any (..))+import Data.Scientific          (floatingOrInteger, toRealFloat)+import Data.Semigroup           (Semigroup (..))+import Data.Text                (Text)+import Data.Void                (Void)+import Dhall.Core               (Chunks (..), DhallDouble (..), Expr (App))+import Dhall.JSON.Util          (pattern V)+import Dhall.Parser             (Src)+import Options.Applicative      (Parser)++import qualified Data.Aeson                 as Aeson+import qualified Data.Aeson.Types           as Aeson.Types import qualified Data.ByteString.Lazy.Char8 as BSL8-import           Data.Either (rights)-import           Data.Foldable (toList)-import qualified Data.Foldable as Foldable-import qualified Data.HashMap.Strict as HM-import           Data.List ((\\))-import qualified Data.List as List+import qualified Data.Foldable              as Foldable+import qualified Data.HashMap.Strict        as HM+import qualified Data.List                  as List import qualified Data.Map-import qualified Data.Map.Merge.Lazy as Data.Map.Merge-import           Data.Monoid (Any(..))-import qualified Data.Ord as Ord-import           Data.Scientific (floatingOrInteger, toRealFloat)-import           Data.Semigroup (Semigroup(..))-import qualified Data.Sequence as Seq+import qualified Data.Map.Merge.Lazy        as Data.Map.Merge+import qualified Data.Ord                   as Ord+import qualified Data.Sequence              as Seq import qualified Data.String-import qualified Data.Text as Text-import           Data.Text (Text)-import qualified Data.Vector as Vector-import           Data.Void (Void)-import qualified Options.Applicative as O-import           Options.Applicative (Parser)--import           Dhall.JSON.Util (pattern V)-import qualified Dhall.Core as D-import           Dhall.Core (Expr(App), Chunks(..), DhallDouble(..))+import qualified Data.Text                  as Text+import qualified Data.Vector                as Vector+import qualified Dhall.Core                 as D import qualified Dhall.Import-import qualified Dhall.Lint as Lint-import qualified Dhall.Map as Map-import qualified Dhall.Optics as Optics+import qualified Dhall.Lint                 as Lint+import qualified Dhall.Map                  as Map+import qualified Dhall.Optics               as Optics import qualified Dhall.Parser-import           Dhall.Parser (Src)-import qualified Dhall.TypeCheck as D+import qualified Dhall.TypeCheck            as D+import qualified Options.Applicative        as O  -- --------------- -- Command options@@ -398,11 +414,11 @@     parseStrict =             O.flag' True             (  O.long "records-strict"-            <> O.help "Fail if any YAML fields are missing from the expected Dhall type"+            <> O.help "Fail if any JSON fields are missing from the expected Dhall type"             )         <|> O.flag' False             (  O.long "records-loose"-            <> O.help "Tolerate YAML fields not present within the expected Dhall type"+            <> O.help "Tolerate JSON fields not present within the expected Dhall type"             )         <|> pure True @@ -502,8 +518,8 @@       _              -> throwM . compileException $ BadDhallType t expr  keyValMay :: Value -> Maybe (Text, Value)-keyValMay (A.Object o) = do-     A.String k <- HM.lookup "key" o+keyValMay (Aeson.Object o) = do+     Aeson.String k <- HM.lookup "key" o      v <- HM.lookup "value" o      return (k, v) keyValMay _ = Nothing@@ -515,26 +531,26 @@     on the command line -} inferSchema :: Value -> Schema-inferSchema (A.Object m) =+inferSchema (Aeson.Object m) =     let convertMap = Data.Map.fromList . HM.toList      in (Record . RecordSchema . convertMap) (fmap inferSchema m)-inferSchema (A.Array xs) =+inferSchema (Aeson.Array xs) =     List (Foldable.foldMap inferSchema xs)-inferSchema (A.String _) =+inferSchema (Aeson.String _) =     Text-inferSchema (A.Number n) =+inferSchema (Aeson.Number n) =     case floatingOrInteger n of         Left (_ :: Double) -> Double         Right (integer :: Integer)             | 0 <= integer -> Natural             | otherwise    -> Integer-inferSchema (A.Bool _) =+inferSchema (Aeson.Bool _) =     Bool-inferSchema A.Null =+inferSchema Aeson.Null =     Optional mempty --- | A record type that `inferSchema` can infer+-- | Aeson record type that `inferSchema` can infer newtype RecordSchema =     RecordSchema { getRecordSchema :: Data.Map.Map Text Schema } @@ -778,11 +794,11 @@ >>> :set -XOverloadedStrings >>> import qualified Dhall.Core as D >>> import qualified Dhall.Map as Map->>> import qualified Data.Aeson as A+>>> import qualified Data.Aeson as Aeson >>> import qualified Data.HashMap.Strict as HM  >>> s = D.Record (Map.fromList [("foo", D.Integer)])->>> v = A.Object (HM.fromList [("foo", A.Number 1)])+>>> v = Aeson.Object (HM.fromList [("foo", Aeson.Number 1)]) >>> dhallFromJSON defaultConversion s v Right (RecordLit (fromList [("foo",IntegerLit 1)])) @@ -790,74 +806,75 @@ dhallFromJSON   :: Conversion -> ExprX -> Value -> Either CompileError ExprX dhallFromJSON (Conversion {..}) expressionType =-    fmap (Optics.rewriteOf D.subExpressions Lint.useToMap) . loop (D.alphaNormalize (D.normalize expressionType))+    fmap (Optics.rewriteOf D.subExpressions Lint.useToMap) . loop [] (D.alphaNormalize (D.normalize expressionType))   where+    loop :: Aeson.Types.JSONPath -> ExprX -> Aeson.Value -> Either CompileError ExprX     -- any ~> Union-    loop t@(D.Union tm) v = do+    loop jsonPath t@(D.Union tm) v = do       let f key maybeType =             case maybeType of               Just _type -> do-                expression <- loop _type v+                expression <- loop jsonPath _type v                  return (D.App (D.Field t key) expression) -              Nothing -> do+              Nothing ->                 case v of-                    A.String text | key == text -> do+                    Aeson.String text | key == text ->                         return (D.Field t key)-                    _ -> do-                        Left (Mismatch t v)+                    _ ->+                        Left (Mismatch t v jsonPath)        case (unions, rights (toList (Map.mapWithKey f tm))) of         (UNone  , _         ) -> Left (ContainsUnion t)         (UStrict, xs@(_:_:_)) -> Left (UndecidableUnion t v xs)-        (_      , [ ]       ) -> Left (Mismatch t v)+        (_      , [ ]       ) -> Left (Mismatch t v jsonPath)         (UFirst , x:_       ) -> Right x         (UStrict, [x]       ) -> Right x      -- object ~> Record-    loop (D.Record r) v@(A.Object o)+    loop jsonPath (D.Record r) v@(Aeson.Object o)         | extraKeys <- HM.keys o \\ Map.keys r         , strictRecs && not (null extraKeys)-        = Left (UnhandledKeys extraKeys (D.Record r) v)+        = Left (UnhandledKeys extraKeys (D.Record r) v jsonPath)         | otherwise         = let f :: Text -> ExprX -> Either CompileError ExprX               f k t | Just value <- HM.lookup k o-                    = loop t value+                    = loop (Aeson.Types.Key k : jsonPath) t value                     | App D.Optional t' <- t                     = Right (App D.None t')                     | App D.List _ <- t                     , omissibleLists                     = Right (D.ListLit (Just t) [])                     | otherwise-                    = Left (MissingKey k t v)+                    = Left (MissingKey k t v jsonPath)            in D.RecordLit <$> Map.traverseWithKey f r      -- key-value list ~> Record-    loop t@(D.Record _) v@(A.Array a)+    loop jsonPath t@(D.Record _) v@(Aeson.Array a)         | not noKeyValArr         , os :: [Value] <- toList a         , Just kvs <- traverse keyValMay os-        = loop t (A.Object $ HM.fromList kvs)+        = loop jsonPath t (Aeson.Object $ HM.fromList kvs)         | noKeyValArr         = Left (NoKeyValArray t v)         | otherwise-        = Left (Mismatch t v)+        = Left (Mismatch t v jsonPath)      -- object ~> List (key, value)-    loop t@(App D.List (D.Record r)) v@(A.Object o)+    loop jsonPath t@(App D.List (D.Record r)) v@(Aeson.Object o)         | not noKeyValMap         , ["mapKey", "mapValue"] == Map.keys r         , Just mapKey   <- Map.lookup "mapKey" r         , Just mapValue <- Map.lookup "mapValue" r         = do-          keyExprMap <- traverse (loop mapValue) o+          keyExprMap <- HM.traverseWithKey  (\k child -> loop (Aeson.Types.Key k : jsonPath) mapValue child) o -          toKey <- do+          toKey <-               case mapKey of-                  D.Text    -> return (\key -> D.TextLit (Chunks [] key))-                  D.Union _ -> return (\key -> D.Field mapKey key)-                  _         -> Left (Mismatch t v)+                  D.Text    -> return $ D.TextLit . Chunks []+                  D.Union _ -> return $ D.Field mapKey+                  _         -> Left (Mismatch t v jsonPath)            let f :: (Text, ExprX) -> ExprX               f (key, val) = D.RecordLit ( Map.fromList@@ -873,59 +890,60 @@         | noKeyValMap         = Left (NoKeyValMap t v)         | otherwise-        = Left (Mismatch t v)+        = Left (Mismatch t v jsonPath)      -- array ~> List-    loop (App D.List t) (A.Array a)+    loop jsonPath (App D.List t) (Aeson.Array a)         = let f :: [ExprX] -> ExprX               f es = D.ListLit                        (if null es then Just (App D.List t) else Nothing)                        (Seq.fromList es)-           in f <$> traverse (loop t) (toList a)+           in f <$> traverse (\(idx, val) -> loop (Aeson.Types.Index idx : jsonPath) t val) (zip [0..] $ toList a)      -- null ~> List-    loop t@(App D.List _) (A.Null)+    loop jsonPath t@(App D.List _) Aeson.Null         = if omissibleLists           then Right (D.ListLit (Just t) [])-          else Left (Mismatch t A.Null)+          else Left (Mismatch t Aeson.Null jsonPath)      -- number ~> Integer-    loop D.Integer (A.Number x)+    loop jsonPath D.Integer (Aeson.Number x)         | Right n <- floatingOrInteger x :: Either Double Integer         = Right (D.IntegerLit n)         | otherwise-        = Left (Mismatch D.Integer (A.Number x))+        = Left (Mismatch D.Integer (Aeson.Number x) jsonPath)      -- number ~> Natural-    loop D.Natural (A.Number x)+    loop jsonPath D.Natural (Aeson.Number x)         | Right n <- floatingOrInteger x :: Either Double Integer         , n >= 0         = Right (D.NaturalLit (fromInteger n))         | otherwise-        = Left (Mismatch D.Natural (A.Number x))+        = Left (Mismatch D.Natural (Aeson.Number x) jsonPath)      -- number ~> Double-    loop D.Double (A.Number x)+    loop _ D.Double (Aeson.Number x)         = Right (D.DoubleLit $ DhallDouble $ toRealFloat x)      -- string ~> Text-    loop D.Text (A.String t)+    loop _ D.Text (Aeson.String t)         = Right (D.TextLit (Chunks [] t))      -- bool ~> Bool-    loop D.Bool (A.Bool t)+    loop _ D.Bool (Aeson.Bool t)         = Right (D.BoolLit t)      -- null ~> Optional-    loop (App D.Optional expr) A.Null+    loop _ (App D.Optional expr) Aeson.Null         = Right $ App D.None expr      -- value ~> Optional-    loop (App D.Optional expr) value-        = D.Some <$> loop expr value+    loop jsonPath (App D.Optional expr) value+        = D.Some <$> loop jsonPath expr value      -- Arbitrary JSON ~> https://prelude.dhall-lang.org/JSON/Type (< v13.0.0)     loop+      _       (D.Pi _ (D.Const D.Type)           (D.Pi _               (D.Record@@ -941,7 +959,7 @@           )       )       value = do-          let outer (A.Object o) =+          let outer (Aeson.Object o) =                   let inner (key, val) =                           D.RecordLit                               [ ("mapKey"  , D.TextLit (D.Chunks [] key))@@ -965,8 +983,8 @@                        keyValues = D.ListLit elementType elements -                  in  (D.App (D.Field "json" "object") keyValues)-              outer (A.Array a) =+                  in  D.App (D.Field "json" "object") keyValues+              outer (Aeson.Array a) =                   let elements = Seq.fromList (fmap outer (Vector.toList a))                        elementType@@ -974,13 +992,13 @@                           | otherwise     = Nothing                    in  D.App (D.Field "json" "array") (D.ListLit elementType elements)-              outer (A.String s) =+              outer (Aeson.String s) =                   D.App (D.Field "json" "string") (D.TextLit (D.Chunks [] s))-              outer (A.Number n) =+              outer (Aeson.Number n) =                   D.App (D.Field "json" "number") (D.DoubleLit (DhallDouble (toRealFloat n)))-              outer (A.Bool b) =+              outer (Aeson.Bool b) =                   D.App (D.Field "json" "bool") (D.BoolLit b)-              outer A.Null =+              outer Aeson.Null =                   D.Field "json" "null"            let result =@@ -1002,6 +1020,7 @@      -- Arbitrary JSON ~> https://prelude.dhall-lang.org/JSON/Type (v13.0.0 <=)     loop+      _       (D.Pi _ (D.Const D.Type)           (D.Pi _               (D.Record@@ -1018,7 +1037,7 @@           )       )       value = do-          let outer (A.Object o) =+          let outer (Aeson.Object o) =                   let inner (key, val) =                           D.RecordLit                               [ ("mapKey"  , D.TextLit (D.Chunks [] key))@@ -1042,8 +1061,8 @@                        keyValues = D.ListLit elementType elements -                  in  (D.App (D.Field "json" "object") keyValues)-              outer (A.Array a) =+                  in  D.App (D.Field "json" "object") keyValues+              outer (Aeson.Array a) =                   let elements = Seq.fromList (fmap outer (Vector.toList a))                        elementType@@ -1051,15 +1070,15 @@                           | otherwise     = Nothing                    in  D.App (D.Field "json" "array") (D.ListLit elementType elements)-              outer (A.String s) =+              outer (Aeson.String s) =                   D.App (D.Field "json" "string") (D.TextLit (D.Chunks [] s))-              outer (A.Number n) =+              outer (Aeson.Number n) =                   case floatingOrInteger n of                       Left floating -> D.App (D.Field "json" "double") (D.DoubleLit (DhallDouble floating))                       Right integer -> D.App (D.Field "json" "integer") (D.IntegerLit integer)-              outer (A.Bool b) =+              outer (Aeson.Bool b) =                   D.App (D.Field "json" "bool") (D.BoolLit b)-              outer A.Null =+              outer Aeson.Null =                   D.Field "json" "null"            let result =@@ -1081,8 +1100,8 @@           return result      -- fail-    loop expr value-        = Left (Mismatch expr value)+    loop jsonPath expr value+        = Left (Mismatch expr value jsonPath)   -- ----------@@ -1111,9 +1130,10 @@   | Mismatch       ExprX   -- Dhall expression       Value -- Aeson value+      Aeson.Types.JSONPath -- JSON Path to the error   -- record specific-  | MissingKey     Text  ExprX Value-  | UnhandledKeys [Text] ExprX Value+  | MissingKey     Text  ExprX Value Aeson.Types.JSONPath+  | UnhandledKeys [Text] ExprX Value Aeson.Types.JSONPath   | NoKeyValArray        ExprX Value   | NoKeyValMap          ExprX Value   -- union specific@@ -1149,21 +1169,22 @@       <> Text.unpack (Text.intercalate sep $ D.pretty <$> xs)         where sep = red "\n--------\n" :: Text -    Mismatch e v -> prefix-      <> "Dhall type expression and " <> format <> " value do not match:"+    Mismatch e v jsonPath -> prefix+      <> showJsonPath jsonPath <> ": Dhall type expression and " <> format <> " value do not match:"       <> "\n\nExpected Dhall type:\n" <> showExpr e       <> "\n\n" <> format <> ":\n"  <> showValue v       <> "\n" -    MissingKey k e v -> prefix-      <> "Key " <> purple (Text.unpack k) <> ", expected by Dhall type:\n"+    MissingKey k e v jsonPath -> prefix+      <> showJsonPath jsonPath <> ": Key " <> purple (Text.unpack k) <> ", expected by Dhall type:\n"       <> showExpr e       <> "\nis not present in " <> format <> " object:\n"       <> showValue v <> "\n" -    UnhandledKeys ks e v -> prefix-      <> "Key(s) " <> purple (Text.unpack (Text.intercalate ", " ks))-      <> " present in the " <> format <> " object but not in the expected Dhall record type. This is not allowed unless you enable the "+    UnhandledKeys ks e v jsonPath -> prefix+      <> showJsonPath jsonPath <> ": Key(s) " <> purple (Text.unpack (Text.intercalate ", " ks))+      <> " present in the " <> format <> " object but not in the expected Dhall"+      <> " record type. This is not allowed unless you enable the "       <> green "--records-loose" <> " flag:"       <> "\n\nExpected Dhall type:\n" <> showExpr e       <> "\n\n" <> format <> ":\n"  <> showValue v@@ -1178,7 +1199,10 @@      NoKeyValMap e v -> prefix       <> "Homogeneous " <> format <> " map objects cannot be converted to Dhall association lists under "-      <> green "--no-keyval-arrays" <> " flag"+      <> green "--no-keyval-maps" <> " flag"       <> "\n\nExpected Dhall type:\n" <> showExpr e       <> "\n\n" <> format <> ":\n"  <> showValue v       <> "\n"++showJsonPath :: Aeson.Types.JSONPath -> String+showJsonPath = Aeson.Types.formatPath . reverse
tasty/Main.hs view
@@ -5,10 +5,12 @@ module Main where  import Data.Monoid ((<>))-import Test.Tasty (TestTree)+import Data.Void   (Void)+import Test.Tasty  (TestTree)  import qualified Data.Aeson           as Aeson import qualified Data.ByteString.Lazy+import qualified Data.Text import qualified Data.Text.IO import qualified Dhall.Core           as Core import qualified Dhall.Import@@ -19,6 +21,7 @@ import qualified GHC.IO.Encoding import qualified Test.Tasty import qualified Test.Tasty.HUnit+import qualified Test.Tasty.Silver  main :: IO () main = do@@ -42,6 +45,13 @@         , testJSONToDhall "./tasty/data/fromArbitraryJSON_13_0_0"         , inferJSONToDhall "./tasty/data/potpourri"         , testCustomConversionJSONToDhall False omissibleLists "./tasty/data/missingList"+        , Test.Tasty.testGroup "Errors"+            [ testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage0" defaultConversion+            , testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage1" defaultConversion+            , testJSONToDhallErrorMessage "./tasty/data/error/mismatchMessage2" defaultConversion+            , testJSONToDhallErrorMessage "./tasty/data/error/unhandledKeys" strictRecs+            , testJSONToDhallErrorMessage "./tasty/data/error/missingKey" strictRecs+            ]         , Test.Tasty.testGroup "Nesting"             [ testDhallToJSON "./tasty/data/nesting0"             , testDhallToJSON "./tasty/data/nesting1"@@ -55,7 +65,10 @@             , testDhallToJSON "./tasty/data/unionKeys"             ]         ]-    where omissibleLists = JSONToDhall.defaultConversion{JSONToDhall.omissibleLists = True}+    where+        defaultConversion = JSONToDhall.defaultConversion+        omissibleLists = defaultConversion{JSONToDhall.omissibleLists = True}+        strictRecs = defaultConversion{JSONToDhall.strictRecs = True}  testDhallToJSON :: String -> TestTree testDhallToJSON prefix = Test.Tasty.HUnit.testCase prefix $ do@@ -96,23 +109,15 @@      bytes <- Data.ByteString.Lazy.readFile inputFile -    value <- do+    value <-         case Aeson.eitherDecode bytes of             Left string -> fail string             Right value -> return value -    schema <- do+    schema <-         if infer-            then do-                return (JSONToDhall.schemaToDhallType (JSONToDhall.inferSchema value))-            else do-                let schemaFile = prefix <> "Schema.dhall"--                schemaText <- Data.Text.IO.readFile schemaFile--                parsedSchema <- Core.throws (Dhall.Parser.exprFromText schemaFile schemaText)--                Dhall.Import.load parsedSchema+            then return (JSONToDhall.schemaToDhallType (JSONToDhall.inferSchema value))+            else let schemaFile = prefix <> "Schema.dhall" in loadSchemaFromFile schemaFile      _ <- Core.throws (Dhall.TypeCheck.typeOf schema) @@ -142,3 +147,38 @@ inferJSONToDhall :: String -> TestTree inferJSONToDhall =     testCustomConversionJSONToDhall True JSONToDhall.defaultConversion++loadSchemaFromFile :: FilePath -> IO (Core.Expr Dhall.Parser.Src Void)+loadSchemaFromFile schemaFile = do+    schemaText <- Data.Text.IO.readFile schemaFile++    parsedSchema <- Core.throws (Dhall.Parser.exprFromText schemaFile schemaText)++    Dhall.Import.load parsedSchema++testJSONToDhallErrorMessage :: String -> JSONToDhall.Conversion -> TestTree+testJSONToDhallErrorMessage prefix conv =+    Test.Tasty.Silver.goldenVsAction prefix goldenFile action converter+    where+        goldenFile = prefix <> ".golden"+        schemaFile = prefix <> "Schema.dhall"+        inputFile  = prefix <> ".json"++        action = do+            bytes <- Data.ByteString.Lazy.readFile inputFile++            value <-+                case Aeson.eitherDecode bytes of+                    Left string -> fail string+                    Right value -> return value++            schema <- loadSchemaFromFile schemaFile++            _ <- Core.throws (Dhall.TypeCheck.typeOf schema)++            case JSONToDhall.dhallFromJSON conv schema value of+                Right _ -> fail $ prefix <> " should fail"+                Left compileError ->+                    return (Data.Text.pack $ show compileError)++        converter = id
+ tasty/data/error/mismatchMessage0.golden view
@@ -0,0 +1,8 @@+[1;31m+Error: [0m$.a.b[1]: Dhall type expression and JSON value do not match:++Expected Dhall type:+Natural++JSON:+0.5
+ tasty/data/error/mismatchMessage0.json view
@@ -0,0 +1,14 @@+{+    "a": {+        "b": [+            10,+            0.5+        ]+    },+    "c": {+        "d": [+            10,+            0.5+        ]+    }+}
+ tasty/data/error/mismatchMessage0Schema.dhall view
@@ -0,0 +1,3 @@+{ a : { b : List Natural }+, c : { d : List Integer }+}
+ tasty/data/error/mismatchMessage1.golden view
@@ -0,0 +1,8 @@+[1;31m+Error: [0m$: Dhall type expression and JSON value do not match:++Expected Dhall type:+Text++JSON:+1
+ tasty/data/error/mismatchMessage1.json view
@@ -0,0 +1,1 @@+1
+ tasty/data/error/mismatchMessage1Schema.dhall view
@@ -0,0 +1,1 @@+Text
+ tasty/data/error/mismatchMessage2.golden view
@@ -0,0 +1,8 @@+[1;31m+Error: [0m$[3]: Dhall type expression and JSON value do not match:++Expected Dhall type:+Natural++JSON:+"oh oh"
+ tasty/data/error/mismatchMessage2.json view
@@ -0,0 +1,1 @@+[ 1, 2, 3, "oh oh" ]
+ tasty/data/error/mismatchMessage2Schema.dhall view
@@ -0,0 +1,1 @@+List Natural
+ tasty/data/error/missingKey.golden view
@@ -0,0 +1,5 @@+[1;31m+Error: [0m$.a: Key [1;35mb[0m, expected by Dhall type:+Bool+is not present in JSON object:+{}
+ tasty/data/error/missingKey.json view
@@ -0,0 +1,4 @@+{+    "a": {+    }+}
+ tasty/data/error/missingKeySchema.dhall view
@@ -0,0 +1,1 @@+{ a: { b: Bool } }
+ tasty/data/error/unhandledKeys.golden view
@@ -0,0 +1,10 @@+[1;31m+Error: [0m$[1]: Key(s) [1;35mb[0m present in the JSON object but not in the expected Dhall record type. This is not allowed unless you enable the [0;32m--records-loose[0m flag:++Expected Dhall type:+{ a : Natural }++JSON:+{+    "b": 2+}
+ tasty/data/error/unhandledKeys.json view
@@ -0,0 +1,4 @@+[+    { "a": 1 },+    { "b": 2 }+]
+ tasty/data/error/unhandledKeysSchema.dhall view
@@ -0,0 +1,1 @@+List { a: Natural }
tasty/data/missingList.dhall view
@@ -1,4 +1,4 @@-{ missing = [] : List Text+{ missingProp = [] : List Text , null = [] : List Text , present = ["some-stuff"] }
tasty/data/missingListSchema.dhall view
@@ -1,1 +1,1 @@-{present : List Text, null  : List Text, missing : List Text}+{present : List Text, null  : List Text, missingProp : List Text}