Wiring up the api and webassembly ui

This commit is contained in:
Jeremy Wall 2022-01-23 14:28:01 -05:00
parent d846e04418
commit 882615a3cd
10 changed files with 260 additions and 45 deletions

77
Cargo.lock generated
View File

@ -650,6 +650,17 @@ dependencies = [
"web-sys",
]
[[package]]
name = "gloo-utils"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c77af6f96a4f9e27c8ac23a88407381a31f4a74c3fb985c85aa79b8d898136"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "h2"
version = "0.3.10"
@ -834,9 +845,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "js-sys"
version = "0.3.55"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
dependencies = [
"wasm-bindgen",
]
@ -1255,6 +1266,26 @@ dependencies = [
"winapi",
]
[[package]]
name = "reqwasm"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b9b71430c0b3f076a806c389b5a8f806171a1b9365692b86e9878ad1312879"
dependencies = [
"futures-channel",
"futures-core",
"futures-sink",
"gloo-utils",
"js-sys",
"pin-project",
"serde",
"serde_json",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@ -1284,6 +1315,20 @@ name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
@ -1711,19 +1756,21 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.78"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
dependencies = [
"bumpalo",
"lazy_static",
@ -1748,9 +1795,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1758,9 +1805,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
dependencies = [
"proc-macro2",
"quote",
@ -1771,9 +1818,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
[[package]]
name = "wasm-bindgen-test"
@ -1816,13 +1863,15 @@ version = "0.1.0"
dependencies = [
"dioxus",
"recipes",
"reqwasm",
"wasm-bindgen",
]
[[package]]
name = "web-sys"
version = "0.3.55"
version = "0.3.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
dependencies = [
"js-sys",
"wasm-bindgen",

31
kitchen/src/api.rs Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2022 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[derive(Debug)]
pub enum ParseError {
IO(std::io::Error),
Syntax(String),
}
impl From<std::io::Error> for ParseError {
fn from(err: std::io::Error) -> Self {
ParseError::IO(err)
}
}
impl From<String> for ParseError {
fn from(s: String) -> Self {
ParseError::Syntax(s)
}
}

View File

@ -19,14 +19,9 @@ use std::path::Path;
use csv;
use crate::api::ParseError;
use recipes::{parse, IngredientAccumulator, Recipe};
#[derive(Debug)]
pub enum ParseError {
IO(std::io::Error),
Syntax(String),
}
// TODO(jwall): We should think a little more closely about
// the error modeling for this application.
macro_rules! try_open {
@ -41,18 +36,6 @@ macro_rules! try_open {
};
}
impl From<std::io::Error> for ParseError {
fn from(err: std::io::Error) -> Self {
ParseError::IO(err)
}
}
impl From<String> for ParseError {
fn from(s: String) -> Self {
ParseError::Syntax(s)
}
}
pub fn parse_recipe<P>(path: P) -> Result<Recipe, ParseError>
where
P: AsRef<Path> + Debug,

View File

@ -11,14 +11,16 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod cli;
mod web;
use std::env;
use std::path::PathBuf;
use clap;
use clap::{clap_app, crate_authors, crate_version};
pub mod api;
mod cli;
mod web;
fn create_app<'a, 'b>() -> clap::App<'a, 'b>
where
'a: 'b,
@ -39,6 +41,7 @@ where
)
(@subcommand serve =>
(about: "Serve the interface via the web")
(@arg recipe_dir: -d --dir +takes_value "Directory containing recipe files to use")
)
)
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
@ -72,8 +75,13 @@ fn main() {
eprintln!("{:?}", e);
}
}
} else if matches.subcommand_matches("serve").is_some() {
} else if let Some(matches) = matches.subcommand_matches("serve") {
println!("Launching web interface...");
async_std::task::block_on(async { web::ui_main().await });
let recipe_dir_path = if let Some(dir) = matches.value_of("recipe_dir") {
PathBuf::from(dir)
} else {
std::env::current_dir().expect("Unable to get current directory. Bailing out.")
};
async_std::task::block_on(async { web::ui_main(recipe_dir_path).await });
}
}

View File

