POC: Swagger/OpenAPI support for Rocket


#1

I found this issue:

Just curious if anyone has started any of this. Right now I’m interested in presentation, not necessarily being able to derive from Swagger/OpenAPI definitions, yet…


Update: I started investigating this on my own. Using the https://crates.io/crates/openapi crate to generate the OpenAPI spec.

here’s some rough code, for this route

#[get("/hello/<name>/<age>")]
fn hello(name: &str, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

and then a quick test:

    #[test]
    fn test_ignition() {
        let lit: Rocket = rocket::ignite().mount("/", routes![hello]);

        let mut paths =  BTreeMap::<String, Operations>::new();
        for route in lit.routes() {
            println!("route: {}", route);

            let mut ops = paths.entry(route.path.path().to_string()).or_insert(Operations {
                                                    get: None,
                                                    post: None,
                                                    put: None,
                                                    patch: None,
                                                    delete: None,
                                                    parameters: None,
                                                });

            let mut parameters = Vec::<Parameter>::new();
            let param_indexes: Vec<(usize, usize)> = route.get_param_indexes(&route.path);
            let url_path = route.path.as_str();
            
            let parameters = param_indexes.into_iter().map(|index| {
               &url_path[{index.0 + 1}..{index.1 - 1}] // strip '<' and '>'
            }).map(|param| { 
                Parameter {
                    name: param.to_string(),
                    location: "path".to_string(),
                    required: Some(true),
                    schema: None,
                    unique_items: None,
                    param_type: Some("string".to_string()),
                    format: None,
                }
             }).collect::<Vec<_>>();

            let mut op = Operation {
                summary: Some(format!("{}", route)),
                description: None,
                consumes: None,
                produces: None,
                schemes: None,
                tags: None,
                operation_id: None,
                responses: BTreeMap::new(),
                parameters: Some(parameters),
            };

            match route.method {
               Method::Get => { ops.get = Some(op) }
               _ => unimplemented!()
            }            
        }

        let mut spec = Spec {
            swagger: "2.0".to_string(),
            info: Info { title: "Hello World".to_string(), version: "0.1".to_string(), terms_of_service: None } ,
            paths: paths,
            definitions: BTreeMap::new(),
            schemes: None,
            host: None,
            base_path: None,
            consumes: None,
            produces: None,
            parameters: None,
            responses: None,
            security_definitions: None,
            tags: None,
        };

        println!("Spec: {}", openapi::to_json(&spec).expect("failed to create json"));
    }

which outputs:

running 1 test
🔧  Configured for development.
    => address: localhost
    => port: 8000
    => log: normal
    => workers: 8
🛰  Mounting '/':
    => GET /hello/<name>/<age>
route: GET /hello/<name>/<age>
Spec: {
  "swagger": "2.0",
  "info": {
    "title": "Hello World",
    "version": "0.1"
  },
  "paths": {
    "///hello/<name>/<age>": {
      "get": {
        "summary": "GET /hello/<name>/<age>",
        "responses": {},
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "type": "string"
          },
          {
            "name": "age",
            "in": "path",
            "required": true,
            "type": "string"
          }
        ]
      }
    }
  },
  "definitions": {}
}
test tests::test_ignition ... ok

Some cleanup is needed, but I’m excited that this was possible! Btw, Rocket won’t allow this out of the box, I had to open up some of the private interfaces.


#2

FYI, I started a library for this: https://github.com/bluejekyll/rocket-openapi

I haven’t published the crate yet as I have a PR open on the Rocket project to support this.