[elm] Add bearer, task, and sendWithCustomError (#5146)

* [elm] Add bearer support

* [elm] Allow mapping request errors

By either using `sendWithCustomError` or `task`.
This commit is contained in:
Erik Timmers
2020-01-29 22:26:41 +01:00
committed by GitHub
parent 84b6804d8f
commit fa295aa2a8
6 changed files with 151 additions and 52 deletions

View File

@@ -64,7 +64,7 @@ public class ElmClientCodegen extends DefaultCodegen implements CodegenConfig {
featureSet = getFeatureSet().modify()
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON))
.securityFeatures(EnumSet.noneOf(SecurityFeature.class))
.securityFeatures(EnumSet.of(SecurityFeature.BearerToken))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,

View File

@@ -2,10 +2,13 @@ module Api exposing
( Request
, request
, send
, sendWithCustomError
, task
, map
, withBasePath
, withTimeout
, withTracker
, withBearerToken
, withHeader
, withHeaders
)
@@ -13,6 +16,7 @@ module Api exposing
import Http
import Json.Decode
import Json.Encode
import Task
import Url.Builder
@@ -46,18 +50,35 @@ request method path pathParams queryParams headerParams body decoder =
send : (Result Http.Error a -> msg) -> Request a -> Cmd msg
send toMsg (Request req) =
send toMsg req =
sendWithCustomError identity toMsg req
sendWithCustomError : (Http.Error -> e) -> (Result e a -> msg) -> Request a -> Cmd msg
sendWithCustomError mapError toMsg (Request req) =
Http.request
{ method = req.method
, headers = req.headers
, url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
, body = req.body
, expect = expectJson toMsg req.decoder
, expect = expectJson mapError toMsg req.decoder
, timeout = req.timeout
, tracker = req.tracker
}
task : Request a -> Task.Task Http.Error a
task (Request req) =
Http.task
{ method = req.method
, headers = req.headers
, url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
, body = req.body
, resolver = jsonResolver req.decoder
, timeout = req.timeout
}
map : (a -> b) -> Request a -> Request b
map fn (Request req) =
Request
@@ -87,6 +108,11 @@ withTracker tracker (Request req) =
Request { req | tracker = Just tracker }
withBearerToken : String -> Request a -> Request a
withBearerToken token (Request req) =
Request { req | headers = Http.header "Authorization" ("Bearer " ++ token) :: req.headers }
withHeader : String -> String -> Request a -> Request a
withHeader key value (Request req) =
Request { req | headers = req.headers ++ [ Http.header key value ] }
@@ -121,36 +147,44 @@ queries =
List.filterMap (\(key, value) -> Maybe.map (Url.Builder.string key) value)
expectJson : (Result Http.Error a -> msg) -> Json.Decode.Decoder a -> Http.Expect msg
expectJson toMsg decoder =
Http.expectStringResponse toMsg <|
\response ->
case response of
Http.BadUrl_ url ->
Err (Http.BadUrl url)
expectJson : (Http.Error -> e) -> (Result e a -> msg) -> Json.Decode.Decoder a -> Http.Expect msg
expectJson mapError toMsg decoder =
Http.expectStringResponse toMsg (Result.mapError mapError << decodeResponse decoder)
Http.Timeout_ ->
Err Http.Timeout
Http.NetworkError_ ->
Err Http.NetworkError
jsonResolver : Json.Decode.Decoder a -> Http.Resolver Http.Error a
jsonResolver decoder =
Http.stringResolver (decodeResponse decoder)
Http.BadStatus_ metadata _ ->
Err (Http.BadStatus metadata.statusCode)
Http.GoodStatus_ _ body ->
if String.isEmpty body then
-- we might 'expect' no body if the return type is `()`
case Json.Decode.decodeString decoder "{}" of
Ok value ->
Ok value
decodeResponse : Json.Decode.Decoder a -> Http.Response String -> Result Http.Error a
decodeResponse decoder response =
case response of
Http.BadUrl_ url ->
Err (Http.BadUrl url)
Err _ ->
decodeBody decoder body
Http.Timeout_ ->
Err Http.Timeout
else
Http.NetworkError_ ->
Err Http.NetworkError
Http.BadStatus_ metadata _ ->
Err (Http.BadStatus metadata.statusCode)
Http.GoodStatus_ _ body ->
if String.isEmpty body then
-- we might 'expect' no body if the return type is `()`
case Json.Decode.decodeString decoder "{}" of
Ok value ->
Ok value
Err _ ->
decodeBody decoder body
else
decodeBody decoder body
decodeBody : Json.Decode.Decoder a -> String -> Result Http.Error a
decodeBody decoder body =

