Skip to content

Commit

Permalink
chore(io): meta abstraction (#6)
Browse files Browse the repository at this point in the history
* chore(io): meta abstraction

* chore(dapp): make everything compiles

* chore(dapp): program test for uploading source

* feat(io): tests for state reading

* feat(prog): introduce method search

* feat(io): abstract domain interface

* chore(tests): use the debug version of meta.wasm

* feat(state): list labels

* chore(io): remove unused HandleOutput

* chore(dep): use gear toolkit at v1.1.1

* chore(clippy): make clippy happy

* feat(io): add plans for identity

* chore(io): make clippy happy
  • Loading branch information
clearloop authored Feb 21, 2024
1 parent 5467be0 commit 84d6a89
Show file tree
Hide file tree
Showing 9 changed files with 942 additions and 804 deletions.
1,313 changes: 733 additions & 580 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ tokio.workspace = true
members = ["state", "xtask"]

[workspace.dependencies]
gstd = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" }
gmeta = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" }
gear-wasm-builder = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" }
gtest = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" }
gclient = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" }
gstd = "1.1.1"
gmeta = "1.1.1"
gear-wasm-builder = "1.1.1"
gtest = "1.1.1"
gclient = "1.1.1"
template-io.path = "io"
tokio = "1"
xshell = "0.2"
Expand Down
11 changes: 11 additions & 0 deletions io/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::Source;
use gstd::{Decode, Encode, String, TypeInfo};

/// The input for the `handle` entry point.
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub struct HandleInput {
pub domain: String,
pub src: Source,
}
58 changes: 10 additions & 48 deletions io/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
#![no_std]

use gmeta::{InOut, Metadata, Out};
use gstd::{prelude::*, ActorId};
use gmeta::{In, Metadata, Out};

mod handler;
mod source;

pub use handler::HandleInput;
pub use source::{Content, Footer, Header, Profile, Source, State};

/// The contract metadata. Used by frontend apps & for describing the types of messages that can be
/// sent in contract's entry points. See also [`Metadata`].
Expand All @@ -13,9 +18,7 @@ impl Metadata for ContractMetadata {
/// I/O types for the `init()` entry point.
type Init = ();
/// I/O types for the `handle()` entry point.
///
/// Here the [`PingPong`] type is used for both incoming and outgoing messages.
type Handle = InOut<PingPong, PingPong>;
type Handle = In<HandleInput>;
/// Types for miscellaneous scenarios.
type Others = ();
/// The input type for the `handle_reply()` entry point.
Expand All @@ -24,48 +27,7 @@ impl Metadata for ContractMetadata {
type Signal = ();
/// I/O types for the `state()` entry point.
///
/// You can also specify just an output ([`Out`]) or input ([`In`](gmeta::In)) type, if both
/// ([`InOut`]) are expected like here.
/// You can also specify just an output ([`Out`]) or input ([`In`]) type, if both
/// ([`In`]) are expected like here.
type State = Out<State>;
}

pub type State = Vec<(ActorId, u128)>;

/// Replies with [`Pong`](PingPong::Pong) if received [`Ping`](PingPong::Ping).
#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub enum PingPong {
Ping,
Pong,
}

/// Queries the contract state.
///
/// Used in the `state` crate.
#[derive(Encode, Decode, TypeInfo)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub enum StateQuery {
/// Gets the list of actors who have [`ping`](PingPong::Ping)ed the contract.
///
/// Returns [`StateQueryReply::Pingers`].
Pingers,
/// Gets the count of [`ping`](PingPong::Ping)s received from the given [`ActorId`].
///
/// Returns [`StateQueryReply::PingCount`].
PingCount(ActorId),
}

/// The result of successfully processed [`StateQuery`].
///
/// Used in the `state` crate.
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub enum StateQueryReply {
/// Returned from [`StateQuery::Pingers`].
Pingers(Vec<ActorId>),
/// Returned from [`StateQuery::PingCount`].
PingCount(u128),
}
82 changes: 82 additions & 0 deletions io/src/source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use gstd::{collections::BTreeMap, ActorId, Decode, Encode, String, TypeInfo, Vec};

/// Profile of the content
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Default, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub struct Profile {
pub title: String,
pub links: BTreeMap<String, String>,
}

/// The header of the page.
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Default, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub struct Header {
/// Title of the page.
pub title: String,
/// Logo of the page.
pub logo: Option<String>,
}

/// Source of the content
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub enum Content {
// TODO: use demo-identity instead.
Profile(Profile),
Markdown(String),
}

impl Content {
/// If the content contains the token.
pub fn contains(&self, token: &str) -> bool {
match self {
Content::Profile(profile) => profile.title.contains(token),
Content::Markdown(markdown) => markdown.contains(token),
}
}
}

