Open-generator-cli problems, or Swagger/OpenApi recommendations

I think it is the following code in your Api.send that adds the extra initial /

Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams

See the samples in

https://package.elm-lang.org/packages/elm/url/latest/Url-Builder#crossOrigin

Cross origin ads / on its own, I think you shouldn’t use it, since there are already the correct number of slashes in swagger.json.

crossOrigin prePath pathSegments parameters =
  prePath ++ "/" ++ String.join "/" pathSegments ++ toQuery parameters

instead we should use our own definition:

joinPath prePath pathSegments parameters =
  prePath ++ String.join "/" pathSegments ++ toQuery parameters

Most likely, you have double slashes in your samples too, but your webserver is less picky, since some will just ignore duplicate /

That’s strange indeed. I would expect an Elm Err as well. I’ll see if I get to reproduce this one.

Ah yes, I think it is in splitting the path string.

Here’s a new release including:

  • Improved handling of reserved and unsafe words;
  • Improved Api functions (including the argument swap);
  • Support for numeric enums;
  • A fix for removing the additional slash in paths.

Not perfect yet. Only works for simple path like

    "/Version"

but not for ones like

    "/Cmd/{cmdName}"

I am not sure why we work with splitted paths, we should just concatenate and replace.

What doesn’t work in the latter case? For me it is working fine.

The reason I use the split here is that I would like to stick with the native Url.Builder. That comes with type-checking and is already tested code.

The 2nd / between in /Cmd/{cmdName} is removed during the process. The Url.Builder code for the path part is really simple, no need to reuse that one.

It is not removed for me. Both for empty and non-empty cmdNames it is working fine. Are you using v7? Are you running in reactor or do you build and minify?

Found the problem, it was my code that misused yours, and due to the splitting, there were problems. I am now using an Api.elm without the splitting, but I will adapt the rest of the code to the splitting.

 sss6elm/src/Api.elm | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/sss6elm/src/Api.elm b/sss6elm/src/Api.elm
index 50788c4..2337230 100644
--- a/sss6elm/src/Api.elm
+++ b/sss6elm/src/Api.elm
@@ -21,7 +21,7 @@ type Request a
         { method : String
         , headers : List Http.Header
         , basePath : String
-        , pathParam : String
+        , pathParams : List String
         , queryParams : List Url.Builder.QueryParameter
         , body : Http.Body
         , decoder : Json.Decode.Decoder a
@@ -36,7 +36,7 @@ request method path pathParams queryParams headerParams body decoder =
         { method = method
         , headers = headers headerParams
         , basePath = "http://localhost:5000"
-        , pathParam = interpolatePath path pathParams
+        , pathParams = interpolatePath path pathParams
         , queryParams = queries queryParams
         , body = Maybe.withDefault Http.emptyBody (Maybe.map Http.jsonBody body)
         , decoder = decoder
@@ -50,7 +50,7 @@ send toMsg (Request req) =
     Http.request
         { method = req.method
         , headers = req.headers
-        , url = joinUrl req.basePath req.pathParam req.queryParams
+        , url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
         , body = req.body
         , expect = Http.expectJson toMsg req.decoder
         , timeout = req.timeout
@@ -64,7 +64,7 @@ map fn (Request req) =
         { method = req.method
         , headers = req.headers
         , basePath = req.basePath
-        , pathParam = req.pathParam
+        , pathParams = req.pathParams
         , queryParams = req.queryParams
         , body = req.body
         , decoder = Json.Decode.map fn req.decoder
@@ -107,19 +107,17 @@ headers =
     List.filterMap (\( key, value ) -> Maybe.map (Http.header key) value)
 
 
-interpolatePath : String -> List ( String, String ) -> String
+interpolatePath : String -> List ( String, String ) -> List String
 interpolatePath rawPath pathParams =
     let
         interpolate =
             \( name, value ) path -> String.replace ("{" ++ name ++ "}") value path
     in
     List.foldl interpolate rawPath pathParams
+        |> String.split "/"
+        |> List.drop 1
 
 
 queries : List ( String, Maybe String ) -> List Url.Builder.QueryParameter
 queries =
     List.filterMap (\( key, value ) -> Maybe.map (Url.Builder.string key) value)
-
-
-joinUrl prePath pathParam parameters =
-    prePath ++ pathParam ++ Url.Builder.toQuery parameters

Everything works fine with your generated code now. Thanks!

A minor that is more Elm-related, related on how elm finds stuff which I still do not fully understand :-). Sometimes dot means inside module sometimes dot is path related. I am trying to define my own wrapper Api.send, so that I can overwrite the configuration like using absolute urls etc. I thought replacing Request by Api.Request would be enough, and that is true for the typing, but not for destructing the request object.

I copied the definition into my own file, just redirect works fine:

send : (Result Http.Error a -> msg) -> Api.Request a -> Cmd msg
send =
    Api.send

but when I try to decompose the Request object, it fails to compile. What is missing?

send : (Result Http.Error a -> msg) -> Api.Request a -> Cmd msg
send toMsg (Api.Request req) =
    Http.request
        { method = req.method
        , headers = req.headers
        , url = Url.Builder.crossOrigin req.basePath req.pathParams req.queryParams
        , body = req.body
        , expect = Http.expectJson toMsg req.decoder
        , timeout = req.timeout
        , tracker = req.tracker
        }

I get

I cannot find a `Api.Request` variant:

59| send toMsg (Api.Request req) =
                ^^^^^^^^^^^
The `Api` module does not expose a `Request` variant. These names seem close

Great things are working well now!

The idea should be that you do not have to overwrite anything. The Request type allows for modifications, like changing the base path or adding headers (see Api.elm). Is there anything you wish to do there which is currently not possible?

The reason you are getting that error is because Request is a so-called opaque type. This means the type itself is exposed but the type constructor is not (which would be the case if the module exposed Request(..)). This means you cannot (1) directly instantiate a Request from outside the module and (2) you cannot do pattern matching on the type. The nice thing about this is that is allows for creating types without exposing the implementation details to the outer world. It forces users to only use the type as was inentend to be used.

Edit: The goal is to have your own send look something similar to this:

send : Server -> (Result Http.Error a -> msg) -> Request a -> Cmd msg
send { basePath, accessToken } toMsg request =
    request
        |> Api.withBasePath basePath
        |> Api.withHeader "ACCESS_TOKEN" accessToken
        |> Api.send toMsg

-- where
type alias Server =
    { basePath : String
    , accessToken : String
    }

Thanks! Good sample for your docs.

Thanks, I copied it from there :smile:

What are the link to the docs? that is one thing I never found for the elm openapi generator.

That’s because it never existed.
I never had (made) the time to write some docs.

I just finished my first attempt (so it might still be a little rough), please have a look here: Introduction · Elm with OpenAPI.

Thank you @eriktimmers

I understood everything directly, except the Api.map part, maybe you should extend that so that is obvious when to use it, in combination with Api.send, or when you build the Request.

Thanks, @mattiasw.

I’ll expand the example on Api.map a bit.
Are there any things missing in the guide that you think should be added?

I will have a 2nd reading through, but I think the next step is to have a small complete sample, like the petstore. However, I understand that that is a non-trivial task.

That’s what I tried to do with the Star Wars example. What do you think about that one?

Sorry, missed that. Are we talking about

I prefer to have a complete sample, so that I can look at all code.

Also, is it possible to publish a working version of it?

Yes we are.

You mean you wouldn’t have to generate the code? I’d rather not add it to the repo as I think it good to make clear this code needs to be generated.

I can try to publish it as well. That actually makes sense.

It now gets published from travis on Main