Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add custom paths #82

Closed
antondc opened this issue Apr 2, 2022 · 2 comments
Closed

How to add custom paths #82

antondc opened this issue Apr 2, 2022 · 2 comments
Assignees

Comments

@antondc
Copy link

antondc commented Apr 2, 2022

I have a working example with one route and custom schema:

use rocket::get;
use rocket::{Build, Rocket};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::openapi;
use rocket_okapi::openapi_get_routes_spec;
use rocket_okapi::settings::OpenApiSettings;
use rocket_okapi::{mount_endpoints_and_merged_docs, openapi_get_routes, swagger_ui::*};

#[openapi] // Let okapi know that we want to document this endpoing
#[get("/test_route")]
fn test_route() -> &'static str {
  "test_route"
}

pub fn get_routes_and_docs(settings: &OpenApiSettings) -> (Vec<rocket::Route>, OpenApi) {
  openapi_get_routes_spec![settings: test_route]
}

fn custom_openapi_spec() -> OpenApi {
  use rocket_okapi::okapi::openapi3::*;
  OpenApi {
    openapi: OpenApi::default_version(),
    info: Info {
      title: "The best API ever".to_owned(),
      description: Some("This is the best API every, please use me!".to_owned()),
      version: "1.2.3".to_owned(),
      ..Default::default()
    },
    servers: vec![Server {
      url: "http://127.0.0.1:8000/".to_owned(),
      description: Some("Localhost".to_owned()),
      ..Default::default()
    }],
    // paths: {
    // ...
    // },
    ..Default::default()
  }
}

pub fn create_server() -> Rocket<Build> {
  let mut building_rocket = rocket::build()
    .mount("/", openapi_get_routes![test_route])
    .mount(
      "/swagger-ui/",
      make_swagger_ui(&SwaggerUIConfig {
        url: "../v1/openapi.json".to_owned(),
        ..Default::default()
      }),
    );

  let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
  let custom_route_spec = (vec![], custom_openapi_spec());
  mount_endpoints_and_merged_docs! {
      building_rocket, "/v1".to_owned(), openapi_settings,
      "/" => custom_route_spec,
  };

  building_rocket
}

#[rocket::main]
async fn main() {
  let launch_result = create_server().launch().await;
  match launch_result {
    Ok(()) => println!("Rocket shut down gracefully."),
    Err(err) => println!("Rocket had an error: {}", err),
  };
}

I am looking for a way to add paths into my schema, so I don't necessarily depend on the structs provided by the app.
Cant see the way, and there are no examples in the library: any help will be welcome.

@antondc antondc changed the title How add custom paths How to add custom paths Apr 2, 2022
@ralpha
Copy link
Collaborator

ralpha commented Jun 5, 2022

You are already on the right track.
You can just add the paths to the custom spec:

fn custom_openapi_spec() -> OpenApi {
  use indexmap::indexmap;
  use rocket_okapi::okapi::openapi3::*;
  use rocket_okapi::okapi::schemars::schema::*;
  OpenApi {
    openapi: OpenApi::default_version(),
    info: Info {
      title: "The best API ever".to_owned(),
      description: Some("This is the best API every, please use me!".to_owned()),
      version: "1.2.3".to_owned(),
      ..Default::default()
    },
    servers: vec![Server {
      url: "http://127.0.0.1:8000/".to_owned(),
      description: Some("Localhost".to_owned()),
      ..Default::default()
    }],
    // Add paths that do not exist in Rocket (or add extra into to existing paths)
    paths: {
        indexmap! {
            "home".to_owned() => PathItem{
            get: Some(
                Operation {
                tags: vec!["HomePage".to_owned()],
                summary: Some("This is my homepage".to_owned()),
                responses: Responses{
                    responses: indexmap!{
                    "200".to_owned() => RefOr::Object(
                        Response{
                        description: "Return the page, no error.".to_owned(),
                        content: indexmap!{
                            "text/html".to_owned() => MediaType{
                            schema: Some(SchemaObject{
                                instance_type: Some(SingleOrVec::Single(Box::new(
                                InstanceType::String
                                ))),
                                ..Default::default()
                            }),
                            ..Default::default()
                            }
                        },
                        ..Default::default()
                        }
                    )
                    },
                    ..Default::default()
                },
                ..Default::default()
                }
            ),
            ..Default::default()
            }
        }
    },
    ..Default::default()
  }
}

The easiest way to figure out the structure is to look at the docs.
OpenApi -> paths: Map<String, PathItem>

Map is the Schemars::Map.
This one depends on the feature flags you use:

#[cfg(not(feature = "preserve_order"))]
pub type Map<K, V> = std::collections::BTreeMap<K, V>;
#[cfg(feature = "preserve_order")]
pub type Map<K, V> = indexmap::IndexMap<K, V>;

Source: https://github.com/GREsau/schemars/blob/9464118c3a3894c95037b3e25cb3ac9116eaf849/schemars/src/lib.rs#L292-L295
In the example above I'll use IndexMap (as this is usually the case), I will use the macro: https://docs.rs/indexmap/latest/indexmap/macro.indexmap.html (to make it easier)

This is basically a 1 to 1 mapping between the json output and the structure (except for some small exceptions).

I hope this helps, if you have more questions let me know.

I also add this code to the example for future reference:

paths: {
indexmap! {
"/home".to_owned() => PathItem{
get: Some(
Operation {
tags: vec!["HomePage".to_owned()],
summary: Some("This is my homepage".to_owned()),
responses: Responses{
responses: indexmap!{
"200".to_owned() => RefOr::Object(
Response{
description: "Return the page, no error.".to_owned(),
content: indexmap!{
"text/html".to_owned() => MediaType{
schema: Some(SchemaObject{
instance_type: Some(SingleOrVec::Single(Box::new(
InstanceType::String
))),
..Default::default()
}),
..Default::default()
}
},
..Default::default()
}
)
},
..Default::default()
},
..Default::default()
}
),
..Default::default()
}
}
},
..Default::default()

@ralpha ralpha self-assigned this Jun 5, 2022
@ralpha
Copy link
Collaborator

ralpha commented Dec 3, 2023

Code example solves this issue. Closing issue.

@ralpha ralpha closed this as completed Dec 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants