I am developing an API with Rust, using Rocket as main framework.
To create the Swagger docs I use Okapi, which allows me to create the docs automatically.
use rocket_okapi::swagger_ui::*;
extern crate dotenv;
use rocket_okapi::{openapi, openapi_get_routes};
#[openapi] // Let okapi know that we want to document this endpoing
#[get("/test_route")]
fn test_route() -> &'static str {
"test_route"
}
#[rocket::main]
pub async fn main() -> () {
rocket::build()
// Mount routes with `openapi_get_routes` instead of rocket `routes``
.mount("/", openapi_get_routes![test_route])
// Mount swagger-ui on thisroute
.mount(
"/docs",
make_swagger_ui(&SwaggerUIConfig {
// The automatically generated openapi.json will be here
url: "../openapi.json".to_owned(),
..Default::default()
}),
)
.launch()
.await
.unwrap();
()
}
Thats good. But I would like to provide okapi the settings for the API, and I wasnt able to do that. I know there is an example at https://github.com/GREsau/okapi/blob/master/examples/custom_schema/src/main.rs, but I couldnt load a custom OpenApi schema in my api.
Also, I would like to load a custom openapi.json; but I don't know how to achieve that.
Related
Background
I need to create a couple of endpoints for an API service project. These API can accept encrypted and un-encrypted one (for development). Both parameters then are passed into a same function. For example:
/api/movie/get with encrypted parameter (for production)
/dev/movie/get with un-encrypted parameter (for development)
Current implementation in actix_web
I use actix_web and routes (not macro) to provide path routing. Modified from the sample code below
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// prefixes all resources and routes attached to it...
web::scope("/app")
// ...so this handles requests for `GET /app/index.html`
.route("/index.html", web::get().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
For each API endpoint, I had to create 2 functions, such as:
/// for encrypted one
/// PATH: /api/movie/get
pub async fn handle_get_movie(
app_data: Data<AppState>,
Json(payload): Json<EncryptedPayload>,
) -> Result<impl Responder, Error> {
// decrypt the content
// this is the only difference between un-encrypted and encrypted endpoint
let params = payload
.unload::<GetMovieInf>()
.map_err(|_| MainError::Malformatted)?;
// other codes here....
Ok(Json(ReplyInf {ok: true}))
}
/// for un-encrypted one
/// PATH: /dev/movie/get
pub async fn handle_get_movie(
app_data: Data<AppState>,
Json(payload): Json<UnencryptedPayload>,
) -> Result<impl Responder, Error> {
// other codes here. These codes are exactly identical with the above function...
Ok(Json(ReplyInf {ok: true}))
}
Questions
Since both functions are similar, does it possible to combine them both into a single function ? a function "overload" maybe ?
The problem is this line Json(payload): Json<UnencryptedPayload> in the parameter function. I tried to use generics like Json<T>. this doesn't work.
I can use the environment variable to control which should be active (EncryptedPayload or UnencryptedPayload). I can use one path for each endpoint (eg: /api/movie/get) and don't have to write the same functionality twice.
I am using actix-web to write a small service. I'm adding integration tests to assess the functionality and have noticed that on every test I have to repeat the same definitions that in my main App except that it's wrapped by the test service:
let app = test::init_service(App::new().service(health_check)).await;
This can be easily extended if you have simple services but then when middleware and more configuration starts to be added tests start to get bulky, in addition it might be easy to miss something and not be assessing the same specs as the main App.
I've been trying to extract the App from the main thread to be able to reuse it my tests without success.
Specifically what I'd like is to create a "factory" for the App:
pub fn get_app() -> App<????> {
App::new()
.wrap(Logger::default())
.wrap(IdentityService::new(policy))
.service(health_check)
.service(login)
}
So that I can write this in my tests
let app = get_app();
let service = test::init_service(app).await;
But the compiler needs the specific return type which seems to be a chorizo composed of several traits and structs, some private.
Has anyone experience with this?
Thanks!
Define a declarative macro app! that builds the App, but define the routes using the procedural API, not the Actix build-in macros such as #[get("/")].
This example uses a database pool as a state - your application might have different kind of states or none at all.
#[macro_export]
macro_rules! app (
($pool: expr) => ({
App::new()
.wrap(middleware::Logger::default())
.app_data(web::Data::new($pool.clone()))
.route("/health", web::get().to(health_get))
.service(web::resource("/items")
.route(web::get().to(items_get))
.route(web::post().to(items_post))
)
});
);
This can be used in the tests as:
#[cfg(test)]
mod tests {
// more code here for get_test_pool
#[test]
async fn test_health() {
let app = test::init_service(app!(get_test_pool().await)).await;
let req = test::TestRequest::get().uri("/health").to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
and in the main app as:
// More code here for get_main_pool
#[actix_web::main]
async fn main() -> Result<(),std::io::Error> {
let pool = get_main_pool().await?;
HttpServer::new(move || app!(pool))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
In this context, get_main_pool must return, say, Result<sqlx::Pool<sqlx::Postgres>, std::io::Error> to be compatible with the signature requirements of actix_web::main. On the other hand, get_test_pool can simply return sqlx::Pool<sqlx::Postgres>.
I was struggling with the same issue using actix-web#4, but I came up with a possible solution. It may not be ideal, but it works for my needs. I needed to bring in actix-service#2.0.2 and actix-http#3.2.2 in Cargo.toml as well.
I created a test.rs file with an initializer that I can use in all my tests. Here is what that file could look like for you:
use actix_web::{test::{self}, App, web, dev::{HttpServiceFactory, ServiceResponse}, Error};
use actix_service::Service;
use actix_http::{Request};
#[cfg(test)]
pub async fn init(service_factory: impl HttpServiceFactory + 'static) -> impl Service<Request, Response = ServiceResponse, Error = Error> {
// connect to your database or other things to pass to AppState
test::init_service(
App::new()
.app_data(web::Data::new(crate::AppState { db }))
.service(service_factory)
).await
}
I use this in my API services to reduce boilerplate in my integration tests. Here is an example:
// ...
#[get("/")]
async fn get_index() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[cfg(test)]
mod tests {
use actix_web::{test::TestRequest};
use super::{get_index};
#[actix_web::test]
async fn test_get_index() {
let mut app = crate::test::init(get_index).await;
let resp = TestRequest::get().uri("/").send_request(&mut app).await;
assert!(resp.status().is_success(), "Something went wrong");
}
}
I believe the issue you ran into is trying to create a factory for App (which is a bit of an anti-pattern in Actix) instead of init_service. If you want to create a function that returns App I believe the preferred convention is to use configure instead. See this issue for reference: https://github.com/actix/actix-web/issues/2039.
I am trying to work on a web app with Diesel and Rocket, by following the rocket guide. I have not able to understand how to do testing of this app.
//in main.rs
#[database("my_db")]
struct PgDbConn(diesel::PgConnection);
#[post("/users", format="application/json", data="<user_info>")]
fn create_new_user(conn: PgDbConn, user_info: Json<NewUser>) {
use schema::users;
diesel::insert_into(users::table).values(&*user_info).execute(&*conn).unwrap();
}
fn main() {
rocket::ignite()
.attach(PgDbConn::fairing())
.mount("/", routes![create_new_user])
.launch();
}
// in test.rs
use crate::PgDbConn;
#[test]
fn test_user_creation() {
let rocket = rocket::ignite().attach(PgDbConn::fairing());
let client = Client::new(rocket).unwrap();
let response = client
.post("/users")
.header(ContentType::JSON)
.body(r#"{"username": "xyz", "email": "temp#abc.com"}"#)
.dispatch();
assert_eq!(response.status(), Status::Ok);
}
But this modifies the database. How can I make sure that the test does not alter the database.
I tried to create two database and use them in the following way(I am not sure if this is recommended)
#[cfg(test)]
#[database("test_db")]
struct PgDbConn(diesel::PgConnection);
#[cfg(not(test))]
#[database("live_db")]
struct PgDbConn(diesel::PgConnection);
Now I thought I can use the test_transaction method of the diesel::connection::Connection trait in the following way:-
use crate::PgDbConn;
#[test]
fn test_user_creation() {
// !!This statment is wrong as PgDbConn is an Fn object instead of a struct
// !!I am not sure how it works but it seems that this Fn object is resolved
// !!into struct only when used as a Request Guard
let conn = PgDbConn;
// Deref trait for PgDbConn is implemented, So I thought that dereferencing
// it will return a diesel::PgConnection
(*conn).test_transaction::<_, (), _>(|| {
let rocket = rocket::ignite().attach(PgDbConn::fairing());
let client = Client::new(rocket).unwrap();
let response = client
.post("/users")
.header(ContentType::JSON)
.body(r#"{"username": "Tushar", "email": "temp#abc.com"}"#)
.dispatch();
assert_eq!(response.status(), Status::Ok);
Ok(())
});
}
The above code obviously fails to compile. Is there a way to resolve this Fn object into the struct and obtain the PgConnection in it. And I am not even sure if this is the right to way to do things.
Is there a recommended way to do testing while using both Rocket and Diesel?
This will fundamentally not work as you imagined there, as conn will be a different connection than whatever rocket generates for you. The test_transaction pattern assumes that you use the same connection for everything.
I'm working with rust, WASM, and yew as frontend framework. I'm building a wrapper around materialize-css, to use it on yew as dependency of reusable components.
To use some materialize-css components it's necesary initialize it. For example, to use a sidenav
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.sidenav');
var instances = M.Sidenav.init(elems, options);
});
The thing is that I'm trying to run that with wasm-bindgen as described in the wasm-bindgen docs, but it doesn't work. Or maybe I do not really understand whats happening.
according to the documentation, this should work:
#[wasm_bindgen( module = "/materialize/js/bin/materialize.js", js_namespace = ["M","Sidenav"] )]
extern "C" {
fn init(el: JsValue, options: JsValue);
}
pub unsafe fn init_sidenav(el: &str, options: &str) {
init(
JsValue::from(document().get_element_by_id(el).expect("not found")),
JsValue::from(options),
);
}
//to use it on yew component
unsafe { materialize::sidenav::init_sidenav(self.props.el_id, &options) };
or this:
#[wasm_bindgen( module = "/materialize/js/bin/materialize.js", js_namespace = M )]
extern "C" {
#[wasm_bindgen]
type Sidenav;
#[wasm_bindgen( static_method_of = Sidenav )]
fn init(el: JsValue, options: JsValue);
}
pub unsafe fn init_sidenav(el: &str, options: &str) {
Sidenav::init(
JsValue::from(document().get_element_by_id(el).expect("not found")),
JsValue::from(options),
);
}
//to use it on yew component
unsafe { materialize::sidenav::init_sidenav(self.props.el_id, &options) };
But neither of them works... In both cases the code compiles without problems but when executed in the browser it jumps errors.
in the first case the error is Uncaught SyntaxError: import not found: init lib.js:1:9
in the second case the error is Uncaught SyntaxError: import not found: Sidenav lib.js:1:9
honestly i dot not understand why this happens. I've been looking for some days for information in the MDN docs about WASM, and the wasm_bindgen docs, but I can't find anything that helps me.
Additionally
I'm working with --target web flag
My project structure
-
|Cargo.toml
|materialize/ (source of materialize-css dist)
|src/
|-components/ (wrappings of components on yew)
|-materialize/ (wasm-bindgen bindings of materialize)
|-lib.rs
|-...
...
I'm about to rewrite a highly modular CMS in Rust, so my question is if it's even possible to have the "core" application set up extension points (actions/hooks), which other plugins / crates is able to "tab" into.
Something like this would suffice, but how would you do this in Rust? The architecture above uses a plugin registry and initiates each plugin's main method from the core by iterating over each of them. However in Rust, since you can't have a global "modules" variable in e.g. a plugin_registry lib crate, I guess this is not the correct thinking in Rust.
Is there a better and more flexible way to make "plugins" integrate seamlessly with a core application? For example, something like an event dispatcher like WordPress uses?
As Shepmaster said, this is a very general question; hence there are many ways to do what you want. And as already mentioned, too, iron is a great example of a modular framework.
However, I'll try to give a useful example of how one could implement such a plugin system. For the example I will assume, that there is some kind of main-crate that can load the plugins and "configure" the CMS. This means that the plugins aren't loaded dynamically!
Structure
First, lets say we have four crates:
rustpress: the big main crate with all WordPress-like functionality
rustpress-plugin: needs to be used by plugin authors (is an own crate in order to avoid using a huge crate like rustpress for every plugin)
rustpress-signature: here we create our plugin which will add a signature to each post
my-blog: this will be the main executable that configures our blog and will run as a web server later
1. The trait/interface
The way to go in Rust are traits. You can compare them to interfaces from other languages. We will now design the trait for plugins which lives in rustpress-plugin:
pub trait Plugin {
/// Returns the name of the plugin
fn name(&self) -> &str;
/// Hook to change the title of a post
fn filter_title(&self, title: &mut String) {}
/// Hook to change the body of a post
fn filter_body(&self, body: &mut String) {}
}
Note that the filter_* methods already have a default implementation that does nothing ({}). This means that plugins don't have to override all methods if they only want to use one hook.
2. Write our plugin
As I said we want to write a plugin that adds our signature to each posts body. To do that we will impl the trait for our own type (in rustpress-signature):
extern crate rustpress_plugin;
use rustpress_plugin::Plugin;
pub struct Signature {
pub text: String,
}
impl Plugin for Signature {
fn name(&self) -> &str {
"Signature Plugin v0.1 by ferris"
}
fn filter_body(&self, body: &mut String) {
body.push_str("\n-------\n"); // add visual seperator
body.push_str(&self.text);
}
}
We created a simple type Signature for which we implement the trait Plugin. We have to implement the name() method and we also override the filter_body() method. In our implementation we just add text to the post body. We did not override filter_title() because we don't need to.
3. Implement the plugin stack
The CMS has to manage all plugins. I assume that the CMS has a main type RustPress that will handle everything. It could look like this (in rustpress):
extern crate rustpress_plugin;
use rustpress_plugin::Plugin;
pub struct RustPress {
// ...
plugins: Vec<Box<Plugin>>,
}
impl RustPress {
pub fn new() -> RustPress {
RustPress {
// ...
plugins: Vec::new(),
}
}
/// Adds a plugin to the stack
pub fn add_plugin<P: Plugin + 'static>(&mut self, plugin: P) {
self.plugins.push(Box::new(plugin));
}
/// Internal function that prepares a post
fn serve_post(&self) {
let mut title = "dummy".to_string();
let mut body = "dummy body".to_string();
for p in &self.plugins {
p.filter_title(&mut title);
p.filter_body(&mut body);
}
// use the finalized title and body now ...
}
/// Starts the CMS ...
pub fn start(&self) {}
}
What we are doing here is storing a Vec full of boxed plugins (we need to box them, because we want ownership, but traits are unsized). When the CMS then prepare a blog-post, it iterates through all plugins and calls all hooks.
4. Configure and start the CMS
Last step is adding the plugin and starting the CMS (putting it all together). We will do this in the my-blog crate:
extern crate rustpress;
extern crate rustpress_plugin;
extern crate rustpress_signature;
use rustpress::RustPress;
use rustpress_plugin::Plugin;
use rustpress_signature::Signature;
fn main() {
let mut rustpress = RustPress::new();
// add plugin
let sig = Signature { text: "Ferris loves you <3".into() };
rustpress.add_plugin(sig);
rustpress.start();
}
You also need to add the dependencies to the Cargo.toml files. I omitted that because it should be fairly easy.
And note again that this is one of many possibilities to create such a system. I hope this example is helpful. You can try it on playground, too.