I have a route, that will unmarshell the incoming entity into a case class.
final case class ProducerMessage(topic: String, event: String, data: spray.json.JsObject)
object ProducerServer {
private val route: Route =
path("producer") {
post {
entity(as[ProducerMessage]) { msg =>
//complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
}
def create(): Future[ServerBinding] {
Http().bindAndHandle(route, getServerIp, getServerPort)
}
}
How do I know, if the process of unmarshell was successfully or not?
When received data is not a valid JSON format, what happen then?
When you have entity(as[T]) the as[T] is used to summon instance of FromRequestUnmarshaller[T] - then depending of the result returned by unmarshaller, entity will continue with passing on T into closure, or if it will fail the Directive.
If you need to do something with the information about rejection, then there are methods like recover, which you can call before apply.
For instance:
entity(as[ProducerMessage])
.map(Right(_): Either[Seq[Rejection], ProducerMessage])
.recover { rejections =>
provide(Left(rejections): Either[Seq[Rejection], ProducerMessage]))
} { value: Either[Seq[Rejection], ProducerMessage] =>
...
}
should let you see if input was rejected and recover/handle it manually.
Related
I'm trying to create a method to which I can pass a mongodb connection pool, an objectId and the collection name to retrieve data.
I came up with the code below but which doesn't compile because of the following error:
error: the method find_one exists for struct
mongodb::Collection<T>, but its trait bounds were not satisfied
label: method cannot be called on mongodb::Collection<T> due to
unsatisfied trait bounds note: the following trait bounds were not
satisfied: T: DeserializeOwned T: Unpin T: std::marker::Send T: Sync label: method cannot be called on mongodb::Collection<T> due
to unsatisfied trait bounds
What I'm I doing wrong?
pub async fn generic_find_by_id<T>(db: &AppContext, object_id: String, collection_name: &str) -> Option<T> {
let collection = db.mongodb_pool.collection::<T>(collection_name);
let id_obj = ObjectId::parse_str(object_id);
let found = match id_obj {
Ok(id) => {
let filter = doc! {"_id": id};
let result = collection.find_one(filter, None).await;
match result {
Ok(result) => {
match result {
Some(result) => {
return Some(result);
}
None => {
return None;
}
}
}
Err(_) => {
return None;
}
}
}
Err(_) => {
return None;
}
};
}
It seems Collection<T>::find_one() is only implemented if T: DeserializeOwned + Unpin + Send + Sync. (See it in the source here: https://docs.rs/mongodb/latest/src/mongodb/coll/mod.rs.html#795). I think Send and Sync have to do with the collection being potentially sent across threads during async function calls. To solve this, you can make your T type implement Unpin and DeserializeOwned. (T automatically implements Send and Sync if all T's members are Send and Sync.)
Imagine the following chain where a user wants to save a list of some sort:
var saveChain = userTappedSaveListSubject
.doOnNext { list -> Void in // create pdf version
let pdfFactory = ArticleListPDFFactory()
list.pdf = try pdfFactory.buildPDF(list)
try database.save(list)
}
.flatMap { list in
AuthorizedNetworking.shared.request(.createList(try ListRequestModel(list)))
.filter(statusCode: 201)
.map { _ in list }
}
.doOnNext { list in
list.uploaded = true
try database.save(list)
try Printer().print(list)
}
.materialize()
.share()
On every operator in the chain errors can occur, which would terminate the stream and the user would be unable to retry saving and printing the list (the whole chain gets disposed).
In the end the user should see either a "success" or "failure" screen by binding the observable to a textField:
Observable.of(
saveChain.elements().map { _ in
("List saved!", subtitle: "Saving successfull")
},
saveChain.errors().map { error in
("Error!", subtitle: error.localizedDescription)
})
.merge()
How should the error be handled?
Here's the obvious fix:
let saveChain = userTappedSaveListSubject
.flatMap { list in
Observable.just(list)
.do(onNext: { list -> Void in // create pdf version
let pdfFactory = ArticleListPDFFactory()
list.pdf = try pdfFactory.buildPDF(list)
try database.save(list)
})
.flatMap { list in
AuthorizedNetworking.shared.request(.createList(try ListRequestModel(list)))
.filter(statusCode: 201)
.map { _ in list }
}
.do(onNext: { list in
list.uploaded = true
try database.save(list)
try Printer().print(list)
})
.materialize()
}
.share()
However, there are a host of problems with this code because of the mixed paradigms.
You are passing around a mutable class inside your Observables. This is problematic because it's a functional paradigm so the system expects the contained type to be either a struct/enum or an immutable class.
Your reliance on side effects to load up said mutable class object again is quite odd and against the paradigm.
I have a network request that can Succeed or Fail
I have encapsulated it in an observable.
I have 2 rules for the request
1) There can never be more then 1 request at the same time
-> there is a share operator i can use for this
2) When the request was Succeeded i don't want to repeat the same
request again and just return the latest value
-> I can use shareReplay(1) operator for this
The problem arises when the request fails, the shareReplay(1) will just replay the latest error and not restart the request again.
The request should start again at the next subscription.
Does anyone have an idea how i can turn this into a Observable chain?
// scenario 1
let obs: Observable<Int> = request().shareReplay(1)
// outputs a value
obs.subscribe()
// does not start a new request but outputs the same value as before
obs.subscribe()
// scenario 2 - in case of an error
let obs: Observable<Int> = request().shareReplay(1)
// outputs a error
obs.subscribe()
// does not start a new request but outputs the same value as before, but in this case i want it to start a new request
obs.subscribe()
This seems to be a exactly doing what i want, but it consists of keeping state outside the observable, anyone know how i can achieve this in a more Rx way?
enum Err: Swift.Error {
case x
}
enum Result<T> {
case value(val: T)
case error(err: Swift.Error)
}
func sample() {
var result: Result<Int>? = nil
var i = 0
let intSequence: Observable<Result<Int>> = Observable<Int>.create { observer in
if let result = result {
if case .value(let val) = result {
return Observable<Int>.just(val).subscribe(observer)
}
}
print("do work")
delay(1) {
if i == 0 {
observer.onError(Err.x)
} else {
observer.onNext(1)
observer.onCompleted()
}
i += 1
}
return Disposables.create {}
}
.map { value -> Result<Int> in Result.value(val: value) }
.catchError { error -> Observable<Result<Int>> in
return .just(.error(err: error))
}
.do(onNext: { result = $0 })
.share()
_ = intSequence
.debug()
.subscribe()
delay(2) {
_ = intSequence
.debug()
.subscribe()
_ = intSequence
.debug()
.subscribe()
}
delay(4) {
_ = intSequence
.debug()
.subscribe()
}
}
sample()
it only generates work when we don't have anything cached, but thing again we need to use side effects to achieve the desired output
As mentioned earlier, RxSwift errors need to be treated as fatal errors. They are errors your stream usually cannot recover from, and usually errors that would not even be user facing.
For that reason - a stream that emits an .error or .completed event, will immediately dispose and you won't receive any more events there.
There are two approaches to tackling this:
Using a Result type like you just did
Using .materialize() (and .dematerialize() if needed). These first operator will turn your Observable<Element> into a Observable<Event<Element>>, meaning instead of an error being emitted and the sequence terminated, you will get an element that tells you it was an error event, but without any termination.
You can read more about error handling in RxSwift in Adam Borek's great blog post about this: http://adamborek.com/how-to-handle-errors-in-rxswift/
If an Observable sequence emits an error, it can never emit another event. However, it is a fairly common practice to wrap an error-prone Observable inside of another Observable using flatMap and catch any errors before they are allowed to propagate through to the outer Observable. For example:
safeObservable
.flatMap {
Requestor
.makeUnsafeObservable()
.catchErrorJustReturn(0)
}
.shareReplay(1)
.subscribe()
I have login endpoint that looks roughly like this
get {
(path("token") & parameters("email", "password")) { (email, password) =>
complete {
DBManager.getUserByEmail(email) match {
case Some(user) =>
// Check everything is return something
UserWire(user)
case None => StatusCodes.NotFound -> "User doesn't exist"
}
}
}
}
DBManager.getUserByEmail returns Option[User]. I just switched to Slick where everything is asynchronous thus method now returns Future[User].
How can I send desired response when future has failed ? I tried this
complete {
DBManager.getUserByEmail(email).map(user => {
// Check everything is return something
UserWire(user)
}).recoverWith { case ex => Future.successful(StatusCodes.NotFound -> "User doesn't exist") }
}
It fails to compile with
Error:(497, 26) type mismatch;
found : scala.concurrent.Future[Product with Serializable]
required: akka.http.scaladsl.marshalling.ToResponseMarshallable
}).recoverWith { case ex => Future.successful(StatusCodes.NotFound -> "User doesn't exist") }
^
How can I fix it ?
In spray you can use onComplete, it gets a future and returns a directive, so in your case:
onComplete(DBManager.getUserByEmail(email)) {
case Success(optUser) =>
complete {optUser.map(UserWire(_)).getOrElse(StatusCodes.NotFound -> "User doesn't exist") }
case Failure(_) =>
complete { InternalServerError() }
}
Although I've never used it seems like Akka http has the same method.
First thing. When you are building reactive application it is good to operate only on successful operations. Handling future fails need more resources than successful operations. Reactive version of DBManager.getUserByEmail should return Future[Option[User]]
When you have future just map result to Marshallable form or response.
get {
(path("token") & parameters("email", "password")) { (email, password) =>
complete {
DBManager.getUserByEmail(email) map {
case Some(user) =>
UserWire(user)
case None =>
StatusCodes.NotFound -> "User doesn't exist"
}
}
}
}
I develop Rest API with scala and play framework.
In my product controller, I do validation of parameters received.
In case they fail from some reason, I would like to response with BadRequest in the middle of the function and not in the last line as scala works..
In the code below - Code continues running to the Ok line.. which is wrong, I want to return !
def getProduct(lang: String, t: String, ids: String) = Action {
val productIdsList = ids.split(",").toList
if (productIdsList.length.equals(1) && productIdsList(0).equals("")) //Validate input params are product Ids and not empty !
{
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "No products IDs", 500)
BadRequest(Json.toJson(errorResponse))//maybe return BadRequest(Json.toJson(errorResponse) ??
}
val results = productService.getProducts(GetProductsRequest(lang,t,productIdsList));
Ok(Json.toJson(results))
// TODO: handle error
}
If implemented as:
return BadRequest(...)
It reply with error:
"method getProduct has return statement; needs result type"
I Understand this is bad practice, so what is the best practice for quitting the function without finishing it (and not throwing exceptions..)
Just put an else branch, so there's nowhere to continue:
def getProduct(lang: String, t: String, ids: String) = Action {
val productIdsList = ids.split(",").toList
if (productIdsList.length.equals(1) && productIdsList(0).equals("")){ //Validate input params are product Ids and not empty !
var errorResponse:ErrorResponse[String] = ErrorResponse(ErrorCode.GeneralError, "No products IDs", 500)
BadRequest(Json.toJson(errorResponse))//maybe return BadRequest(Json.toJson(errorResponse) ??
}else{
val results = productService.getProducts(GetProductsRequest(lang,t,productIdsList));
Ok(Json.toJson(results))
// TODO: handle error
}
}