@ -11,16 +11,53 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use static_dir::static_dir;
use warp::{hyper::Uri, Filter};
use std::path::PathBuf;
pub async fn ui_main() {
use async_std::fs::{read_dir, read_to_string, DirEntry};
use async_std::stream::StreamExt;
use static_dir::static_dir;
use warp::{http::StatusCode, hyper::Uri, Filter};
use crate::api::ParseError;
pub async fn get_recipes(recipe_dir_path: PathBuf) -> Result<Vec<String>, ParseError> {
let mut entries = read_dir(recipe_dir_path).await?;
let mut entry_vec = Vec::new();
while let Some(res) = entries.next().await {
let entry: DirEntry = res?;
if entry.file_type().await?.is_dir() || entry.file_name().to_string_lossy() != "menu.txt" {
// add it to the entry
let recipe_contents = read_to_string(entry.path()).await?;
entry_vec.push(recipe_contents);
}
}
Ok(entry_vec)
}
pub async fn ui_main(recipe_dir_path: PathBuf) {
let root = warp::path::end().map(|| warp::redirect::found(Uri::from_static("/ui")));
let ui = warp::path("ui").and(static_dir!("webdist/"));
// api route goes here eventually.
let api = warp::path!("api" / " v1").map(|| format!("API stuff goes here!"));
// TODO(jwall): Figure out how to make and_then work to simplify the below.
let api = warp::path("api")
.and(warp::path("v1"))
.and(warp::path("recipes"))
.then(move || {
let recipe_dir_path = (&recipe_dir_path).clone();
eprintln!("servicing api request.");
async move {
match get_recipes(recipe_dir_path).await {
Ok(recipes) => {
warp::reply::with_status(warp::reply::json(&recipes), StatusCode::OK)
}
Err(e) => warp::reply::with_status(
warp::reply::json(&format!("Error: {:?}", e)),
StatusCode::INTERNAL_SERVER_ERROR,
),
}
}
});
let routes = root.or(ui).or(api).boxed();
let routes = root.or(ui).or(api).with(warp::log("access log"));
// TODO(jwall): Take listen address as an argument to this function instead.
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;

2
run.sh
View File

@ -14,4 +14,4 @@
make clean kitchen
cd kitchen
cargo run -- serve
cargo run -- serve --dir ../examples

View File

@ -7,6 +7,11 @@ edition = "2021"
[dependencies]
recipes = {path = "../recipes" }
reqwasm = "0.4.0"
[dependencies.wasm-bindgen]
version = "0.2.79"
#features = [ "console" ]
[dependencies.dioxus]
version = "0.1.7"

View File

@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod typings;
mod web;
fn main() {

32
web/src/typings.rs Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2022 Jeremy Wall
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)`
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
}
#[macro_export]
macro_rules! console_log {
// Note that this is using the `log` function imported above during
// `bare_bones`
($($t:tt)*) => {{
use crate::typings::log;
(log(&format_args!($($t)*).to_string()))
}}
}

View File

@ -11,10 +11,79 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(non_snake_case)]
use std::iter::Iterator;
use crate::console_log;
use dioxus::prelude::*;
use reqwasm::http;
use recipes::{parse, Recipe};
#[derive(Props, PartialEq)]
struct RecipeListProps {
recipe_list: Vec<Recipe>,
}
/// Component to list available recipes.
fn RecipeList(cx: Scope) -> Element {
let props = use_state(&cx, || RecipeListProps {
recipe_list: vec![],
});
use_future(&cx, || {
let props = props.for_async();
async move {
let req = http::Request::get("/api/v1/recipes").send().await;
match req {
Ok(resp) => {
if resp.status() != 200 {
console_log!("Status: {}", resp.status());
} else {
console_log!("We got a valid response back!");
let recipe_list = match resp.json::<Vec<String>>().await {
Ok(recipes) => recipes,
Err(e) => {
console_log!("Eror getting recipe list as json {}", e);
Vec::new()
}
};
let mut parsed_list = Vec::new();
for r in recipe_list {
let recipe = match parse::as_recipe(&r) {
Ok(r) => r,
Err(e) => {
console_log!("Error parsing recipe {}", e);
break;
}
};
console_log!("We parsed a recipe {}", recipe.title);
parsed_list.push(recipe);
}
props.set(RecipeListProps {
recipe_list: parsed_list,
});
}
}
Err(e) => {
console_log!("Error: {}", e);
}
}
}
});
cx.render(rsx! {
ul {
(&props.recipe_list).into_iter().map(|i| {
let title = &i.title;
rsx!(li { "{title}" })
})
}
})
}
pub fn ui(cx: Scope) -> Element {
cx.render(rsx! {
div { "hello chefs!" }
RecipeList { }
})
}
}