Skip to content

Commit de74b10

Browse files
committed
feat: Implement basic Module Build Server
1 parent 0591e9e commit de74b10

File tree

19 files changed

+2428
-108
lines changed

19 files changed

+2428
-108
lines changed

Cargo.lock

Lines changed: 1810 additions & 107 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
[workspace]
22
members = [
33
"rust/example-crate",
4+
"rust/usuba"
45
]
56

67
# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
78
resolver = "2"
89

910
[workspace.dependencies]
11+
async-trait = { version = "0.1" }
12+
axum = { version = "0.7" }
13+
blake3 = { version = "1.5" }
14+
bytes = { version = "1" }
1015
js-sys = { version = "0.3" }
16+
redb = { version = "2" }
17+
serde = { version = "1" }
18+
serde_json = { version = "1" }
19+
tempfile = { version = "3" }
20+
thiserror = { version = "1" }
21+
tokio = { version = "1" }
1122
tracing = { version = "0.1" }
1223
tracing-subscriber = { version = "0.3", features = ["env-filter", "tracing-log", "json"] }
1324
tracing-web = { version = "0.1" }
25+
utoipa = { version = "4" }
26+
utoipa-swagger-ui = { version = "7" }
1427
wasm-bindgen = { version = "0.2" }
1528
web-sys = { version = "0.3" }
16-
wit-bindgen = { version = "0.24" }
29+
wit-bindgen = { version = "0.25" }
1730

1831
[profile.release]
1932
opt-level = 'z'

rust/usuba/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "usuba"
3+
description = "An anything-to-Common-Wasm build server"
4+
version = "0.1.0"
5+
edition = "2021"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
async-trait = { workspace = true }
11+
axum = { workspace = true, features = ["multipart"] }
12+
blake3 = { workspace = true }
13+
bytes = { workspace = true }
14+
redb = { workspace = true }
15+
serde = { workspace = true }
16+
serde_json = { workspace = true }
17+
tempfile = { workspace = true }
18+
thiserror = { workspace = true }
19+
tokio = { workspace = true, features = ["rt-multi-thread", "io-util", "process", "fs"] }
20+
tracing = { workspace = true }
21+
tracing-subscriber = { workspace = true }
22+
utoipa = { workspace = true, features = ["axum_extras"] }
23+
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
24+
# gcloud-sdk = { version = "0.24.6", features = ["google-cloud-aiplatform-v1"] }
25+

rust/usuba/Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FROM rust:1.75 as builder
2+
3+
WORKDIR /build-root
4+
5+
COPY "./Cargo.toml" "./Cargo.lock" ./
6+
COPY ./rust ./rust
7+
RUN cargo build --release --bin usuba
8+
9+
FROM node:latest
10+
11+
WORKDIR /usuba
12+
13+
EXPOSE 8888
14+
15+
RUN apt-get update && apt-get install -y libssl-dev ca-certificates
16+
RUN npm install -g @bytecodealliance/jco
17+
18+
COPY --from=builder /build-root/target/release/usuba /usr/bin/usuba
19+
20+
ENV RUST_LOG="debug"
21+
22+
ENTRYPOINT ["/usr/bin/usuba"]

rust/usuba/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Usuba
2+
3+
An anything-to-Common-Wasm build server.
4+

rust/usuba/src/bake/bake.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use crate::UsubaError;
2+
use async_trait::async_trait;
3+
use bytes::Bytes;
4+
5+
#[async_trait]
6+
pub trait Bake {
7+
async fn bake(&self, wit: Bytes, source_code: Bytes) -> Result<Bytes, UsubaError>;
8+
}

