Actix and Inventory crate - rust

I'm trying to make it possible to register an actix route automatically. To do so I found the crate Inventory which seems to answer my need.
I have the following code:
#[get("/hello2/{name}")]
async fn greet2(req: HttpRequest) -> String {
let name = req.match_info().get("name").unwrap_or("World");
let a = format!("Hello2 {}!", &name);
a
}
pub struct TestStruct {
test: fn(HttpRequest) -> dyn Future<Output = String>
}
inventory::collect!(TestStruct);
inventory::submit! {
let mut a = TestStruct {
test: <greet2 as HttpServiceFactory>::register
};
a.test.push(greet2::register::greet2);
a
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let mut app = App::new();
for route in inventory::iter::<AutomaticHttpService> {
app = app.service(route);
}
app
})
.bind(("127.0.0.1", 8080))?
.workers(2)
.run()
.await
}
My code does not compile as I cannot access the requested function pointer and I cannot use the HttpServiceFactory trait as it is not declared in the same file. I also tried to use the std::any::Any type and then cast to HttpServiceFactory (maybe using unsafe) but without success because its size was not known at compile time.
Would you have any clues to unblock my situation?

Related

Need help creating a plugin system for the game engine

Github repository
A little about how the plugin system should roughly work.
The Plugin trait implements the build() method, which is called when the plugin is loaded. The build() method takes App struct, where App is the application structure.
Now my code looks cumbersome. So the question is, are there any other options?
pub trait Component {
fn start(&mut self);
fn update(&mut self);
}
pub struct App {
runners: Vec<Box<dyn Fn(&mut App)>>,
components: Vec<Box<dyn Component>>,
}
&mut App
Initially, the build() method took &mut App, but this did not allow me to do the following:
impl Plugin for WinitWSIPlugin {
fn build(&self, app: &mut App) {
app.set_runner(move || {
// Error here
app.open_window(...);
});
}
}
As I understood the compiler did not allow me to do this, because I passed a reference to the App structure, which may soon be irrelevant.
Rc<RefCell>
In the following implementation, I passed Rc<RefCell<App>>, but calling borrow_mut() to call open_window(...) had a panic, even though I had not manually called borrow_mut() before.
impl Plugin for WinitWSIPlugin {
fn build(&self, app: Rc<RefCell<App>>) {
app.clone().borrow().set_runner(move || {
let AppSystem::Windowed { info } = app.borrow().system();
let mut winit_windows = WinitWindows::default();
let event_loop = winit::event_loop::EventLoop::new();
/*===== Panic this =====*/
app.borrow_mut().open_window(winit_windows.create_window(
&event_loop,
app.borrow().id(),
&info,
));
});
}
}
The last revision I stopped at.
Using Mutex in those fields of the App structure that will be used in the plugins. That way, I won't have to call borrow_mut() even if I need to change the value. It will be enough to call borrow()
impl Plugin for WinitWSIPlugin {
fn build(&self, app: Rc<RefCell<App>>) {
app.clone().borrow().set_runner(move || {
let AppSystem::Windowed { info } = app.borrow().system();
let mut winit_windows = WinitWindows::default();
let event_loop = winit::event_loop::EventLoop::new();
app.borrow().open_window(winit_windows.create_window(
&event_loop,
app.borrow().id(),
&info,
));
for component in app.borrow().components().borrow_mut().iter_mut() {
component.init(app.clone());
}
let app = app.clone();
event_loop.run(move |event, _, control_flow| {
control_flow.set_wait();
for component in app.borrow().components().borrow_mut().iter_mut() {
component.update(app.clone());
}
match event {
winit::event::Event::WindowEvent {
window_id: _,
event,
} => match event {
winit::event::WindowEvent::CloseRequested => control_flow.set_exit(),
_ => (),
},
_ => (),
}
});
});
}
}
You could get around having to share App (which I'm not sure will work anyways) by just passing it into the closure as a parameter:
struct App {
runners: Vec<Box<dyn Fn(&mut App)>>,
}
impl App {
fn set_runner(&mut self, f: impl Fn(&mut App) + 'static) {
self.runners.push(Box::new(f));
}
fn run(&mut self) {
// take the runners out of self to avoid borrow & mutable borrow at the same time
let runners = std::mem::take(&mut self.runners);
for runner in runners.iter() {
// pass self into the runner so it can change app state and create windows etc
runner(self);
}
// put the runners back.
self.runners = runners;
}
fn open_window(&mut self) {}
}
trait Plugin {
fn build(&self, app: &mut App);
}
struct WinitWSIPlugin;
impl Plugin for WinitWSIPlugin {
fn build(&self, app: &mut App) {
// the closure now takes a `&mut App` as parameter
// the argument type is optional and just shown for demonstration here
app.set_runner(move |app: &mut App| {
app.open_window();
});
}
}

How do I use PickleDB with Rocket/Juniper Context?

I'm trying to write a Rocket / Juniper / Rust based GraphQL Server using PickleDB - an in-memory key/value store.
The pickle db is created / loaded at the start and given to rocket to manage:
fn rocket() -> Rocket {
let pickle_path = var_os(String::from("PICKLE_PATH")).unwrap_or(OsString::from("pickle.db"));
let pickle_db_dump_policy = PickleDbDumpPolicy::PeriodicDump(Duration::from_secs(120));
let pickle_serialization_method = SerializationMethod::Bin;
let pickle_db: PickleDb = match Path::new(&pickle_path).exists() {
false => PickleDb::new(pickle_path, pickle_db_dump_policy, pickle_serialization_method),
true => PickleDb::load(pickle_path, pickle_db_dump_policy, pickle_serialization_method).unwrap(),
};
rocket::ignite()
.manage(Schema::new(Query, Mutation))
.manage(pickle_db)
.mount(
"/",
routes![graphiql, get_graphql_handler, post_graphql_handler],
)
}
And I want to retrieve the PickleDb instance from the Rocket State in my Guard:
pub struct Context {
pickle_db: PickleDb,
}
impl juniper::Context for Context {}
impl<'a, 'r> FromRequest<'a, 'r> for Context {
type Error = ();
fn from_request(_request: &'a Request<'r>) -> request::Outcome<Context, ()> {
let pickle_db = _request.guard::<State<PickleDb>>()?.inner();
Outcome::Success(Context { pickle_db })
}
}
This does not work because the State only gives me a reference:
26 | Outcome::Success(Context { pickle_db })
| ^^^^^^^^^ expected struct `pickledb::pickledb::PickleDb`, found `&pickledb::pickledb::PickleDb`
When I change my Context struct to contain a reference I get lifetime issues which I'm not yet familiar with:
15 | pickle_db: &PickleDb,
| ^ expected named lifetime parameter
I tried using 'static which does make rust quite unhappy and I tried to use the request lifetime (?) 'r of the FromRequest, but that does not really work either...
How do I get this to work? As I'm quite new in rust, is this the right way to do things?
I finally have a solution, although the need for unsafe indicates it is sub-optimal :)
#![allow(unsafe_code)]
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::env;
use std::path::Path;
use std::time::Duration;
pub static mut PICKLE_DB: Option<PickleDb> = None;
pub fn cache_init() {
let pickle_path = env::var(String::from("PICKLE_PATH")).unwrap_or(String::from("pickle.db"));
let pickle_db_dump_policy = PickleDbDumpPolicy::PeriodicDump(Duration::from_secs(120));
let pickle_serialization_method = SerializationMethod::Json;
let pickle_db = match Path::new(&pickle_path).exists() {
false => PickleDb::new(
pickle_path,
pickle_db_dump_policy,
pickle_serialization_method,
),
true => PickleDb::load(
pickle_path,
pickle_db_dump_policy,
pickle_serialization_method,
)
.unwrap(),
};
unsafe {
PICKLE_DB = Some(pickle_db);
}
}
pub fn cache_get<V>(key: &str) -> Option<V>
where
V: DeserializeOwned + std::fmt::Debug,
{
unsafe {
let pickle_db = PICKLE_DB
.as_ref()
.expect("cache uninitialized - call cache_init()");
pickle_db.get::<V>(key)
}
}
pub fn cache_set<V>(key: &str, value: &V) -> Result<(), pickledb::error::Error>
where
V: Serialize,
{
unsafe {
let pickle_db = PICKLE_DB
.as_mut()
.expect("cache uninitialized - call cache_init()");
pickle_db.set::<V>(key, value)?;
Ok(())
}
}
This can be simply imported and used as expected, but I think I'll run into issues when the load gets to high...

