use crate::abi;
use fastly_shared::FastlyStatus;
use std::io::Write;
use thiserror::Error;
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Endpoint {
handle: u32,
name: String,
}
impl std::fmt::Debug for Endpoint {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fmt.debug_struct("Endpoint")
.field("name", &self.name)
.finish()
}
}
#[derive(Copy, Clone, Debug, Error, PartialEq, Eq)]
pub enum LogError {
#[error("endpoint not found, or is reserved")]
InvalidEndpoint,
#[error("malformed endpoint name")]
MalformedEndpointName,
}
impl TryFrom<&str> for Endpoint {
type Error = LogError;
fn try_from(name: &str) -> Result<Self, Self::Error> {
Self::try_from_name(name)
}
}
impl TryFrom<String> for Endpoint {
type Error = LogError;
fn try_from(name: String) -> Result<Self, Self::Error> {
Self::try_from_name(&name)
}
}
impl std::io::Write for Endpoint {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut nwritten = 0;
let status = unsafe {
abi::fastly_log::write(self.handle(), buf.as_ptr(), buf.len(), &mut nwritten)
};
match status {
FastlyStatus::OK => Ok(nwritten),
FastlyStatus::BADF => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"fastly_log::write failed: invalid log endpoint handle",
)),
FastlyStatus::BUFLEN => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"fastly_log::write failed: log line too long",
)),
_ => Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("fastly_log::write failed: {:?}", status),
)),
}
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Endpoint {
pub(crate) unsafe fn handle(&self) -> u32 {
self.handle
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn from_name(name: &str) -> Self {
Self::try_from_name(name).unwrap()
}
pub fn try_from_name(name: &str) -> Result<Self, LogError> {
validate_endpoint_name(name)?;
let mut handle = 0u32;
let status =
unsafe { abi::fastly_log::endpoint_get(name.as_ptr(), name.len(), &mut handle) };
match status {
FastlyStatus::OK => Ok(Endpoint {
handle,
name: name.to_owned(),
}),
FastlyStatus::INVAL => Err(LogError::InvalidEndpoint),
_ => panic!("fastly_log::endpoint_get failed"),
}
}
}
fn validate_endpoint_name(name: &str) -> Result<(), LogError> {
if name.is_empty() || name.find(|c| c == '\n' || c == ':').is_some() {
Err(LogError::MalformedEndpointName)
} else {
Ok(())
}
}
pub fn set_panic_endpoint<E>(endpoint: E) -> Result<(), LogError>
where
E: TryInto<Endpoint, Error = LogError>,
{
let endpoint = endpoint.try_into()?;
std::panic::set_hook(Box::new(move |info| {
write!(endpoint.clone(), "{}", info.to_string()).expect("write succeeds in panic hook");
}));
Ok(())
}