GithubHelp home page GithubHelp logo

openapi / actix-swagger Goto Github PK

View Code? Open in Web Editor NEW
111.0 8.0 8.0 227 KB

Swagger code generator for actix-web framework

Home Page: https://crates.io/crates/cargo-swagg

License: MIT License

Rust 100.00%
rest-api actix-web swagger openapi3 rust-lang

actix-swagger's People

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  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  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  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  avatar  avatar  avatar  avatar  avatar

actix-swagger's Issues

no default features for actix-web

If/when the actix-web dependency is updated to v3, the dependency in Cargo.toml should ideally be:

- actix-web = "3.0.2"
+ actix-web = { version = "3.0.2", default-features = false }

Parsing arguments fails

I'm trying to use it as per docs, but getting error at the stage of parsing arguments:

> cargo swagg ./AlpacaDeviceAPI_v1.yaml --out-file src/api.rs
error: Found argument './AlpacaDeviceAPI_v1.yaml' which wasn't expected, or isn't valid in this context

USAGE:
    cargo-swagg.exe [OPTIONS] <source>

For more information try --help

Am I missing something?

Extract header value

    pub mod parameters {
        use actix_web::{FromRequest, HttpRequest};
        use serde::Serialize;

        #[derive(Debug, Serialize, Clone)]
        struct ParseHeaderError {
            error: String,
            message: String,
        }

        fn extract_header(req: &HttpRequest, name: String) -> Result<String, ParseHeaderError> {
            let header_error = ParseHeaderError {
                error: "header_required".to_string(),
                message: format!("header '{}' is required", name),
            };

            let header = req.headers().get(name).ok_or(header_error.clone())?;
            let value = header.to_str().map_err(|_| header_error)?.to_string();
            Ok(value)
        }

        pub struct AccessToken(pub String);

        impl FromRequest for AccessToken {
            type Config = ();
            type Error = actix_web::Error;
            type Future = futures::future::Ready<Result<Self, Self::Error>>;

            #[inline]
            fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
                match extract_header(&req, "X-Access-Token".to_string()) {
                    Ok(value) => futures::future::ok(AccessToken(value)),
                    Err(reason) => match serde_json::to_string(&reason) {
                        Ok(json) => futures::future::err(actix_web::error::ErrorBadRequest(json)),
                        Err(error) => {
                            futures::future::err(actix_web::error::ErrorInternalServerError(error))
                        }
                    },
                }
            }
        }
    }

Cannot use project with Actix-web 4.2.1

error[E0432]: unresolved imports `actix_web::http::header::IntoHeaderValue`, `actix_web::http::HeaderName`, `actix_web::http::HeaderValue`
  --> /home/rafal/.cargo/registry/src/github.com-1ecc6299db9ec823/actix-swagger-0.3.0/src/lib.rs:10:26
   |
10 |     http::header::{self, IntoHeaderValue},
   |                          ^^^^^^^^^^^^^^^
   |                          |
   |                          no `IntoHeaderValue` in `http::header`
   |                          help: a similar name exists in the module: `TryIntoHeaderValue`
11 |     http::{HeaderName, HeaderValue},
   |            ^^^^^^^^^^  ^^^^^^^^^^^ no `HeaderValue` in `http`
   |            |
   |            no `HeaderName` in `http`


Convert generated response to `actix_swagger::Answer` through `Into`

    pub mod session_get {
        use super::responses;
        use actix_swagger::ContentType;
        use actix_web::http::StatusCode;
        use serde::Serialize;

        pub type Answer = actix_swagger::Answer<'static, Response>;

        #[derive(Debug, Serialize)]
        #[serde(untagged)]
        pub enum Response {
            Ok(responses::SessionGetSuccess),
            Unauthorized,
            Unexpected,
        }

        impl Into<Answer> for Response {
            #[inline]
            fn into(self) -> Answer {
                let status = match self {
                    Self::Ok(_) => StatusCode::OK,
                    Self::Unauthorized => StatusCode::UNAUTHORIZED,
                    Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR,
                };

                let content_type = match self {
                    Self::Ok(_) => Some(ContentType::Json),
                    Self::Unauthorized => None,
                    Self::Unexpected => None,
                };

                Answer::new(self).status(status).content_type(content_type)
            }
        }

rel #17

Generate path with `Answer`

    pub mod session_get {
        use super::responses;
        use actix_swagger::ContentType;
        use actix_web::http::StatusCode;
        use serde::Serialize;

        pub type Answer = actix_swagger::Answer<'static, Response>;

        #[derive(Debug, Serialize)]
        #[serde(untagged)]
        pub enum Response {
            Ok(responses::SessionGetSuccess),
            Unauthorized,
            Unexpected,
        }

        impl Response {
            #[inline]
            pub fn into_answer<'a>(self) -> Answer {
                let status = match self {
                    Self::Ok(_) => StatusCode::OK,
                    Self::Unauthorized => StatusCode::UNAUTHORIZED,
                    Self::Unexpected => StatusCode::INTERNAL_SERVER_ERROR,
                };

                let content_type = match self {
                    Self::Ok(_) => Some(ContentType::Json),
                    Self::Unauthorized => None,
                    Self::Unexpected => None,
                };

                Answer::new(self).status(status).content_type(content_type)
            }
        }
    }

