1use dotloom_constraints::{Certainty, DiagnosticKind, Status};
4use dotloom_document::{ConstraintId, EntityId};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use crate::CommandError;
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct DiagnosticReport {
14 pub kind: DiagnosticKind,
16 pub certainty: Certainty,
18 pub constraints: Vec<ConstraintId>,
20 pub templates: Vec<(EntityId, String)>,
22 pub edits: Vec<(EntityId, String)>,
24 pub entities: Vec<EntityId>,
26 pub labels: Vec<String>,
28 pub residual: Option<f64>,
30 pub message: String,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct NearestValue {
38 pub entity: EntityId,
40 pub param: String,
42 pub requested: f64,
44 pub feasible: f64,
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct SolveFailure {
52 pub status: Status,
54 pub diagnostics: Vec<DiagnosticReport>,
56 pub nearest: Vec<NearestValue>,
58}
59
60#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)]
62#[serde(tag = "code", rename_all = "camelCase", rename_all_fields = "camelCase")]
63#[non_exhaustive]
64pub enum EngineError {
65 #[error("stale request: expected revision {expected}, document is at {actual}")]
67 Stale {
68 expected: u64,
70 actual: u64,
72 },
73 #[error("engine busy: {reason}")]
75 Busy {
76 reason: String,
78 },
79 #[error("{error}")]
81 Command {
82 error: CommandError,
84 },
85 #[error("invalid result: {message}")]
87 Invalid {
88 message: String,
90 },
91 #[error("constraints not satisfied: {:?}", failure.status)]
93 Solve {
94 failure: SolveFailure,
96 },
97 #[error("independent check failed for {what}: residual {residual:e} > {tolerance:e}")]
99 Validation {
100 what: String,
102 residual: f64,
104 tolerance: f64,
106 },
107 #[error("cancelled")]
109 Cancelled,
110 #[error("nothing to undo")]
112 NothingToUndo,
113 #[error("nothing to redo")]
115 NothingToRedo,
116 #[error("no active {what}")]
118 NotActive {
119 what: String,
121 },
122 #[error("cannot load document: {message}")]
124 Load {
125 message: String,
127 },
128 #[error("plugin error: {message}")]
130 Plugin {
131 message: String,
133 },
134}
135
136impl From<CommandError> for EngineError {
137 fn from(error: CommandError) -> Self {
138 Self::Command { error }
139 }
140}
141
142impl EngineError {
143 #[must_use]
145 pub fn code(&self) -> &'static str {
146 match self {
147 Self::Stale { .. } => "stale",
148 Self::Busy { .. } => "busy",
149 Self::Command { .. } => "command",
150 Self::Invalid { .. } => "invalid",
151 Self::Solve { .. } => "solve",
152 Self::Validation { .. } => "validation",
153 Self::Cancelled => "cancelled",
154 Self::NothingToUndo => "nothingToUndo",
155 Self::NothingToRedo => "nothingToRedo",
156 Self::NotActive { .. } => "notActive",
157 Self::Load { .. } => "load",
158 Self::Plugin { .. } => "plugin",
159 }
160 }
161
162 #[must_use]
164 pub fn solve_status(&self) -> Option<Status> {
165 match self {
166 Self::Solve { failure } => Some(failure.status),
167 _ => None,
168 }
169 }
170}