Retrieving error status from Haskell MongoDB driver - mongodb

I have a MongoDB with one collection user that contains a unique index on email:
import Data.Bson (Value (Int32))
import Database.MongoDB (Index (..), createIndex, (=:))
createIndex $ Index "user" [ "email" =: Int32 1 ] "email" True False
I have written a function that inserts a new user if the email address it is not already in use, and should fail if the email address is already taken:
import Data.Bson (Value (ObjId))
import Database.MongoDB (Action, ObjectId, (=:))
import qualified Database.MongoDB as M (insert)
data User = User
{ email :: Text
, firstName :: Text
, lastName :: Text
} deriving Show
data MongoEntity a = MongoEntity ObjectId a
createIfNotExists :: User
-> Action Handler (Either Text (MongoEntity User))
createIfNotExists (user#User {..}) = do
value <- M.insert "user" [ "email" =: email
, "firstName" =: firstName
, "lastName" =: lastName
]
case value of
ObjId objectId -> return (Right $ MongoEntity objectId user)
_ -> return $ Left "no document"
I would like to detect any errors throw by the M.insert (such as a duplicate key) and return an
error Text message.
Since the M.insert runs in the Action monad, I assume it sets an error status if it fails, but I can't figure out how to retrieve it. I assume that I need something like
error <- getErrorStatus -- or whatever it is called
immediately after the value <- M.insert ... line and then test the error in the case ... of expression.
By the way, I tried writing createIfNotExists using findAndModify but I couldn't capture a failure with that either.

I've never used this module, but reading the documentation, the best I could think of is :
Change the type for createIfNotExists
createIfNotExists :: MonadIO m =>
User -> Action m (Either String (MongoEntity User))
Use catchJust from Control.Exception.Base. The author seems to discourage the usage of Control.Monad.Error.Class.
main :: IO ()
main = do
pipe <- connect (host "127.0.0.1")
let user = User "jd#bar.baz" "John" "Doe"
eitherEntity <- catchJust writeFailureErrorStr
(access pipe master "user" $ createIfNotExists user)
(return . Left)
close pipe
writeFailureErrorStr :: Failure -> Maybe String
writeFailureErrorStr (WriteFailure _err str) = Just str
writeFailureErrorStr _other = Nothing
You probably want to check the _err code value too, since there could be other reasons for WriteFailure.
My understanding of the documentation is that it is not possible to handle errors out of IO.
I'm so confused by this way of doing that I think I'm probably wrong. Please show me a more elegant way if there is one.
More generally, I think that checking existence by try and fail is not very nice. I'd rather do a find request and insert if no result.

From Jean-Baptiste Potonnier's answer and after reading two articles by Michael Snoyman (http://www.yesodweb.com/blog/2014/06/exceptions-transformers and https://www.fpcomplete.com/user/snoyberg/general-haskell/exceptions/exceptions-and-monad-transformers), I rewrote my code as follows:
import Control.Exception.Lifted (handleJust)
import Data.Bson (Value (ObjId))
import qualified Data.Bson as B (Value)
import qualified Data.Text as T (pack)
import Database.MongoDB (Action, Failure (..), (=:))
import qualified Database.MongoDB as M (insert)
createIfNotExists :: User -> Action Handler (Either Failure (MongoEntity User))
createIfNotExists user =
handleJust writeFailureHandler (return . Left) $ do
value <- insertUser user
return $
case value of
ObjId objectId -> Right $ MongoEntity (T.pack $ show objectId) user
_ -> error "Unexpected missing document"
where
writeFailureHandler :: Failure -> Maybe Failure
writeFailureHandler writeFailure#WriteFailure{} = Just writeFailure
writeFailureHandler _ = Nothing
insertUser :: User -> Action Handler B.Value
insertUser User{..} =
M.insert "users" [ "email" =: email
, "firstName" =: firstName
, "lastName" =: lastName
]
using the lifted-base package.

Related

How can I access mongodb from purescript?

How can I access mongodb from purescript?
the only package I found was purescript-node-mongodb
but it looks outdated, and it doesn't install with spago
is there any other recommendation?
if not, should I use nodejs driver?
Found this fork that solves the problem:
https://github.com/j-nava/purescript-mongo
it was as easy as:
queryEmail :: String -> Query User
queryEmail email = Q.by { email: Q.eq email }
findUserEmail email db = do
col <- Mongo.collection "users" db
users <- Mongo.find (queryEmail email) defaultFindOptions col
pure case users of
[] -> Nothing
arr -> arr # Array.head

Parse multiple ResultSets from select query with Anorm 2.4

I am continuing my journey with Play, Scala & Anorm, and I faced a following problem:
One of my repository classes holds a logic to fetch list of email from DB for a user, as follows:
val sql =
"""
|select * from user_email where user_id = {user_id}
|;--
""".stripMargin
SQL(sql).on(
'user_id -> user.id
).as(UserEmail.simpleParser *)
While a parser is something like this:
val simpleParser: RowParser[UserEmail] = (
SqlParser.get[Muid](qualifiedColumnNameOf("", Identifiable.Column.Id)) ~
AuditMetadata.generateParser("") ~
SqlParser.get[Muid]("user_id") ~
SqlParser.get[String]("email") ~
SqlParser.get[Boolean]("verified") ~
SqlParser.get[Muid]("verification_code_id") ~
SqlParser.get[Option[DateTime]]("verification_sent_date")
) map {
case id ~ audit ~ userId ~ email ~ emailVerified ~ emailVerificationCode ~ emailVerificationSentDate =>
UserEmail(
id,
audit,
userId,
email,
emailVerified,
emailVerificationCode,
emailVerificationSentDate
)
}
When I execute this in test, I am getting the following error:
[error] Multiple ResultSets were returned by the query. (AbstractJdbc2Statement.java:354)
...
It is expected, that there are more than a simple result; however, I am puzzled about how to parse this case correctly.
I was under assumption, that:
UserEmail.simpleParser single is for simple row
and
UserEmail.simpleParser * is to handle multiple rows
I could not figure this put based on documentation, and, at least for now, didn't find anything useful anywhere else.
How do I parse multiple rows from the result set?
Update: I just found this gist (https://gist.github.com/davegurnell/4b432066b39949850b04) with pretty good explanation and created a ResultSetParser like so:
val multipleParser: ResultSetParser[List[UserEmail]] = UserEmail.simpleParser.*
And... that didn't help!
Thanks,

Form mapping that handles optional fields with custom validation

My form looks like:
case class RegistrationForm(department: Option[String], name: String, email: String, employeeId: Option[Int])
Now what I need to do is, if the user entered the department input, then employeeId should be None. And if they left the department empty, then employeeId will be a required field.
My mapping is currently this but it doesn't handle this logic:
val registrationForm = Form(
mapping(
"department" -> optional(String)
"name" -> nonEmptyText,
"email" -> nonEmptyText,
"employeeId" -> optional(number)
)(RegistrationForm.apply)(RegistrationForm.unapply)
)
Also, in my form, how do I create a hidden input field and bind it to my form property, because sometimes my url will be like:
/users/register?employeeId=293838
So I want to create:
<input type="hidden" name="employeeId" value="???" />
So this employeeId hidden input should be bound to the form.
You can use verifying to build constraints between form fields after they've all successfully been bound to the case class.
val registrationForm = Form(
mapping(
"department" -> optional(String)
"name" -> nonEmptyText,
"email" -> nonEmptyText,
"employeeId" -> optional(number)
)(RegistrationForm.apply)(RegistrationForm.unapply)
.verifying("Some error message..", reg =>
(reg.department.isEmpty || reg.employeeId.isEmpty) && (reg.department.nonEmpty || reg.employeeId.nonEmpty)
)
)
The first argument of verifying is an error message to use when the constraint is broken, and the second is a function of the case class you're binding to that returns Boolean. If the function returns false, the Form will contain a global error. I'm not 100% sure that's the logic you're going for in my example, as your wording is a bit strange (sounds like you're describing exclusive OR). You can also chain multiple verifying operations together if needed.
I'm not sure what your question about hidden fields is asking. A hidden field within the html form will be sent to the server with everything else, and bind just as any other field will.

Dependent validation for form mapping in play framework 2

Can I create mapping for form, where first validation depend from second value?
val orderForm = Form(
mapping(
"requiredDelivery" -> boolean,
"deliveryAddress" -> text,
"comment" -> text)
(Order.apply)(Order.unapply)
)
I want to check deliveryAddress for nonEmptyText if requiredDelivery is true
Try:
val orderForm = Form(
mapping(
"requiredDelivery" -> boolean,
"deliveryAddress" -> text,
"comment" -> text)
(Order.apply)(Order.unapply) verifying("Address is required!", fields => fields match {
case order => (order.requiredDelivery && order.deliveryAddress!="") || !order.requiredDelivery
})
)
Any form validation error that is a result from the "verifying" clause after the mapping definition goes into the "global errors". The form helpers won't display these errors, so any typical template will come up blank but not able to successfully submit the form.
The fix is to add something like this to your form template:
#if(userForm.hasGlobalErrors) {
<ul>
#for(error <- userForm.globalErrors) {
<li>#error.message</li>
}
</ul>
}
You can learn more by looking at this page and skipping to the "Display errors in a view template" section - http://www.playframework.com/documentation/2.3-SNAPSHOT/ScalaForms

Play framework Scala - get session value in a form

I am using the Play 2.0.4 framework with Scala.
I have to models which are User and Team.
case class User {
var email: String,
var username: String
}
case class Team {
var sport: String,
var captain: String //is the username of a User
}
In my controllers for Users and Teams the objects are created via forms. For a User this works perfect. And with a successfull request a put the username in the session using .withSession(). Also works fine.
But now I am struggeling with creating a team and retrieving the username from the session.
It looks like
val teamForm = Form[Team](
mapping(
sport -> nonEmptyText,
//I actually don't have an input for captain as it should be retrieved from the session
)
) (
((sport, _) => User(sport, request.session.get("username"))
((team: Team) => Some(team.sport, team.captain))
)
And the problem is that request is unknown in the "context" of a form.
Does anyone have an idea how to solve that?
Unless I am missing something fundamental, you can just change your val teamForm to a def.
def teamForm(request:Request[_]) = Form[Team](
mapping(
sport -> nonEmptyText,
//I actually don't have an input for captain as it should be retrieved from the session
)
) (
((sport, _) => User(sport, request.session.get("username"))
((team: Team) => Some(team.sport, team.captain))
)