Usage issue

Hi,
I have issue to integrate the library. My code is the following:

Main:

fn main() {
    HttpServer::new(move || {
        App::new()
            .data(AppState {
                pool: pool.clone(),
                log: log.clone(),
            })
            .wrap(middleware::Logger::default())
            .route("/", web::get().to(status))
            .route("/test{_:/?}", web::get().to(test))
    })
    .bind(format!("{}:{}", config.server.host, config.server.port))?
    .run()
    .await
}

Handler:

pub async fn status() -> Result<impl Responder, AppError> {
    Ok(web::HttpResponse::Ok().json(Status {
        status: "Up".to_string(),
    }))
}

pub async fn test(state: web::Data<AppState>) -> Result<impl Responder, AppError> {
    let sublog = state.log.new(o!("handler" => "test"));

    let client: Client = get_client(state.pool.clone(), sublog.clone()).await?;

    let result = db::get_test(&client).await;

    result
        .map(|test| HttpResponse::Ok().json(test))
        .map_err(log_error(sublog))
}

Discussion about implementation

https://t.me/rustlang_ru/285259 https://teleg.one/rustlang_ru/285259 (Russian)

Translated:
made a sufficient version of the code generator for actix_swagger.
Generates this: https://github.com/sergeysova/actix-swagger/blob/master/cargo-swagg/out.rs.

In principle, it's enough to write applications, I have about this kind of communication. But there is no execution of the contract of request handlers here. There is no obligation to correctly process the request body in the handler. I could not find a way to do it through actix_web::dev::Factory.

Here is a discussion issue: actix/actix-web#1339.

By actix-swagger:
I will now write a conversion of yaml structures to the code generator format. Most likely there will be a HashMap/BTreeMap bundle to correctly resolve links. The first implementation will be very stupid and will be able to work only with links in requestBody, params and so on. There will also be no support for importing from files.

What do you need help with?

  • Have a look at the code. Almost everything is written there to get the first working version.
  • It would be cool to throw tests on the code generator (called printer). I think trivial inline snapshots would be enough. Give different configuration options to input and get sane code.
  • Convert your personal openapi3 snapshots into cargo-swagg structures and see if it's enough to have or need something else. If you need something else, create an issue/pr with implementation.

Project goals:
To input the openapi3 specification, to get the generated code at the output that does not need to be corrected by hand. At the same time, you may commit it into the repo and watch changes in the guitar after manual re-generation. You can take it to a separate crate, re-generate it into ci and deploy the crate of the new version. Update it in the project when it is convenient.

How to use:

We have to figure this out:

how to get the request handler to execute the contract on the request body, query, and headers.

I was thinking of making my analogue actix_web::dev::Factory, the so-called BoundFactory. Which would prescribe the implementation for Fn. BoundFactory is a trait that requires a function to have several mandatory parameters for specific types.

In this way, handlers could have the first arguments of clear types, such as request_body: request_bodies::Example, query: session_create::Query, and the rest of the arguments would be derived types, so you can pull out the necessary things, such as actix_web::web::Data.

But I'm relying on the privacy of Handler - actix/actix-web#1339.

