packages feed

graphula 2.1.1.0 → 2.1.2.0

raw patch · 7 files changed

+132/−47 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Graphula: insertEither :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Either String (Entity a))
+ Graphula: insertKeyedEither :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> a -> m (Either String (Entity a))
+ Graphula.Class: insertEither :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Either String (Entity a))
+ Graphula.Class: insertKeyedEither :: (MonadGraphulaFrontend m, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> a -> m (Either String (Entity a))
+ Graphula.Dependencies: insertWithPossiblyRequiredKeyEither :: (InsertWithPossiblyRequiredKey requirement, PersistEntityBackend record ~ SqlBackend, PersistEntity record, Monad m, MonadGraphulaFrontend m, InsertConstraint requirement record) => requirement (Key record) -> record -> m (Either String (Entity record))
- Graphula: GenerationFailureMaxAttemptsToConstrain :: TypeRep -> GenerationFailure
+ Graphula: GenerationFailureMaxAttemptsToConstrain :: Maybe String -> TypeRep -> GenerationFailure
- Graphula: GenerationFailureMaxAttemptsToInsert :: TypeRep -> GenerationFailure
+ Graphula: GenerationFailureMaxAttemptsToInsert :: Maybe String -> TypeRep -> GenerationFailure
- Graphula.Node: GenerationFailureMaxAttemptsToConstrain :: TypeRep -> GenerationFailure
+ Graphula.Node: GenerationFailureMaxAttemptsToConstrain :: Maybe String -> TypeRep -> GenerationFailure
- Graphula.Node: GenerationFailureMaxAttemptsToInsert :: TypeRep -> GenerationFailure
+ Graphula.Node: GenerationFailureMaxAttemptsToInsert :: Maybe String -> TypeRep -> GenerationFailure

Files

README.md view
@@ -32,6 +32,7 @@  module Main (module Main) where +import Control.Exception (try, Exception(..)) import Control.Monad.IO.Class import Control.Monad.IO.Unlift import Control.Monad.Logger (NoLoggingT)@@ -170,6 +171,28 @@   n `shouldSatisfy` (not . null) ``` +## Generation Failures++Generating the graph can fail if you ask it do impossible things, such as+generate entities that collide on a unique constraint. In such cases, you will+receive an informative error:++```haskell+generationFailureSpec :: IO ()+generationFailureSpec = do+  result <- try $ runGraphulaT Nothing runDB $ do+    school <- node @School () mempty++    -- collision that will never resolve+    nodeKeyed @School (entityKey school) () mempty++  case result of+    Left ex ->+      displayException @GenerationFailure ex+        `shouldBe` "GenerationFailureMaxAttemptsToInsert (Just \"entity already exists by this key\") School"+    Right _ -> pure ()+```+ ## Running It  ```haskell@@ -202,6 +225,7 @@   describe "graphula" . parallel $ do     it "generates and links arbitrary graphs of data" simpleSpec     it "allows logging graphs" loggingSpec+    it "shows informative generation failures" generationFailureSpec  runDB :: MonadUnliftIO m => ReaderT SqlBackend (NoLoggingT (ResourceT m)) a -> m a runDB f = runSqlite "test.db" $ do
graphula.cabal view
@@ -1,6 +1,6 @@ cabal-version:      1.12 name:               graphula-version:            2.1.1.0+version:            2.1.2.0 license:            MIT license-file:       LICENSE maintainer:         Freckle Education
src/Graphula.hs view
@@ -211,38 +211,42 @@   logNode _ = pure ()  instance (MonadIO m, MonadIO n) => MonadGraphulaFrontend (GraphulaT n m) where-  insert mKey n = do+  insertEither mKey n = do     RunDB runDB <- asks dbRunner     lift . runDB $ case mKey of       Nothing ->         insertUnique n >>= \case-          Nothing -> pure Nothing-          Just key -> getEntity key+          Nothing -> pure $ Left "entity violated a unique constraint"+          Just key -> maybe (Left "inserted entity not found") Right <$> getEntity key       Just key -> do         existingKey <- get key-        whenNothing existingKey $ do-          existingUnique <- checkUnique n-          whenNothing existingUnique $ do-            Persist.insertKey key n-            getEntity key+        case existingKey of+          Just _ -> pure $ Left "entity already exists by this key"+          Nothing -> do+            existingUnique <- checkUnique n+            case existingUnique of+              Just _ -> pure $ Left "entity would violate unique constraint"+              Nothing -> do+                Persist.insertKey key n+                maybe (Left "inserted entity not found") Right <$> getEntity key -  insertKeyed key n = do+  insertKeyedEither key n = do     RunDB runDB <- asks dbRunner     lift . runDB $ do       existingKey <- get key-      whenNothing existingKey $ do-        existingUnique <- checkUnique n-        whenNothing existingUnique $ do-          Persist.insertKey key n-          getEntity key+      case existingKey of+        Just _ -> pure $ Left "entity already exists by this key"+        Nothing -> do+          existingUnique <- checkUnique n+          case existingUnique of+            Just _ -> pure $ Left "entity would violate unique constraint"+            Nothing -> do+              Persist.insertKey key n+              maybe (Left "inserted entity not found") Right <$> getEntity key    remove key = do     RunDB runDB <- asks dbRunner     lift . runDB $ delete key--whenNothing :: Applicative m => Maybe a -> m (Maybe b) -> m (Maybe b)-whenNothing Nothing f = f-whenNothing (Just _) _ = pure Nothing  runGraphulaT   :: MonadUnliftIO m
src/Graphula/Class.hs view
@@ -66,7 +66,19 @@     => Maybe (Key a)     -> a     -> m (Maybe (Entity a))+  insert mk a = either (const Nothing) Just <$> insertEither mk a +  insertEither+    :: ( PersistEntityBackend a ~ SqlBackend+       , PersistEntity a+       , Monad m+       , GraphulaSafeToInsert a+       )+    => Maybe (Key a)+    -> a+    -> m (Either String (Entity a))+  insertEither mk a = maybe (Left "Unable to insert entity") Right <$> insert mk a+   insertKeyed     :: ( PersistEntityBackend a ~ SqlBackend        , PersistEntity a@@ -75,11 +87,24 @@     => Key a     -> a     -> m (Maybe (Entity a))+  insertKeyed k a = either (const Nothing) Just <$> insertKeyedEither k a +  insertKeyedEither+    :: ( PersistEntityBackend a ~ SqlBackend+       , PersistEntity a+       , Monad m+       )+    => Key a+    -> a+    -> m (Either String (Entity a))+  insertKeyedEither k a = maybe (Left "Unable to insert keyed entity") Right <$> insertKeyed k a+   remove     :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m)     => Key a     -> m ()++  {-# MINIMAL (insert | insertEither), (insertKeyed | insertKeyedEither), remove #-}  class MonadGraphulaBackend m where   type Logging m :: Type -> Constraint
src/Graphula/Dependencies.hs view
@@ -107,10 +107,10 @@     :: ( HasEot a        , HasEot (Dependencies a)        , GHasDependencies-          (Proxy a)-          (Proxy (Dependencies a))-          (Eot a)-          (Eot (Dependencies a))+           (Proxy a)+           (Proxy (Dependencies a))+           (Eot a)+           (Eot (Dependencies a))        )     => a     -> Dependencies a@@ -177,16 +177,28 @@     => requirement (Key record)     -> record     -> m (Maybe (Entity record))+  insertWithPossiblyRequiredKeyEither+    :: ( PersistEntityBackend record ~ SqlBackend+       , PersistEntity record+       , Monad m+       , MonadGraphulaFrontend m+       , InsertConstraint requirement record+       )+    => requirement (Key record)+    -> record+    -> m (Either String (Entity record))   justKey :: key -> requirement key  instance InsertWithPossiblyRequiredKey Optional where   type InsertConstraint Optional = GraphulaSafeToInsert   insertWithPossiblyRequiredKey (Optional key) = MonadGraphulaFrontend.insert key+  insertWithPossiblyRequiredKeyEither (Optional key) = MonadGraphulaFrontend.insertEither key   justKey = Optional . Just  instance InsertWithPossiblyRequiredKey Required where   type InsertConstraint Required = NoConstraint   insertWithPossiblyRequiredKey (Required key) = MonadGraphulaFrontend.insertKeyed key+  insertWithPossiblyRequiredKeyEither (Required key) = MonadGraphulaFrontend.insertKeyedEither key   justKey = Required  -- | Abstract constraint that some @a@ can generate a key
src/Graphula/Node.hs view
@@ -142,22 +142,20 @@   -> Int   -> m (Maybe (KeyForInsert a, a))   -> m (Entity a)-attempt maxEdits maxInserts source = loop 0 0+attempt maxEdits maxInserts source = loop 0 0 Nothing  where-  loop :: Int -> Int -> m (Entity a)-  loop numEdits numInserts-    | numEdits >= maxEdits = die GenerationFailureMaxAttemptsToConstrain-    | numInserts >= maxInserts = die GenerationFailureMaxAttemptsToInsert+  loop :: Int -> Int -> Maybe String -> m (Entity a)+  loop numEdits numInserts mmsg+    | numEdits >= maxEdits = die $ GenerationFailureMaxAttemptsToConstrain mmsg+    | numInserts >= maxInserts = die $ GenerationFailureMaxAttemptsToInsert mmsg     | otherwise =         source >>= \case-          Nothing -> loop (succ numEdits) numInserts+          Nothing -> loop (succ numEdits) numInserts Nothing           --               ^ failed to edit, only increments this           Just (mKey, value) ->-            insertWithPossiblyRequiredKey mKey value >>= \case-              Nothing -> loop (succ numEdits) (succ numInserts)-              --               ^ failed to insert, but also increments this. Are we-              --                 sure that's what we want?-              Just a -> pure a+            insertWithPossiblyRequiredKeyEither mKey value >>= \case+              Left msg -> loop (succ numEdits) (succ numInserts) $ Just msg+              Right a -> pure a  -- | Generate a node with an explictly-given key --@@ -198,22 +196,20 @@   -> Key a   -> m (Maybe a)   -> m (Entity a)-attempt' maxEdits maxInserts key source = loop 0 0+attempt' maxEdits maxInserts key source = loop 0 0 Nothing  where-  loop :: Int -> Int -> m (Entity a)-  loop numEdits numInserts-    | numEdits >= maxEdits = die GenerationFailureMaxAttemptsToConstrain-    | numInserts >= maxInserts = die GenerationFailureMaxAttemptsToInsert+  loop :: Int -> Int -> Maybe String -> m (Entity a)+  loop numEdits numInserts mmsg+    | numEdits >= maxEdits = die $ GenerationFailureMaxAttemptsToConstrain mmsg+    | numInserts >= maxInserts = die $ GenerationFailureMaxAttemptsToInsert mmsg     | otherwise =         source >>= \case-          Nothing -> loop (succ numEdits) numInserts+          Nothing -> loop (succ numEdits) numInserts Nothing           --               ^ failed to edit, only increments this           Just value ->-            insertKeyed key value >>= \case-              Nothing -> loop (succ numEdits) (succ numInserts)-              --               ^ failed to insert, but also increments this. Are we-              --                 sure that's what we want?-              Just a -> pure a+            insertKeyedEither key value >>= \case+              Left msg -> loop (succ numEdits) (succ numInserts) $ Just msg+              Right a -> pure a  die   :: forall a m@@ -224,9 +220,9 @@  data GenerationFailure   = -- | Could not satisfy constraints defined using 'ensure'-    GenerationFailureMaxAttemptsToConstrain TypeRep+    GenerationFailureMaxAttemptsToConstrain (Maybe String) TypeRep   | -- | Could not satisfy database constraints on 'insert'-    GenerationFailureMaxAttemptsToInsert TypeRep+    GenerationFailureMaxAttemptsToInsert (Maybe String) TypeRep   deriving stock (Show, Eq)  instance Exception GenerationFailure
test/README.lhs view
@@ -32,6 +32,7 @@  module Main (module Main) where +import Control.Exception (try, Exception(..)) import Control.Monad.IO.Class import Control.Monad.IO.Unlift import Control.Monad.Logger (NoLoggingT)@@ -170,6 +171,28 @@   n `shouldSatisfy` (not . null) ``` +## Generation Failures++Generating the graph can fail if you ask it do impossible things, such as+generate entities that collide on a unique constraint. In such cases, you will+receive an informative error:++```haskell+generationFailureSpec :: IO ()+generationFailureSpec = do+  result <- try $ runGraphulaT Nothing runDB $ do+    school <- node @School () mempty++    -- collision that will never resolve+    nodeKeyed @School (entityKey school) () mempty++  case result of+    Left ex ->+      displayException @GenerationFailure ex+        `shouldBe` "GenerationFailureMaxAttemptsToInsert (Just \"entity already exists by this key\") School"+    Right _ -> pure ()+```+ ## Running It  ```haskell@@ -202,6 +225,7 @@   describe "graphula" . parallel $ do     it "generates and links arbitrary graphs of data" simpleSpec     it "allows logging graphs" loggingSpec+    it "shows informative generation failures" generationFailureSpec  runDB :: MonadUnliftIO m => ReaderT SqlBackend (NoLoggingT (ResourceT m)) a -> m a runDB f = runSqlite "test.db" $ do