1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! A very general library for IO based messages communication.
//!
//! # Documentation
//!
//! The library has 2 major parts:
//!
//! 1. Serializer
//! 2. Channel
//!
//! ## Serializer
//!
//! Serializer is a special struct (trait) which can read and write message
//! type from a bytes stream. You should manually implement this trait to some
//! struct and provide it with a message and error types.
//!
//! If feature `json-serializer` is enabled (it is enabled by default), then
//! you can use `JsonSerializer` trait which only has message and error types.
//! The serialization/deserialization logic is implemented using `serde_json`
//! crate.
//!
//! ## Channel
//!
//! Channel is a special struct (trait) which allows you to read and write
//! messages from generic reader and writer (rust's `Read` and `Write` traits).
//! There are many different traits related to channels. The main one is
//! `OwnedChannel`.
//!
//! The channel mod implements some handy functions for making channels from
//! the current process's stdio streams or unix sockets.
//!
//! ## Client and server
//!
//! An actual communication can be implemented using channels only. The library
//! provides some simple functions to create a client channel, a `Server` struct
//! to implement a messages listener, and a related `deamon` function to spawn
//! this `Server` struct in a background thread.
//!
//! In general you'd want to either:
//!
//! - Make two binaries: client and server. In server binary use `Server` struct
//! to handle incoming messages, process them and return answers if needed.
//! In client binary use `process_stdio` function to spawn the server binary
//! and use stdin/stdout channel to communicate with it.
//! - Make client and server binaries again, but instead of using stdin/stdout
//! channel you can use a unix socket. This can allow you to implement a
//! multiple parties communication.
//!
//! # Example
//!
//! In this example we will spawn the server binary as a child of the client
//! binary process.
//!
//! ```ignore
//! use std::process::Command;
//!
//! use ioserverlib::prelude::*;
//! use ioserverlib::server::Server;
//!
//! #[derive(Debug, serde::Serialize, serde::Deserialize)]
//! enum Message {
//! Ping,
//! Pong
//! }
//!
//! struct Serializer;
//!
//! impl JsonSerializer for Serializer {
//! type Error = Box<dyn std::error::Error>;
//! type Message = Message;
//! }
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut args = std::env::args().skip(1);
//!
//! match args.next().as_deref() {
//! Some("client") => {
//! let mut command = Command::new(std::env::current_exe()?);
//!
//! let command = command.arg("server");
//!
//! let (mut child, mut channel) = ioserverlib::client::process_stdio(command, Serializer)?;
//!
//! channel.write(Message::Ping)?;
//!
//! dbg!(channel.read()?);
//!
//! child.kill()?;
//! }
//!
//! Some("server") => {
//! let channel = ioserverlib::channel::stdio(Serializer);
//!
//! let mut server = Server::new(channel, |message| {
//! match message {
//! Message::Ping => Some(Message::Pong),
//! Message::Pong => None
//! }
//! });
//!
//! loop {
//! if let Err(err) = server.update() {
//! eprintln!("server error: {err}");
//! }
//! }
//! }
//!
//! Some(command) => eprintln!("unknown command: {command}"),
//! _ => eprintln!("missing command: client or server")
//! }
//!
//! Ok(())
//! }
//! ```
//!
//! > $ cargo run \-\- client
//! >
//! > [src/main.rs:32:13] channel.read()? = Pong
//!
//! > $ echo '"Ping"' | cargo run \-\- server
//! >
//! > "Pong"