GithubHelp home page GithubHelp logo

wolfadex / elm-open-api-cli Goto Github PK

View Code? Open in Web Editor NEW
25.0 25.0 6.0 3.06 MB

A CLI tool for generating Elm modules from Open API specs.

Home Page: https://www.npmjs.com/package/elm-open-api

Nix 0.02% Elm 99.96% Shell 0.02%
elm elm-lang openapi openapi-generator openapi-spec

elm-open-api-cli's Introduction

Hi! ๐Ÿ‘‹

My name is Wolfgang and I like to build things. I mostly program because it takes up so little space, but I also craft, cook, and bake. I've also enjoyed doing metal and wood work, but I don't have the space for it in my apartment.


I'm currently:

  • building elm-open-api, for generating Elm SDKs from OpenAPI specs
  • a member of the Intelligence team at Vendr
  • author of Elm Weekly
  • traveling with my wife
  • various code experiments

Connect with me:

wolfadex wolfadex Wolfgang Schuster wolfgangschuster.wordpress.com/

elm-open-api-cli's People

Contributors

brzezinskip avatar dependabot[bot] avatar jamesrweb avatar lawik avatar minibill avatar qluxzz avatar wolfadex avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

elm-open-api-cli's Issues

Use path segments parameter for Url.crossOrigin

It'd be great if the urls could be generated using Url.crossOrigin's path segments parameter. In my case, an openapi endpoint /example/{x} results in this Elm code

            Url.Builder.crossOrigin
                (String.replace
                    "{x}"
                    config.params.x
                    "http://example.com/example/{x}"
                )
                []
                ...

which then results in an url with a trailing slash http://example.com/example/x/. However, this then does not match the endpoint's url in the openapi spec which is without trailing slash.

Would it be possible to have the Elm code being generated like this:

            Url.Builder.crossOrigin
                "http://example.com"
                [ "example"
                , config.params.x
                ]
                ...

Then the resulting url would be without trailing slash and hence matching the openapi spec.

Moved from wolfadex/elm-open-api#6

cc @myrho

Name collision with the custom error name of `Error`

The Amadeus OAS (as seen in the example/ directory) has an error response named error which collides with the custom type this tool generates.

Moving the helper code like the custom error type, the resolveWhatever, and related code to their own module separate from the Api module will resolve this.

Improve type CLI arg to better support future types/packages

          Would this differ at all between someone wanting to use `avh4/elm-program-test` and `lamdera/program-test`? I think so but want to make sure. Asking because I'm wondering if the effect type should be the full package name. Something like
elm/http.cmd
elm/http.cmdrisky
elm/http.task
elm/http.taskrisky
dillonkearns/elm-pages.backendtask
lamdera/program-test.programtest
lamdera/program-test.programtesttask

those are verbose, but also more clear as to where they're coming from, especially if we added say avh4/elm-program-test because it'd be more difficult to tell that apart from the Lamdera one.

Would love to get your thoughts

Originally posted by @wolfadex in #108 (comment)

Errors with `--output` and `--namespace`

  • Using the argument --output=src-generated/Api.elm creates ./src-generated/Api.elm but has the default module name.
  • Using the argument --namespace=src-generated/Api.elm creates ./generates/src-generated/Api.elm with the module name Api.elm
  • Using both arguments results in the correct file name & location, but a module name of src-generated/Api.elm

Typos in the readme

Currently says: [--moduel-name <moudle name>]
Should say: [--module-name <module name>]

Function names

We currently have:
name - Cmd
nameTask - Task
nameBackendTask - BackendTask
nameEffect - Command (lamdera/program-test)

And all the risky variants. If we add avh/elm-program-test this is probably going to become unwieldy.

Proposal: multiple modules.
If there is a single package just use Cmd/Task in Module.Api
If there are multiple packages just use Cmd/Task in Module.Api.Package

So, current users are unaffected, and users with multiple types will get things like:

Github.Api.ElmHttp.getStarsTask
Github.Api.Avh4ElmProgramTest.getStarsTask

Instead of having to invent a new suffix for each package and type combination.

Schema gets weird \" escapes from Yaml parsing

