mirror of
https://github.com/LUDevNet/ParadoxServer.git
synced 2025-12-30 17:39:41 -06:00
More BoxedFilters & add openapi endpoint
This commit is contained in:
@@ -10,12 +10,15 @@ handlebars = "3.5"
|
||||
pretty_env_logger = "0.4.0"
|
||||
structopt = "0.3.21"
|
||||
color-eyre = "0.5.10"
|
||||
#indexmap = "1"
|
||||
mapr = "0.8.0"
|
||||
notify = "5.0.0-pre.13"
|
||||
openapiv3 = "0.5"
|
||||
paradox-typed-db = { git = "https://github.com/Xiphoseer/paradox-typed-db.git" }
|
||||
percent-encoding = "2.1.0"
|
||||
pin-project = "1.0"
|
||||
regex = "1.4"
|
||||
serde_yaml = "0.8"
|
||||
toml = "0.5"
|
||||
tracing = "0.1"
|
||||
|
||||
@@ -44,3 +47,7 @@ features = ["rt-multi-thread", "macros", "signal"]
|
||||
[dependencies.serde]
|
||||
version = "1"
|
||||
features = ["derive"]
|
||||
|
||||
[build-dependencies]
|
||||
openapiv3 = "0.5"
|
||||
serde_yaml = "0.8"
|
||||
|
||||
10
build.rs
Normal file
10
build.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use openapiv3::OpenAPI;
|
||||
use serde_yaml::{from_str, Error};
|
||||
|
||||
fn main() {
|
||||
let text = include_str!("res/api.yaml");
|
||||
let result: Result<OpenAPI, Error> = from_str(text);
|
||||
if let Err(e) = result {
|
||||
panic!("{}", e);
|
||||
}
|
||||
}
|
||||
58
res/api.html
Normal file
58
res/api.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>API | LU-Explorer</title>
|
||||
<!--<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />-->
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@3/swagger-ui.css"></script>
|
||||
<!--<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />-->
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js" charset="UTF-8"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "v0/openapi.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
tryItOutEnabled: true,
|
||||
persistAuthorization: true,
|
||||
withCredentials: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "BaseLayout"
|
||||
});
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
225
res/api.yaml
Normal file
225
res/api.yaml
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: LU-Explorer API
|
||||
version: "0.1"
|
||||
tags:
|
||||
- name: db
|
||||
description: Queries on database tables
|
||||
- name: locale
|
||||
description: Queries the translations
|
||||
components:
|
||||
securitySchemes:
|
||||
basic_auth:
|
||||
type: http
|
||||
scheme: basic
|
||||
schemas:
|
||||
Behavior:
|
||||
type: object
|
||||
properties: {}
|
||||
Tables:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
TableDef:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
columns:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
data_type:
|
||||
type: string
|
||||
LocaleNode:
|
||||
type: object
|
||||
properties:
|
||||
value:
|
||||
type: string
|
||||
int_keys:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
str_keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
ErrorModel:
|
||||
type: number
|
||||
paths:
|
||||
"/v0/tables":
|
||||
get:
|
||||
tags:
|
||||
- db
|
||||
description: List all database table names
|
||||
responses:
|
||||
"200":
|
||||
description: The tables of the database
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Tables"
|
||||
default:
|
||||
description: Unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorModel"
|
||||
"/v0/tables/{name}/def":
|
||||
get:
|
||||
tags:
|
||||
- db
|
||||
description: Show the definiton of a database table
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TableDef"
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
"/v0/tables/{name}/{key}":
|
||||
get:
|
||||
tags:
|
||||
- db
|
||||
description: Show data for a key in a table
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: name
|
||||
schema:
|
||||
type: string
|
||||
- in: path
|
||||
required: true
|
||||
name: key
|
||||
schema:
|
||||
type: string
|
||||
"/v0/locale/{path}":
|
||||
get:
|
||||
tags:
|
||||
- locale
|
||||
description: Get a single locale node
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/LocaleNode"
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: path
|
||||
schema:
|
||||
type: string
|
||||
"/v0/locale/{path}/$all":
|
||||
get:
|
||||
tags:
|
||||
- locale
|
||||
description: Get a locale subtree
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties: {}
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: path
|
||||
schema:
|
||||
type: string
|
||||
"/v0/rev":
|
||||
get:
|
||||
description: List all supported reverse lookup scopes
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
"/v0/rev/component_types":
|
||||
get:
|
||||
description: List all component types in the database
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema: { type: array, items: { type: string } }
|
||||
"/v0/rev/component_types/{type}":
|
||||
get:
|
||||
description: List all component IDs and associated objects for a component type
|
||||
This is a reverse lookup of the `ComponentsRegistry` table
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema: { type: array, items: { type: string } }
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: type
|
||||
schema:
|
||||
type: number
|
||||
"/v0/rev/component_types/{type}/{id}":
|
||||
get:
|
||||
description: List all component IDs and associated objects for a component type
|
||||
This is a reverse lookup of the `ComponentsRegistry` table
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema: { type: array, items: { type: string } }
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: type
|
||||
schema:
|
||||
type: number
|
||||
- in: path
|
||||
required: true
|
||||
name: id
|
||||
schema:
|
||||
type: number
|
||||
"/v0/rev/behaviors/{id}":
|
||||
get:
|
||||
description: Get all data for a specific behavior ID
|
||||
responses:
|
||||
"200":
|
||||
description: The request was successfull
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Behavior"
|
||||
parameters:
|
||||
- in: path
|
||||
required: true
|
||||
name: id
|
||||
schema:
|
||||
type: number
|
||||
49
src/api/docs.rs
Normal file
49
src/api/docs.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use openapiv3::{OpenAPI, SecurityRequirement, Server};
|
||||
use std::{convert::Infallible, future::Future, sync::Arc, task::Poll};
|
||||
use warp::{
|
||||
filters::BoxedFilter,
|
||||
hyper::StatusCode,
|
||||
reply::{json, with_status, Json, WithStatus},
|
||||
Filter,
|
||||
};
|
||||
|
||||
use crate::auth::AuthKind;
|
||||
|
||||
pub struct OpenApiFuture {
|
||||
/// The openapi structure
|
||||
inner: Arc<OpenAPI>,
|
||||
}
|
||||
|
||||
impl Future for OpenApiFuture {
|
||||
type Output = Result<WithStatus<Json>, Infallible>;
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
Poll::Ready(Ok(with_status(json(self.inner.as_ref()), StatusCode::OK)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the openapi endpoint
|
||||
pub fn openapi(
|
||||
url: String,
|
||||
auth_kind: AuthKind,
|
||||
) -> Result<BoxedFilter<(WithStatus<Json>,)>, serde_yaml::Error> {
|
||||
let text = include_str!("../../res/api.yaml");
|
||||
let mut data: OpenAPI = serde_yaml::from_str(text)?;
|
||||
data.servers.push(Server {
|
||||
url,
|
||||
description: Some(String::from("The current server")),
|
||||
..Default::default()
|
||||
});
|
||||
if auth_kind == AuthKind::Basic {
|
||||
let mut req = SecurityRequirement::new();
|
||||
req.insert("basic_auth".to_string(), vec![]);
|
||||
data.security = Some(vec![req]);
|
||||
}
|
||||
let arc = Arc::new(data);
|
||||
Ok(warp::path("openapi.json")
|
||||
.and_then(move || OpenApiFuture { inner: arc.clone() })
|
||||
.boxed())
|
||||
}
|
||||
@@ -10,11 +10,14 @@ use assembly_data::{fdb::mem::Database, xml::localization::LocaleNode};
|
||||
use paradox_typed_db::TypedDatabase;
|
||||
use percent_encoding::percent_decode_str;
|
||||
use warp::{
|
||||
filters::BoxedFilter,
|
||||
path::Tail,
|
||||
reply::{Json, WithStatus},
|
||||
Filter, Reply,
|
||||
};
|
||||
|
||||
use crate::auth::AuthKind;
|
||||
|
||||
use self::{
|
||||
adapter::{LocaleAll, LocalePod},
|
||||
rev::{make_api_rev, ReverseLookup},
|
||||
@@ -22,6 +25,7 @@ use self::{
|
||||
};
|
||||
|
||||
pub mod adapter;
|
||||
mod docs;
|
||||
pub mod rev;
|
||||
pub mod tables;
|
||||
|
||||
@@ -145,12 +149,14 @@ pub fn locale_api(lr: Arc<LocaleNode>) -> impl Fn(Tail) -> Option<warp::reply::J
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_api<'a>(
|
||||
db: Database<'a>,
|
||||
pub(crate) fn make_api(
|
||||
url: String,
|
||||
auth_kind: AuthKind,
|
||||
db: Database<'static>,
|
||||
tydb: &'static TypedDatabase<'static>,
|
||||
rev: &'static ReverseLookup,
|
||||
lr: Arc<LocaleNode>,
|
||||
) -> impl Filter<Extract = (WithStatus<Json>,), Error = Infallible> + Clone + 'a {
|
||||
) -> BoxedFilter<(WithStatus<Json>,)> {
|
||||
let v0_base = warp::path("v0");
|
||||
let v0_tables = warp::path("tables").and(make_api_tables(db));
|
||||
let v0_locale = warp::path("locale")
|
||||
@@ -159,7 +165,16 @@ pub(crate) fn make_api<'a>(
|
||||
.map(map_opt);
|
||||
|
||||
let v0_rev = warp::path("rev").and(make_api_rev(tydb, rev));
|
||||
let v0 = v0_base.and(v0_tables.or(v0_locale).unify().or(v0_rev).unify());
|
||||
let v0_openapi = docs::openapi(url, auth_kind).unwrap();
|
||||
let v0 = v0_base.and(
|
||||
v0_tables
|
||||
.or(v0_locale)
|
||||
.unify()
|
||||
.or(v0_rev)
|
||||
.unify()
|
||||
.or(v0_openapi)
|
||||
.unify(),
|
||||
);
|
||||
|
||||
// v1
|
||||
let dbf = db_filter(db);
|
||||
@@ -174,5 +189,5 @@ pub(crate) fn make_api<'a>(
|
||||
// catch all
|
||||
let catch_all = make_api_catch_all();
|
||||
|
||||
v0.or(v1).unify().or(catch_all).unify()
|
||||
v0.or(v1).unify().or(catch_all).unify().boxed()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,13 @@ pub struct Api<T, E> {
|
||||
}
|
||||
|
||||
fn rev_api(_db: &TypedDatabase, _rev: Rev) -> Result<Json, CastError> {
|
||||
Ok(warp::reply::json(&["skill_ids"]))
|
||||
Ok(warp::reply::json(&[
|
||||
"behaviors",
|
||||
"component_types",
|
||||
"mission_types",
|
||||
"object_types",
|
||||
"skill_ids",
|
||||
]))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
||||
@@ -9,8 +9,9 @@ use assembly_data::fdb::{
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::{ser::SerializeSeq, Serialize};
|
||||
use warp::{
|
||||
filters::BoxedFilter,
|
||||
reply::{Json, WithStatus},
|
||||
Filter, Rejection,
|
||||
Filter,
|
||||
};
|
||||
|
||||
use super::{db_filter, map_opt_res, map_res};
|
||||
@@ -197,9 +198,7 @@ fn table_key_api(db: Database<'_>, name: String, key: String) -> Result<Option<J
|
||||
Ok(Some(warp::reply::json(&RowIter { cols, to_rows })))
|
||||
}
|
||||
|
||||
pub(super) fn make_api_tables(
|
||||
db: Database<'_>,
|
||||
) -> impl Filter<Extract = (WithStatus<Json>,), Error = Rejection> + Clone + Send + '_
|
||||
pub(super) fn make_api_tables(db: Database<'static>) -> BoxedFilter<(WithStatus<Json>,)>
|
||||
//where
|
||||
//H: Filter<Extract = (ArcHandle<B, FDBHeader>,), Error = Infallible> + Clone + Send,
|
||||
{
|
||||
@@ -239,4 +238,5 @@ pub(super) fn make_api_tables(
|
||||
.unify()
|
||||
.or(table_get)
|
||||
.unify()
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,14 @@ use warp::{
|
||||
|
||||
use crate::config::AuthConfig;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum AuthKind {
|
||||
/// No authentication
|
||||
None,
|
||||
/// HTTP Basic Auth
|
||||
Basic,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AuthImpl {
|
||||
None,
|
||||
|
||||
@@ -18,6 +18,7 @@ fn default_lu_json_cache() -> PathBuf {
|
||||
#[derive(Deserialize)]
|
||||
pub struct CorsOptions {
|
||||
pub all: bool,
|
||||
#[serde(default)]
|
||||
pub domains: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tracing::info;
|
||||
use warp::{fs::File, Filter, Rejection};
|
||||
use warp::{filters::BoxedFilter, fs::File, Filter};
|
||||
|
||||
pub fn make_fallback(
|
||||
lu_json_path: PathBuf,
|
||||
) -> impl Filter<Extract = (File,), Error = Rejection> + Clone {
|
||||
pub fn make_fallback(lu_json_path: PathBuf) -> BoxedFilter<(File,)> {
|
||||
let maps_dir = lu_json_path.join("maps");
|
||||
info!("Maps on '{}'", maps_dir.display());
|
||||
let maps = warp::path("maps").and(warp::fs::dir(maps_dir));
|
||||
|
||||
51
src/main.rs
51
src/main.rs
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
str::FromStr,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
@@ -14,7 +15,7 @@ use paradox_typed_db::TypedDatabase;
|
||||
use structopt::StructOpt;
|
||||
use template::make_spa_dynamic;
|
||||
use tokio::runtime::Handle;
|
||||
use warp::{filters::BoxedFilter, Filter};
|
||||
use warp::{filters::BoxedFilter, hyper::Uri, path::FullPath, Filter, Reply};
|
||||
|
||||
mod api;
|
||||
mod auth;
|
||||
@@ -26,6 +27,8 @@ mod template;
|
||||
|
||||
use crate::{
|
||||
api::rev::ReverseLookup,
|
||||
auth::AuthKind,
|
||||
config::AuthConfig,
|
||||
fallback::make_fallback,
|
||||
redirect::{add_host_filters, add_redirect_filters, base_filter},
|
||||
template::{load_meta_template, FsEventHandler, TemplateUpdateTask},
|
||||
@@ -71,13 +74,17 @@ async fn main() -> color_eyre::Result<()> {
|
||||
};
|
||||
|
||||
let cfg_g = &cfg.general;
|
||||
let lu_res = cfg.data.lu_res_prefix.clone().unwrap_or_else(|| {
|
||||
if let Some(b) = cfg_g.base.as_deref() {
|
||||
format!("{}://{}/{}/lu-res", scheme, &cfg_g.domain, b)
|
||||
} else {
|
||||
format!("{}://{}/lu-res", scheme, &cfg_g.domain)
|
||||
}
|
||||
});
|
||||
let canonical_base_url = if let Some(b) = cfg_g.base.as_deref() {
|
||||
format!("{}://{}/{}", scheme, &cfg_g.domain, b)
|
||||
} else {
|
||||
format!("{}://{}", scheme, &cfg_g.domain)
|
||||
};
|
||||
|
||||
let lu_res = cfg
|
||||
.data
|
||||
.lu_res_prefix
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("{}/lu-res", canonical_base_url));
|
||||
|
||||
let lu_res_prefix = Box::leak(lu_res.clone().into_boxed_str());
|
||||
let tydb = TypedDatabase::new(lr.clone(), lu_res_prefix, tables)?;
|
||||
@@ -88,8 +95,28 @@ async fn main() -> color_eyre::Result<()> {
|
||||
let lu_json_path = cfg.data.lu_json_cache.clone();
|
||||
let fallback_routes = make_fallback(lu_json_path);
|
||||
|
||||
let api_routes = make_api(db, data, rev, lr.clone());
|
||||
let api = warp::path("api").and(fallback_routes.or(api_routes));
|
||||
let auth_kind = if matches!(cfg.auth, Some(AuthConfig { basic: Some(_) })) {
|
||||
AuthKind::Basic
|
||||
} else {
|
||||
AuthKind::None
|
||||
};
|
||||
let api_url = format!("{}/api/", canonical_base_url);
|
||||
let api_uri = Uri::from_str(&api_url).unwrap();
|
||||
|
||||
let api_file = include_str!("../res/api.html");
|
||||
let api_swagger = warp::path::end()
|
||||
.and(warp::path::full())
|
||||
.map(move |path: FullPath| {
|
||||
if path.as_str().ends_with('/') {
|
||||
warp::reply::html(api_file).into_response()
|
||||
} else {
|
||||
warp::redirect(api_uri.clone()).into_response()
|
||||
}
|
||||
})
|
||||
.boxed();
|
||||
|
||||
let api_routes = make_api(api_url, auth_kind, db, data, rev, lr.clone());
|
||||
let api = warp::path("api").and(fallback_routes.or(api_swagger).or(api_routes));
|
||||
|
||||
let spa_path = &cfg.data.explorer_spa;
|
||||
let spa_index = spa_path.join("index.html");
|
||||
@@ -159,7 +186,9 @@ async fn main() -> color_eyre::Result<()> {
|
||||
cors = cors.allow_origin(key.as_ref());
|
||||
}
|
||||
}
|
||||
cors = cors.allow_methods(vec!["GET"]);
|
||||
cors = cors
|
||||
.allow_methods(vec!["OPTIONS", "GET"])
|
||||
.allow_headers(vec!["authorization"]);
|
||||
let to_serve = routes.with(cors);
|
||||
let server = warp::serve(to_serve);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use regex::{Captures, Regex};
|
||||
use serde::Serialize;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::{debug, error, info};
|
||||
use warp::{path::FullPath, Filter};
|
||||
use warp::{filters::BoxedFilter, path::FullPath, Filter};
|
||||
|
||||
fn make_meta_template(text: &str) -> Cow<str> {
|
||||
let re = Regex::new("<meta\\s+(name|property)=\"(.*?)\"\\s+content=\"(.*)\"\\s*/?>").unwrap();
|
||||
@@ -352,12 +352,12 @@ fn meta<'r>(
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_lifetimes)] // false positive?
|
||||
pub(crate) fn make_spa_dynamic<'r>(
|
||||
pub(crate) fn make_spa_dynamic(
|
||||
data: &'static TypedDatabase<'static>,
|
||||
hb: Arc<RwLock<Handlebars<'r>>>,
|
||||
hb: Arc<RwLock<Handlebars<'static>>>,
|
||||
domain: &str,
|
||||
// hnd: ArcHandle<B, FDBHeader>,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = Infallible> + Clone + 'r {
|
||||
) -> BoxedFilter<(impl warp::Reply,)> {
|
||||
let dom = {
|
||||
let d = Box::leak(domain.to_string().into_boxed_str()) as &str;
|
||||
warp::any().map(move || d)
|
||||
@@ -393,4 +393,5 @@ pub(crate) fn make_spa_dynamic<'r>(
|
||||
},
|
||||
)
|
||||
.map(handlebars)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user