How do I pass App data to service route handler function in actix-web when using function decorations?

I found in the docs an example of how to create global state, protected by Mutex, shared among processing threads that is made available to all your route handlers. Perfect! However, I prefer to use attributes attached to my functions to wire up my route handlers. I do not know the syntax (if permitted) to use attributed functions and also pass in the global state.
Here is the example from the actix-web docs, from https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html
use std::sync::Mutex;
use actix_web::{web, App};
struct MyData {
counter: usize,
}
/// Use `Data<T>` extractor to access data in handler.
fn index(data: web::Data<Mutex<MyData>>) {
let mut data = data.lock().unwrap();
data.counter += 1;
}
fn main() {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
let app = App::new()
// Store `MyData` in application storage.
.register_data(data.clone())
.service(
web::resource("/index.html").route(
web::get().to(index)));
}
Notice how the route handler named index is being passed the web::Data.
Now here are some snippets of my code.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
pub mod request;
pub mod routes;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
#[get("/")]
fn index() -> impl Responder {
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
mod expression_execute {
#[post("/expression/execute")]
fn route(req: web::Json<ExpressionExecuteRequest>) -> HttpResponse {
// ... lots of code omitted ...
if response.has_error() {
HttpResponse::Ok().json(response)
}
else {
HttpResponse::BadRequest().json(response)
}
}
}
pub fn shy_service(ip : &str, port : &str) {
HttpServer::new(|| {
App::new()
.service(index)
.service(expression_execute::route)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
Notice how I am calling method App::service to wire up my route handlers.
Also notice how my route handler does not receive global state (because I have not yet added it to my app). If I used a similar pattern as the docs using register_data to create global App data, what changes do I make to my method signature, the get and post attributes and anything else so that I can pass that global state to the handler?
Or is it not possible using get and post attributes to gain access to global state?
The two cases you listed really don't have much difference:
//# actix-web = "1.0.8"
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
struct MyData {
counter: usize,
}
#[get("/")]
fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
let mut data = data.lock().unwrap();
data.counter += 1;
println!("Endpoint visited: {}", data.counter);
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
pub fn shy_service(ip : &str, port : &str) {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
HttpServer::new(move || {
App::new()
.register_data(data.clone())
.service(index)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
fn main() {
shy_service("127.0.0.1", "8080");
}
You can verify that it works by simply curl the http endpoint. For multiple extractors, you'll have to use tuple:
#[post("/expression/execute")]
fn route((req, data): (web::Json<ExpressionExecuteRequest>, web::Data<Mutex<MyData>>)) -> HttpResponse {
unimplemented!()
}

How to add special NotReady logic to tokio-io?

I'm trying to make a Stream that would wait until a specific character is in buffer. I know there's read_until() on BufRead but I actually need a custom solution, as this is a stepping stone to implement waiting until a specific string in in buffer (or, for example, a regexp match happens).
In my project where I first encountered the problem, problem was that future processing just hanged when I get a Ready(_) from inner future and return NotReady from my function. I discovered I shouldn't do that per docs (last paragraph). However, what I didn't get, is what's the actual alternative that is promised in that paragraph. I read all the published documentation on the Tokio site and it doesn't make sense for me at the moment.
So following is my current code. Unfortunately I couldn't make it simpler and smaller as it's already broken. Current result is this:
Err(Custom { kind: Other, error: Error(Shutdown) })
Err(Custom { kind: Other, error: Error(Shutdown) })
Err(Custom { kind: Other, error: Error(Shutdown) })
<ad infinum>
Expected result is getting some Ok(Ready(_)) out of it, while printing W and W', and waiting for specific character in buffer.
extern crate futures;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_io_timeout;
extern crate tokio_process;
use futures::stream::poll_fn;
use futures::{Async, Poll, Stream};
use tokio_core::reactor::Core;
use tokio_io::AsyncRead;
use tokio_io_timeout::TimeoutReader;
use tokio_process::CommandExt;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct Process {
child: tokio_process::Child,
stdout: Arc<Mutex<tokio_io_timeout::TimeoutReader<tokio_process::ChildStdout>>>,
}
impl Process {
fn new(
command: &str,
reader_timeout: Option<Duration>,
core: &tokio_core::reactor::Core,
) -> Self {
let mut cmd = Command::new(command);
let cat = cmd.stdout(Stdio::piped());
let mut child = cat.spawn_async(&core.handle()).unwrap();
let stdout = child.stdout().take().unwrap();
let mut timeout_reader = TimeoutReader::new(stdout);
timeout_reader.set_timeout(reader_timeout);
let timeout_reader = Arc::new(Mutex::new(timeout_reader));
Self {
child,
stdout: timeout_reader,
}
}
}
fn work() -> Result<(), ()> {
let window = Arc::new(Mutex::new(Vec::new()));
let mut core = Core::new().unwrap();
let process = Process::new("cat", Some(Duration::from_secs(20)), &core);
let mark = Arc::new(Mutex::new(b'c'));
let read_until_stream = poll_fn({
let window = window.clone();
let timeout_reader = process.stdout.clone();
move || -> Poll<Option<u8>, std::io::Error> {
let mut buf = [0; 8];
let poll;
{
let mut timeout_reader = timeout_reader.lock().unwrap();
poll = timeout_reader.poll_read(&mut buf);
}
match poll {
Ok(Async::Ready(0)) => Ok(Async::Ready(None)),
Ok(Async::Ready(x)) => {
{
let mut window = window.lock().unwrap();
println!("W: {:?}", *window);
println!("buf: {:?}", &buf[0..x]);
window.extend(buf[0..x].into_iter().map(|x| *x));
println!("W': {:?}", *window);
if let Some(_) = window.iter().find(|c| **c == *mark.lock().unwrap()) {
Ok(Async::Ready(Some(1)))
} else {
Ok(Async::NotReady)
}
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => Err(e),
}
}
});
let _stream_thread = thread::spawn(move || {
for o in read_until_stream.wait() {
println!("{:?}", o);
}
});
match core.run(process.child) {
Ok(_) => {}
Err(e) => {
println!("Child error: {:?}", e);
}
}
Ok(())
}
fn main() {
work().unwrap();
}
This is complete example project.
If you need more data you need to call poll_read again until you either find what you were looking for or poll_read returns NotReady.
You might want to avoid looping in one task for too long, so you can build yourself a yield_task function to call instead if poll_read didn't return NotReady; it makes sure your task gets called again ASAP after other pending tasks were run.
To use it just run return yield_task();.
fn yield_inner() {
use futures::task;
task::current().notify();
}
#[inline(always)]
pub fn yield_task<T, E>() -> Poll<T, E> {
yield_inner();
Ok(Async::NotReady)
}
Also see futures-rs#354: Handle long-running, always-ready futures fairly #354.
With the new async/await API futures::task::current is gone; instead you'll need a std::task::Context reference, which is provided as parameter to the new std::future::Future::poll trait method.
If you're already manually implementing the std::future::Future trait you can simply insert:
context.waker().wake_by_ref();
return std::task::Poll::Pending;
Or build yourself a Future-implementing type that yields exactly once:
pub struct Yield {
ready: bool,
}
impl core::future::Future for Yield {
type Output = ();
fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> core::task::Poll<Self::Output> {
let this = self.get_mut();
if this.ready {
core::task::Poll::Ready(())
} else {
cx.waker().wake_by_ref();
this.ready = true; // ready next round
core::task::Poll::Pending
}
}
}
pub fn yield_task() -> Yield {
Yield { ready: false }
}
And then use it in async code like this:
yield_task().await;

How can I chain two futures on the same resource without having to define every single method combination ahead of time?

I am writing the code to bootstrap and connect to a 2G/3G network using a SIM800L modem. This modem is interfaced with a single serial channel, which I've muxed outside of this project into 4 channels (data, text interface, control interface, status messages).
In order to bootstrap this, I need to run a series of sequential commands. This sequence changes based on the output of the modem (is the SIM locked? What kind of info does the SIM need to be unlocked? What kind of APN are we getting on? What kind of network selection do we want?). I initially thought that this would be a perfect application for futures as each individual operation can be very costly in terms of time spent idling (AT+COPS, one of the command, takes up to 10s to return).
I'm on to something like this, which, while it compiles and seems to execute commands sequentially, the third operation comes out empty. My question is twofold: why do the commands run not pop up in the result of the last future, and is there a more robust way of doing something like this?
#![feature(conservative_impl_trait)]
extern crate futures;
extern crate tokio_core;
use std::sync::{Arc, Mutex};
use futures::{future, Future};
use tokio_core::reactor::Core;
use futures::sync::oneshot;
use std::thread;
use std::io;
use std::time::Duration;
pub struct Channel {
operations: Arc<Mutex<Vec<String>>>,
}
impl Channel {
pub fn ops(&mut self) -> Box<Future<Item = Vec<String>, Error = io::Error>> {
println!("{:?}", self.operations);
let ops = Arc::clone(&self.operations);
let ops = ops.lock().unwrap();
future::ok::<Vec<String>, io::Error>(ops.to_vec()).boxed()
}
pub fn run(&mut self, command: &str) -> Box<Future<Item = Vec<String>, Error = io::Error>> {
let (tx, rx) = oneshot::channel::<Vec<String>>();
let ops = Arc::clone(&self.operations);
let str_cmd = String::from(command);
thread::spawn(move || {
thread::sleep(Duration::new(0, 10000));
let mut ops = ops.lock().unwrap();
ops.push(str_cmd.clone());
println!("Pushing op: {}", str_cmd.clone());
tx.send(vec!["OK".to_string()])
});
rx.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "Test"))
.boxed()
}
}
pub struct Channels {
inner_object: Arc<Mutex<Channel>>,
}
impl Channels {
pub fn one(&self, cmd: &str) -> Box<Future<Item = Vec<String>, Error = io::Error>> {
let v = Arc::clone(&self.inner_object);
let mut v = v.lock().unwrap();
v.run(&cmd)
}
pub fn ops(&self) -> Box<Future<Item = Vec<String>, Error = io::Error>> {
let v = Arc::clone(&self.inner_object);
let mut v = v.lock().unwrap();
v.ops()
}
pub fn run_command(&self) -> Box<Future<Item = (), Error = io::Error>> {
let a = self.one("AT+CMEE=2");
let b = self.one("AT+CREG=0");
let c = self.ops();
Box::new(a.and_then(|result_1| {
assert_eq!(result_1, vec![String::from("OK")]);
b.and_then(|result_2| {
assert_eq!(result_2, vec![String::from("OK")]);
c.map(move |ops| {
assert_eq!(
ops.as_slice(),
["AT+CMEE=2".to_string(), "AT+CREG=0".to_string()]
);
})
})
}))
}
}
fn main() {
let mut core = Core::new().expect("Core should be created");
let channels = Channels {
inner_object: Arc::new(Mutex::new(Channel {
operations: Arc::new(Mutex::new(vec![])),
})),
};
let result = core.run(channels.run_command()).expect("Should've worked");
println!("{:?}", result);
}
playground
why do the commands run not pop up in the result of the last future
Because you haven't sequenced the operations to occur in that way:
let a = self.one("AT+CMEE=2");
let b = self.one("AT+CREG=0");
let c = self.ops();
This immediately builds:
a, b — promises that sleep a while before they respond
c — a promise that gets the ops in the vector
At the point in time that c is created, the sleeps have yet to terminate, so there have been no operations performed, so the vector will be empty.
Future::and_then is intended to be used to define sequential operations. This is complicated in your case as you want to use self in the body of the and_then closure. You can clone the Arc<Channel> and use that instead.
You'll note that I've made a number of simplifications:
Returning a String instead of Vec<String>
Removing unused mut qualifiers and a Mutex
Returning the operations Vec directly.
extern crate futures;
extern crate tokio_core;
use std::sync::{Arc, Mutex};
use futures::Future;
use tokio_core::reactor::Core;
use futures::sync::oneshot;
use std::thread;
use std::io;
use std::time::Duration;
pub struct Channel {
operations: Arc<Mutex<Vec<String>>>,
}
impl Channel {
fn ops(&self) -> Vec<String> {
self.operations.lock().unwrap().clone()
}
fn command(&self, command: &str) -> Box<Future<Item = String, Error = io::Error>> {
let (tx, rx) = oneshot::channel();
let ops = Arc::clone(&self.operations);
let str_cmd = String::from(command);
thread::spawn(move || {
thread::sleep(Duration::new(0, 10000));
println!("Pushing op: {}", str_cmd);
ops.lock().unwrap().push(str_cmd);
tx.send("OK".to_string())
});
Box::new(rx.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "Test")))
}
}
struct Channels {
data: Arc<Channel>,
}
impl Channels {
fn run_command(&self) -> Box<Future<Item = (), Error = io::Error>> {
let d2 = Arc::clone(&self.data);
let d3 = Arc::clone(&self.data);
Box::new(
self.data
.command("AT+CMEE=2")
.and_then(move |cmee_answer| {
assert_eq!(cmee_answer, "OK"); // This should be checked in `command` and be a specific Error
d2.command("AT+CREG=0")
})
.map(move |creg_answer| {
assert_eq!(creg_answer, "OK"); // This should be checked in `command` and be a specific Error
let ops = d3.ops();
assert_eq!(ops, ["AT+CMEE=2", "AT+CREG=0"])
}),
)
}
}
fn main() {
let mut core = Core::new().expect("Core should be created");
let channels = Channels {
data: Arc::new(Channel {
operations: Arc::new(Mutex::new(vec![])),
}),
};
let result = core.run(channels.run_command()).expect("Should've worked");
println!("{:?}", result);
}
However, this isn't the type of code I usually see with futures. Instead of taking &self, many futures take self. Let's see how that would look:
extern crate futures;
extern crate tokio_core;
use std::sync::{Arc, Mutex};
use futures::Future;
use tokio_core::reactor::Core;
use futures::sync::oneshot;
use std::thread;
use std::io;
use std::time::Duration;
#[derive(Clone)]
pub struct Channel {
operations: Arc<Mutex<Vec<String>>>,
}
impl Channel {
fn ops(&self) -> Arc<Mutex<Vec<String>>> {
Arc::clone(&self.operations)
}
fn command(self, command: &str) -> Box<Future<Item = (Self, String), Error = io::Error>> {
let (tx, rx) = oneshot::channel();
let str_cmd = String::from(command);
thread::spawn(move || {
thread::sleep(Duration::new(0, 10000));
println!("Pushing op: {}", str_cmd);
self.operations.lock().unwrap().push(str_cmd);
tx.send((self, "OK".to_string()))
});
Box::new(rx.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "Test")))
}
}
struct Channels {
data: Channel,
}
impl Channels {
fn run_command(self) -> Box<Future<Item = (), Error = io::Error>> {
Box::new(
self.data
.clone()
.command("AT+CMEE=2")
.and_then(|(channel, cmee_answer)| {
assert_eq!(cmee_answer, "OK");
channel.command("AT+CREG=0")
})
.map(|(channel, creg_answer)| {
assert_eq!(creg_answer, "OK");
let ops = channel.ops();
let ops = ops.lock().unwrap();
assert_eq!(*ops, ["AT+CMEE=2", "AT+CREG=0"]);
}),
)
}
}
fn main() {
let mut core = Core::new().expect("Core should be created");
let channels = Channels {
data: Channel {
operations: Arc::new(Mutex::new(vec![])),
},
};
let result = core.run(channels.run_command()).expect("Should've worked");
println!("{:?}", result);
}

Resources