Skip to content

Commit ae1aebd

Browse files
committed
update api errors + add service endpoints
1 parent b5d4117 commit ae1aebd

File tree

5 files changed

+202
-45
lines changed

5 files changed

+202
-45
lines changed

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ThisBuild / organization := "app.softnetwork"
3030

3131
name := "generic-persistence-api"
3232

33-
ThisBuild / version := "0.3.2.4"
33+
ThisBuild / version := "0.3.3"
3434

3535
ThisBuild / scalaVersion := "2.12.11"
3636

server/src/main/scala/app/softnetwork/api/server/ApiEndpoint.scala

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
package app.softnetwork.api.server
22

33
import akka.http.scaladsl.server.Route
4-
import app.softnetwork.api.server.ApiErrors._
54
import app.softnetwork.api.server.config.ServerSettings
65
import app.softnetwork.serialization.commonFormats
76
import org.json4s.Formats
87
import sttp.capabilities.WebSockets
98
import sttp.capabilities.akka.AkkaStreams
10-
import sttp.model.StatusCode
119
import sttp.tapir.server.ServerEndpoint
1210
import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter
1311
import sttp.tapir.swagger.bundle.SwaggerInterpreter
14-
import sttp.tapir.generic.auto._
15-
import sttp.tapir.json.json4s.jsonBody
1612
import sttp.tapir._
1713
import sttp.tapir.swagger.SwaggerUIOptions
1814

@@ -34,45 +30,6 @@ trait ApiEndpoint {
3430

3531
implicit def formats: Formats = commonFormats
3632

37-
import app.softnetwork.serialization._
38-
39-
protected lazy val errors: EndpointOutput.OneOf[ErrorInfo, ErrorInfo] =
40-
oneOf[ErrorInfo](
41-
// returns required http code for different types of ErrorInfo.
42-
// For secured endpoint you need to define
43-
// all cases before defining security logic
44-
oneOfVariant(
45-
statusCode(StatusCode.Forbidden).and(
46-
jsonBody[Forbidden]
47-
.description("When user doesn't have role for the endpoint")
48-
)
49-
),
50-
oneOfVariant(
51-
statusCode(StatusCode.Unauthorized).and(
52-
jsonBody[Unauthorized]
53-
.description("When user doesn't authenticated or token is expired")
54-
)
55-
),
56-
oneOfVariant(
57-
statusCode(StatusCode.NotFound)
58-
.and(jsonBody[NotFound].description("When something not found"))
59-
),
60-
oneOfVariant(
61-
statusCode(StatusCode.BadRequest)
62-
.and(jsonBody[BadRequest].description("Bad request"))
63-
),
64-
oneOfVariant(
65-
statusCode(StatusCode.InternalServerError)
66-
.and(jsonBody[InternalServerError].description("For exceptional cases"))
67-
),
68-
// default case below.
69-
oneOfDefaultVariant(
70-
jsonBody[ErrorMessage]
71-
.description("Default result")
72-
.example(ErrorMessage("Test error message"))
73-
)
74-
)
75-
7633
}
7734

