The Exception Handling section in Daml documentation discusses two options for handling exceptions: using Either data type and using try-catch block. For your use case you need to utilize both.
Here’s an example with a custom exception.
exception ZeroAge
where
message "Age cannot be zero"
template Student
with
age : Decimal
name : Text
school : Party
where
signatory school
choice SetAge : ContractId Student
with
newage : Decimal
controller school
do
if newage == 0.0
then throw ZeroAge
else create this with age = newage
template TestHelper
with
tester : Party
where
signatory tester
nonconsuming choice Test_SetAge : Either Text Text
with
studentCid : ContractId Student
newage : Decimal
controller tester
do
try do
exercise studentCid SetAge with newage
return $ Right "Success"
catch
(ex : ZeroAge) -> do
return $ Left $ message ex
testSetAge = script do
hs1 <- allocateParty "High School 1"
alice_studentCid <- submit hs1 do
createCmd Student with
age = -1.0
name = "Alice"
school = hs1
thCid <- submit hs1 do
createCmd TestHelper with
tester = hs1
test_result <- submit hs1 do
exerciseCmd thCid Test_SetAge with
studentCid = alice_studentCid
newage = 0.0
test_result === Left "Age cannot be zero"
return ()
Note that, because exceptions are not propagated across submit, you cannot catch the exception thrown by SetAge choice in a try-catch block in Daml Script, and you need to utilize a helper template to catch the exception. For details on why this is the case and some additional examples check out previous discussions on the following threads