From 072e50ce835d0a767c2a7b5652180c60edf02e62 Mon Sep 17 00:00:00 2001 From: Ho Kim Date: Fri, 12 Jul 2024 20:15:30 +0000 Subject: [PATCH] feat: add embedded gateway support for debugging --- .github/workflows/gh_pages.yml | 2 +- Cargo.toml | 5 + Justfile | 2 + README.md | 6 + crates/cassette-core/Cargo.toml | 3 + crates/cassette-core/src/document.rs | 30 ++++ crates/cassette-core/src/lib.rs | 1 + crates/cassette-core/src/net/gateway.rs | 65 +++++--- crates/cassette-core/src/task.rs | 1 + crates/cassette-gateway/Cargo.toml | 1 + crates/cassette-gateway/src/db.rs | 163 +------------------- crates/cassette-loader-core/Cargo.toml | 26 ++++ crates/cassette-loader-core/src/lib.rs | 189 ++++++++++++++++++++++++ crates/cassette-loader-file/Cargo.toml | 37 +++++ crates/cassette-loader-file/build.rs | 38 +++++ crates/cassette-loader-file/src/lib.rs | 59 ++++++++ crates/cassette/Cargo.toml | 4 + crates/cassette/src/hooks/gateway.rs | 48 ++++-- {samples => examples}/hello_world.yaml | 0 index.html | 1 - 20 files changed, 486 insertions(+), 195 deletions(-) create mode 100644 crates/cassette-core/src/document.rs create mode 100644 crates/cassette-loader-core/Cargo.toml create mode 100644 crates/cassette-loader-core/src/lib.rs create mode 100644 crates/cassette-loader-file/Cargo.toml create mode 100644 crates/cassette-loader-file/build.rs create mode 100644 crates/cassette-loader-file/src/lib.rs rename {samples => examples}/hello_world.yaml (100%) diff --git a/.github/workflows/gh_pages.yml b/.github/workflows/gh_pages.yml index af13b02..7c28565 100644 --- a/.github/workflows/gh_pages.yml +++ b/.github/workflows/gh_pages.yml @@ -55,7 +55,7 @@ jobs: run: sed -i '/\[\[hooks\]\]/,$d' 'Trunk.toml' - name: Build - run: trunk build --release + run: trunk build --release --features examples - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/Cargo.toml b/Cargo.toml index e6a8a49..c3cc7a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ members = [ "crates/cassette", "crates/cassette-core", "crates/cassette-gateway", + "crates/cassette-loader-core", + "crates/cassette-loader-file", "crates/cassette-operator", "crates/cassette-plugin-kubernetes", ] @@ -46,9 +48,11 @@ gloo-net = { version = "0.5", default-features = false, features = [ "json", ] } gloo-utils = { version = "0.2", default-features = false } +include_dir = { version = "0.7" } inflector = { package = "Inflector", version = "0.11" } k8s-openapi = { version = "0.22", features = ["latest", "schemars"] } kube = { version = "0.91", default-features = false } +once_cell = { version = "1.19" } patternfly-yew = { version = "0.6", features = [ "icons-fab", "tree", @@ -58,6 +62,7 @@ regex = { version = "1.10", default-features = false } schemars = { version = "0.8", default-features = false, features = ["uuid1"] } serde = { version = "1.0", default-features = false } serde_json = { version = "1.0", default-features = false } +serde_yml = { version = "0.0", default-features = false } tokio = { version = "1.38", default-features = false } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } diff --git a/Justfile b/Justfile index 499c70a..15a4e6a 100644 --- a/Justfile +++ b/Justfile @@ -55,6 +55,8 @@ build *ARGS: ( _trunk "build" ARGS ) run *ARGS: ( _trunk "serve" ARGS ) +run-examples *ARGS: ( _trunk "serve" "--features" "examples" ARGS ) + run-gateway *ARGS: cargo run --package 'cassette-gateway' --release diff --git a/README.md b/README.md index cbc2a7e..bb57330 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ This is a Cloud-native Template-based dynamic declarative web UI framework built You can get started by experiencing the various features here: https://api.ulagbulag.io +You can also deploy your own example app using one-line simple command: + +```bash +just run-examples # trunk serve --features examples +``` + ## Tutorial TBD diff --git a/crates/cassette-core/Cargo.toml b/crates/cassette-core/Cargo.toml index d68a921..424a5bb 100644 --- a/crates/cassette-core/Cargo.toml +++ b/crates/cassette-core/Cargo.toml @@ -24,6 +24,9 @@ default = [] actix-web = ["dep:actix-web"] ui = ["dep:gloo-net", "dep:yew"] +# for demo ONLY +examples = [] + [dependencies] actix-web = { workspace = true, optional = true } garde = { workspace = true } diff --git a/crates/cassette-core/src/document.rs b/crates/cassette-core/src/document.rs new file mode 100644 index 0000000..078f744 --- /dev/null +++ b/crates/cassette-core/src/document.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize, Serializer}; + +use crate::{cassette::CassetteCrd, component::CassetteComponentCrd}; + +#[derive(Clone, Debug, Deserialize)] +#[serde(tag = "kind")] +pub enum Document { + Cassette(CassetteCrd), + CassetteComponent(CassetteComponentCrd), +} + +impl Serialize for Document { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + #[serde(untagged)] + enum DocumentSer<'a> { + Cassette(&'a CassetteCrd), + CassetteComponent(&'a CassetteComponentCrd), + } + + let document = match self { + Document::Cassette(cr) => DocumentSer::Cassette(cr), + Document::CassetteComponent(cr) => DocumentSer::CassetteComponent(cr), + }; + document.serialize(serializer) + } +} diff --git a/crates/cassette-core/src/lib.rs b/crates/cassette-core/src/lib.rs index 99df4d8..415321a 100644 --- a/crates/cassette-core/src/lib.rs +++ b/crates/cassette-core/src/lib.rs @@ -1,5 +1,6 @@ pub mod cassette; pub mod component; +pub mod document; #[cfg(feature = "ui")] pub mod net; pub mod result; diff --git a/crates/cassette-core/src/net/gateway.rs b/crates/cassette-core/src/net/gateway.rs index d0a26a7..f36fffd 100644 --- a/crates/cassette-core/src/net/gateway.rs +++ b/crates/cassette-core/src/net/gateway.rs @@ -59,30 +59,59 @@ pub fn use_query(key: &str) -> Option { #[hook] pub fn use_gateway() -> String { - use_query("gateway").unwrap_or_else(|| { - #[cfg(debug_assertions)] - { - "http://localhost:8080".into() - } - - #[cfg(not(debug_assertions))] - { - "/v1/cassette".into() - } - }) + #[cfg(feature = "examples")] + { + "(embedded)".into() + } + + #[cfg(not(feature = "examples"))] + { + use_query("gateway").unwrap_or_else(|| { + #[cfg(debug_assertions)] + { + "http://localhost:8080".into() + } + + #[cfg(not(debug_assertions))] + { + "/v1/cassette".into() + } + }) + } } #[hook] pub fn use_gateway_status() -> String { - let state = use_fetch_unchecked::(move || FetchRequest { - method: Method::GET, - name: "gateway health", - url: "/_health", - }); - state.to_string() + #[cfg(feature = "examples")] + { + "healthy".into() + } + + #[cfg(not(feature = "examples"))] + { + let state = use_fetch_unchecked::(move || FetchRequest { + method: Method::GET, + name: "gateway health", + url: "/_health", + }); + state.to_string() + } } #[hook] pub fn use_namespace() -> String { - use_query("namespace").unwrap_or_else(|| "default".into()) + #[cfg(feature = "examples")] + { + DEFAULT_NAMESPACE.into() + } + + #[cfg(not(feature = "examples"))] + { + use_query("namespace").unwrap_or_else(|| DEFAULT_NAMESPACE.into()) + } } + +#[cfg(feature = "examples")] +pub const DEFAULT_NAMESPACE: &str = "examples"; +#[cfg(not(feature = "examples"))] +pub const DEFAULT_NAMESPACE: &str = "default"; diff --git a/crates/cassette-core/src/task.rs b/crates/cassette-core/src/task.rs index 868466f..b63f2d0 100644 --- a/crates/cassette-core/src/task.rs +++ b/crates/cassette-core/src/task.rs @@ -71,6 +71,7 @@ pub enum TaskState { #[serde(transparent)] pub struct TaskSpec(Map); +#[cfg(feature = "ui")] impl TaskSpec { pub fn get_string(&self, key: &str) -> TaskResult { self.0 diff --git a/crates/cassette-gateway/Cargo.toml b/crates/cassette-gateway/Cargo.toml index 8db8d2e..836037c 100644 --- a/crates/cassette-gateway/Cargo.toml +++ b/crates/cassette-gateway/Cargo.toml @@ -33,6 +33,7 @@ rustls-tls = ["actix-web/rustls", "kube/rustls-tls"] [dependencies] cassette-core = { path = "../cassette-core", features = ["actix-web"] } +cassette-loader-core = { path = "../cassette-loader-core" } cassette-plugin-kubernetes = { path = "../cassette-plugin-kubernetes", optional = true, features = [ "api", ] } diff --git a/crates/cassette-gateway/src/db.rs b/crates/cassette-gateway/src/db.rs index ea35a3a..6e86244 100644 --- a/crates/cassette-gateway/src/db.rs +++ b/crates/cassette-gateway/src/db.rs @@ -1,13 +1,10 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - sync::Arc, -}; +use std::sync::Arc; use cassette_core::{ cassette::{Cassette, CassetteCrd, CassetteRef}, component::CassetteComponentCrd, }; -use kube::ResourceExt; +use cassette_loader_core::CassetteDB as CassetteDBInner; use tokio::sync::RwLock; use uuid::Uuid; @@ -43,159 +40,3 @@ impl CassetteDB { self.0.write().await.remove_component(cr) } } - -#[derive(Default)] -struct CassetteDBInner { - cassettes: BTreeMap>>, - components: BTreeMap, - components_scopes: BTreeMap, -} - -impl CassetteDBInner { - fn get(&self, namespace: &str, id: Uuid) -> Option { - let Cassette { - id, - component: component_name, - name, - group, - description, - priority, - } = self - .cassettes - .get(namespace) - .and_then(|cassettes| cassettes.iter().find(|cassette| cassette.id == id).cloned())?; - - let scope = Scope { - namespace: namespace.into(), - name: component_name, - }; - let component_id = self.components_scopes.get(&scope)?; - let component = self - .components - .get(component_id) - .map(|cr| cr.spec.clone())?; - - Some(Cassette { - id, - component, - name, - group, - description, - priority, - }) - } - - fn list(&self, namespace: &str) -> Vec { - self.cassettes - .get(namespace) - .map(|cassettes| { - cassettes - .iter() - .cloned() - .filter_map(|cassette| self.find_component(namespace, cassette)) - .collect() - }) - .unwrap_or_default() - } -} - -impl CassetteDBInner { - fn insert(&mut self, cr: CassetteCrd) { - let id = match cr.uid().and_then(|uid| uid.parse().ok()) { - Some(uid) => uid, - None => return, - }; - let namespace = cr.namespace().unwrap_or_else(|| "default".into()); - let name = cr.name_any(); - - let cassette = Cassette { - id, - component: cr.spec.component, - name, - group: cr.spec.group, - description: cr.spec.description, - priority: cr.spec.priority, - }; - self.cassettes - .entry(namespace) - .or_default() - .insert(cassette); - } - - fn remove(&mut self, cr: CassetteCrd) { - let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) { - Some(uid) => uid, - None => return, - }; - let namespace = cr.namespace().unwrap_or_else(|| "default".into()); - - if let Some(cassettes) = self.cassettes.get_mut(&namespace) { - cassettes.remove(&id); - if cassettes.is_empty() { - self.cassettes.remove(&namespace); - } - } - } -} - -impl CassetteDBInner { - fn find_component(&self, namespace: &str, cassette: Cassette) -> Option { - let Cassette { - id, - component, - name, - group, - description, - priority, - } = cassette; - - let scope = Scope { - namespace: namespace.into(), - name: component, - }; - let component = self.components_scopes.get(&scope).copied()?; - - Some(Cassette { - id, - component, - name, - group, - description, - priority, - }) - } - - fn insert_component(&mut self, cr: CassetteComponentCrd) { - let id = match cr.uid().and_then(|uid| uid.parse().ok()) { - Some(uid) => uid, - None => return, - }; - let namespace = cr.namespace().unwrap_or_else(|| "default".into()); - let name = cr.name_any(); - - let scope = Scope { namespace, name }; - - self.components.insert(id, cr); - self.components_scopes.insert(scope, id); - } - - fn remove_component(&mut self, cr: CassetteComponentCrd) { - let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) { - Some(uid) => uid, - None => return, - }; - let namespace = cr.namespace().unwrap_or_else(|| "default".into()); - let name = cr.name_any(); - - let scope = Scope { namespace, name }; - - self.components.remove(&id); - self.components_scopes.remove(&scope); - } -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Scope { - namespace: String, - name: String, -} diff --git a/crates/cassette-loader-core/Cargo.toml b/crates/cassette-loader-core/Cargo.toml new file mode 100644 index 0000000..f786ce3 --- /dev/null +++ b/crates/cassette-loader-core/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "cassette-loader-core" + +authors = { workspace = true } +description = { workspace = true } +documentation = "https://docs.rs/cassette-loader-file" +edition = { workspace = true } +include = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +readme = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +[lints] +workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cassette-core = { path = "../cassette-core" } + +kube = { workspace = true } +uuid = { workspace = true } diff --git a/crates/cassette-loader-core/src/lib.rs b/crates/cassette-loader-core/src/lib.rs new file mode 100644 index 0000000..782ead1 --- /dev/null +++ b/crates/cassette-loader-core/src/lib.rs @@ -0,0 +1,189 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cassette_core::{ + cassette::{Cassette, CassetteCrd, CassetteRef}, + component::CassetteComponentCrd, + net::gateway::DEFAULT_NAMESPACE, +}; +use kube::ResourceExt; +use uuid::Uuid; + +#[derive(Debug)] +pub struct CassetteDB { + cassettes: BTreeMap>>, + components: BTreeMap, + components_scopes: BTreeMap, + default_namespace: String, +} + +impl Default for CassetteDB { + fn default() -> Self { + Self::new(DEFAULT_NAMESPACE.into()) + } +} + +impl CassetteDB { + pub fn new(default_namespace: String) -> Self { + Self { + cassettes: BTreeMap::default(), + components: BTreeMap::default(), + components_scopes: BTreeMap::default(), + default_namespace, + } + } + + pub fn get(&self, namespace: &str, id: Uuid) -> Option { + let Cassette { + id, + component: component_name, + name, + group, + description, + priority, + } = self + .cassettes + .get(namespace) + .and_then(|cassettes| cassettes.iter().find(|cassette| cassette.id == id).cloned())?; + + let scope = Scope { + namespace: namespace.into(), + name: component_name, + }; + let component_id = self.components_scopes.get(&scope)?; + let component = self + .components + .get(component_id) + .map(|cr| cr.spec.clone())?; + + Some(Cassette { + id, + component, + name, + group, + description, + priority, + }) + } + + pub fn list(&self, namespace: &str) -> Vec { + self.cassettes + .get(namespace) + .map(|cassettes| { + cassettes + .iter() + .cloned() + .filter_map(|cassette| self.find_component(namespace, cassette)) + .collect() + }) + .unwrap_or_default() + } +} + +impl CassetteDB { + pub fn insert(&mut self, cr: CassetteCrd) { + let id = match cr.uid().and_then(|uid| uid.parse().ok()) { + Some(uid) => uid, + None => return, + }; + let namespace = cr + .namespace() + .unwrap_or_else(|| self.default_namespace.clone()); + let name = cr.name_any(); + + let cassette = Cassette { + id, + component: cr.spec.component, + name, + group: cr.spec.group, + description: cr.spec.description, + priority: cr.spec.priority, + }; + self.cassettes + .entry(namespace) + .or_default() + .insert(cassette); + } + + pub fn remove(&mut self, cr: CassetteCrd) { + let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) { + Some(uid) => uid, + None => return, + }; + let namespace = cr + .namespace() + .unwrap_or_else(|| self.default_namespace.clone()); + + if let Some(cassettes) = self.cassettes.get_mut(&namespace) { + cassettes.remove(&id); + if cassettes.is_empty() { + self.cassettes.remove(&namespace); + } + } + } +} + +impl CassetteDB { + fn find_component(&self, namespace: &str, cassette: Cassette) -> Option { + let Cassette { + id, + component, + name, + group, + description, + priority, + } = cassette; + + let scope = Scope { + namespace: namespace.into(), + name: component, + }; + let component = self.components_scopes.get(&scope).copied()?; + + Some(Cassette { + id, + component, + name, + group, + description, + priority, + }) + } + + pub fn insert_component(&mut self, cr: CassetteComponentCrd) { + let id = match cr.uid().and_then(|uid| uid.parse().ok()) { + Some(uid) => uid, + None => return, + }; + let namespace = cr + .namespace() + .unwrap_or_else(|| self.default_namespace.clone()); + let name = cr.name_any(); + + let scope = Scope { namespace, name }; + + self.components.insert(id, cr); + self.components_scopes.insert(scope, id); + } + + pub fn remove_component(&mut self, cr: CassetteComponentCrd) { + let id: Uuid = match cr.uid().and_then(|uid| uid.parse().ok()) { + Some(uid) => uid, + None => return, + }; + let namespace = cr + .namespace() + .unwrap_or_else(|| self.default_namespace.clone()); + let name = cr.name_any(); + + let scope = Scope { namespace, name }; + + self.components.remove(&id); + self.components_scopes.remove(&scope); + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct Scope { + namespace: String, + name: String, +} diff --git a/crates/cassette-loader-file/Cargo.toml b/crates/cassette-loader-file/Cargo.toml new file mode 100644 index 0000000..dc3dfbd --- /dev/null +++ b/crates/cassette-loader-file/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "cassette-loader-file" + +authors = { workspace = true } +description = { workspace = true } +documentation = "https://docs.rs/cassette-loader-file" +edition = { workspace = true } +include = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +readme = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +[lints] +workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +cassette-core = { path = "../cassette-core" } + +include_dir = { workspace = true } +serde_json = { workspace = true } +serde_yml = { workspace = true } + +[dependencies] +cassette-core = { path = "../cassette-core" } +cassette-loader-core = { path = "../cassette-loader-core" } + +kube = { workspace = true } +once_cell = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +uuid = { workspace = true } diff --git a/crates/cassette-loader-file/build.rs b/crates/cassette-loader-file/build.rs new file mode 100644 index 0000000..de64545 --- /dev/null +++ b/crates/cassette-loader-file/build.rs @@ -0,0 +1,38 @@ +use std::{env, fs::File, io::Write}; + +use cassette_core::document::Document; +use include_dir::{include_dir, Dir}; + +const OUT_SRC: &str = "examples.yaml"; + +fn main() { + static EXAMPLES: Dir<'_> = include_dir!("examples"); + + let contents = EXAMPLES + .files() + .filter(|file| file.path().to_string_lossy().ends_with(".yaml")) + .filter_map(|file| file.contents_utf8()) + .flat_map(|docs| { + docs.split("---\n") + .map(|doc| doc.trim()) + .filter(|doc| !doc.is_empty()) + }); + + // YAML -> Documents + let documents: Vec = contents + .map(|doc| ::serde_yml::from_str(doc).expect("failed to parse example resource file")) + .collect(); + + // Documents -> JSON + let documents_str = + ::serde_json::to_string(&documents).expect("failed to serialize example resources to JSON"); + + let out_dir = env::var("OUT_DIR").expect("cannot parse `OUT_DIR` environment variable"); + let mut dst_file = File::create(format!("{out_dir}/{OUT_SRC}")) + .expect("failed to create example resource dst file"); + + dst_file + .write_all(documents_str.as_bytes()) + .expect("failed to write example resources"); + dst_file.flush().expect("failed to flush example resources"); +} diff --git a/crates/cassette-loader-file/src/lib.rs b/crates/cassette-loader-file/src/lib.rs new file mode 100644 index 0000000..2670fb8 --- /dev/null +++ b/crates/cassette-loader-file/src/lib.rs @@ -0,0 +1,59 @@ +use cassette_core::document::Document; +use cassette_loader_core::CassetteDB; +use kube::ResourceExt; +use once_cell::sync::OnceCell; +use tracing::error; +use uuid::Uuid; + +pub fn db() -> &'static CassetteDB { + static DB: OnceCell = OnceCell::new(); + + DB.get_or_init(|| { + // create a memory db + let mut db = CassetteDB::default(); + + // parse the documents + let documents: Vec = match ::serde_json::from_str(DOCUMENTS) { + Ok(documents) => documents, + Err(error) => { + error!("Failed to parse embedded example documents: {error}"); + return db; + } + }; + + // insert all documents + documents + .into_iter() + .enumerate() + .for_each(|(id, document)| match document { + Document::Cassette(cr) => db.insert(cr.generate_uid(id)), + Document::CassetteComponent(cr) => db.insert_component(cr.generate_uid(id)), + }); + db + }); + + DB.get().unwrap() +} + +trait GenerateUid { + fn generate_uid(self, id: usize) -> Self; +} + +impl GenerateUid for T +where + Self: ResourceExt, +{ + fn generate_uid(mut self, id: usize) -> Self { + let uid = &mut self.meta_mut().uid; + if uid.is_none() { + *uid = id + .try_into() + .ok() + .map(Uuid::from_u128) + .map(|id| id.to_string()); + } + self + } +} + +const DOCUMENTS: &str = include_str!(concat!(env!("OUT_DIR"), "/examples.yaml")); diff --git a/crates/cassette/Cargo.toml b/crates/cassette/Cargo.toml index ef0bf09..e676818 100644 --- a/crates/cassette/Cargo.toml +++ b/crates/cassette/Cargo.toml @@ -24,6 +24,9 @@ default = ["full"] full = ["kubernetes"] experimental = [] +# for demo ONLY +examples = ["cassette-core/examples", "cassette-loader-file"] + # plugins kubernetes = ["dep:cassette-plugin-kubernetes"] @@ -32,6 +35,7 @@ built = { workspace = true } [dependencies] cassette-core = { path = "../cassette-core", features = ["ui"] } +cassette-loader-file = { path = "../cassette-loader-file", optional = true } cassette-plugin-kubernetes = { path = "../cassette-plugin-kubernetes", optional = true, features = [ "ui", ] } diff --git a/crates/cassette/src/hooks/gateway.rs b/crates/cassette/src/hooks/gateway.rs index 40f2466..1cfd829 100644 --- a/crates/cassette/src/hooks/gateway.rs +++ b/crates/cassette/src/hooks/gateway.rs @@ -1,9 +1,11 @@ +#[cfg(not(feature = "examples"))] +use cassette_core::net::{ + fetch::FetchRequest, + gateway::{use_fetch, Method}, +}; use cassette_core::{ cassette::{Cassette, CassetteRef}, - net::{ - fetch::{FetchRequest, FetchState}, - gateway::{use_fetch, use_namespace, Method}, - }, + net::{fetch::FetchState, gateway::use_namespace}, }; use uuid::Uuid; use yew::prelude::*; @@ -11,19 +13,37 @@ use yew::prelude::*; #[hook] pub fn use_cassette(id: Uuid) -> UseStateHandle>> { let namespace = use_namespace(); - use_fetch(move || FetchRequest { - method: Method::GET, - name: "get", - url: format!("/c/{namespace}/{id}"), - }) + + #[cfg(feature = "examples")] + { + use_state(|| FetchState::Completed(::cassette_loader_file::db().get(&namespace, id))) + } + + #[cfg(not(feature = "examples"))] + { + use_fetch(move || FetchRequest { + method: Method::GET, + name: "get", + url: format!("/c/{namespace}/{id}"), + }) + } } #[hook] pub fn use_cassette_list() -> UseStateHandle>> { let namespace = use_namespace(); - use_fetch(move || FetchRequest { - method: Method::GET, - name: "list", - url: format!("/c/{namespace}/"), - }) + + #[cfg(feature = "examples")] + { + use_state(|| FetchState::Completed(::cassette_loader_file::db().list(&namespace))) + } + + #[cfg(not(feature = "examples"))] + { + use_fetch(move || FetchRequest { + method: Method::GET, + name: "list", + url: format!("/c/{namespace}/"), + }) + } } diff --git a/samples/hello_world.yaml b/examples/hello_world.yaml similarity index 100% rename from samples/hello_world.yaml rename to examples/hello_world.yaml diff --git a/index.html b/index.html index 950025d..b474f9a 100644 --- a/index.html +++ b/index.html @@ -23,7 +23,6 @@