How to Decode a sealed trait to JSON - Circe - akka

I have the sealed trait below and its case class and I would like to transform it into JSON to give it as a response in my Akka Http app.
sealed trait HttpRestError {
val statusCode: StatusCode
val code: String
val message: String
}
case class UnauthorizedError() extends HttpRestError {
override val statusCode: StatusCode = Unauthorized
override val code: String = "Unauthorized"
override val message: String = "Unauthorized"
}
And the Route part
def login2: Route = {
path("test") {
pathEndOrSingleSlash {
get {
onComplete(authService.testUser.toRestError[UnauthorizedError]) {
case util.Success(f) => f match {
case Left(error) =>
complete((error.statusCode, error))
case Right(v) => complete(OK -> v)
}
case util.Failure(ex) =>
complete(StatusCodes.InternalServerError)
}
}
}
}
}
The problem is that when the Either returns the Left side the response is empty, but the error code is correct.
Any idea?

Related

io.ktor.client.features.ClientRequestException: Client request(api_endpoint) invalid: 404 Not Found

I am mocking HttpClient to mock it's post request to an API endpoint. Here's the test case code :-
class SomeClassTest {
#Mock
lateinit var someClassObj: SomeClass
#Mock
var httpClient = HttpClient(CIO)
// Read config properties
private val testEnv = createTestEnvironment {
config = HoconApplicationConfig(ConfigFactory.load("application.conf"))
}
private fun getMockedHttpClientCall(customResponse: String): HttpClientCall {
// Return HttpClientCall object with custom response and status = OK
val httpClientCall = mockk<HttpClientCall> {
every { client } returns mockk {}
coEvery { receive(io.ktor.util.reflect.typeInfo<String>()) } returns customResponse
every { coroutineContext } returns EmptyCoroutineContext
every { attributes } returns Attributes()
every { request } returns object : HttpRequest {
override val call: HttpClientCall = this#mockk
override val attributes: Attributes = Attributes()
override val content: OutgoingContent = object : OutgoingContent.NoContent() {}
override val headers: Headers = Headers.Empty
override val method: HttpMethod = HttpMethod.Get
override val url: Url = Url("/")
}
every { response } returns object : HttpResponse() {
override val call: HttpClientCall = this#mockk
override val content: ByteReadChannel = ByteReadChannel(customResponse)
override val coroutineContext: CoroutineContext = EmptyCoroutineContext
override val headers: Headers = Headers.Empty
override val requestTime: GMTDate = GMTDate.START
override val responseTime: GMTDate = GMTDate.START
override val status: HttpStatusCode = HttpStatusCode.OK
override val version: HttpProtocolVersion = HttpProtocolVersion.HTTP_1_1
}
}
return httpClientCall
}
#Before
fun setUp() {
MockitoAnnotations.openMocks(HttpClient::class)
MockitoAnnotations.openMocks(SomeClass::class)
someClassObj = SomeClass(httpClient)
}
#Test
fun Test() = withApplication(testEnv) {
runBlocking {
val mockedEngine = MockEngine { request ->
respond(
content = "{\"response\":\"some sample response\"}",
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json")
)
}
this#SomeClassTest.httpClient = HttpClient(engine = mockedEngine)
someClassObj.setHttpClient(httpClient)
Mockito.`when`(httpClient.post<HttpResponse>(Configuration.properties.getAPIEndpoint.toString())).thenReturn(getMockedHttpClientCall("{\"response\":\"some sample response\"}").response)
assertEquals("{\"response\":\"some sample response\"}", someClassObj.func())
}
}
}
The class "SomeClass" is dedicated to call external APIs. It takes an HttpClient object in its constructor and has a setter method setHttpClient(). I am using MockEngine to mock the HttpClient.
The "someClassObj.func()" function makes a post request to the API endpoint.
The API endpoint is stored in the configuration file which I am reading in the Mockito stubbing statement.
The function "getMockedHttpClientCall()" takes a custom response as String, and uses MockK to return an "HttpClientCall" object with its HttpResponse = customResponse. This is used in the Mockito stubbing statement (in the thenReturn part). I have mocked the entire HttpClientCall because I couldn't find any other way to mock only the HttpResponse.
When I run the test case, I get an error saying io.ktor.client.features.ClientRequestException: Client request(<the api endpoint>) invalid: 404 Not Found. Text: "". However, when I make the same POST request in postman, I am getting the desired response from the API. Any idea why this may be happening?

angulardart and django_rest. unable to get data from the backend

