diff --git a/.idea/webResources.xml b/.idea/webResources.xml
new file mode 100644
index 0000000..bfbe3fe
--- /dev/null
+++ b/.idea/webResources.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index c671e3e..bb5d388 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -19,7 +19,9 @@ COPY ./src ./src
RUN rm ./target/release/deps/simplify_truths*
RUN cargo build --release
-FROM node:20.14.0 as spec
+FROM node:20.14.0 as static
+
+COPY ./src/resources/static ./src/resources/static
WORKDIR /spec
@@ -33,8 +35,8 @@ LABEL authors="Martin Berg Alstad"
# copy the build artifact from the build stage
COPY --from=build /simplify_truths/target/release/simplify_truths .
-# copy the generated html file for REDOC documentation
-COPY --from=spec /spec/dist/index.html ./openapi/index.html
+# copy the static html files
+COPY --from=static ./src/resources/static ./static
EXPOSE 8000
diff --git a/http/index.http b/http/index.http
new file mode 100644
index 0000000..4371bf4
--- /dev/null
+++ b/http/index.http
@@ -0,0 +1,17 @@
+### GET index page
+
+GET {{url}}
+
+### GET OpenAPI page
+
+GET {{url}}/openapi
+
+### GET should fallback to 404 page
+
+GET {{url}}/something-that-does-not-exist
+
+> {%
+ client.test("Response status is 404", () => {
+ client.assert(response.status === 404, "Response status is not 404");
+ });
+%}
diff --git a/http/simplify.http b/http/simplify.http
index dea1797..32c5f91 100644
--- a/http/simplify.http
+++ b/http/simplify.http
@@ -1,11 +1,3 @@
-### GET index page
-
-GET {{url}}
-
-### GET OpenAPI page
-
-GET {{url}}/openapi
-
### GET Atomic Expression
GET {{url}}/simplify/A
@@ -131,33 +123,3 @@ GET {{url}}/simplify/{{expression}}
### GET with simplify="true"
GET {{url}}/simplify/A?simplify=true&hide=NONE&sort=DEFAULT&caseSensitive=false&hideIntermediate=false
-
-### GET only table
-
-GET {{url}}/table/A
-
-> {%
- client.test("Response body contains only the truth table", () => {
- client.assert(response.body.truthTable, "Response body does not contain the truth table")
- });
-%}
-
-### GET table and hide intermediate values
-
-< {%
- import {expression} from "./common";
-
- expression("A & B | C")
-%}
-GET {{url}}/table/{{expression}}?hideIntermediateSteps=true
-
-> {%
- client.test("Response body does not contain intermediate steps", () => {
- const header = response.body.truthTable.header;
- const matrix = response.body.truthTable.truthMatrix;
- client.assert(header.length === 4, "Response body contains intermediate steps")
- for (let i = 0; i < matrix.length; i++) {
- client.assert(matrix[i].length === 4, "Response body contains intermediate steps")
- }
- });
-%}
diff --git a/http/table.http b/http/table.http
new file mode 100644
index 0000000..b822da7
--- /dev/null
+++ b/http/table.http
@@ -0,0 +1,29 @@
+### GET only table
+
+GET {{url}}/table/A
+
+> {%
+ client.test("Response body contains only the truth table", () => {
+ client.assert(response.body.truthTable, "Response body does not contain the truth table")
+ });
+%}
+
+### GET table and hide intermediate values
+
+< {%
+ import {expression} from "./common";
+
+ expression("A & B | C")
+%}
+GET {{url}}/table/{{expression}}?hideIntermediateSteps=true
+
+> {%
+ client.test("Response body does not contain intermediate steps", () => {
+ const header = response.body.truthTable.header;
+ const matrix = response.body.truthTable.truthMatrix;
+ client.assert(header.length === 4, "Response body contains intermediate steps")
+ for (let i = 0; i < matrix.length; i++) {
+ client.assert(matrix[i].length === 4, "Response body contains intermediate steps")
+ }
+ });
+%}
diff --git a/spec/package.json b/spec/package.json
index f914812..18f7f77 100644
--- a/spec/package.json
+++ b/spec/package.json
@@ -5,7 +5,7 @@
"author": "Martin Berg Alstad",
"scripts": {
"tsp-compile": "tsp compile . --output-dir dist",
- "redoc-build": "redocly build-docs dist/@typespec/openapi3/openapi.v2.yaml --output dist/index.html"
+ "redoc-build": "redocly build-docs dist/@typespec/openapi3/openapi.v2.yaml --output ../src/resources/static/openapi.html"
},
"dependencies": {
"@typespec/compiler": "latest",
diff --git a/src/config.rs b/src/config.rs
index 89fce49..f53240a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1 +1,7 @@
pub const PORT: u16 = 8000;
+pub const IS_DEV: bool = cfg!(debug_assertions);
+pub const RESOURCE_DIR: &str = if IS_DEV {
+ "./src/resources/static"
+} else {
+ "./static"
+};
diff --git a/src/main.rs b/src/main.rs
index 3340dd4..eb21eae 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ use tower_http::trace::TraceLayer;
use tracing::Level;
use crate::routing::routes::*;
+use crate::routing::routes::index::not_found;
mod expressions;
mod parsing;
@@ -29,7 +30,7 @@ async fn main() {
let routes = simplify::router()
.merge(table::router())
.merge(index::router())
- .merge(util::router());
+ .fallback(not_found);
let app = routes
.layer(CorsLayer::new().allow_origin(Any))
diff --git a/src/resources/static/not-found.html b/src/resources/static/not-found.html
new file mode 100644
index 0000000..930a8b9
--- /dev/null
+++ b/src/resources/static/not-found.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ 404 Not Found
+
+
+
+
+
+
diff --git a/src/resources/static/openapi.html b/src/resources/static/openapi.html
new file mode 100644
index 0000000..d53a584
--- /dev/null
+++ b/src/resources/static/openapi.html
@@ -0,0 +1,351 @@
+
+
+
+
+
+ Simplify Truth Expressions
+
+
+
+
+
+
+
+
+
+ Simplify Truth Expressions (v2)
Download OpenAPI specification:Download
Simplify_simplify
Responses
Response samples
Content typeapplication/json
+
+
+
+
diff --git a/src/routing/routes/index.rs b/src/routing/routes/index.rs
index 7e292bf..1373e63 100644
--- a/src/routing/routes/index.rs
+++ b/src/routing/routes/index.rs
@@ -1,14 +1,17 @@
-use axum::body::Body;
+use axum::extract::Path;
use axum::http::StatusCode;
-use axum::response::{Html, IntoResponse, Response};
-use tokio::fs::File;
-use tokio_util::io::ReaderStream;
+use axum::response::{IntoResponse, Response};
+use crate::expressions::expression::Expression;
use crate::router;
+use crate::routing::error::{Error, ErrorKind};
+use crate::routing::response::IsLegalResponse;
+use crate::utils::axum::load_html;
router!(
get "/" => index,
- get "/openapi" => open_api
+ get "/openapi" => open_api,
+ get "/is-valid/:exp" => is_valid
);
async fn index() -> &'static str {
@@ -16,17 +19,22 @@ async fn index() -> &'static str {
}
async fn open_api() -> Response {
- let file_path = if cfg!(debug_assertions) {
- "./spec/dist/index.html"
- } else {
- "./openapi/index.html"
- };
- let file = match File::open(file_path).await {
- Ok(file) => file,
- Err(err) => return (StatusCode::NOT_FOUND, format!("File not found: {err}")).into_response(),
- };
- let stream = ReaderStream::new(file);
- let body = Body::from_stream(stream);
-
- Html(body).into_response()
+ match load_html("openapi.html").await {
+ Ok(html) => html.into_response(),
+ Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response()
+ }
+}
+
+async fn is_valid(Path(path): Path) -> Response {
+ match Expression::try_from(path.as_str()) {
+ Ok(_) => IsLegalResponse { is_legal: true }.into_response(),
+ Err(error) => Error::new(error.to_string(), ErrorKind::InvalidExpression).into_response()
+ }
+}
+
+pub(crate) async fn not_found() -> Response {
+ match load_html("not-found.html").await {
+ Ok(html) => (StatusCode::NOT_FOUND, html).into_response(),
+ Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response()
+ }
}
diff --git a/src/routing/routes/mod.rs b/src/routing/routes/mod.rs
index e476db7..46e0b20 100644
--- a/src/routing/routes/mod.rs
+++ b/src/routing/routes/mod.rs
@@ -3,5 +3,3 @@ pub(crate) mod index;
pub(crate) mod simplify;
pub(crate) mod table;
-
-pub(crate) mod util;
diff --git a/src/routing/routes/util.rs b/src/routing/routes/util.rs
deleted file mode 100644
index 08f3e69..0000000
--- a/src/routing/routes/util.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-use axum::extract::Path;
-use axum::response::{IntoResponse, Response};
-
-use crate::expressions::expression::Expression;
-use crate::router;
-use crate::routing::error::{Error, ErrorKind};
-use crate::routing::response::IsLegalResponse;
-
-router!(
- get "/is-legal/:exp" => is_legal
-);
-
-async fn is_legal(Path(path): Path) -> Response {
- match Expression::try_from(path.as_str()) {
- Ok(_) => IsLegalResponse { is_legal: true }.into_response(),
- Err(error) => Error::new(error.to_string(), ErrorKind::InvalidExpression).into_response()
- }
-}
diff --git a/src/utils/axum.rs b/src/utils/axum.rs
index 6efaa15..10d694a 100644
--- a/src/utils/axum.rs
+++ b/src/utils/axum.rs
@@ -1,3 +1,10 @@
+use axum::body::Body;
+use axum::response::Html;
+use tokio::fs::File;
+use tokio_util::io::ReaderStream;
+
+use crate::config::RESOURCE_DIR;
+
/// Create an axum router function with the given body or routes.
/// # Examples
/// ```
@@ -40,3 +47,21 @@ macro_rules! routes {
$(.route($route, axum::routing::$method($func)))*
};
}
+
+/// Load an HTML file from the given file path, relative to the resource directory.
+/// # Arguments
+/// * `file_path` - The path to the HTML file.
+/// # Returns
+/// The HTML file as a `Html` object containing the content-type 'text/html' or an error message if the file is not found or cannot be read.
+/// # Examples
+/// ```
+/// let html = load_html("openapi.html").await.unwrap();
+/// ```
+pub async fn load_html(file_path: &str) -> Result, String> {
+ let file = match File::open(format!("{}/{}", RESOURCE_DIR, file_path)).await {
+ Ok(file) => file,
+ Err(err) => return Err(format!("File not found: {err}")),
+ };
+ let stream = ReaderStream::new(file);
+ Ok(Html(Body::from_stream(stream)))
+}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index d23a11a..4ac8329 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,3 +1,3 @@
pub mod array;
pub mod serialize;
-mod axum;
\ No newline at end of file
+pub mod axum;
\ No newline at end of file