I did not understand how to implement BoundFactory or even achieve the goal through Handle trait (actix/actix-web#1275).

I'd be so grateful for any help.

Remove Answer<'static from route implementation

We can generate type alias. Instead of this:

pub async fn route(
    app: web::Data<crate::App>,
) -> Answer<'static, register_request::Response> {

Generate result type:

pub async fn route(
    app: web::Data<crate::App>,
) -> register_request::Answer {

Isolate implementation details from application

Instead of passing body, header, path extractors to a route:

pub async fn route(
    body: web::Json<request_bodies::Register>,
    app: web::Data<crate::App>,
) -> Answer<'static, register_request::Response> {

Generate a single extractor for all data. Router implementation doesn't know about API details:

pub async fn route(
    params: paths::register_request::Params,
    app: web::Data<crate::App>,
) -> Answer<'static, register_request::Response> {

Custom attributes

Single attribute:

schema:
  properties:
    demo:
      type: string
        x-rust-attribute: serde(skip_serializing_if = "Option::is_none")

Should generate something like:

struct Example {
  #[serde(skip_serializing_if = "Option::is_none")]
  demo: String,
}

Multiple attributes:

schema:
  properties:
    demo:
      type: string
        x-rust-attribute:
          - serde(skip_serializing_if = “Option::is_none”)
          - validate(url)

Output:

struct Example {
  #[serde(skip_serializing_if = "Option::is_none")]
  #[validate(url)]
  demo: String,
}

Generator fails on simple input

Openapi.yaml

x-generator: NSwag v13.3.0.0 (NJsonSchema v10.1.11.0 (Newtonsoft.Json v12.0.0.0))
openapi: 3.0.0
info:
  title: Settings API
  description: Settings and presets service
  version: v1
servers:
  - url: 'https://myUrl'
paths:
  /:
    get:
      tags:
        - About
      operationId: About_Get
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageModel'
  /settings/api:
    get:
      tags:
        - About
      operationId: About_Get2
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageModel'
  /Healthcheck:
    get:
      tags:
        - Healthcheck
      operationId: Healthcheck_Get
      responses:
        '200':
          description: ''
  /settings/api/Healthcheck:
    get:
      tags:
        - Healthcheck
      operationId: Healthcheck_Get2
      responses:
        '200':
          description: ''
components:
  schemas:
    MessageModel:
      type: object
      additionalProperties: false
      properties:
        message:
          type: string
          nullable: true
    Integration:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: guid
        createdBy:
          type: string
          format: guid
        createdOn:
          type: string
          format: date-time
        modifiedBy:
          type: string
          format: guid
          nullable: true
        modifiedOn:
          type: string
          format: date-time
          nullable: true
        deletedReason:
          type: string
          nullable: true
        deletedBy:
          type: string
          format: guid
          nullable: true
        deletedOn:
          type: string
          format: date-time
          nullable: true
        companyId:
          type: string
          format: guid
        title:
          type: string
          nullable: true
  securitySchemes:
    Bearer:
      type: apiKey
      description: Please insert Auth token into field
      name: Authorization
      in: header
security:
  - Bearer: []

Command:

cargo-swagg ./openapi.yaml --out-file ./src/api.rs

Expected result:

Got API

Actual result:

#![allow(dead_code, unused_imports)]
pub mod api {
    #[doc = "Settings and presets service"]
    pub struct SettingsApi {
        api: actix_swagger::Api,
    }
    impl SettingsApi {
        pub fn new() -> Self {
            Self {
                api: actix_swagger::Api::new(),
            }
        }
    }
    impl Default for SettingsApi {
        fn default() -> Self {
            let api = Self::new();
            api
        }
    }
    impl actix_web::dev::HttpServiceFactory for SettingsApi {
        fn register(self, config: &mut actix_web::dev::AppService) {
            self.api.register(config);
        }
    }
    use super::paths;
    use actix_swagger::{Answer, Method};
    use actix_web::{dev::Factory, FromRequest};
    use std::future::Future;
    impl SettingsApi {}
}
pub mod components {
    pub mod parameters {
        use serde::{Deserialize, Serialize};
    }
    pub mod request_bodies {
        use serde::{Deserialize, Serialize};
    }
    pub mod responses {
        use serde::{Deserialize, Serialize};
    }
}
pub mod paths {
    use super::components::{parameters, responses};
}

Ignore clippy warnings in generated code

I think it worth adding

#![allow(clippy::all, clippy::restriction, clippy::pedantic, clippy::nursery, clippy::cargo)]

to the existing #![allow(dead_code, unused_imports)] disabled warnings.

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.