this example is from a tutorial and I get an error
Exception: Server error; cause: Expected a value of type 'int', but got one of type 'String'
terminal pycharm backend gives data code 200
[09/Feb/2020 11:45:48] "GET /adverts/ HTTP/1.1" 200 492
class Hero {
final int id;
String name;
Hero(this.id, this.name);
factory Hero.fromJson(Map<String, dynamic> hero) =>
Hero(_toInt(hero['id']), hero['name']);
Map toJson() => {'id': id, 'name': name};
}
int _toInt(id) => id is int ? id : int.parse(id);
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart';
import 'hero.dart';
class HeroService {
static final _headers = {'Content-Type': 'application/json'};
static const _heroesUrl = 'http://127.0.0.1:8000/heroes'; // URL to
web API
final Client _http;
HeroService(this._http);
Future<List<Hero>> getAll() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = (_extractData(response) as List)
.map((value) => Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
Future<Hero> create(String name) async {
try {
final response = await _http.post(_heroesUrl,
headers: _headers, body: json.encode({'name': name}));
return Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => json.decode(resp.body)['data'];
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return Exception('Server error; cause: $e');
}
}
I don't have json this -> data: and to get the data you need from the string -> dynamic _extractData (Response resp) => json.decode (resp.body) ['data']; delete ['data'] or edit JSON generation django_rest, if you have information on how to get a JSON like {'data':[ ]} in drf, I'm happy to read it

Marshall or UnMarshall case object with SprayJsonSupport

I have a case object in an actor like this:
object UserActor{
case object getResult
def props = Props(new UserActor)
}
class UserActor extends Actor {
def receive = {
case getResult =>
val reply = sender
reply ! "Hello world"
}
}
which I want to use in a route as this:
val route: Route =
path("users") { id =>
get {
complete {
userActor ? getResult
}
}
}
However, I got the error, expected ToResponseMarshallable. So question, how do I marshall case object using SprayJsonSupport?

akka spray : java.lang.IllegalArgumentException: requirement failed

I am new one in akka spray :)
I have this router actor:
class ProjectResource extends HttpServiceActor with DefaultJsonFormats {
import spray.http.MediaTypes.`application/json`
def receive = runRoute {
pathPrefix("rest" / "1.2") {
path("users" / Segment / "projects" / Segment / "auth_url") {
case (key, id) =>
get {
respondWithMediaType(`application/json`) {
requestContext =>
val responder = context actorOf Responder.props(sender(), requestContext)
context actorOf GetTask.props(key, id, responder)
}
}
}
}
}
}
and responder:
class Responder(replyTo: ActorRef, ctx: RequestContext) extends Actor with DefaultJsonFormats {
implicit val authFormat = jsonFormat2(AuthDTO)
def receive = {
case x: AuthDTO =>
ctx.complete(200, x)
context stop self
}
}
and when i tried to
curl -v http://localhost:8080/rest/1.2/users/123/projects/1/auth_url
i got this error (on ctx.complete(200, x) string ...):
[ERROR] [07/11/2016 18:40:32.954] [default-akka.actor.default-dispatcher-3] [akka://default/user/projectResource] Error during processing of request HttpRequest(GET,http://localhost:8080/rest/1.2/users/123/projects/1/auth_url,List(User-Agent: curl/7.35.0, Host: localhost:8080),Empty,HTTP/1.1)
java.lang.IllegalArgumentException: requirement failed
What i am doing wrong? Help Please!

unmarshall nested json in spray-json

Using spray-json (as I'm using spray-client) in order to get a latitude,longitude object from the google maps API I need to have the whole response structure set up:
case class AddrComponent(long_name: String, short_name: String, types: List[String])
case class Location(lat: Double, lng: Double)
case class ViewPort(northeast: Location, southwest: Location)
case class Geometry(location: Location, location_type: String, viewport: ViewPort)
case class EachResult(address_components: List[AddrComponent],
formatted_address: String,
geometry: Geometry,
types: List[String])
case class GoogleApiResult[T](status: String, results: List[T])
object AddressProtocol extends DefaultJsonProtocol {
implicit val addrFormat = jsonFormat3(AddrComponent)
implicit val locFormat = jsonFormat2(Location)
implicit val viewPortFormat = jsonFormat2(ViewPort)
implicit val geomFormat = jsonFormat3(Geometry)
implicit val eachResFormat = jsonFormat4(EachResult)
implicit def GoogleApiFormat[T: JsonFormat] = jsonFormat2(GoogleApiResult.apply[T])
}
import AddressProtocol._
Is there any way I can just get Location from the json in the response and avoid all this gumph?
The spray-client code:
implicit val system = ActorSystem("test-system")
import system.dispatcher
private val pipeline = sendReceive ~> unmarshal[GoogleApiResult[EachResult]]
def getPostcode(postcode: String): Point = {
val url = s"http://maps.googleapis.com/maps/api/geocode/json?address=$postcode,+UK&sensor=true"
val future = pipeline(Get(url))
val result = Await.result(future, 10 seconds)
result.results.size match {
case 0 => throw new PostcodeNotFoundException(postcode)
case x if x > 1 => throw new MultipleResultsException(postcode)
case _ => {
val location = result.results(0).geometry.location
new Point(location.lng, location.lat)
}
}
}
Or alternatively how can I use jackson with spray-client?
Following jrudolph's advice to json-lenses I also got in quite a bit of fiddling but finally got things to work. I found it quite difficult (as a newbie) and also I am sure this solution is far from the most elegant - nevertheless I think this might help people or inspire others for improvements.
Given JSON:
{
"status": 200,
"code": 0,
"message": "",
"payload": {
"statuses": {
"emailConfirmation": "PENDING",
"phoneConfirmation": "DONE",
}
}
}
And case class for unmarshalling statuses only:
case class UserStatus(emailConfirmation: String, phoneConfirmation: String)
One can do this to unmarshal response:
import scala.concurrent.Future
import spray.http.HttpResponse
import spray.httpx.unmarshalling.{FromResponseUnmarshaller, MalformedContent}
import spray.json.DefaultJsonProtocol
import spray.json.lenses.JsonLenses._
import spray.client.pipelining._
object UserStatusJsonProtocol extends DefaultJsonProtocol {
implicit val userStatusUnmarshaller = new FromResponseUnmarshaller[UserStatus] {
implicit val userStatusJsonFormat = jsonFormat2(UserStatus)
def apply(response: HttpResponse) = try {
Right(response.entity.asString.extract[UserStatus]('payload / 'statuses))
} catch { case x: Throwable =>
Left(MalformedContent("Could not unmarshal user status.", x))
}
}
}
import UserStatusJsonProtocol._
def userStatus(userId: String): Future[UserStatus] = {
val pipeline = sendReceive ~> unmarshal[UserStatus]
pipeline(Get(s"/api/user/${userId}/status"))
}