7835
object ApiEndpoint {

server/src/main/scala/app/softnetwork/api/server/ApiErrors.scala

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ package app.softnetwork.api.server
33
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
44
import akka.http.scaladsl.server.Directives.complete
55
import akka.http.scaladsl.server.Route
6+
import app.softnetwork.serialization.commonFormats
67
import org.json4s.Formats
8+
import sttp.model.StatusCode
9+
import sttp.tapir.EndpointOutput.OneOf
10+
import sttp.tapir.server.PartialServerEndpointWithSecurityOutput
11+
import sttp.tapir._
12+
import sttp.tapir.generic.auto.SchemaDerivation
13+
import sttp.tapir.json.json4s.TapirJson4s
714

8-
object ApiErrors {
15+
import scala.concurrent.Future
16+
17+
object ApiErrors extends SchemaDerivation with TapirJson4s {
918

1019
sealed trait ErrorInfo {
1120
val message: String
@@ -44,4 +53,141 @@ object ApiErrors {
4453
case r: ErrorMessage => complete(HttpResponse(StatusCodes.OK, entity = r))
4554
}
4655

56+
implicit def formats: Formats = commonFormats
57+
58+
import app.softnetwork.serialization._
59+
60+
val forbiddenVariant: EndpointOutput.OneOfVariant[ApiErrors.Forbidden] =
61+
oneOfVariant(
62+
statusCode(StatusCode.Forbidden).and(
63+
jsonBody[ApiErrors.Forbidden]
64+
.description("When user doesn't have role for the endpoint")
65+
)
66+
)
67+
68+
val unauthorizedVariant: EndpointOutput.OneOfVariant[ApiErrors.Unauthorized] =
69+
oneOfVariant(
70+
statusCode(StatusCode.Unauthorized).and(
71+
jsonBody[ApiErrors.Unauthorized]
72+
.description("When user doesn't authenticated or token is expired")
73+
)
74+
)
75+
76+
val notFoundVariant: EndpointOutput.OneOfVariant[ApiErrors.NotFound] =
77+
oneOfVariant(
78+
statusCode(StatusCode.NotFound)
79+
.and(jsonBody[ApiErrors.NotFound].description("When something not found"))
80+
)
81+
82+
val badRequestVariant: EndpointOutput.OneOfVariant[ApiErrors.BadRequest] =
83+
oneOfVariant(
84+
statusCode(StatusCode.BadRequest)
85+
.and(jsonBody[ApiErrors.BadRequest].description("Bad request"))
86+
)
87+
88+
val internalServerErrorVariant: EndpointOutput.OneOfVariant[ApiErrors.InternalServerError] =
89+
oneOfVariant(
90+
statusCode(StatusCode.InternalServerError)
91+
.and(jsonBody[ApiErrors.InternalServerError].description("For exceptional cases"))
92+
)
93+
94+
val defaultErrorVariant: EndpointOutput.OneOfVariant[ApiErrors.ErrorMessage] =
95+
oneOfDefaultVariant(
96+
jsonBody[ApiErrors.ErrorMessage]
97+
.description("Default result")
98+
.example(ApiErrors.ErrorMessage("Test error message"))
99+
)
100+
101+
protected lazy val oneOfApiErrors
102+
: EndpointOutput.OneOf[ApiErrors.ErrorInfo, ApiErrors.ErrorInfo] =
103+
oneOf[ApiErrors.ErrorInfo](
104+
// returns required http code for different types of ErrorInfo.
105+
// For secured endpoint you need to define
106+
// all cases before defining security logic
107+
forbiddenVariant,
108+
unauthorizedVariant,
109+
notFoundVariant,
110+
badRequestVariant,
111+
internalServerErrorVariant,
112+
// default case below.
113+
defaultErrorVariant
114+
)
115+
116+
def oneOfEitherApiErrors[T: Manifest: Schema]()
117+
: OneOf[Either[ApiErrors.ErrorInfo, T], Either[ApiErrors.ErrorInfo, T]] = {
118+
oneOf[Either[ApiErrors.ErrorInfo, T]](
119+
oneOfVariantValueMatcher(StatusCode.Ok, jsonBody[Right[ApiErrors.ErrorInfo, T]]) {
120+
case Right(_) => true
121+
},
122+
oneOfVariantValueMatcher(
123+
StatusCode.Forbidden,
124+
jsonBody[Left[ApiErrors.Forbidden, T]]
125+
.description("When user doesn't have role for the endpoint")
126+
) { case Left(ApiErrors.Forbidden(_)) =>
127+
true
128+
},
129+
oneOfVariantValueMatcher(
130+
StatusCode.Unauthorized,
131+
jsonBody[Left[ApiErrors.Unauthorized, T]]
132+
.description("When user doesn't authenticated or token is expired")
133+
) { case Left(ApiErrors.Unauthorized(_)) =>
134+
true
135+
},
136+
oneOfVariantValueMatcher(
137+
StatusCode.NotFound,
138+
jsonBody[Left[ApiErrors.NotFound, T]].description("When something not found")
139+
) { case Left(ApiErrors.NotFound(_)) =>
140+
true
141+
},
142+
oneOfVariantValueMatcher(
143+
StatusCode.BadRequest,
144+
jsonBody[Left[ApiErrors.BadRequest, T]].description("Bad request")
145+
) { case Left(ApiErrors.BadRequest(_)) =>
146+
true
147+
},
148+
oneOfVariantValueMatcher(
149+
StatusCode.InternalServerError,
150+
jsonBody[Left[ApiErrors.InternalServerError, T]].description("For exceptional cases")
151+
) { case Left(ApiErrors.InternalServerError(_)) =>
152+
true
153+
},
154+
oneOfDefaultVariant(
155+
jsonBody[Left[ApiErrors.ErrorMessage, T]]
156+
.description("Default result")
157+
.and(statusCode(StatusCode.BadRequest))
158+
)
159+
)
160+
161+
}
162+
163+
def withApiErrorVariants[SECURITY_INPUT, PRINCIPAL, INPUT, SECURITY_OUTPUT, OUTPUT](
164+
body: => PartialServerEndpointWithSecurityOutput[
165+
SECURITY_INPUT,
166+
PRINCIPAL,
167+
INPUT,
168+
Unit,
169+
SECURITY_OUTPUT,
170+
OUTPUT,
171+
Any,
172+
Future
173+
]
174+
): PartialServerEndpointWithSecurityOutput[
175+
SECURITY_INPUT,
176+
PRINCIPAL,
177+
INPUT,
178+
Any,
179+
SECURITY_OUTPUT,
180+
OUTPUT,
181+
Any,
182+
Future
183+
] =
184+
body.errorOutVariants(
185+
forbiddenVariant,
186+
unauthorizedVariant,
187+
notFoundVariant,
188+
badRequestVariant,
189+
internalServerErrorVariant,
190+
defaultErrorVariant
191+
)
192+
47193
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package app.softnetwork.api.server
2+
3+
import app.softnetwork.concurrent.Completion
4+
import app.softnetwork.persistence.message.{Command, CommandResult, ErrorMessage}
5+
import app.softnetwork.persistence.service.Service
6+
import app.softnetwork.persistence.typed.scaladsl.EntityPattern
7+
import sttp.tapir.generic.auto.SchemaDerivation
8+
import sttp.tapir.Tapir
9+
import sttp.tapir.json.json4s.TapirJson4s
10+
11+
import scala.language.implicitConversions
12+
13+
trait ServiceEndpoints[C <: Command, R <: CommandResult]
14+
extends ApiEndpoint
15+
with Tapir
16+
with SchemaDerivation
17+
with TapirJson4s
18+
with Service[C, R]
19+
with Completion { _: EntityPattern[C, R] =>
20+
21+
implicit def resultToApiError(result: R): ApiErrors.ErrorInfo =
22+
result match {
23+
case error: ErrorMessage => ApiErrors.ErrorMessage(error.message)
24+
case _ => ApiErrors.ErrorMessage("Unknown")
25+
}
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package app.softnetwork.session.service
2+
3+
import app.softnetwork.api.server.ServiceEndpoints
4+
import app.softnetwork.persistence.message.{Command, CommandResult}
5+
import app.softnetwork.persistence.typed.scaladsl.EntityPattern
6+
import com.softwaremill.session.{
7+
GetSessionTransport,
8+
SetSessionTransport,
9+
TapirCsrfCheckMode,
10+
TapirEndpoints,
11+
TapirSessionContinuity
12+
}
13+
import org.softnetwork.session.model.Session
14+
15+
trait ServiceWithSessionEndpoints[C <: Command, R <: CommandResult]
16+
extends ServiceEndpoints[C, R]
17+
with TapirEndpoints { _: EntityPattern[C, R] =>
18+
def sessionEndpoints: SessionEndpoints
19+
20+
def sc: TapirSessionContinuity[Session] = sessionEndpoints.sc
21+
22+
def st: SetSessionTransport = sessionEndpoints.st
23+
24+
def gt: GetSessionTransport = sessionEndpoints.gt
25+
26+
def checkMode: TapirCsrfCheckMode[Session] = sessionEndpoints.checkMode
27+
28+
}

0 commit comments

Comments
 (0)