use std::future::Future;
use std::marker::PhantomData;
use std::ops::AsyncFn;
use std::pin::Pin;
use crate::bus::{Codec, ContractBody, MessagePack};
use crate::participant::context::{ResetContext, SetupContext, StepContext};
use crate::participant::server::ServerOutcome;
use crate::participant::spec::StepSchedule;
#[doc(hidden)]
pub mod __meta {
pub use const_format::concatcp as __concatcp;
#[derive(Clone, Copy)]
pub struct ConstSchema {
bytes: [u8; 65_536],
len: usize,
}
impl Default for ConstSchema {
fn default() -> Self {
Self::new()
}
}
impl ConstSchema {
pub const fn new() -> Self {
Self {
bytes: [0; 65_536],
len: 0,
}
}
pub const fn from_str(value: &str) -> Self {
Self::new().push_str(value)
}
#[must_use]
pub const fn push_str(mut self, value: &str) -> Self {
let value = value.as_bytes();
assert!(
self.len + value.len() <= self.bytes.len(),
"phoxal: const config schema exceeds 64 KiB"
);
let mut index = 0;
while index < value.len() {
self.bytes[self.len + index] = value[index];
index += 1;
}
self.len += value.len();
self
}
pub const fn as_str(&self) -> &str {
let (used, _) = self.bytes.split_at(self.len);
unsafe { core::str::from_utf8_unchecked(used) }
}
}
pub const fn __bytes_of<const N: usize>(s: &str) -> [u8; N] {
let bytes = s.as_bytes();
assert!(
bytes.len() == N,
"phoxal: metadata manifest length mismatch"
);
let mut out = [0u8; N];
let mut i = 0;
while i < N {
out[i] = bytes[i];
i += 1;
}
out
}
}
pub trait ParticipantConfig: serde::de::DeserializeOwned + Send + 'static {
#[doc(hidden)]
const __SCHEMA: __meta::ConstSchema;
const SCHEMA_JSON: &'static str = Self::__SCHEMA.as_str();
}
impl ParticipantConfig for () {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::from_str(r#"{"type":"null"}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Option<T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"anyOf":["#)
.push_str(T::SCHEMA_JSON)
.push_str(r#",{"type":"null"}]}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Vec<T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"array","items":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::BTreeMap<String, T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::HashMap<String, T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
macro_rules! primitive_config_schema {
($ty:ty => $schema:literal) => {
impl ParticipantConfig for $ty {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::from_str($schema);
}
};
}
primitive_config_schema!(bool => r#"{"type":"boolean"}"#);
primitive_config_schema!(String => r#"{"type":"string"}"#);
primitive_config_schema!(char => r#"{"type":"string","minLength":1,"maxLength":1}"#);
primitive_config_schema!(i8 => r#"{"type":"integer","format":"int8"}"#);
primitive_config_schema!(i16 => r#"{"type":"integer","format":"int16"}"#);
primitive_config_schema!(i32 => r#"{"type":"integer","format":"int32"}"#);
primitive_config_schema!(i64 => r#"{"type":"integer","format":"int64"}"#);
primitive_config_schema!(i128 => r#"{"type":"integer"}"#);
primitive_config_schema!(isize => r#"{"type":"integer"}"#);
primitive_config_schema!(u8 => r#"{"type":"integer","format":"uint8","minimum":0,"maximum":255}"#);
primitive_config_schema!(u16 => r#"{"type":"integer","format":"uint16","minimum":0,"maximum":65535}"#);
primitive_config_schema!(u32 => r#"{"type":"integer","format":"uint32","minimum":0}"#);
primitive_config_schema!(u64 => r#"{"type":"integer","format":"uint64","minimum":0}"#);
primitive_config_schema!(u128 => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(usize => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(f32 => r#"{"type":"number","format":"float"}"#);
primitive_config_schema!(f64 => r#"{"type":"number","format":"double"}"#);
#[doc(hidden)]
pub trait ParticipantSpec: Sized + Send + Sync + 'static {
const KIND: &'static str;
const PARTICIPANT_CLASS: &'static str;
const ID: &'static str;
#[doc(hidden)]
type LaunchPolicy: crate::participant::launch::ParticipantLaunchPolicy;
type Config: ParticipantConfig;
type State: Send + 'static;
type Api: Send + 'static;
#[doc(hidden)]
fn __new() -> Self;
#[doc(hidden)]
fn __retain_embedded_metadata();
}
#[allow(async_fn_in_trait)]
pub trait Participant: ParticipantSpec {
async fn setup(
&self,
ctx: &mut SetupContext<Self>,
config: Self::Config,
) -> crate::Result<(Self::State, Self::Api)>;
async fn step(
&self,
_api: &Self::Api,
_step: StepContext,
_state: &mut Self::State,
) -> crate::Result<()> {
Ok(())
}
async fn reset(
&self,
_ctx: ResetContext,
_api: &Self::Api,
_state: &mut Self::State,
) -> crate::Result<()> {
Ok(())
}
async fn shutdown(&self, _api: &Self::Api, _state: &mut Self::State) -> crate::Result<()> {
Ok(())
}
#[doc(hidden)]
fn __step_schedule() -> Option<StepSchedule> {
None
}
}
pub(crate) struct QueryRegistration<R: Participant> {
topic: String,
handler: Box<dyn ErasedQueryHandler<R>>,
}
impl<R: Participant> QueryRegistration<R> {
pub(crate) fn new<Req, Resp, H>(topic: String, handler: H) -> Self
where
Req: ContractBody,
Resp: ContractBody,
H: for<'a> AsyncFn(
&'a R,
&'a R::Api,
Req,
&'a mut R::State,
) -> crate::bus::QueryResult<Resp>
+ Send
+ Sync
+ 'static,
{
Self {
topic,
handler: Box::new(TypedQueryHandler::<H, Req, Resp> {
handler,
_types: PhantomData,
}),
}
}
pub(crate) fn topic(&self) -> &str {
&self.topic
}
pub(crate) async fn dispatch(
&self,
participant: &R,
api: &R::Api,
state: &mut R::State,
request: Vec<u8>,
) -> ServerOutcome {
self.handler
.dispatch(participant, api, state, request)
.await
}
}
trait ErasedQueryHandler<R: Participant>: Send + Sync {
fn dispatch<'a>(
&'a self,
participant: &'a R,
api: &'a R::Api,
state: &'a mut R::State,
request: Vec<u8>,
) -> Pin<Box<dyn Future<Output = ServerOutcome> + 'a>>;
}
struct TypedQueryHandler<H, Req, Resp> {
handler: H,
_types: PhantomData<fn(Req) -> Resp>,
}
impl<R, H, Req, Resp> ErasedQueryHandler<R> for TypedQueryHandler<H, Req, Resp>
where
R: Participant,
Req: ContractBody,
Resp: ContractBody,
H: for<'a> AsyncFn(&'a R, &'a R::Api, Req, &'a mut R::State) -> crate::bus::QueryResult<Resp>
+ Send
+ Sync
+ 'static,
{
fn dispatch<'a>(
&'a self,
participant: &'a R,
api: &'a R::Api,
state: &'a mut R::State,
request: Vec<u8>,
) -> Pin<Box<dyn Future<Output = ServerOutcome> + 'a>> {
Box::pin(async move {
let request = MessagePack::decode::<Req>(&request).map_err(|error| {
crate::bus::QueryFailure::invalid_argument(format!(
"decode query request for '{}': {error}",
Req::TOPIC
))
})?;
let response = (self.handler)(participant, api, request, state).await?;
let payload = MessagePack::encode(&response).map_err(|error| {
crate::bus::QueryFailure::internal(format!(
"encode query response for '{}': {error}",
Resp::TOPIC
))
})?;
Ok(crate::participant::server::ServerReply { payload })
})
}
}