From 109699d17da18e1e264090a262361a9115ba451a Mon Sep 17 00:00:00 2001 From: Leonardo Taglialegne Date: Tue, 25 Jun 2024 17:59:48 +0200 Subject: [PATCH] Add multiple servers as an option --- src/Cli.elm | 34 +++++++++- src/Common.elm | 4 ++ src/OpenApi/Generate.elm | 106 ++++++++++++++++++++++---------- tests/Test/OpenApi/Generate.elm | 2 +- 4 files changed, 109 insertions(+), 37 deletions(-) diff --git a/src/Cli.elm b/src/Cli.elm index 2355257..bc1bb90 100644 --- a/src/Cli.elm +++ b/src/Cli.elm @@ -37,7 +37,7 @@ type alias CliOptions = , swaggerConversionUrl : String , swaggerConversionCommand : Maybe String , swaggerConversionCommandArgs : List String - , server : Maybe String + , server : OpenApi.Generate.Server } @@ -71,7 +71,9 @@ program = |> Cli.OptionsParser.with (Cli.Option.keywordArgList "swagger-conversion-command-args") |> Cli.OptionsParser.with - (Cli.Option.optionalKeywordArg "server") + (Cli.Option.optionalKeywordArg "server" + |> Cli.Option.validateMap serverValidation + ) |> Cli.OptionsParser.withDoc """ options: @@ -91,6 +93,13 @@ program = If not specified this will be extracted from the OAS or default to root of the web application. + You can pass in an object to define multiple servers, like + {"dev": "http://localhost", "prod": "https://example.com"}. + + This will add a `server` parameter to functions and define + a `Servers` module with your servers. You can pass in an + empty object if you have fully dynamic servers. + --auto-convert-swagger If passed in, and a Swagger doc is encountered, will attempt to convert it to an Open API file. If not passed in, and a Swagger doc is encountered, @@ -148,6 +157,25 @@ effectTypeValidation effectType = Err <| "Unexpected effect type: " ++ effectType +serverValidation : Maybe String -> Result String OpenApi.Generate.Server +serverValidation server = + case Maybe.withDefault "" server of + "" -> + Ok OpenApi.Generate.Default + + input -> + case Json.Decode.decodeString (Json.Decode.dict Json.Decode.string) input of + Ok servers -> + Ok <| OpenApi.Generate.Multiple servers + + Err _ -> + if String.startsWith "{" input then + Err <| "Invalid JSON: " ++ input + + else + Ok <| OpenApi.Generate.Single input + + run : Pages.Script.Script run = Pages.Script.withCliOptions program @@ -354,7 +382,7 @@ generateFileFromOpenApiSpec : { outputModuleName : Maybe String , generateTodos : Maybe String , effectTypes : List OpenApi.Generate.EffectType - , server : Maybe String + , server : OpenApi.Generate.Server } -> OpenApi.OpenApi -> BackendTask.BackendTask FatalError.FatalError ( List Elm.File, List CliMonad.Message ) diff --git a/src/Common.elm b/src/Common.elm index 31a0271..dc22d78 100644 --- a/src/Common.elm +++ b/src/Common.elm @@ -22,6 +22,7 @@ type Module | Types | Api | Common + | Servers moduleToNamespace : List String -> Module -> List String @@ -36,6 +37,9 @@ moduleToNamespace namespace module_ = Api -> namespace ++ [ "Api" ] + Servers -> + namespace ++ [ "Servers" ] + Common -> [ "OpenApi", "Common" ] diff --git a/src/OpenApi/Generate.elm b/src/OpenApi/Generate.elm index 3b35c0b..31d73fa 100644 --- a/src/OpenApi/Generate.elm +++ b/src/OpenApi/Generate.elm @@ -2,6 +2,7 @@ module OpenApi.Generate exposing ( ContentSchema(..) , EffectType(..) , Mime + , Server(..) , files , sanitizeModuleName ) @@ -72,6 +73,12 @@ type ContentSchema | BytesContent Mime +type Server + = Default + | Single String + | Multiple (Dict.Dict String String) + + type alias AuthorizationInfo = { headers : Elm.Expression -> List ( Elm.Expression, Elm.Expression ) , params : List ( String, Elm.Annotation.Annotation ) @@ -83,7 +90,7 @@ files : { namespace : List String , generateTodos : Bool , effectTypes : List EffectType - , server : Maybe String + , server : Server } -> OpenApi.OpenApi -> Result CliMonad.Message ( List Elm.File, List CliMonad.Message ) @@ -151,6 +158,25 @@ files { namespace, generateTodos, effectTypes, server } apiSpec = } ) ] + ++ (case server of + Multiple servers -> + servers + |> Dict.toList + |> List.map + (\( key, value ) -> + ( Common.Servers + , Elm.string value + |> Elm.declaration key + |> Elm.exposeWith + { exposeConstructor = True + , group = Just "Servers" + } + ) + ) + + _ -> + [] + ) in ( allDecls |> List.Extra.gatherEqualsBy Tuple.first @@ -223,7 +249,7 @@ formatModuleDocs = ) -pathDeclarations : Maybe String -> List EffectType -> List String -> CliMonad (List ( Common.Module, Elm.Declaration )) +pathDeclarations : Server -> List EffectType -> List String -> CliMonad (List ( Common.Module, Elm.Declaration )) pathDeclarations server effectTypes namespace = CliMonad.fromApiSpec OpenApi.paths |> CliMonad.andThen @@ -399,7 +425,7 @@ requestBodyToDeclarations namespace name reference = |> CliMonad.withPath name -toRequestFunctions : Maybe String -> List EffectType -> List String -> String -> String -> OpenApi.Operation.Operation -> CliMonad (List ( Common.Module, Elm.Declaration )) +toRequestFunctions : Server -> List EffectType -> List String -> String -> String -> OpenApi.Operation.Operation -> CliMonad (List ( Common.Module, Elm.Declaration )) toRequestFunctions server effectTypes namespace method pathUrl operation = let functionName : String @@ -666,6 +692,7 @@ toRequestFunctions server effectTypes namespace method pathUrl operation = , errorTypeAnnotation = errorTypeAnnotation , authorizationInfo = auth , bodyParams = params + , server = server } ) ) @@ -679,7 +706,7 @@ toRequestFunctions server effectTypes namespace method pathUrl operation = |> CliMonad.withPath pathUrl -replacedUrl : Maybe String -> List String -> String -> OpenApi.Operation.Operation -> CliMonad (Elm.Expression -> Elm.Expression) +replacedUrl : Server -> List String -> String -> OpenApi.Operation.Operation -> CliMonad (Elm.Expression -> Elm.Expression) replacedUrl server namespace pathUrl operation = let params : List (OpenApi.Reference.ReferenceOr OpenApi.Parameter.Parameter) @@ -750,35 +777,40 @@ replacedUrl server namespace pathUrl operation = OpenApi.servers |> CliMonad.fromApiSpec |> CliMonad.map - (\servers -> - \config -> - let - initialUrl : String - initialUrl = - case server of - Just cliServer -> - if String.startsWith "/" pathUrl then - cliServer ++ pathUrl - - else - cliServer ++ "/" ++ pathUrl - - Nothing -> - case servers of - [] -> - pathUrl - - firstServer :: _ -> - if String.startsWith "/" pathUrl then - OpenApi.Server.url firstServer ++ pathUrl + (\servers config -> + let + initialUrl : Elm.Expression + initialUrl = + let + appendPath : String -> Elm.Expression + appendPath resolvedServer = + if String.startsWith "/" pathUrl then + Elm.string <| resolvedServer ++ pathUrl - else - OpenApi.Server.url firstServer ++ "/" ++ pathUrl - in - List.foldl - (\( replacement, _ ) -> replacement config) - (Elm.string initialUrl) - replacements + else + Elm.string <| resolvedServer ++ "/" ++ pathUrl + in + case server of + Single cliServer -> + cliServer |> appendPath + + Default -> + case servers of + [] -> + "" |> appendPath + + firstServer :: _ -> + OpenApi.Server.url firstServer |> appendPath + + Multiple _ -> + Elm.Op.append + (Elm.get "server" config) + (appendPath "") + in + List.foldl + (\( replacement, _ ) -> replacement config) + initialUrl + replacements ) else @@ -1121,12 +1153,20 @@ toConfigParamAnnotation : , errorTypeAnnotation : Elm.Annotation.Annotation , authorizationInfo : AuthorizationInfo , bodyParams : List ( String, Elm.Annotation.Annotation ) + , server : Server } -> CliMonad ({ requireToMsg : Bool } -> Elm.Annotation.Annotation) toConfigParamAnnotation namespace options = CliMonad.map (\urlParams { requireToMsg } -> - (options.authorizationInfo.params + ((case options.server of + Multiple _ -> + [ ( "server", Elm.Annotation.string ) ] + + _ -> + [] + ) + ++ options.authorizationInfo.params ++ (if requireToMsg then [ ( "toMsg" , Elm.Annotation.function diff --git a/tests/Test/OpenApi/Generate.elm b/tests/Test/OpenApi/Generate.elm index b8712f4..b0053a8 100644 --- a/tests/Test/OpenApi/Generate.elm +++ b/tests/Test/OpenApi/Generate.elm @@ -77,7 +77,7 @@ suite = { namespace = namespace , generateTodos = False , effectTypes = [ OpenApi.Generate.Cmd, OpenApi.Generate.Task ] - , server = Nothing + , server = OpenApi.Generate.Default } oas in