candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
//! A loopback HTTP server that answers from a route function and records every request line it served.

use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::net::TcpListener;
use std::net::TcpStream;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

pub struct Reply {
  pub status: u16,
  pub headers: Vec<(&'static str, String)>,
  pub body: Vec<u8>,
}

impl Reply {
  pub fn new(status: u16, body: impl Into<Vec<u8>>) -> Self {
    Self {
      status,
      headers: Vec::new(),
      body: body.into(),
    }
  }

  pub fn header(mut self, name: &'static str, value: impl Into<String>) -> Self {
    self.headers.push((name, value.into()));
    self
  }
}

pub struct Server {
  pub url: String,
  requests: Arc<Mutex<Vec<String>>>,
}

type Route = dyn Fn(&str, &str) -> Reply + Send + Sync;

impl Server {
  /// Serves each connection on its own thread and replies with `Connection: close`.
  pub fn start(route: impl Fn(&str, &str) -> Reply + Send + Sync + 'static) -> Self {
    let listener = TcpListener::bind("127.0.0.1:0").unwrap();
    let url = format!("http://{}", listener.local_addr().unwrap());
    let requests = Arc::new(Mutex::new(Vec::new()));
    let seen = Arc::clone(&requests);
    let route: Arc<Route> = Arc::new(route);
    thread::spawn(move || {
      for stream in listener.incoming() {
        let Ok(stream) = stream else { continue };
        let (route, seen) = (Arc::clone(&route), Arc::clone(&seen));
        thread::spawn(move || serve(stream, &*route, &seen));
      }
    });
    Self { url, requests }
  }

  pub fn requests(&self) -> Vec<String> {
    self.requests.lock().unwrap().clone()
  }
}

fn serve(mut stream: TcpStream, route: &Route, seen: &Mutex<Vec<String>>) {
  let mut reader = BufReader::new(stream.try_clone().unwrap());
  let mut line = String::new();
  if reader.read_line(&mut line).is_err() {
    return;
  }
  let mut header = String::new();
  while reader.read_line(&mut header).is_ok_and(|n| n > 2) {
    header.clear();
  }
  let mut parts = line.split_whitespace();
  let (method, path) = (parts.next().unwrap_or(""), parts.next().unwrap_or(""));
  seen.lock().unwrap().push(format!("{method} {path}"));
  let reply = route(method, path);
  let mut head = format!(
    "HTTP/1.1 {} Reply\r\nContent-Length: {}\r\nConnection: close\r\n",
    reply.status,
    reply.body.len()
  );
  for (name, value) in &reply.headers {
    head.push_str(&format!("{name}: {value}\r\n"));
  }
  head.push_str("\r\n");
  let _ = stream.write_all(head.as_bytes());
  if method != "HEAD" {
    let _ = stream.write_all(&reply.body);
  }
}

/// A local address nothing listens on: the port of a listener that has just been closed.
pub fn closed_url() -> String {
  let listener = TcpListener::bind("127.0.0.1:0").unwrap();
  format!("http://{}", listener.local_addr().unwrap())
}