use std::collections::BTreeMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{Asset, Comment, DependencyEdge, Task};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "coverage", rename_all = "kebab-case")]
pub enum Coverage {
Covered,
Uncovered {
reason: String,
},
}
impl Coverage {
#[must_use]
pub fn reason(&self) -> Option<&str> {
match self {
Self::Covered => None,
Self::Uncovered { reason } => Some(reason),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct VersionCoverage {
pub fields: Coverage,
pub comments: Coverage,
}
impl VersionCoverage {
#[must_use]
pub fn whole() -> Self {
Self {
fields: Coverage::Covered,
comments: Coverage::Covered,
}
}
#[must_use]
pub fn none(reason: impl Into<String>) -> Self {
let reason = reason.into();
Self {
fields: Coverage::Uncovered {
reason: reason.clone(),
},
comments: Coverage::Uncovered { reason },
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "outcome", rename_all = "kebab-case")]
pub enum PartOutcome {
Landed,
Rejected {
error: String,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
#[schemars(!skip_serializing_if)]
stale: bool,
},
Unknown {
error: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum RequestKind {
Read,
Mutation,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(try_from = "WholeTaskShape")]
pub struct WholeTask {
task: Task,
comments: Vec<Comment>,
assets: Vec<Asset>,
#[serde(default, skip_serializing_if = "Option::is_none")]
edges: Option<Vec<DependencyEdge>>,
}
#[derive(Deserialize, JsonSchema)]
struct WholeTaskShape {
task: Task,
comments: Vec<Comment>,
assets: Vec<Asset>,
#[serde(default)]
edges: Option<Vec<DependencyEdge>>,
}
impl TryFrom<WholeTaskShape> for WholeTask {
type Error = String;
fn try_from(shape: WholeTaskShape) -> Result<Self, Self::Error> {
Self::new(shape.task, shape.comments, shape.assets, shape.edges)
.ok_or_else(|| "a whole task carries the version it is at".to_owned())
}
}
impl WholeTask {
#[must_use]
pub fn new(
task: Task,
comments: Vec<Comment>,
assets: Vec<Asset>,
edges: Option<Vec<DependencyEdge>>,
) -> Option<Self> {
task.version.as_ref()?;
Some(Self {
task,
comments,
assets,
edges,
})
}
#[must_use]
pub fn task(&self) -> &Task {
&self.task
}
#[must_use]
pub fn version(&self) -> &str {
self.task.version.as_deref().unwrap_or_default()
}
#[must_use]
pub fn comments(&self) -> &[Comment] {
&self.comments
}
#[must_use]
pub fn assets(&self) -> &[Asset] {
&self.assets
}
#[must_use]
pub fn edges(&self) -> Option<&[DependencyEdge]> {
self.edges.as_deref()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct JournalEntry {
pub part: String,
pub kind: RequestKind,
#[serde(flatten)]
pub outcome: PartOutcome,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub fields: BTreeMap<String, Value>,
#[serde(default)]
pub shown: Shown,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "shown", rename_all = "kebab-case")]
pub enum Shown {
#[default]
Nothing,
Version {
version: String,
},
Whole {
task: Box<WholeTask>,
},
}
impl Shown {
#[must_use]
pub fn version(&self) -> Option<&str> {
match self {
Self::Nothing => None,
Self::Version { version } => Some(version),
Self::Whole { task } => Some(task.version()),
}
}
#[must_use]
pub fn whole(&self) -> Option<&WholeTask> {
match self {
Self::Whole { task } => Some(task),
Self::Nothing | Self::Version { .. } => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct WriteJournal {
pub requests: Vec<JournalEntry>,
}
impl WriteJournal {
#[must_use]
pub fn read(&self) -> bool {
self.requests
.iter()
.any(|entry| entry.kind == RequestKind::Read)
}
pub fn mutations(&self) -> impl Iterator<Item = &JournalEntry> {
self.requests
.iter()
.filter(|entry| entry.kind == RequestKind::Mutation)
}
#[must_use]
pub fn stale(&self) -> bool {
self.mutations()
.any(|entry| matches!(entry.outcome, PartOutcome::Rejected { stale: true, .. }))
}
#[must_use]
pub fn unknown(&self) -> bool {
self.mutations()
.any(|entry| matches!(entry.outcome, PartOutcome::Unknown { .. }))
}
}