rust/usuba/src/bake/javascript.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::io::Cursor;
2+
use tracing::instrument;
3+
4+
use super::Bake;
5+
use async_trait::async_trait;
6+
use bytes::Bytes;
7+
use tempfile::TempDir;
8+
9+
use tokio::process::Command;
10+
11+
#[derive(Debug)]
12+
pub struct JavaScriptBaker {}
13+
14+
#[async_trait]
15+
impl Bake for JavaScriptBaker {
16+
#[instrument]
17+
async fn bake(&self, wit: Bytes, source_code: Bytes) -> Result<Bytes, crate::UsubaError> {
18+
let workspace = TempDir::new()?;
19+
debug!(
20+
"Created temporary workspace in {}",
21+
workspace.path().display()
22+
);
23+
24+
let wasm_path = workspace.path().join("module.wasm");
25+
let js_path = workspace.path().join("module.js");
26+
let wit_path = workspace.path().join("module.wit");
27+
28+
let (mut wit_file, mut js_file) = tokio::try_join!(
29+
tokio::fs::File::create(&wit_path),
30+
tokio::fs::File::create(&js_path),
31+
)?;
32+
33+
debug!(?wit_path, ?js_path, "Created temporary input files");
34+
35+
let mut wit_cursor = Cursor::new(wit);
36+
let mut js_cursor = Cursor::new(source_code);
37+
38+
tokio::try_join!(
39+
tokio::io::copy(&mut wit_cursor, &mut wit_file),
40+
tokio::io::copy(&mut js_cursor, &mut js_file),
41+
)?;
42+
43+
debug!(?wit_path, ?js_path, "Populated temporary input files");
44+
45+
let mut command = Command::new("jco");
46+
47+
command
48+
.arg("componentize")
49+
.arg("-w")
50+
.arg(wit_path.display().to_string())
51+
.arg("-o")
52+
.arg(wasm_path.display().to_string())
53+
.arg(js_path.display().to_string());
54+
55+
let child = command.spawn()?;
56+
let output = child.wait_with_output().await?;
57+
58+
if output.stderr.len() > 0 {
59+
warn!("{}", String::from_utf8_lossy(&output.stderr));
60+
}
61+
62+
debug!("Finished building with jco");
63+
64+
let wasm_bytes = tokio::fs::read(&wasm_path).await?;
65+
66+
Ok(wasm_bytes.into())
67+
}
68+
}

rust/usuba/src/bake/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
mod bake;
2+
mod javascript;
3+
4+
pub use bake::*;
5+
pub use javascript::*;
6+
7+
use async_trait::async_trait;
8+
use bytes::Bytes;
9+
10+
pub enum Baker {
11+
JavaScript,
12+
}
13+
14+
#[async_trait]
15+
impl Bake for Baker {
16+
async fn bake(&self, wit: Bytes, source_code: Bytes) -> Result<Bytes, crate::UsubaError> {
17+
match self {
18+
Baker::JavaScript => (JavaScriptBaker {}).bake(wit, source_code).await,
19+
}
20+
}
21+
}

rust/usuba/src/bin/usuba.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#[macro_use]
2+
extern crate tracing;
3+
4+
use std::net::SocketAddr;
5+
6+
use tracing::Level;
7+
use tracing_subscriber::{fmt::Layer, layer::SubscriberExt, FmtSubscriber};
8+
use usuba::{serve, UsubaError};
9+
10+
#[tokio::main]
11+
pub async fn main() -> Result<(), UsubaError> {
12+
let subscriber = FmtSubscriber::builder()
13+
.with_max_level(Level::TRACE)
14+
.finish();
15+
tracing::subscriber::set_global_default(subscriber.with(Layer::default().pretty()))?;
16+
17+
let socket_address: SocketAddr = "127.0.0.1:8080".parse()?;
18+
let listener = tokio::net::TcpListener::bind(socket_address).await?;
19+
20+
info!("Server listening on {}", socket_address);
21+
22+
serve(listener).await?;
23+
24+
Ok(())
25+
}

