ocaml-export 0.6.0.0 → 0.7.0.0
raw patch · 13 files changed
+199/−53 lines, 13 files
Files
- ocaml-export.cabal +12/−4
- src/OCaml/BuckleScript/Encode.hs +6/−0
- src/OCaml/BuckleScript/Internal/Module.hs +3/−3
- src/OCaml/BuckleScript/Internal/Package.hs +3/−5
- src/OCaml/BuckleScript/Internal/Spec.hs +63/−2
- src/OCaml/BuckleScript/Spec.hs +33/−23
- src/OCaml/Export.hs +2/−1
- src/OCaml/Internal/Common.hs +6/−2
- test/FileApp.hs +15/−2
- test/Product.hs +1/−1
- test/ProductApp.hs +24/−2
- test/Spec.hs +9/−4
- test/Sum.hs +22/−4
ocaml-export.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 1958ab6ed0c01872336472224fcf702904c8cfb26230a75529ea37b5ab11a6d4+-- hash: 8ba9cf12795b26fa199a0b74560d1d13c2ee2f1c686baed377ef3127ff1d9789 name: ocaml-export-version: 0.6.0.0+version: 0.7.0.0 synopsis: Convert Haskell types in OCaml types description: Use GHC.Generics and Typeable to convert Haskell types to OCaml types. Convert aeson serialization to ocaml. category: Web@@ -23,10 +23,15 @@ type: git location: https://github.com/plow-technologies/ocaml-export +flag servant-spec+ description: Test ocaml-export against a servant server. Internal test use only.+ manual: True+ default: False+ library hs-source-dirs: src- ghc-options: -Wall -Wredundant-constraints -fprint-potential-instances+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -Wredundant-constraints -fprint-potential-instances build-depends: QuickCheck , aeson@@ -37,6 +42,7 @@ , file-embed , filepath , formatting+ , hspec , hspec-golden-aeson >=0.4.0.0 , mtl , quickcheck-arbitrary-adt@@ -68,7 +74,7 @@ main-is: Spec.hs hs-source-dirs: test- ghc-options: -Wall+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints build-depends: QuickCheck , aeson@@ -91,6 +97,8 @@ , wai , wai-extra , warp+ if flag(servant-spec)+ cpp-options: -DSERVANT_SPEC other-modules: Dependency File
src/OCaml/BuckleScript/Encode.hs view
@@ -10,6 +10,7 @@ OCaml type to JSON (aeson) encoder. -} +{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -405,7 +406,12 @@ let jsonConstructorName = T.pack . Aeson.constructorTagModifier ao . T.unpack $ name constructorMatchCase = "|" <+> stext name <+> "->" encodeTag = pair (dquotes "tag") ("Aeson.Encode.string" <+> dquotes (stext jsonConstructorName))+#if MIN_VERSION_aeson(1,1,0)+ encodeContents = ";" <+> pair (dquotes "contents") ("Aeson.Encode.array [| |]")+ pure $ jsonEncodeObject constructorMatchCase encodeTag (Just encodeContents)+#else pure $ jsonEncodeObject constructorMatchCase encodeTag Nothing+#endif renderSum (OCamlValueConstructor (NamedConstructor name value)) = do let constructorParams = constructorParameters 0 value
src/OCaml/BuckleScript/Internal/Module.hs view
@@ -122,7 +122,7 @@ class HasOCamlType api where mkType :: Proxy api -> Options -> Bool -> Map.Map String EmbeddedOCamlFiles -> [Text] mkInterface :: Proxy api -> Options -> Map.Map String EmbeddedOCamlFiles -> [Text]- mkSpec :: Proxy api -> Options -> [Text] -> Text -> Text -> Map.Map String EmbeddedOCamlFiles -> [Text]+ mkSpec :: Proxy api -> Options -> [Text] -> Maybe Text -> Text -> Map.Map String EmbeddedOCamlFiles -> [Text] instance (HasOCamlTypeFlag a ~ flag, HasOCamlType' flag (a :: *)) => HasOCamlType a where mkType = mkType' (Proxy :: Proxy flag)@@ -140,7 +140,7 @@ class HasOCamlType' (flag :: Nat) api where mkType' :: Proxy flag -> Proxy api -> Options -> Bool -> Map.Map String EmbeddedOCamlFiles -> [Text] mkInterface' :: Proxy flag -> Proxy api -> Options -> Map.Map String EmbeddedOCamlFiles -> [Text]- mkSpec' :: Proxy flag -> Proxy api -> Options -> [Text] -> Text -> Text -> Map.Map String EmbeddedOCamlFiles -> [Text]+ mkSpec' :: Proxy flag -> Proxy api -> Options -> [Text] -> Maybe Text -> Text -> Map.Map String EmbeddedOCamlFiles -> [Text] instance (KnownSymbol subModule, HasOCamlType b) => HasOCamlType' 4 (OCamlSubModule subModule :> b) where mkType' _ Proxy options interface fileMap = ["module " <> (T.pack $ symbolVal (Proxy :: Proxy subModule)) <> " = struct\n"] <> (mkType (Proxy :: Proxy b) options interface fileMap) <> ["\nend"]@@ -172,7 +172,7 @@ Just (Just v) -> [decodeUtf8 v] _ -> fail $ "Unable to find the embedded file for " ++ typeName - mkSpec' _ Proxy _options modules url goldendir _fileMap = + mkSpec' _ Proxy _options modules url goldendir _fileMap = [typeInFileToOCamlSpec (Proxy :: Proxy a) typeParameterRefCount modules url goldendir] where parameters = fmap show . snd . splitTyConApp $ typeRep (Proxy :: Proxy a)
src/OCaml/BuckleScript/Internal/Package.hs view
@@ -86,8 +86,6 @@ type NoDependency = '[] -- -- ============================================== -- Data Types -- ==============================================@@ -111,12 +109,12 @@ = SpecOptions { specDir :: FilePath -- ^ Directory in which to store the OCaml spec, relative to 'packageRootDir'. , goldenDir :: FilePath -- ^ Location of golden JSON files produced by Haskell, relative to 'packageRootDir'.- , servantURL :: String -- ^ The URL of the automated Servant spec server to run OCaml specs against.+ , servantURL :: Maybe String -- ^ If Just url then run the automated Servant spec server to run OCaml specs against it, otherwise test files only. } -- | Default 'SpecOptions'. defaultSpecOptions :: SpecOptions-defaultSpecOptions = SpecOptions "/__tests__" "/__tests__/golden" "localhost:8081"+defaultSpecOptions = SpecOptions "/__tests__" "/__tests__/golden" (Just "localhost:8081") -- ==============================================@@ -181,7 +179,7 @@ case mSpecOptions packageOptions of Nothing -> pure () Just specOptions -> do- let specF = (<> "\n") . T.intercalate "\n\n" $ mkSpec (Proxy :: Proxy api) (defaultOptions {dependencies = ds}) moduls (T.pack $ servantURL specOptions) (T.pack $ goldenDir specOptions) (packageEmbeddedFiles packageOptions)+ let specF = (<> "\n") . T.intercalate "\n\n" $ mkSpec (Proxy :: Proxy api) (defaultOptions {dependencies = ds}) moduls (T.pack <$> servantURL specOptions) (T.pack $ goldenDir specOptions) (packageEmbeddedFiles packageOptions) let specBody = if specF /= "" then ("let () =\n" <> specF) else "" createDirectoryIfMissing True (rootDir </> (specDir specOptions)) T.writeFile (specFp <> "_spec" <.> "ml") specBody
src/OCaml/BuckleScript/Internal/Spec.hs view
@@ -31,6 +31,7 @@ , MkOCamlSpecAPI , mkGoldenFiles+ , runGoldenSpec -- utility functions , OCamlSpecAPI@@ -45,16 +46,25 @@ import GHC.TypeLits -- aeson-import Data.Aeson (ToJSON)+import Data.Aeson (FromJSON, ToJSON) +-- hspec+import Test.Hspec (Spec) -- hspec-aeson-golden import Test.Aeson.Internal.ADT.GoldenSpecs (mkGoldenFileForType)+import Test.Aeson.GenericSpecs+ ( roundtripAndGoldenADTSpecsWithSettings+ , defaultSettings+ , GoldenDirectoryOption (..)+ , Settings (..)+ ) -- ocaml-export import OCaml.Internal.Common hiding ((</>)) import OCaml.BuckleScript.Internal.Module import OCaml.BuckleScript.Internal.Package-+-- QuickCheck+import Test.QuickCheck (Arbitrary) -- quickcheck-arbitrary-adt import Test.QuickCheck.Arbitrary.ADT @@ -142,8 +152,59 @@ mkGoldenFiles' _ Proxy size fp = mkGoldenFileForType size (Proxy :: Proxy a) fp +-- | Make hspec-aeson-golden golden files for each type in an OCamlPackage+class HasRunGoldenSpec a where+ runGoldenSpec :: Proxy a -> Int -> FilePath -> Spec +instance (HasRunGoldenSpecFlag a ~ flag, HasRunGoldenSpec' flag (a :: *)) => HasRunGoldenSpec a where+ runGoldenSpec = runGoldenSpec' (Proxy :: Proxy flag) +type family (HasRunGoldenSpecFlag a) :: Nat where+ HasRunGoldenSpecFlag (OCamlPackage a b :> c) = 7+ HasRunGoldenSpecFlag ((OCamlModule a :> b) :<|> c) = 6+ HasRunGoldenSpecFlag (OCamlModule a :> b) = 5+ HasRunGoldenSpecFlag (OCamlSubModule a :> b) = 4+ HasRunGoldenSpecFlag (HaskellTypeName a (OCamlTypeInFile b c)) = 3+ HasRunGoldenSpecFlag (OCamlTypeInFile a b) = 3+ HasRunGoldenSpecFlag (a :> b) = 2+ HasRunGoldenSpecFlag a = 1++class HasRunGoldenSpec' (flag :: Nat) a where+ runGoldenSpec' :: Proxy flag -> Proxy a -> Int -> FilePath -> Spec++instance (HasRunGoldenSpec a) => HasRunGoldenSpec' 7 (OCamlPackage packageName deps :> a) where+ runGoldenSpec' _ Proxy size fp = runGoldenSpec (Proxy :: Proxy a) size fp++instance (HasRunGoldenSpec a, HasRunGoldenSpec b) => HasRunGoldenSpec' 6 ((OCamlModule modules :> a) :<|> b) where+ runGoldenSpec' _ Proxy size fp = do+ runGoldenSpec (Proxy :: Proxy a) size fp+ runGoldenSpec (Proxy :: Proxy b) size fp+ +instance (HasRunGoldenSpec a) => HasRunGoldenSpec' 5 (OCamlModule modules :> a) where+ runGoldenSpec' _ Proxy size fp = runGoldenSpec (Proxy :: Proxy a) size fp++instance (HasRunGoldenSpec a) => HasRunGoldenSpec' 4 (OCamlSubModule subModule :> a) where+ runGoldenSpec' _ Proxy size fp = runGoldenSpec (Proxy :: Proxy a) size fp++instance (Arbitrary b, Eq b, Show b, ToADTArbitrary b, FromJSON b, ToJSON b) => HasRunGoldenSpec' 3 (HaskellTypeName a (OCamlTypeInFile b c)) where+ runGoldenSpec' _ Proxy size fp =+ roundtripAndGoldenADTSpecsWithSettings+ (defaultSettings {sampleSize = size, goldenDirectoryOption = CustomDirectoryName fp})+ (Proxy :: Proxy b)++instance (Arbitrary a, Eq a, Show a, ToADTArbitrary a, FromJSON a, ToJSON a) => HasRunGoldenSpec' 3 (OCamlTypeInFile a b) where+ runGoldenSpec' _ Proxy size fp =+ roundtripAndGoldenADTSpecsWithSettings+ (defaultSettings {sampleSize = size, goldenDirectoryOption = CustomDirectoryName fp})+ (Proxy :: Proxy a)++instance (HasRunGoldenSpec a, HasRunGoldenSpec b) => HasRunGoldenSpec' 2 (a :> b) where+ runGoldenSpec' _ Proxy size fp = do+ runGoldenSpec (Proxy :: Proxy a) size fp+ runGoldenSpec (Proxy :: Proxy b) size fp++instance (Arbitrary a, Eq a, Show a, ToADTArbitrary a, FromJSON a, ToJSON a) => HasRunGoldenSpec' 1 a where+ runGoldenSpec' _ Proxy size fp = roundtripAndGoldenADTSpecsWithSettings (defaultSettings {sampleSize = size, goldenDirectoryOption = CustomDirectoryName fp}) (Proxy :: Proxy a) -- | Convert an OCamlPackage into a servant API. type family MkOCamlSpecAPI a :: * where
src/OCaml/BuckleScript/Spec.hs view
@@ -3,6 +3,7 @@ module OCaml.BuckleScript.Spec ( mkSampleServerAndGoldenSpec+ , mkGoldenDirSpec , toOCamlSpec , typeInFileToOCamlSpec ) where@@ -36,9 +37,29 @@ modul = T.intercalate "." (textUppercaseFirst <$> modules) :: Text smodul = stext $ if modul /= "" then modul <> "." else "" urlModul = T.intercalate "/" (textUppercaseFirst <$> modules) :: Text- mkSampleServerAndGoldenSpec (OCamlPrimitive _) _ _mod _url _fp = "" +-- | test OCaml type serialization (bs-aeson) against Haskell produced golden files+mkGoldenDirSpec :: OCamlDatatype -> Maybe Int -> [Text] -> Text -> Doc+mkGoldenDirSpec (OCamlDatatype _ typeName constructors) mParameterRefCount modules goldenDir =+ indent 2 $+ "AesonSpec.goldenDirSpec" <$$> (indent 2 $+ decoders+ <$$> encoders+ <$$> (dquotes down)+ <$$> (dquotes (stext $ goldenDir </> textUppercaseFirst typeName)) <> ";")+ where+ (tprDecoders, tprEncoders) = renderTypeParameterVals mParameterRefCount constructors+ decoders = if tprDecoders == "" then (smodul <> "decode" <> up) else ("(" <> smodul <> "decode" <> up <+> (stext tprDecoders) <> ")")+ encoders = if tprEncoders == "" then (smodul <> "encode" <> up) else ("(" <> smodul <> "encode" <> up <+> (stext tprEncoders) <> ")")+ up = stext . textUppercaseFirst $ typeName+ down = stext . textLowercaseFirst $ typeName+ modul = T.intercalate "." (textUppercaseFirst <$> modules) :: Text+ smodul = stext $ if modul /= "" then modul <> "." else ""++mkGoldenDirSpec (OCamlPrimitive _) _ _ _ = ""++ renderTypeParameterVals :: Maybe Int -> OCamlConstructor -> (Text,Text) renderTypeParameterVals (Just count) _ = renderTypeParameterValsAux count renderTypeParameterVals _ (OCamlValueConstructor vc) =@@ -65,29 +86,18 @@ getOCamlValues (RecordConstructor _ value) = [value] getOCamlValues (MultipleConstructors cs) = concat $ getOCamlValues <$> cs -toOCamlSpec :: OCamlType a => a -> [Text] -> Text -> Text -> Text-toOCamlSpec a modules url fp =- pprinter $ mkSampleServerAndGoldenSpec (toOCamlType a) Nothing modules url fp+toOCamlSpec :: OCamlType a => a -> [Text] -> Maybe Text -> Text -> Text+toOCamlSpec a modules mUrl fp =+ case mUrl of+ Just url -> pprinter $ mkSampleServerAndGoldenSpec (toOCamlType a) Nothing modules url fp+ Nothing -> pprinter $ mkGoldenDirSpec (toOCamlType a) Nothing modules fp -- | OCamlTypeInFile do not require an instance of OCamlType since they are -- hand written OCaml files for Haskell types. Use typeable to get the -- type name.-{--typeInFileToOCamlSpec :: Text -> Int -> [Text] -> Text -> Text -> Text-typeInFileToOCamlSpec aTyConName typeParameterRefCount modules url fp =- pprinter $ mkSampleServerAndGoldenSpec- (OCamlDatatype (HaskellTypeMetaData aTyConName "" "")- aTyConName $ OCamlValueConstructor $ NamedConstructor aTyConName $ OCamlEmpty)- (Just typeParameterRefCount)- modules- url- fp--}-typeInFileToOCamlSpec :: OCamlType a => a -> Int -> [Text] -> Text -> Text -> Text-typeInFileToOCamlSpec a typeParameterRefCount modules url fp =- pprinter $ mkSampleServerAndGoldenSpec- (toOCamlType a)- (Just typeParameterRefCount)- modules- url- fp+typeInFileToOCamlSpec :: OCamlType a => a -> Int -> [Text] -> Maybe Text -> Text -> Text+typeInFileToOCamlSpec a typeParameterRefCount modules mUrl fp =+ case mUrl of+ Just url ->+ pprinter $ mkSampleServerAndGoldenSpec (toOCamlType a) (Just typeParameterRefCount) modules url fp+ Nothing -> pprinter $ mkGoldenDirSpec (toOCamlType a) (Just typeParameterRefCount) modules fp
src/OCaml/Export.hs view
@@ -41,7 +41,8 @@ , mkOCamlSpecServer , MkOCamlSpecAPI , mkGoldenFiles-+ , runGoldenSpec+ -- re-export , Proxy (..) , (:>)
src/OCaml/Internal/Common.hs view
@@ -8,7 +8,6 @@ -} - {-# LANGUAGE OverloadedStrings #-} module OCaml.Internal.Common where@@ -71,7 +70,12 @@ } defaultOptions :: Options-defaultOptions = Options {includeOCamlInterface = False, aesonOptions = Aeson.defaultOptions, dependencies = Map.empty}+defaultOptions =+ Options+ { includeOCamlInterface = False+ , aesonOptions = Aeson.defaultOptions+ , dependencies = Map.empty+ } data TypeMetaData = TypeMetaData { topLevelOCamlTypeMetaData :: Maybe OCamlTypeMetaData -- Just if not primitive
test/FileApp.hs view
@@ -24,9 +24,22 @@ spec :: Spec spec = do runIO $ mkGoldenFiles (Proxy :: Proxy FilePackage) 10 "test/interface/golden/golden/file"-+ -- runGoldenSpec (Proxy :: Proxy FilePackage) 10 "test/interface/golden/golden/file" let dir = "test/interface/temp"- runIO $ mkPackage (Proxy :: Proxy FilePackage) (PackageOptions dir "file" fileMap True $ Just $ SpecOptions "__tests__/file" "golden/file" "http://localhost:8083")++ -- create spec to be tested against servant+ runIO $+ mkPackage+ (Proxy :: Proxy FilePackage)+ (PackageOptions dir "file" fileMap True $+ Just $ SpecOptions "__tests__/file-servant" "golden/file" (Just "http://localhost:8083"))++ -- create spec to be tested against files only+ runIO $+ mkPackage+ (Proxy :: Proxy FilePackage)+ (PackageOptions dir "file" fileMap True $+ Just $ SpecOptions "__tests__/file" "golden/file" Nothing) describe "OCaml Declaration with Interface: Product Types" $ compareInterfaceFiles "File"
test/Product.hs view
@@ -417,7 +417,7 @@ deriving (Eq,Show,Generic,ToJSON,FromJSON) instance (ToADTArbitrary a, Arbitrary a) => ToADTArbitrary (NewTypeKey a)-instance Arbitrary a => Arbitrary (NewTypeKey a) where+instance Arbitrary (NewTypeKey a) where arbitrary = NewTypeKey <$> arbitrary instance (Typeable a, OCamlType a) => OCamlType (NewTypeKey a)
test/ProductApp.hs view
@@ -16,10 +16,32 @@ spec :: Spec spec = do- runIO $ mkGoldenFiles (Proxy :: Proxy ProductPackage) 10 "test/interface/golden/golden/product" + runIO $ mkGoldenFiles (Proxy :: Proxy ProductPackage) 10 "test/interface/golden/golden/product"+ runGoldenSpec (Proxy :: Proxy ProductPackage) 10 "test/interface/golden/golden/product" let dir = "test/interface/temp"- runIO $ mkPackage (Proxy :: Proxy ProductPackage) (PackageOptions dir "product" fileMap True $ Just $ SpecOptions "__tests__/product" "golden/product" "http://localhost:8081")++ -- create spec to be tested against servant+ runIO $+ mkPackage+ (Proxy :: Proxy ProductPackage)+ (PackageOptions dir "product" fileMap True $+ Just $+ SpecOptions+ "__tests__/product-servant"+ "golden/product"+ (Just "http://localhost:8081"))++ -- create spec to be tested against files only+ runIO $+ mkPackage+ (Proxy :: Proxy ProductPackage)+ (PackageOptions dir "product" fileMap True $+ Just $+ SpecOptions+ "__tests__/product"+ "golden/product"+ Nothing) describe "OCaml Declaration with Interface: Product Types" $ do compareInterfaceFiles "Person"
test/Spec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-}@@ -7,7 +8,6 @@ {-# LANGUAGE TypeOperators #-} -- base-import Control.Concurrent (forkIO) import Data.Monoid ((<>)) -- hspec import Test.Hspec@@ -17,10 +17,14 @@ import qualified Product as Product import qualified ProductApp as Product import qualified Sum as Sum-import SumApp import OCaml.Export++#ifdef SERVANT_SPEC+import Control.Concurrent (forkIO)+import SumApp -- warp import Network.Wai.Handler.Warp+#endif main :: IO () main = do@@ -34,8 +38,9 @@ it "mkOCamlTypeMetaData on package A and B should equal mkOCamlTypeMetaData on B which has A as a dependency" $ (mkOCamlTypeMetaData (Proxy :: Proxy Product.ProductPackage)) <> (mkOCamlTypeMetaData (Proxy :: Proxy D.DependencyPackageWithoutProduct)) `shouldBe` mkOCamlTypeMetaData (Proxy :: Proxy D.DependencyPackage)-+ +#ifdef SERVANT_SPEC _ <- forkIO $ run 8081 Product.productPackageApp _ <- forkIO $ run 8082 sumPackageApp run 8083 File.filePackageApp- pure ()+#endif
test/Sum.hs view
@@ -48,9 +48,6 @@ compareInterfaceFiles :: FilePath -> SpecWith () compareInterfaceFiles = compareFiles "test/interface" "sum" True -compareNoInterfaceFiles :: FilePath -> SpecWith ()-compareNoInterfaceFiles = compareFiles "test/nointerface" "sum" False- mkGolden :: forall a. (ToADTArbitrary a, ToJSON a) => Proxy a -> IO () mkGolden Proxy = mkGoldenFileForType 10 (Proxy :: Proxy a) "test/interface/golden/golden/sum" @@ -67,8 +64,29 @@ spec :: Spec spec = do runIO mkGoldenFiles+ runGoldenSpec (Proxy :: Proxy SumPackage) 10 "test/interface/golden/golden/sum"+ let dir = "test/interface/temp"- runIO $ mkPackage (Proxy :: Proxy SumPackage) (PackageOptions dir "sum" Map.empty True $ Just $ SpecOptions "__tests__/sum" "golden/sum" "http://localhost:8082")++ -- create spec to be tested against servant+ runIO $+ mkPackage+ (Proxy :: Proxy SumPackage)+ (PackageOptions dir "sum" Map.empty True $+ Just $ SpecOptions+ "__tests__/sum-servant"+ "golden/sum"+ (Just "http://localhost:8082"))++ -- create spec to be tested against files only+ runIO $+ mkPackage+ (Proxy :: Proxy SumPackage)+ (PackageOptions dir "sum" Map.empty True $+ Just $ SpecOptions+ "__tests__/sum"+ "golden/sum"+ Nothing) describe "OCaml Declaration with Interface: Sum Types" $ do compareInterfaceFiles "OnOrOff"