/// Footer abstraction.
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Default, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub struct Footer {
/// Centered information in footer.
pub info: String,
}

/// Source of the page.
#[derive(Encode, Decode, TypeInfo, PartialEq, Eq, Debug, Clone)]
#[codec(crate = gstd::codec)]
#[scale_info(crate = gstd::scale_info)]
pub struct Source {
pub labels: Vec<String>,
pub header: Header,
pub content: Content,
pub footer: Footer,
}

/// Domain of pages.
///
/// TODO:
///
/// 1) access control for the domain.
/// 2) enable this interface in the next version.
#[allow(unused)]
pub struct Domain {
/// add a new field for the owner struct.
///
/// - owner: ActorId
/// - identity: programId.
pub owner: ActorId,
pub paths: BTreeMap<String, Source>,
/// people who has edit access to the domain source.
pub editors: Vec<ActorId>,
}

/// Program state.
pub type State = BTreeMap<String, Source>;
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
edition = "2021"
use_field_init_shorthand = true
newline_style = "Unix"
force_explicit_abi = false
28 changes: 13 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#![no_std]

use gstd::{collections::HashMap, msg, prelude::*, ActorId};
use gstd::{collections::BTreeMap, msg, prelude::*};
use template_io::*;

static mut STATE: Option<HashMap<ActorId, u128>> = None;
static mut STATE: Option<BTreeMap<String, Source>> = None;

// The `init()` entry point.
#[no_mangle]
Expand All @@ -14,23 +14,21 @@ extern fn init() {
// The `handle()` entry point.
#[no_mangle]
extern fn handle() {
let payload = msg::load().expect("Failed to load payload");

if let PingPong::Ping = payload {
let pingers = unsafe { STATE.as_mut().expect("State isn't initialized") };

pingers
.entry(msg::source())
.and_modify(|ping_count| *ping_count = ping_count.saturating_add(1))
.or_insert(1);

msg::reply(PingPong::Pong, 0).expect("Failed to reply from `handle()`");
}
let payload = msg::load::<HandleInput>().expect("Invalid payload");
let state = unsafe { STATE.as_mut().expect("State isn't initialized") };

// TODO:
//
// 1) format checks.
// 2) use domain instead of simple data source.
// 3) sub paths for this domain.
// 4) integration with identity interface.
state.insert(payload.domain, payload.src);
}

// The `state()` entry point.
#[no_mangle]
extern fn state() {
let state = unsafe { STATE.take().expect("State isn't initialized") };
msg::reply(State::from_iter(state), 0).expect("Failed to reply from `state()`");
msg::reply(state, 0).expect("Failed to reply from `state()`");
}
57 changes: 42 additions & 15 deletions state/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,55 @@
#![no_std]

use gstd::{prelude::*, ActorId};
use gstd::{collections::BTreeMap, prelude::*, Vec};
use template_io::*;

#[gmeta::metawasm]
pub mod metafns {
pub type State = template_io::State;

pub fn query(state: State, query: StateQuery) -> StateQueryReply {
match query {
StateQuery::Pingers => StateQueryReply::Pingers(pingers(state)),
StateQuery::PingCount(actor) => StateQueryReply::PingCount(ping_count(state, actor)),
}
}
pub type State = template_io::State;

pub fn pingers(state: State) -> Vec<ActorId> {
state.iter().map(|(pinger, _)| *pinger).collect()
}
/// Returns all domains (pages) that matches the search input.
///
/// For the source of the competition:
/// - domain name
/// - labels
/// - header title
/// - content
pub fn search(state: State, input: String) -> BTreeMap<String, Source> {
let tokens: Vec<&str> = input.split_whitespace().collect();

pub fn ping_count(state: State, actor: ActorId) -> u128 {
state
.iter()
.find_map(|(some_actor, count)| (some_actor == &actor).then_some(count))
.copied()
.unwrap_or_default()
.filter_map(|(domain, source)| {
if tokens.iter().any(|t| {
domain.contains(t)
|| source.labels.iter().any(|l| l.contains(t))
|| source.header.title.contains(t)
|| source.content.contains(t)
}) {
return Some((domain.clone(), source.clone()));
}
None
})
.collect()
}

/// List all labels
pub fn labels(state: State) -> Vec<String> {
let mut labels: Vec<String> = state
.values()
.map(|s| s.labels.clone())
.collect::<Vec<Vec<String>>>()
.into_iter()
.flatten()
.collect();
labels.sort();
labels.dedup();
labels
}

// TODO:
//
// 1) list sub paths of a domain.
// 2)
}
Loading

0 comments on commit 84d6a89

Please sign in to comment.