From edb5a1c84db63393ff8fc181673dbbca206a01ad Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Thu, 15 Nov 2012 13:24:14 +0100
Subject: [PATCH] Add more basic validation types

This mirrors the ht.py types PositiveInt, NonNegative, etc., except
that they work at a more generic level (any numeric type, respectively
any non-empty list).

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Adeodato Simo <dato@google.com>
---
 htest/Test/Ganeti/Types.hs | 67 ++++++++++++++++++++++++++++++++++++--
 htools/Ganeti/Types.hs     | 56 +++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+), 2 deletions(-)

diff --git a/htest/Test/Ganeti/Types.hs b/htest/Test/Ganeti/Types.hs
index d4e6d4ed2..c066a22d3 100644
--- a/htest/Test/Ganeti/Types.hs
+++ b/htest/Test/Ganeti/Types.hs
@@ -31,23 +31,37 @@ module Test.Ganeti.Types
   , AllocPolicy(..)
   , DiskTemplate(..)
   , InstanceStatus(..)
+  , NonEmpty(..)
   ) where
 
-import Test.QuickCheck
+import Test.QuickCheck as QuickCheck hiding (Result)
+import Test.HUnit
 
 import Test.Ganeti.TestHelper
 import Test.Ganeti.TestCommon
 
-import Ganeti.Types
+import Ganeti.BasicTypes
+import Ganeti.Types as Types
 
 -- * Arbitrary instance
 
+instance (Arbitrary a, Ord a, Num a, Show a) =>
+  Arbitrary (Types.Positive a) where
+  arbitrary = do
+    (QuickCheck.Positive i) <- arbitrary
+    Types.mkPositive i
+
 $(genArbitrary ''AllocPolicy)
 
 $(genArbitrary ''DiskTemplate)
 
 $(genArbitrary ''InstanceStatus)
 
+instance (Arbitrary a) => Arbitrary (Types.NonEmpty a) where
+  arbitrary = do
+    QuickCheck.NonEmpty lst <- arbitrary
+    Types.mkNonEmpty lst
+
 prop_AllocPolicy_serialisation :: AllocPolicy -> Property
 prop_AllocPolicy_serialisation = testSerialisation
 
@@ -57,8 +71,57 @@ prop_DiskTemplate_serialisation = testSerialisation
 prop_InstanceStatus_serialisation :: InstanceStatus -> Property
 prop_InstanceStatus_serialisation = testSerialisation
 
+-- | Tests building non-negative numbers.
+prop_NonNeg_pass :: QuickCheck.NonNegative Int -> Property
+prop_NonNeg_pass (QuickCheck.NonNegative i) =
+  case mkNonNegative i of
+    Bad msg -> failTest $ "Fail to build non-negative: " ++ msg
+    Ok nn -> fromNonNegative nn ==? i
+
+-- | Tests building non-negative numbers.
+prop_NonNeg_fail :: QuickCheck.Positive Int -> Property
+prop_NonNeg_fail (QuickCheck.Positive i) =
+  case mkNonNegative (negate i)::Result (Types.NonNegative Int) of
+    Bad _ -> passTest
+    Ok nn -> failTest $ "Built non-negative number '" ++ show nn ++
+             "' from negative value " ++ show i
+
+-- | Tests building positive numbers.
+prop_Positive_pass :: QuickCheck.Positive Int -> Property
+prop_Positive_pass (QuickCheck.Positive i) =
+  case mkPositive i of
+    Bad msg -> failTest $ "Fail to build positive: " ++ msg
+    Ok nn -> fromPositive nn ==? i
+
+-- | Tests building positive numbers.
+prop_Positive_fail :: QuickCheck.NonNegative Int -> Property
+prop_Positive_fail (QuickCheck.NonNegative i) =
+  case mkPositive (negate i)::Result (Types.Positive Int) of
+    Bad _ -> passTest
+    Ok nn -> failTest $ "Built positive number '" ++ show nn ++
+             "' from negative or zero value " ++ show i
+
+-- | Tests building non-empty lists.
+prop_NonEmpty_pass :: QuickCheck.NonEmptyList [Char] -> Property
+prop_NonEmpty_pass (QuickCheck.NonEmpty xs) =
+  case mkNonEmpty xs of
+    Bad msg -> failTest $ "Fail to build non-empty list: " ++ msg
+    Ok nn -> fromNonEmpty nn ==? xs
+
+-- | Tests building positive numbers.
+case_NonEmpty_fail :: Assertion
+case_NonEmpty_fail = do
+  assertEqual "building non-empty list from an empty list"
+    (Bad "Received empty value for non-empty list") (mkNonEmpty ([]::[Int]))
+
 testSuite "Types"
   [ 'prop_AllocPolicy_serialisation
   , 'prop_DiskTemplate_serialisation
   , 'prop_InstanceStatus_serialisation
+  , 'prop_NonNeg_pass
+  , 'prop_NonNeg_fail
+  , 'prop_Positive_pass
+  , 'prop_Positive_fail
+  , 'prop_NonEmpty_pass
+  , 'case_NonEmpty_fail
   ]
