@@ -3,9 +3,18 @@ package app.softnetwork.api.server
33import akka .http .scaladsl .model .{HttpResponse , StatusCodes }
44import akka .http .scaladsl .server .Directives .complete
55import akka .http .scaladsl .server .Route
6+ import app .softnetwork .serialization .commonFormats
67import 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}
0 commit comments