Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for testcontainers #253

Merged
merged 9 commits into from
Jul 24, 2024
Merged
1,152 changes: 1,129 additions & 23 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 22 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ leptos_meta = { version = "0.6", features = ["nightly"] }
leptos_router = { version = "0.6", features = ["nightly"] }
log = "0.4"
simple_logger = "4.0"
tokio = { version = "1", optional = true, features = ["rt-multi-thread"] }
tokio = { version = "1", optional = true, features = ["rt-multi-thread", "signal"] }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
wasm-bindgen = "=0.2.92"
Expand All @@ -27,7 +27,7 @@ tracing = { version = "0.1.37", optional = true }
http = "1.1.0"
serde = { version = "1.0", features = ["derive"] }
candid = "0.10.3"
ic-agent = { version = "0.34.0", default-features = false, features = [
ic-agent = { version = "0.36.0", default-features = false, features = [
"pem",
"reqwest",
] }
Expand Down Expand Up @@ -82,23 +82,25 @@ redis = { version = "0.25.2", features = [
bb8 = { version = "0.8.3", optional = true }
bb8-redis = { version = "0.15.0", optional = true }
gob-cloudflare = { git = "https://github.com/go-bazzinga/gob-cloudflare", rev = "c847ba87ecc73a33520b24bd62503420d7e23e3e", default-features = false, optional = true }
yral-metadata-client = { git = "https://github.com/go-bazzinga/yral-metadata", rev = "c2c5d254ac273681a66966d62b6f01785679a1be", default-features = false }
yral-metadata-types = { git = "https://github.com/go-bazzinga/yral-metadata", rev = "c2c5d254ac273681a66966d62b6f01785679a1be", default-features = false }

yral-metadata-client = { git = "https://github.com/go-bazzinga/yral-metadata", rev = "c394bf9af3f32d81c1ac50b966c25dafafa2545b", default-features = false }
yral-metadata-types = { git = "https://github.com/go-bazzinga/yral-metadata", rev = "c394bf9af3f32d81c1ac50b966c25dafafa2545b", default-features = false }
gloo-utils = { version = "0.2.0", features = ["serde"] }
tonic = { version = "0.11.0", features = [
"tls",
"tls-webpki-roots",
], optional = true }
prost = { version = "0.12.4", optional = true }
hmac = { version = "0.12.1", optional = true }
testcontainers = { version = "0.20.0", optional = true }
yral-testcontainers = { git = "https://github.com/go-bazzinga/yral-testcontainers", rev = "f9d2c01c498d58fca0595a48bdc3f9400e57ec2f", optional = true }

[build-dependencies]
serde = { version = "1.0", features = ["derive"] }
candid_parser = "0.1.1"
serde_json = "1.0.110"
convert_case = "0.6.0"
tonic-build = "0.11.0"
anyhow = "1.0.86"

[features]
hydrate = [
Expand Down Expand Up @@ -147,6 +149,7 @@ redis-kv = []
cloudflare = ["dep:gob-cloudflare"]
backend-admin = []
ga4 = []
mock-wallet-history = ["dep:rand_chacha"]
release-bin = [
"ssr",
"cloudflare",
Expand All @@ -164,8 +167,20 @@ release-lib = [
"oauth-hydrate",
"ga4",
]

mock-wallet-history = ["dep:rand_chacha"]
local-bin = [
"ssr",
"redis-kv",
"local-auth",
"backend-admin",
"dep:testcontainers",
"dep:yral-testcontainers",
]
local-lib = [
"hydrate",
"redis-kv",
"local-auth",
"backend-admin",
]

[profile.wasm-release]
inherits = "release"
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ Addtionally, Cargo.toml may need updating as new versions of the dependencies ar
cargo leptos watch
```

## Local Testing with testcontainers

```bash
./local-run.sh
```

## Installing Additional Tools

By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
Expand Down
245 changes: 160 additions & 85 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,106 +1,181 @@
use std::{collections::HashMap, env, ffi::OsStr, fs, io, path::PathBuf};
use anyhow::Result;

use candid_parser::Principal;
use convert_case::{Case, Casing};
use serde::Deserialize;
mod build_common {
use std::{collections::HashMap, env, ffi::OsStr, fs, io, path::PathBuf};

#[derive(Deserialize)]
struct CanId {
ic: Principal,
}
use anyhow::Result;
use candid_parser::Principal;
use convert_case::{Case, Casing};
use serde::Deserialize;

fn read_candid_ids() -> io::Result<HashMap<String, CanId>> {
let can_ids_file = fs::File::open("did/canister_ids.json")?;
let reader = io::BufReader::new(can_ids_file);
Ok(serde_json::from_reader(reader).expect("invalid candid ids"))
}
#[derive(Deserialize)]
struct CanId {
ic: Principal,
local: Principal,
}

fn read_candid_ids() -> Result<HashMap<String, CanId>> {
let can_ids_file = fs::File::open("did/canister_ids.json")?;
let reader = io::BufReader::new(can_ids_file);
Ok(serde_json::from_reader(reader)?)
}

fn generate_canister_id_mod(can_ids: Vec<(String, Principal)>) -> String {
let mut canister_id_mod = String::new();
for (canister, can_id) in can_ids {
let can_upper = canister.to_case(Case::UpperSnake);
// CANISTER_NAME_ID: Principal = Principal::from_slice(&[..]);
canister_id_mod.push_str(&format!(
"pub const {can_upper}_ID: candid::Principal = candid::Principal::from_slice(&{:?});\n",
can_id.as_slice()
));
}
canister_id_mod
}

fn build_gprc_client() -> Result<(), Box<dyn std::error::Error>> {
let proto_file = "contracts/projects/warehouse_events/warehouse_events.proto";
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
fn build_canister_ids(out_dir: &str) -> Result<()> {
let can_ids = read_candid_ids()?;
let mut local_can_ids = Vec::<(String, Principal)>::new();
let mut ic_can_ids = Vec::<(String, Principal)>::new();
for (canister, can_id) in can_ids {
local_can_ids.push((canister.clone(), can_id.local));
ic_can_ids.push((canister, can_id.ic));
}

tonic_build::configure()
.build_client(true)
.build_server(false)
.out_dir(out_dir)
.compile(&[proto_file], &["proto"])?;
let local_canister_id_mod = generate_canister_id_mod(local_can_ids);
let ic_canister_id_mod = generate_canister_id_mod(ic_can_ids);

let canister_id_mod_contents = format!(
r#"
#[cfg(any(feature = "local-bin", feature = "local-lib"))]
mod local {{
{local_canister_id_mod}
}}

#[cfg(not(any(feature = "local-bin", feature = "local-lib")))]
mod ic {{
{ic_canister_id_mod}
}}
#[cfg(any(feature = "local-bin", feature = "local-lib"))]
pub use local::*;
#[cfg(not(any(feature = "local-bin", feature = "local-lib")))]
pub use ic::*;
"#
);
let canister_id_mod_path = PathBuf::from(out_dir).join("canister_ids.rs");
fs::write(canister_id_mod_path, canister_id_mod_contents)?;

let proto_file = "contracts/projects/off_chain/off_chain.proto";
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
Ok(())
}

tonic_build::configure()
.build_client(true)
.build_server(false)
.out_dir(out_dir)
.compile(&[proto_file], &["proto"])?;
fn build_did_intf() -> Result<()> {
println!("cargo:rerun-if-changed=did/*");

let mut candid_config = candid_parser::bindings::rust::Config::new();
candid_config.set_target(candid_parser::bindings::rust::Target::Agent);
let mut did_mod_contents = String::new();

// create $OUT_DIR/did
let out_dir = env::var("OUT_DIR").unwrap();
let did_dir = PathBuf::from(&out_dir).join("did");
fs::create_dir_all(&did_dir)?;

// Auto generate bindings for did files
for didinfo in fs::read_dir("did")? {
let didpath = didinfo?.path();
if didpath.extension() != Some(OsStr::new("did")) {
continue;
}
let file_name = didpath.file_stem().unwrap().to_str().unwrap();

// compile bindings from did
let service_name: String = file_name.to_case(Case::Pascal);
candid_config.set_service_name(service_name);
let (type_env, actor) =
candid_parser::pretty_check_file(&didpath).unwrap_or_else(|e| {
panic!(
"invalid did file: {}, err: {e}",
didpath.as_os_str().to_string_lossy()
)
});
let bindings =
candid_parser::bindings::rust::compile(&candid_config, &type_env, &actor);

// write bindings to $OUT_DIR/did/<did file>.rs
let mut binding_file = did_dir.clone();
binding_file.push(file_name);
binding_file.set_extension("rs");
fs::write(&binding_file, bindings)?;

// #[path = "$OUT_DIR/did/<did file>.rs"] pub mod <did file>;
did_mod_contents.push_str(&format!(
"#[path = \"{}\"] pub mod {};\n",
binding_file.to_string_lossy(),
file_name
));
}

Ok(())
// create mod file for did bindings
// ideally i'd like to manually write this file in src/canister/mod.rs
// but can't, due to https://github.com/rust-lang/rfcs/issues/752
let binding_mod_file = PathBuf::from(&out_dir).join("did").join("mod.rs");
fs::write(binding_mod_file, did_mod_contents)?;

build_canister_ids(&out_dir)?;

Ok(())
}

pub fn build_common() -> Result<()> {
build_did_intf()?;
Ok(())
}
}

fn main() -> io::Result<()> {
println!("cargo:rerun-if-changed=did/*");
#[cfg(feature = "ssr")]
mod build_ssr {
use std::{env, path::PathBuf};

let can_ids = read_candid_ids()?;
use anyhow::Result;

let mut candid_config = candid_parser::bindings::rust::Config::new();
candid_config.set_target(candid_parser::bindings::rust::Target::Agent);
let mut did_mod_contents = String::new();
fn build_gprc_client() -> Result<()> {
println!("cargo:rerun-if-changed=contracts/projects/*");

// create $OUT_DIR/did
let out_dir = env::var("OUT_DIR").unwrap();
let did_dir = PathBuf::from(&out_dir).join("did");
fs::create_dir_all(&did_dir)?;
let proto_file = "contracts/projects/warehouse_events/warehouse_events.proto";
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

// Auto generate bindings for did files
for didinfo in fs::read_dir("did")? {
let didpath = didinfo?.path();
if didpath.extension() != Some(OsStr::new("did")) {
continue;
}
let file_name = didpath.file_stem().unwrap().to_str().unwrap();

// compile bindings from did
candid_config.set_canister_id(
can_ids
.get(file_name)
.unwrap_or_else(|| panic!("no canister id for {file_name}"))
.ic,
);
let service_name: String = file_name.to_case(Case::Pascal);
candid_config.set_service_name(service_name);
let (type_env, actor) = candid_parser::pretty_check_file(&didpath).unwrap_or_else(|e| {
panic!(
"invalid did file: {}, err: {e}",
didpath.as_os_str().to_string_lossy()
)
});
let bindings = candid_parser::bindings::rust::compile(&candid_config, &type_env, &actor);

// write bindings to $OUT_DIR/did/<did file>.rs
let mut binding_file = did_dir.clone();
binding_file.push(file_name);
binding_file.set_extension("rs");
fs::write(&binding_file, bindings)?;

// #[path = "$OUT_DIR/did/<did file>.rs"] pub mod <did file>;
did_mod_contents.push_str(&format!(
"#[path = \"{}\"] pub mod {};\n",
binding_file.to_string_lossy(),
file_name
));
tonic_build::configure()
.build_client(true)
.build_server(false)
.out_dir(out_dir)
.compile(&[proto_file], &["proto"])?;

let proto_file = "contracts/projects/off_chain/off_chain.proto";
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

tonic_build::configure()
.build_client(true)
.build_server(false)
.out_dir(out_dir)
.compile(&[proto_file], &["proto"])?;

Ok(())
}

// create mod file for did bindings
// ideally i'd like to manually write this file in src/canister/mod.rs
// but can't, due to https://github.com/rust-lang/rfcs/issues/752
let binding_mod_file = PathBuf::from(&out_dir).join("did").join("mod.rs");
fs::write(binding_mod_file, did_mod_contents)?;
pub fn build_ssr() -> Result<()> {
build_gprc_client()?;

Ok(())
}
}

// Build GRPC client
match build_gprc_client() {
Ok(_) => (),
Err(e) => panic!("Failed to build GRPC client: {e}"),
fn main() -> Result<()> {
#[cfg(feature = "ssr")]
{
build_ssr::build_ssr()?;
}

build_common::build_common()?;

Ok(())
}
18 changes: 12 additions & 6 deletions did/canister_ids.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
{
"configuration": {
"ic": "efsfj-sqaaa-aaaap-qatwa-cai"
"ic": "efsfj-sqaaa-aaaap-qatwa-cai",
"local": "bnz7o-iuaaa-aaaaa-qaaaa-cai"
},
"data_backup": {
"ic": "jwktp-qyaaa-aaaag-abcfa-cai"
"ic": "jwktp-qyaaa-aaaag-abcfa-cai",
"local": "bkyz2-fmaaa-aaaaa-qaaaq-cai"
},
"individual_user_template": {
"ic": "dc47w-kaaaa-aaaak-qav3q-cai"
"ic": "dc47w-kaaaa-aaaak-qav3q-cai",
"local": "bd3sg-teaaa-aaaaa-qaaba-cai"
},
"platform_orchestrator": {
"ic": "74zq4-iqaaa-aaaam-ab53a-cai"
"ic": "74zq4-iqaaa-aaaam-ab53a-cai",
"local": "bw4dl-smaaa-aaaaa-qaacq-cai"
},
"post_cache": {
"ic": "zyajx-3yaaa-aaaag-acoga-cai"
"ic": "zyajx-3yaaa-aaaag-acoga-cai",
"local": "be2us-64aaa-aaaaa-qaabq-cai"
},
"user_index": {
"ic": "rimrc-piaaa-aaaao-aaljq-cai"
"ic": "rimrc-piaaa-aaaao-aaljq-cai",
"local": "br5f7-7uaaa-aaaaa-qaaca-cai"
}
}
4 changes: 4 additions & 0 deletions local-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

cargo leptos build --bin-features local-bin --lib-features local-lib || exit 1
LEPTOS_SITE_ROOT="./target/site" LEPTOS_HASH_FILES=true ./target/debug/hot-or-not-web-leptos-ssr
1 change: 1 addition & 0 deletions src/canister/generated.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include!(concat!(env!("OUT_DIR"), "/did/mod.rs"));
include!(concat!(env!("OUT_DIR"), "/canister_ids.rs"));
Loading
Loading