Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e2ac0e8

Browse files
committedMay 29, 2024·
Bring back the akka module
1 parent bf45267 commit e2ac0e8

File tree

8 files changed

+2540
-1
lines changed

8 files changed

+2540
-1
lines changed
 

‎README.md

+19
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,25 @@ implicit val endpoint: ApiEndpoint = ApiEndpoint.Static("http://localhost:8080/a
9292
val jac = JsonApiClient.instance
9393
```
9494

95+
#### akka-http client (to be deprecated in favor of pekko-http)
96+
97+
```scala
98+
// needs ActorSystem and Materializer for akka-http
99+
// the ApiEndPoint is used to as the "root" where to launch queries
100+
import io.lemonlabs.uri.typesafe.dsl._
101+
import akka.actor.ActorSystem
102+
import akka.stream.ActorMaterializer
103+
import com.qvantel.jsonapi.ApiEndpoint
104+
import com.qvantel.jsonapi.JsonApiClient
105+
import com.qvantel.jsonapi.client.akka.AkkaClient._
106+
107+
implicit val system: ActorSystem = ActorSystem()
108+
implicit val materializer: ActorMaterializer = ActorMaterializer()
109+
implicit val endpoint: ApiEndpoint = ApiEndpoint.Static("http://localhost:8080/api")
110+
111+
val jac = JsonApiClient.instance
112+
```
113+
95114
#### http4s client
96115
Setup for http4s client
97116
```scala
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
Copyright (c) 2017, Qvantel
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
* Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above copyright
10+
notice, this list of conditions and the following disclaimer in the
11+
documentation and/or other materials provided with the distribution.
12+
* Neither the name of the Qvantel nor the
13+
names of its contributors may be used to endorse or promote products
14+
derived from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL Qvantel BE LIABLE FOR ANY
20+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*/
27+
package com.qvantel.jsonapi.akka
28+
29+
import com.qvantel.jsonapi.model.ErrorObject
30+
31+
import _root_.spray.json.DefaultJsonProtocol._
32+
import _root_.spray.json._
33+
34+
import akka.event.LoggingAdapter
35+
import akka.http.scaladsl.model.StatusCodes._
36+
import akka.http.scaladsl.model.{StatusCode, IllegalRequestException, ContentType}
37+
import akka.http.scaladsl.settings.RoutingSettings
38+
import akka.http.scaladsl.model.{HttpEntity, HttpResponse, MediaTypes}
39+
import akka.http.scaladsl.server.Directives._
40+
import akka.http.scaladsl.server._
41+
import akka.http.scaladsl.server.AuthenticationFailedRejection._
42+
43+
import scala.util.control.NonFatal
44+
45+
trait AkkaExceptionHandlerTrait {
46+
47+
import AkkaExceptionHandlerObject._
48+
49+
val defaultAkkaRejectionHandler: RejectionHandler = RejectionHandler
50+
.newBuilder()
51+
.handle {
52+
case AuthenticationFailedRejection(cause, _) =>
53+
val rejectionMessage = cause match {
54+
case CredentialsMissing => "The resource requires authentication, which was not supplied with the request"
55+
case CredentialsRejected => "The supplied authentication is invalid"
56+
}
57+
completeJsonApiError(Unauthorized, "Authentication Failed", rejectionMessage)
58+
59+
case AuthorizationFailedRejection =>
60+
completeJsonApiError(Forbidden,
61+
"Authorization Failed",
62+
"The supplied authentication is not authorized to access this resource")
63+
64+
case MalformedFormFieldRejection(name, msg, _) =>
65+
completeJsonApiError(BadRequest, "Malformed Form Field", "The form field '" + name + "' was malformed:\n" + msg)
66+
67+
case MalformedHeaderRejection(headerName, msg, _) =>
68+
completeJsonApiError(BadRequest,
69+
"Malformed Header",
70+
s"The value of HTTP header '$headerName' was malformed:\n" + msg)
71+
72+
case MalformedQueryParamRejection(name, msg, _) =>
73+
completeJsonApiError(BadRequest,
74+
"Malformed Query Param",
75+
"The query parameter '" + name + "' was malformed:\n" + msg)
76+
77+
case MalformedRequestContentRejection(msg, _) =>
78+
completeJsonApiError(BadRequest, "Malformed Request Content", "The request content was malformed:\n" + msg)
79+
80+
case MethodRejection(supported) =>
81+
completeJsonApiError(MethodNotAllowed,
82+
"HTTP method not allowed",
83+
"HTTP method not allowed, supported methods: " + supported.toString)
84+
85+
case SchemeRejection(supported) =>
86+
completeJsonApiError(BadRequest,
87+
"Uri scheme not allowed",
88+
"Uri scheme not allowed, supported schemes: " + supported)
89+
90+
case MissingCookieRejection(cookieName) =>
91+
completeJsonApiError(BadRequest, "Missing Cookie", s"Request is missing required cookie '$cookieName'")
92+
93+
case MissingFormFieldRejection(fieldName) =>
94+
completeJsonApiError(BadRequest, "Missing Form Field", s"Request is missing required form field '$fieldName'")
95+
96+
case MissingHeaderRejection(headerName) =>
97+
completeJsonApiError(BadRequest, "Missing Header", s"Request is missing required HTTP header '$headerName'")
98+
99+
case MissingQueryParamRejection(paramName) =>
100+
completeJsonApiError(BadRequest,
101+
"Missing Query Param",
102+
s"Request is missing required query parameter '$paramName'")
103+
104+
case RequestEntityExpectedRejection =>
105+
completeJsonApiError(BadRequest, "Request Entity Expected", "Request entity expected but not supplied")
106+
107+
case TooManyRangesRejection(_) =>
108+
completeJsonApiError(RangeNotSatisfiable, "Too Many Ranges", "Request contains too many ranges")
109+
110+
case UnsatisfiableRangeRejection(unsatisfiableRanges, _) =>
111+
completeJsonApiError(
112+
RangeNotSatisfiable,
113+
"Unsatisfiable Range",
114+
unsatisfiableRanges.mkString("None of the following requested Ranges were satisfiable:\n", "\n", "")
115+
)
116+
117+
case UnacceptedResponseContentTypeRejection(supported) =>
118+
completeJsonApiError(
119+
NotAcceptable,
120+
"Unaccepted Response Content Type",
121+
"Resource representation is only available with these Content-Types:\n" + supported.mkString("\n")
122+
)
123+
124+
case UnacceptedResponseEncodingRejection(supported) =>
125+
completeJsonApiError(
126+
NotAcceptable,
127+
"Unaccepted Response Encoding",
128+
"Resource representation is only available with these Content-Encodings:\n" + supported.mkString("\n")
129+
)
130+
131+
case UnsupportedRequestContentTypeRejection(supported) =>
132+
completeJsonApiError(UnsupportedMediaType,
133+
"Unsupported Request Content-Type",
134+
"There was a problem with the requests Content-Type:\n" + supported.mkString(" or "))
135+
136+
case UnsupportedRequestEncodingRejection(supported) =>
137+
completeJsonApiError(BadRequest,
138+
"Unsupported Request Encoding",
139+
"The request Content-Encoding must be the following:\n" + supported.value)
140+
141+
case ValidationRejection(msg, _) =>
142+
completeJsonApiError(BadRequest, "Validation Rejection", msg)
143+
}
144+
.handleNotFound {
145+
completeJsonApiError(NotFound, NotFound.reason, NotFound.defaultMessage)
146+
}
147+
.result()
148+
149+
def defaultAkkaExceptionHandler(implicit settings: RoutingSettings, log: LoggingAdapter): ExceptionHandler =
150+
ExceptionHandler {
151+
case e: IllegalRequestException => {
152+
extractRequestContext { ctx =>
153+
log.warning("Illegal request {}\n\t{}\n\tCompleting with '{}' response", ctx.request, e.getMessage, e.status)
154+
complete(jsonApiErrorResponse(e.status, "Illegal Request", e.info.format(settings.verboseErrorMessages)))
155+
}
156+
}
157+
case NonFatal(e) => {
158+
extractRequestContext { ctx =>
159+
log.error(e, "Error during processing of request {}", ctx.request)
160+
complete(
161+
jsonApiErrorResponse(InternalServerError,
162+
InternalServerError.reason,
163+
if (e.getMessage != null) e.getMessage else InternalServerError.defaultMessage))
164+
}
165+
}
166+
}
167+
}
168+
169+
object AkkaExceptionHandlerObject extends Rejection {
170+
171+
def jsonApiError(code: StatusCode, title: String, detail: String): JsValue =
172+
JsObject("errors" -> List(
173+
ErrorObject(status = Some(code.intValue.toString), title = Some(title), detail = Some(detail))).toJson)
174+
175+
def jsonApiErrorResponse(code: StatusCode, title: String, detail: String): HttpResponse =
176+
HttpResponse(
177+
status = code,
178+
entity =
179+
HttpEntity(ContentType(MediaTypes.`application/vnd.api+json`), jsonApiError(code, title, detail).prettyPrint))
180+
181+
def completeJsonApiError(code: StatusCode, title: String, detail: String): Route =
182+
complete(jsonApiErrorResponse(code, title, detail))
183+
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.