#![allow(dead_code)]
use optative::Lifecycle;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Serialize, Deserialize)]
pub struct Greeting {
pub person: String,
pub message: String,
}
impl Lifecycle for Greeting {
type Key = String;
type State = Greeting;
type Context = Api;
type Output = ();
type Error = ureq::Error;
fn key(&self) -> String {
self.person.clone()
}
fn enter(self, api: &mut Api, _: &mut ()) -> Result<Greeting, Self::Error> {
api.create(&self)?;
Ok(self)
}
fn reconcile_self(
self,
state: &mut Greeting,
api: &mut Api,
_: &mut (),
) -> Result<(), Self::Error> {
if state.message != self.message {
api.update(&self)?;
*state = self;
}
Ok(())
}
fn exit(state: Greeting, api: &mut Api, _: &mut ()) -> Result<(), Self::Error> {
api.remove(&state)
}
}
pub struct Api {
pub base_url: String,
}
impl Api {
pub fn create(&self, g: &Greeting) -> Result<(), ureq::Error> {
ureq::post(&format!("{}/greetings/{}", self.base_url, g.person)).send(&g.message)?;
Ok(())
}
pub fn update(&self, g: &Greeting) -> Result<(), ureq::Error> {
ureq::put(&format!("{}/greetings/{}", self.base_url, g.person)).send(&g.message)?;
Ok(())
}
pub fn remove(&self, g: &Greeting) -> Result<(), ureq::Error> {
ureq::delete(&format!("{}/greetings/{}", self.base_url, g.person)).call()?;
Ok(())
}
}
pub type ServerStore = Arc<Mutex<HashMap<String, String>>>;
pub fn spawn_greetings_server() -> (String, ServerStore) {
let store: ServerStore = Arc::new(Mutex::new(HashMap::new()));
let store_for_thread = store.clone();
let server = tiny_http::Server::http("127.0.0.1:0").unwrap();
let addr = server.server_addr().to_ip().unwrap();
let base_url = format!("http://{addr}");
thread::spawn(move || {
for mut request in server.incoming_requests() {
let url = request.url().to_string();
let Some(name) = url.strip_prefix("/greetings/").map(str::to_string) else {
let _ = request.respond(tiny_http::Response::empty(404));
continue;
};
match request.method() {
tiny_http::Method::Post => {
let mut body = String::new();
request.as_reader().read_to_string(&mut body).unwrap();
let mut s = store_for_thread.lock().unwrap();
use std::collections::hash_map::Entry;
let resp = match s.entry(name) {
Entry::Vacant(e) => {
e.insert(body);
201
}
Entry::Occupied(_) => 409,
};
let _ = request.respond(tiny_http::Response::empty(resp));
}
tiny_http::Method::Put => {
let mut body = String::new();
request.as_reader().read_to_string(&mut body).unwrap();
let mut s = store_for_thread.lock().unwrap();
use std::collections::hash_map::Entry;
let resp = match s.entry(name) {
Entry::Occupied(mut e) => {
e.insert(body);
204
}
Entry::Vacant(_) => 404,
};
let _ = request.respond(tiny_http::Response::empty(resp));
}
tiny_http::Method::Delete => {
let mut s = store_for_thread.lock().unwrap();
if s.remove(&name).is_some() {
let _ = request.respond(tiny_http::Response::empty(204));
} else {
let _ = request.respond(tiny_http::Response::empty(404));
}
}
_ => {
let _ = request.respond(tiny_http::Response::empty(405));
}
}
}
});
(base_url, store)
}