use http::header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE, COOKIE};
pub use tablo_core::protocol::{
body_bytes, body_string, cookie_header, field_error, filter_options, input_value,
multipart_body, response_cookies, rows,
};
use tablo_core::{Auth, DeclarationError, MountError, Panel, Resource, RouterBuilderPanelExt};
use toasty::Db;
use topcoat::router::{Body, Router, RouterBuilderDiscoverExt, response::Response};
use uuid::Uuid;
pub async fn memory_db(models: toasty::schema::ModelSet) -> Db {
let db = Db::builder()
.models(models)
.connect("sqlite::memory:")
.await
.expect("connect");
db.push_schema().await.expect("push_schema");
db
}
pub fn panel() -> Panel {
Panel::new("admin").auth(Auth::disabled())
}
pub fn mount(db: Db, panel: Panel) -> topcoat::Result<Router> {
Ok(Router::builder()
.discover()
.app_context(db)
.panel(panel)?
.build())
}
pub fn refusal<T>(mounted: topcoat::Result<T>) -> Vec<DeclarationError> {
let Err(error) = mounted else {
panic!("the panel must not mount");
};
error
.downcast_ref::<MountError>()
.unwrap_or_else(|| panic!("a declaration mistake refuses the panel, got {error}"))
.errors()
.to_vec()
}
pub fn router_with<R: Resource>(db: Db, auth: Auth) -> Router {
mount(db, Panel::new("admin").auth(auth).resource::<R>()).expect("panel builds")
}
pub fn panel_router<R: Resource>(db: Db) -> Router {
mount(db, panel().resource::<R>()).expect("panel builds")
}
pub async fn post(
router: &Router,
uri: &str,
csrf: &str,
content_type: String,
body: String,
) -> Response<Body> {
let request = http::Request::builder()
.method(http::Method::POST)
.uri(uri)
.header(CONTENT_TYPE, content_type)
.header(COOKIE, format!("{}={csrf}", tablo_core::csrf::COOKIE_NAME))
.body(Body::from(body))
.expect("request builds");
router.handle(request).await
}
pub async fn post_multipart(
router: &Router,
uri: &str,
csrf: &str,
boundary: &str,
body: String,
) -> Response<Body> {
post(
router,
uri,
csrf,
format!("multipart/form-data; boundary={boundary}"),
body,
)
.await
}
pub async fn get(router: &Router, uri: &str) -> Response<Body> {
let request = http::Request::builder()
.uri(uri)
.body(Body::empty())
.expect("request builds");
router.handle(request).await
}
pub async fn get_with_cookies(
router: &Router,
uri: &str,
cookies: &[(&str, String)],
) -> Response<Body> {
let mut request = http::Request::builder().uri(uri);
if let Some(jar) = cookie_header(cookies.iter().map(|(name, value)| (*name, value.as_str()))) {
request = request.header(COOKIE, jar);
}
router.handle(request.body(Body::empty()).unwrap()).await
}
pub async fn post_form(
router: &Router,
uri: &str,
cookies: &[(&str, String)],
body: String,
) -> Response<Body> {
let mut request = http::Request::builder()
.method(http::Method::POST)
.uri(uri)
.header(CONTENT_TYPE, "application/x-www-form-urlencoded");
if let Some(jar) = cookie_header(cookies.iter().map(|(name, value)| (*name, value.as_str()))) {
request = request.header(COOKIE, jar);
}
router.handle(request.body(Body::from(body)).unwrap()).await
}
pub async fn post_fields(router: &Router, uri: &str, fields: &[(&str, &str)]) -> Response<Body> {
let csrf = new_csrf();
let mut body = format!("csrf_token={csrf}");
for (name, value) in fields {
body.push_str(&format!("&{name}={value}"));
}
post(
router,
uri,
&csrf,
"application/x-www-form-urlencoded".to_string(),
body,
)
.await
}
pub fn new_csrf() -> String {
Uuid::new_v4().to_string()
}
pub fn csp(response: &Response<Body>) -> &str {
response
.headers()
.get(CONTENT_SECURITY_POLICY)
.expect("response carries a policy")
.to_str()
.expect("the policy is ASCII")
}