I have no idea why this happens on my schema and not on the github-spec.json. But it is consistent:

{
  "components": {
    "responses": {},
    "schemas": {}
  },
  "info": {
    "title": "Sample",
    "version": "0.1"
  },
  "openapi": "3.0.0",
  "paths": {},
  "security": [],
  "servers": [],
  "tags": []
}

Removing the option for yaml and forcing a simple Json.decode works great.

Running the code as is gives this:

-- CUSTOM ERROR --------------- 
Problem with the value at json.openapi:

    "\"3.0.0\""

Invalid version format

OAuth2 Security Schema Code Generation

Many thanks for all your hard work on elm-open-api, it's much appreciated! Further to our discussion on Elm Discourse, here's information regarding the problem and the solution I've implemented.

Description

When trying to generate code for an API that uses OAuth2 for security, the following error is generated:

Warning: Todo: Unsupported security schema: Oauth2
  at /users/me/ -> GET

To Reproduce

To reproduce this, add the following to the security section of your openapi.json schema file:

        "securitySchemes": {
            "OAuth2PasswordBearer": {
                "type": "oauth2",
                "flows": {
                    "password": {
                        "scopes": {},
                        "tokenUrl": "token"
                    }
                }
            }
        }

Additionally, here is the full openapi.json file that I'm currently using:

Full openapi.json file
{
    "openapi": "3.1.0",
    "info": {
        "title": "Paracosm API",
        "summary": "Web API for the `paracosm` framework.",
        "version": "0.1.0"
    },
    "servers": [
        {
            "url": "http://127.0.0.1:8000",
            "description": "Development environment"
        }
    ],
    "paths": {
        "/token": {
            "post": {
                "summary": "Log in to get access token for API through docs..",
                "operationId": "login_for_docs_token_post",
                "requestBody": {
                    "content": {
                        "application/x-www-form-urlencoded": {
                            "schema": {
                                "$ref": "#/components/schemas/Body_login_for_docs_token_post"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Token"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation Error",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HTTPValidationError"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/json-token": {
            "post": {
                "summary": "Log in to get access token for API.",
                "operationId": "login_json_token_post",
                "parameters": [
                    {
                        "name": "username",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "title": "Username"
                        }
                    },
                    {
                        "name": "password",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "title": "Password"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Token"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation Error",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HTTPValidationError"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/users/": {
            "post": {
                "summary": "Create a new user.",
                "operationId": "create_user_users__post",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/UserCreate"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserPublic"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation Error",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HTTPValidationError"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/users/me/": {
            "get": {
                "summary": "Get current user information.",
                "operationId": "me_users_me__get",
                "responses": {
                    "200": {
                        "description": "Successful Response",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserPublic"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "OAuth2PasswordBearer": []
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "Body_login_for_docs_token_post": {
                "properties": {
                    "grant_type": {
                        "anyOf": [
                            {
                                "type": "string",
                                "pattern": "password"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "title": "Grant Type"
                    },
                    "username": {
                        "type": "string",
                        "title": "Username"
                    },
                    "password": {
                        "type": "string",
                        "title": "Password"
                    },
                    "scope": {
                        "type": "string",
                        "title": "Scope",
                        "default": ""
                    },
                    "client_id": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "title": "Client Id"
                    },
                    "client_secret": {
                        "anyOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "title": "Client Secret"
                    }
                },
                "type": "object",
                "required": [
                    "username",
                    "password"
                ],
                "title": "Body_login_for_docs_token_post"
            },
            "HTTPValidationError": {
                "properties": {
                    "detail": {
                        "items": {
                            "$ref": "#/components/schemas/ValidationError"
                        },
                        "type": "array",
                        "title": "Detail"
                    }
                },
                "type": "object",
                "title": "HTTPValidationError"
            },
            "Token": {
                "properties": {
                    "access_token": {
                        "type": "string",
                        "title": "Access Token"
                    },
                    "token_type": {
                        "type": "string",
                        "title": "Token Type"
                    }
                },
                "type": "object",
                "required": [
                    "access_token",
                    "token_type"
                ],
                "title": "Token"
            },
            "UserCreate": {
                "properties": {
                    "username": {
                        "type": "string",
                        "title": "Username"
                    },
                    "email": {
                        "type": "string",
                        "title": "Email"
                    },
                    "full_name": {
                        "type": "string",
                        "title": "Full Name"
                    },
                    "password": {
                        "type": "string",
                        "title": "Password"
                    },
                    "repeated_password": {
                        "type": "string",
                        "title": "Repeated Password"
                    }
                },
                "type": "object",
                "required": [
                    "username",
                    "email",
                    "full_name",
                    "password",
                    "repeated_password"
                ],
                "title": "UserCreate"
            },
            "UserPublic": {
                "properties": {
                    "username": {
                        "type": "string",
                        "title": "Username"
                    },
                    "email": {
                        "type": "string",
                        "title": "Email"
                    },
                    "full_name": {
                        "type": "string",
                        "title": "Full Name"
                    },
                    "id": {
                        "type": "integer",
                        "title": "Id"
                    }
                },
                "type": "object",
                "required": [
                    "username",
                    "email",
                    "full_name",
                    "id"
                ],
                "title": "UserPublic"
            },
            "ValidationError": {
                "properties": {
                    "loc": {
                        "items": {
                            "anyOf": [
                                {
                                    "type": "string"
                                },
                                {
                                    "type": "integer"
                                }
                            ]
                        },
                        "type": "array",
                        "title": "Location"
                    },
                    "msg": {
                        "type": "string",
                        "title": "Message"
                    },
                    "type": {
                        "type": "string",
                        "title": "Error Type"
                    }
                },
                "type": "object",
                "required": [
                    "loc",
                    "msg",
                    "type"
                ],
                "title": "ValidationError"
            }
        },
        "securitySchemes": {
            "OAuth2PasswordBearer": {
                "type": "oauth2",
                "flows": {
                    "password": {
                        "scopes": {},
                        "tokenUrl": "token"
                    }
                }
            }
        }
    }
}

I'm passing a json web token around for auth, and I can extend the code generated by elm-open-api relatively easily to enable authentication. For example, I can change the /users/me endpoint, which requires auth, from this:

meUsersMeGet :
    { toMsg :
        Result (OpenApi.Common.Error ParacosmApi.Types.MeUsersMeGet_Error String) ParacosmApi.Types.UserPublic
        -> msg
    }
    -> Cmd msg
meUsersMeGet config =
    Http.request
        { url = "http://127.0.0.1:8000/users/me/"
        , method = "GET"
        , headers = []
        , expect =
            OpenApi.Common.expectJsonCustom
                config.toMsg
                (Dict.fromList [])
                ParacosmApi.Json.decodeUserPublic
        , body = Http.emptyBody
        , timeout = Nothing
        , tracker = Nothing
        }

to this:

meUsersMeGet :
    { toMsg :
        Result (OpenApi.Common.Error ParacosmApi.Types.MeUsersMeGet_Error String) ParacosmApi.Types.UserPublic
        -> msg
    -- Next line changed
    , token : ParacosmApi.Types.Token 
    }
    -> Cmd msg
meUsersMeGet config =
    Http.request
        { url = "http://127.0.0.1:8000/users/me/"
        , method = "GET"
       -- Next line changed
        , headers = [ Http.header "Authorization" ("Bearer " ++ config.token.access_token) ]
        , expect =
            OpenApi.Common.expectJsonCustom
                config.toMsg
                (Dict.fromList [])
                ParacosmApi.Json.decodeUserPublic
        , body = Http.emptyBody
        , timeout = Nothing
        , tracker = Nothing
        }

And the API works as expected.

Additional Context

I am a novice when it comes to making OpenAPI compliant APIs, so there's a good chance some of the configuration is wrong on my end. I followed this section of the FastAPI docs to get to this point: https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/

Support for lenient decoding

I don't know if anyone else has a use for this, but I am working with an internal Open API spec that apparently is not completely valid according to the spec. Out of hundreds of endpoints, there were a handful with empty responses objects (so the JSON would contain literally "responses":{}). I didn't really care about the offending endpoints, so in order to keep just the good ones, I ended up vendoring this project and the elm-open-api project with a modified decoder for "paths" that just filters out the malformed endpoints.
https://github.com/pete-murphy/elm-open-api-cli/blob/95e76eec0399f573037450d6beecd0b958664b1e/src/OpenApi.elm#L111-L126
I figured I'd share in case anyone else has a similar issue, though I don't have any idea about how the API for this tool could have been extended to support my use case, or if anyone would even want that.

Support custom properties, i.e. `x-*`

This will require some design work as these can be nearly any value.

  • How do we let users hook into the CLI?
  • How do we let users generate code in a safe/compatible way?

Usage description incorrect

Hi! Thanks for adding support for risky requests. However, the feature does not work as described in the usage message.

  --effect-types                     Which kind of APIs to generate, the options are:
                                      - cmd: input -> Cmd msg
                                      - riskycmd: as above, but using Http.riskyRequest
                                      - task: input -> Task Http.Error msg
                                      - riskytask: as above, but using Http.riskyRequest
                                      - backendtask: for dillonkearns/elm-pages

The command actually works with cmdrisky and taskrisky instead.

File upload with additional parameters not supported

I have an OpenAPI spec with the following request:

"/api/files": {
      "post": {
        "tags": [
          "files"
        ],
        "summary": "Create File",
        "operationId": "create_file_api_files_post",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_create_file_api_files_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonCreateFileOutput"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }

Where the body is defined as:

"Body_create_file_api_files_post": {
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "title": "File"
          },
          "description": {
            "type": "string",
            "title": "Description"
          }
        },
        "type": "object",
        "required": [
          "file",
          "description"
        ],
        "title": "Body_create_file_api_files_post"
      }

When I give that to elm-open-api it produces:

-- config.body is "{ body : Bytes.Bytes }"
createFileApiFilesPost config =
    Http.request
        { method = "POST"
        , headers = []
        , expect = Http.expectJson config.toMsg decodeJsonCreateFileOutput
        , body = Http.bytesBody "multipart/form-data" config.body
        , timeout = Nothing
        , tracker = Nothing
        , url = "/api/files"
        }

API Specs with custom response media types from vendors as defined in RFC6838 don't work

Response media types such as application/vnd.github.v3+json, or in my case application/vnd.amadeus+json, should be parsed as valid JSON APIs but in my case I get:

Error! Could not get application's application/json content
Path: airlines

As described in section 3.2 or RFC6838, vnd additions are simply vendor registrations, followed by a possible version and then the response format therein.

Not sure how easy this is to fix and deploy but it is a common enough thing that it would be worth having included in the parser and validator for content type resolution and elsewhere this is used, read or verified, etc.

Some definitions fail to generate correctly in generated output

When generating the SpaceTraders API from their spec, the generated output is invalid. For example some types have no definitions.

Command:

elm-open-api \"https://stoplight.io/api/v1/projects/spacetraders/spacetraders/nodes/reference/SpaceTraders.json?fromExportButton=true&snapshotType=http_service&deref=optimizedBundle\" --output-dir src --module-name \"Generated.SpacetradersApi\" --generateTodos true

Example of the issue:
Screenshot 2024-03-31 223317

Also there are cases such as type alias ActivityLevel = String but this type should be limited to the 4 valid values WEAK, GROWING, STRONG, RESTRICTED as documented in the spec. - Striked out as this is an issue with their own types in the spec.

Notes:

No errors are thrown during generation but warnings about Warning: Error! Multiple security requirements but this is expected? I suppose this is something being worked on still, or what's the plan for security topics?

Support for custom headers

Is there a way send custom headers with the generated Cmd/Task endpoints? For instance for cache control, authentication ...

Improve module name generation

Hi, thank you for this awesome tool! It is going to be super helpful for me!

I ran into an issue with a module name being generated that was not "Elm compliant". If a service provider includes an OAS file with a strangely formatted title such as the one below the module and filename generated are invalid. Possibly, add additional sanitization to ensure that the module and filename have valid names.

{
  "info": {
    "title": "service API (params in:body)",
}  

I guess this might become Service_API__Params_In_Body_.elm?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.