rust/usuba/src/error.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// use std::fmt::Display;
2+
3+
use axum::{extract::multipart::MultipartError, http::StatusCode, response::IntoResponse, Json};
4+
use blake3::HexError;
5+
use redb::{CommitError, DatabaseError, StorageError, TableError, TransactionError};
6+
use serde::{Deserialize, Serialize};
7+
use thiserror::Error;
8+
use tracing::subscriber::SetGlobalDefaultError;
9+
use utoipa::ToSchema;
10+
11+
#[derive(Debug, Error)]
12+
pub enum UsubaError {
13+
#[error("Bad request body")]
14+
BadRequest,
15+
#[error("Failed to bake the module: {0}")]
16+
BakeFailure(String),
17+
#[error("Invalid configuration: {0}")]
18+
InvalidConfiguration(String),
19+
#[error("Module not found")]
20+
ModuleNotFound,
21+
#[error("An internal error occurred")]
22+
Internal(String),
23+
}
24+
25+
impl From<std::net::AddrParseError> for UsubaError {
26+
fn from(value: std::net::AddrParseError) -> Self {
27+
UsubaError::InvalidConfiguration(format!("{}", value))
28+
}
29+
}
30+
31+
impl From<std::io::Error> for UsubaError {
32+
fn from(value: std::io::Error) -> Self {
33+
error!("{}", value);
34+
UsubaError::Internal(format!("{}", value))
35+
}
36+
}
37+
38+
impl From<MultipartError> for UsubaError {
39+
fn from(_value: MultipartError) -> Self {
40+
UsubaError::BadRequest
41+
}
42+
}
43+
44+
impl From<SetGlobalDefaultError> for UsubaError {
45+
fn from(value: SetGlobalDefaultError) -> Self {
46+
error!("{}", value);
47+
UsubaError::Internal(format!("{}", value))
48+
}
49+
}
50+
51+
impl From<StorageError> for UsubaError {
52+
fn from(value: StorageError) -> Self {
53+
error!("{}", value);
54+
UsubaError::ModuleNotFound
55+
}
56+
}
57+
58+
impl From<TransactionError> for UsubaError {
59+
fn from(value: TransactionError) -> Self {
60+
error!("{}", value);
61+
UsubaError::Internal(format!("{}", value))
62+
}
63+
}
64+
65+
impl From<TableError> for UsubaError {
66+
fn from(value: TableError) -> Self {
67+
error!("{}", value);
68+
UsubaError::Internal(format!("{}", value))
69+
}
70+
}
71+
72+
impl From<CommitError> for UsubaError {
73+
fn from(value: CommitError) -> Self {
74+
error!("{}", value);
75+
UsubaError::Internal(format!("{}", value))
76+
}
77+
}
78+
79+
impl From<DatabaseError> for UsubaError {
80+
fn from(value: DatabaseError) -> Self {
81+
error!("{}", value);
82+
UsubaError::Internal(format!("{}", value))
83+
}
84+
}
85+
86+
impl From<HexError> for UsubaError {
87+
fn from(_value: HexError) -> Self {
88+
UsubaError::BadRequest
89+
}
90+
}
91+
92+
impl IntoResponse for UsubaError {
93+
fn into_response(self) -> axum::response::Response {
94+
let status = match self {
95+
UsubaError::BadRequest => StatusCode::BAD_REQUEST,
96+
UsubaError::BakeFailure(_) => StatusCode::INTERNAL_SERVER_ERROR,
97+
UsubaError::InvalidConfiguration(_) => StatusCode::BAD_REQUEST,
98+
UsubaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
99+
UsubaError::ModuleNotFound => StatusCode::NOT_FOUND,
100+
};
101+
102+
(
103+
status,
104+
Json(ErrorResponse {
105+
error: self.to_string(),
106+
}),
107+
)
108+
.into_response()
109+
}
110+
}
111+
112+
#[derive(Serialize, Deserialize, ToSchema)]
113+
pub struct ErrorResponse {
114+
error: String,
115+
}

0 commit comments

Comments
 (0)