diff --git a/htools/Ganeti/Types.hs b/htools/Ganeti/Types.hs
index 9ce9aa6d8..4f1014a75 100644
--- a/htools/Ganeti/Types.hs
+++ b/htools/Ganeti/Types.hs
@@ -40,11 +40,67 @@ module Ganeti.Types
   , DiskTemplate(..)
   , diskTemplateToRaw
   , diskTemplateFromRaw
+  , NonNegative
+  , fromNonNegative
+  , mkNonNegative
+  , Positive
+  , fromPositive
+  , mkPositive
+  , NonEmpty
+  , fromNonEmpty
+  , mkNonEmpty
   ) where
 
+import qualified Text.JSON as JSON
+
 import qualified Ganeti.Constants as C
 import qualified Ganeti.THH as THH
 
+-- * Generic types
+
+-- | Type that holds a non-negative value.
+newtype NonNegative a = NonNegative { fromNonNegative :: a }
+  deriving (Show, Read, Eq)
+
+-- | Smart constructor for 'NonNegative'.
+mkNonNegative :: (Monad m, Num a, Ord a, Show a) => a -> m (NonNegative a)
+mkNonNegative i | i >= 0 = return (NonNegative i)
+                | otherwise = fail $ "Invalid value for non-negative type '" ++
+                              show i ++ "'"
+
+instance (JSON.JSON a, Num a, Ord a, Show a) => JSON.JSON (NonNegative a) where
+  showJSON = JSON.showJSON . fromNonNegative
+  readJSON v = JSON.readJSON v >>= mkNonNegative
+
+-- | Type that holds a positive value.
+newtype Positive a = Positive { fromPositive :: a }
+  deriving (Show, Read, Eq)
+
+-- | Smart constructor for 'Positive'.
+mkPositive :: (Monad m, Num a, Ord a, Show a) => a -> m (Positive a)
+mkPositive i | i > 0 = return (Positive i)
+             | otherwise = fail $ "Invalid value for positive type '" ++
+                           show i ++ "'"
+
+instance (JSON.JSON a, Num a, Ord a, Show a) => JSON.JSON (Positive a) where
+  showJSON = JSON.showJSON . fromPositive
+  readJSON v = JSON.readJSON v >>= mkPositive
+
+-- | Type that holds a non-null list.
+newtype NonEmpty a = NonEmpty { fromNonEmpty :: [a] }
+  deriving (Show, Read, Eq)
+
+-- | Smart constructor for 'NonEmpty'.
+mkNonEmpty :: (Monad m) => [a] -> m (NonEmpty a)
+mkNonEmpty [] = fail "Received empty value for non-empty list"
+mkNonEmpty xs = return (NonEmpty xs)
+
+instance (JSON.JSON a) => JSON.JSON (NonEmpty a) where
+  showJSON = JSON.showJSON . fromNonEmpty
+  readJSON v = JSON.readJSON v >>= mkNonEmpty
+
+-- * Ganeti types
+
 -- | Instance disk template type.
 $(THH.declareSADT "DiskTemplate"
        [ ("DTDiskless",   'C.dtDiskless)
-- 
GitLab