View File

@@ -33,8 +33,8 @@ import Uuid exposing (Uuid){{/includeUuid}}
{-| {{{notes}}}
-}
{{/notes}}
{{operationId}} : {{#allParams}}{{^required}}Maybe {{/required}}{{#isListContainer}}List {{/isListContainer}}{{#datatypeWithEnum}}{{.}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{#isModel}}Api.Data.{{/isModel}}{{dataType}}{{/datatypeWithEnum}} -> {{/allParams}}Api.Request {{^responses}}(){{/responses}}{{#responses}}{{#-first}}{{^dataType}}(){{/dataType}}{{#isMapContainer}}(Dict.Dict String {{/isMapContainer}}{{#isListContainer}}(List {{/isListContainer}}{{^primitiveType}}{{^isUuid}}Api.Data.{{/isUuid}}{{/primitiveType}}{{#items}}{{#isModel}}Api.Data.{{/isModel}}{{/items}}{{dataType}}{{#isListContainer}}){{/isListContainer}}{{#isMapContainer}}){{/isMapContainer}}{{/-first}}{{/responses}}
{{operationId}}{{#allParams}} {{>paramName}}{{/allParams}} =
{{operationId}} : {{#allParams}}{{^required}}Maybe {{/required}}{{#isListContainer}}List {{/isListContainer}}{{#datatypeWithEnum}}{{.}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{#isModel}}Api.Data.{{/isModel}}{{dataType}}{{/datatypeWithEnum}} -> {{/allParams}}{{#authMethods}}{{#isBasicBearer}}String -> {{/isBasicBearer}}{{/authMethods}}Api.Request {{^responses}}(){{/responses}}{{#responses}}{{#isDefault}}{{^dataType}}(){{/dataType}}{{#isMapContainer}}(Dict.Dict String {{/isMapContainer}}{{#isListContainer}}(List {{/isListContainer}}{{^primitiveType}}{{^isUuid}}Api.Data.{{/isUuid}}{{/primitiveType}}{{#items}}{{#isModel}}Api.Data.{{/isModel}}{{/items}}{{dataType}}{{#isListContainer}}){{/isListContainer}}{{#isMapContainer}}){{/isMapContainer}}{{/isDefault}}{{/responses}}
{{operationId}}{{#allParams}} {{>paramName}}{{/allParams}}{{#authMethods}}{{#isBasicBearer}} auth_token{{/isBasicBearer}}{{/authMethods}} =
Api.request
"{{httpMethod}}"
"{{path}}"
@@ -42,5 +42,6 @@ import Uuid exposing (Uuid){{/includeUuid}}
[{{#queryParams}} ( "{{baseName}}", {{#required}}Just <| {{/required}}{{^required}}Maybe.map {{/required}}{{>paramToString}} {{>paramName}} ){{#-last}} {{/-last}}{{^-last}},{{/-last}}{{/queryParams}}]
[{{#headerParams}} ( "{{baseName}}", {{#required}}Just <| {{/required}}{{^required}}Maybe.map {{/required}}{{>paramToString}} {{>paramName}} ){{#-last}} {{/-last}}{{^-last}},{{/-last}}{{/headerParams}}]
{{#bodyParam}}({{#required}}Just ({{/required}}{{^required}}Maybe.map {{/required}}{{#isModel}}Api.Data.{{/isModel}}{{>recordFieldValueEncoder}} {{>paramName}}{{#required}}){{/required}}){{/bodyParam}}{{^bodyParam}}Nothing{{/bodyParam}}
{{^responses}}(Json.Decode.succeed ()){{/responses}}{{#responses}}{{#isDefault}}{{^dataType}}(Json.Decode.succeed ()){{/dataType}}{{#dataType}}{{>recordFieldValueDecoder}}{{/dataType}}{{/isDefault}}{{/responses}}
{{^responses}}(Json.Decode.succeed ()){{/responses}}{{#responses}}{{#isDefault}}{{^dataType}}(Json.Decode.succeed ()){{/dataType}}{{#dataType}}{{>recordFieldValueDecoder}}{{/dataType}}{{/isDefault}}{{/responses}}{{#authMethods}}{{#isBasicBearer}}
|> Api.withBearerToken auth_token{{/isBasicBearer}}{{/authMethods}}
{{/operation}}{{/operations}}

View File

@@ -97,6 +97,16 @@ paths:
responses:
"200":
description: Default response
/secured:
post:
summary: Secured endpoint
security:
- BearerAuth: []
responses:
"200":
description: Authenticated
"401":
description: Unauthenticated
/uuid:
get:
parameters:
@@ -278,3 +288,8 @@ components:
type: string
in_the_middle:
type: string
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT

View File

@@ -2,10 +2,13 @@ module Api exposing
( Request
, request
, send
, sendWithCustomError
, task
, map
, withBasePath
, withTimeout
, withTracker
, withBearerToken
, withHeader
, withHeaders
)
@@ -13,6 +16,7 @@ module Api exposing
import Http
import Json.Decode
import Json.Encode
import Task
import Url.Builder
@@ -46,18 +50,35 @@ request method path pathParams queryParams headerParams body decoder =
send : (Result Http.Error a -> msg) -> Request a -> Cmd msg
send toMsg (Request req) =
send toMsg req =
sendWithCustomError identity toMsg req
sendWithCustomError : (Http.Error -> e) -> (Result e a -> msg) -> Request a -> Cmd msg
sendWithCustomError mapError toMsg (Request req) =
Http.request
{ method = req.method
, headers = req.headers
, url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
, body = req.body
, expect = expectJson toMsg req.decoder
, expect = expectJson mapError toMsg req.decoder
, timeout = req.timeout
, tracker = req.tracker
}
task : Request a -> Task.Task Http.Error a
task (Request req) =
Http.task
{ method = req.method
, headers = req.headers
, url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
, body = req.body
, resolver = jsonResolver req.decoder
, timeout = req.timeout
}
map : (a -> b) -> Request a -> Request b
map fn (Request req) =
Request
@@ -87,6 +108,11 @@ withTracker tracker (Request req) =
Request { req | tracker = Just tracker }
withBearerToken : String -> Request a -> Request a
withBearerToken token (Request req) =
Request { req | headers = Http.header "Authorization" ("Bearer " ++ token) :: req.headers }
withHeader : String -> String -> Request a -> Request a
withHeader key value (Request req) =
Request { req | headers = req.headers ++ [ Http.header key value ] }
@@ -121,36 +147,44 @@ queries =
List.filterMap (\(key, value) -> Maybe.map (Url.Builder.string key) value)
expectJson : (Result Http.Error a -> msg) -> Json.Decode.Decoder a -> Http.Expect msg
expectJson toMsg decoder =
Http.expectStringResponse toMsg <|
\response ->
case response of
Http.BadUrl_ url ->
Err (Http.BadUrl url)
expectJson : (Http.Error -> e) -> (Result e a -> msg) -> Json.Decode.Decoder a -> Http.Expect msg
expectJson mapError toMsg decoder =
Http.expectStringResponse toMsg (Result.mapError mapError << decodeResponse decoder)
Http.Timeout_ ->
Err Http.Timeout
Http.NetworkError_ ->
Err Http.NetworkError
jsonResolver : Json.Decode.Decoder a -> Http.Resolver Http.Error a
jsonResolver decoder =
Http.stringResolver (decodeResponse decoder)
Http.BadStatus_ metadata _ ->
Err (Http.BadStatus metadata.statusCode)
Http.GoodStatus_ _ body ->
if String.isEmpty body then
-- we might 'expect' no body if the return type is `()`
case Json.Decode.decodeString decoder "{}" of
Ok value ->
Ok value
decodeResponse : Json.Decode.Decoder a -> Http.Response String -> Result Http.Error a
decodeResponse decoder response =
case response of
Http.BadUrl_ url ->
Err (Http.BadUrl url)
Err _ ->
decodeBody decoder body
Http.Timeout_ ->
Err Http.Timeout
else
Http.NetworkError_ ->
Err Http.NetworkError
Http.BadStatus_ metadata _ ->
Err (Http.BadStatus metadata.statusCode)
Http.GoodStatus_ _ body ->
if String.isEmpty body then
-- we might 'expect' no body if the return type is `()`
case Json.Decode.decodeString decoder "{}" of
Ok value ->
Ok value
Err _ ->
decodeBody decoder body
else
decodeBody decoder body
decodeBody : Json.Decode.Decoder a -> String -> Result Http.Error a
decodeBody decoder body =

View File

@@ -18,6 +18,7 @@ module Api.Request.Default exposing
, maybeGet
, pathStringIntegerEnumerationGet, Enumeration(..), enumerationVariants
, queryGet, Enum(..), enumVariants
, securedPost
, uuidGet
)
@@ -165,6 +166,20 @@ queryGet string_query int_query enum_query =
securedPost : String -> Api.Request ()
securedPost auth_token =
Api.request
"POST"
"/secured"
[]
[]
[]
Nothing
(Json.Decode.succeed ())
|> Api.withBearerToken auth_token
uuidGet : Maybe Uuid -> Api.Request Uuid
uuidGet value_query =
Api.request