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 + + + +
+

404

+

Oops! Page not found.

+ Go back to the documentation +
+ + 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

path Parameters
exp
required
string
query Parameters
object (SimplifyOptions)

Responses

Response samples

Content type
application/json
{
  • "before": "string",
  • "after": "string",
  • "orderOfOperations": [ ],
  • "expression": {
    }
}
+ + + + 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