use std::collections::BTreeMap;
use std::future::Future;
use std::pin::Pin;
use serde_json::{Map, Value as JsonValue};
use super::transition::{
Effect, Idempotency, ResourceKind, ResourceSpec, TransitionResultKind, TransitionSpec,
};
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Clone, Debug, Default)]
pub struct ResourceCtx {
_placeholder: (),
}
impl ResourceCtx {
pub fn new_test() -> Self {
Self::default()
}
}
#[derive(Debug)]
pub enum ResourceError {
NotFound(String),
Unavailable(String),
Internal(String),
}
#[derive(Debug, Clone)]
pub struct ResourceSnapshot {
pub id: String,
pub kind: String,
pub name: Option<String>,
pub state: Option<String>,
pub node: String,
pub properties: Map<String, JsonValue>,
pub labels: BTreeMap<String, String>,
pub transitions: Vec<TransitionAffordance>,
pub streams: Vec<SnapshotStreamSpec>,
pub revision: Option<String>,
pub metadata: Map<String, JsonValue>,
}
#[derive(Debug, Clone, Default)]
pub struct TransitionAffordance {
pub spec: TransitionSpec,
pub available: bool,
pub unavailable_reason: Option<String>,
}
impl TransitionAffordance {
pub fn name(&self) -> &str {
&self.spec.name
}
}
#[derive(Debug, Clone, Default)]
pub struct SnapshotStreamSpec {
pub name: String,
pub kind: String,
}
impl ResourceSnapshot {
pub fn to_query_value(&self) -> JsonValue {
use serde_json::Map;
let mut o = Map::new();
o.insert("id".into(), JsonValue::String(self.id.clone()));
o.insert("kind".into(), JsonValue::String(self.kind.clone()));
o.insert(
"name".into(),
self.name
.clone()
.map(JsonValue::String)
.unwrap_or(JsonValue::Null),
);
o.insert(
"state".into(),
self.state
.clone()
.map(JsonValue::String)
.unwrap_or(JsonValue::Null),
);
o.insert("node".into(), JsonValue::String(self.node.clone()));
o.insert(
"properties".into(),
JsonValue::Object(self.properties.clone()),
);
let labels_obj: Map<String, JsonValue> = self
.labels
.iter()
.map(|(k, v)| (k.clone(), JsonValue::String(v.clone())))
.collect();
o.insert("labels".into(), JsonValue::Object(labels_obj));
let transitions: Vec<JsonValue> = self
.transitions
.iter()
.map(transition_affordance_to_query_json)
.collect();
o.insert("transitions".into(), JsonValue::Array(transitions));
let streams: Vec<JsonValue> = self
.streams
.iter()
.map(|s| {
let mut m = Map::new();
m.insert("name".into(), JsonValue::String(s.name.clone()));
m.insert("kind".into(), JsonValue::String(s.kind.clone()));
JsonValue::Object(m)
})
.collect();
o.insert("streams".into(), JsonValue::Array(streams));
o.insert(
"revision".into(),
self.revision
.clone()
.map(JsonValue::String)
.unwrap_or(JsonValue::Null),
);
o.insert("metadata".into(), JsonValue::Object(self.metadata.clone()));
JsonValue::Object(o)
}
}
fn transition_affordance_to_query_json(t: &TransitionAffordance) -> JsonValue {
use serde_json::Map;
let spec = &t.spec;
let mut m = Map::new();
m.insert("name".into(), JsonValue::String(spec.name.clone()));
if let Some(title) = &spec.title {
m.insert("title".into(), JsonValue::String(title.clone()));
}
m.insert(
"allowedStates".into(),
JsonValue::Array(
spec.allowed_states
.iter()
.cloned()
.map(JsonValue::String)
.collect(),
),
);
if let Some(s) = &spec.input_schema {
m.insert("inputSchema".into(), s.clone());
}
if let Some(s) = &spec.output_schema {
m.insert("outputSchema".into(), s.clone());
}
m.insert(
"result".into(),
JsonValue::String(
match spec.result {
TransitionResultKind::Sync => "sync",
TransitionResultKind::AsyncJob => "async-job",
}
.into(),
),
);
m.insert(
"idempotency".into(),
JsonValue::String(
match spec.idempotency {
Idempotency::None => "none",
Idempotency::Supported => "supported",
Idempotency::Required => "required",
}
.into(),
),
);
m.insert(
"effect".into(),
JsonValue::String(
match spec.effect {
Effect::Safe => "safe",
Effect::UnsafeIdempotent => "unsafe-idempotent",
Effect::Unsafe => "unsafe",
}
.into(),
),
);
m.insert(
"requiredScopes".into(),
JsonValue::Array(
spec.required_scopes
.iter()
.cloned()
.map(JsonValue::String)
.collect(),
),
);
m.insert("available".into(), JsonValue::Bool(t.available));
m.insert(
"unavailableReason".into(),
t.unavailable_reason
.clone()
.map(JsonValue::String)
.unwrap_or(JsonValue::Null),
);
JsonValue::Object(m)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JobHandle {
pub id: String,
pub kind: ResourceKind,
pub location: String,
pub created: bool,
}
#[derive(Debug, Clone)]
pub enum TransitionOutcome {
Completed {
output: Option<JsonValue>,
snapshot: ResourceSnapshot,
},
Accepted {
job: JobHandle,
output: Option<JsonValue>,
},
}
pub trait Resource: Send + Sync + 'static {
fn spec(&self) -> ResourceSpec;
fn snapshot<'a>(
&'a self,
ctx: ResourceCtx,
) -> DynFuture<'a, Result<ResourceSnapshot, ResourceError>>;
}