1use chrono::{DateTime, Utc};
17use perfgate_types::{RunReceipt, VerdictCounts, VerdictStatus};
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20use std::collections::BTreeMap;
21
22pub const BASELINE_SCHEMA_V1: &str = "perfgate.baseline.v1";
24
25pub const PROJECT_SCHEMA_V1: &str = "perfgate.project.v1";
27
28pub const VERDICT_SCHEMA_V1: &str = "perfgate.verdict.v1";
30
31pub const AUDIT_SCHEMA_V1: &str = "perfgate.audit.v1";
33
34#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
36#[serde(rename_all = "snake_case")]
37pub enum BaselineSource {
38 #[default]
40 Upload,
41 Promote,
43 Migrate,
45 Rollback,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
51pub struct BaselineRecord {
52 pub schema: String,
54 pub id: String,
56 pub project: String,
58 pub benchmark: String,
60 pub version: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub git_ref: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub git_sha: Option<String>,
68 pub receipt: RunReceipt,
70 #[serde(default)]
72 pub metadata: BTreeMap<String, String>,
73 #[serde(default)]
75 pub tags: Vec<String>,
76 pub created_at: DateTime<Utc>,
78 pub updated_at: DateTime<Utc>,
80 pub content_hash: String,
82 pub source: BaselineSource,
84 #[serde(default)]
86 pub deleted: bool,
87}
88
89impl BaselineRecord {
90 pub fn etag(&self) -> String {
92 format!("\"sha256:{}\"", self.content_hash)
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
98pub struct VerdictRecord {
99 pub schema: String,
101 pub id: String,
103 pub project: String,
105 pub benchmark: String,
107 pub run_id: String,
109 pub status: VerdictStatus,
111 pub counts: VerdictCounts,
113 pub reasons: Vec<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub git_ref: Option<String>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub git_sha: Option<String>,
121 pub created_at: DateTime<Utc>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct SubmitVerdictRequest {
128 pub benchmark: String,
129 pub run_id: String,
130 pub status: VerdictStatus,
131 pub counts: VerdictCounts,
132 pub reasons: Vec<String>,
133 pub git_ref: Option<String>,
134 pub git_sha: Option<String>,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
139pub struct ListVerdictsQuery {
140 pub benchmark: Option<String>,
142 pub status: Option<VerdictStatus>,
144 pub since: Option<DateTime<Utc>>,
146 pub until: Option<DateTime<Utc>>,
148 #[serde(default = "default_limit")]
150 pub limit: u32,
151 #[serde(default)]
153 pub offset: u64,
154}
155
156impl Default for ListVerdictsQuery {
157 fn default() -> Self {
158 Self {
159 benchmark: None,
160 status: None,
161 since: None,
162 until: None,
163 limit: default_limit(),
164 offset: 0,
165 }
166 }
167}
168
169impl ListVerdictsQuery {
170 pub fn new() -> Self {
171 Self::default()
172 }
173 pub fn with_benchmark(mut self, b: impl Into<String>) -> Self {
174 self.benchmark = Some(b.into());
175 self
176 }
177 pub fn with_status(mut self, s: VerdictStatus) -> Self {
178 self.status = Some(s);
179 self
180 }
181 pub fn with_limit(mut self, l: u32) -> Self {
182 self.limit = l;
183 self
184 }
185 pub fn with_offset(mut self, o: u64) -> Self {
186 self.offset = o;
187 self
188 }
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
193pub struct ListVerdictsResponse {
194 pub verdicts: Vec<VerdictRecord>,
195 pub pagination: PaginationInfo,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
200pub struct BaselineVersion {
201 pub version: String,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub git_ref: Option<String>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub git_sha: Option<String>,
209 pub created_at: DateTime<Utc>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub created_by: Option<String>,
214 pub is_current: bool,
216 pub source: BaselineSource,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
222pub struct RetentionPolicy {
223 pub max_versions: Option<u32>,
225 pub max_age_days: Option<u32>,
227 pub preserve_tags: Vec<String>,
229}
230
231impl Default for RetentionPolicy {
232 fn default() -> Self {
233 Self {
234 max_versions: Some(50),
235 max_age_days: Some(365),
236 preserve_tags: vec!["production".to_string(), "stable".to_string()],
237 }
238 }
239}
240
241#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
243#[serde(rename_all = "snake_case")]
244pub enum VersioningStrategy {
245 #[default]
247 RunId,
248 Timestamp,
250 GitSha,
252 Manual,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
258pub struct Project {
259 pub schema: String,
261 pub id: String,
263 pub name: String,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub description: Option<String>,
268 pub created_at: DateTime<Utc>,
270 pub retention: RetentionPolicy,
272 pub versioning: VersioningStrategy,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
278pub struct ListBaselinesQuery {
279 pub benchmark: Option<String>,
281 pub benchmark_prefix: Option<String>,
283 pub git_ref: Option<String>,
285 pub git_sha: Option<String>,
287 pub tags: Option<String>,
289 pub since: Option<DateTime<Utc>>,
291 pub until: Option<DateTime<Utc>>,
293 #[serde(default)]
295 pub include_receipt: bool,
296 #[serde(default = "default_limit")]
298 pub limit: u32,
299 #[serde(default)]
301 pub offset: u64,
302}
303
304impl Default for ListBaselinesQuery {
305 fn default() -> Self {
306 Self {
307 benchmark: None,
308 benchmark_prefix: None,
309 git_ref: None,
310 git_sha: None,
311 tags: None,
312 since: None,
313 until: None,
314 include_receipt: false,
315 limit: default_limit(),
316 offset: 0,
317 }
318 }
319}
320
321fn default_limit() -> u32 {
322 50
323}
324
325impl ListBaselinesQuery {
326 pub fn new() -> Self {
327 Self::default()
328 }
329 pub fn with_benchmark(mut self, b: impl Into<String>) -> Self {
330 self.benchmark = Some(b.into());
331 self
332 }
333 pub fn with_benchmark_prefix(mut self, p: impl Into<String>) -> Self {
334 self.benchmark_prefix = Some(p.into());
335 self
336 }
337 pub fn with_offset(mut self, o: u64) -> Self {
338 self.offset = o;
339 self
340 }
341 pub fn with_limit(mut self, l: u32) -> Self {
342 self.limit = l;
343 self
344 }
345 pub fn with_receipts(mut self) -> Self {
346 self.include_receipt = true;
347 self
348 }
349 pub fn parsed_tags(&self) -> Vec<String> {
350 self.tags
351 .as_ref()
352 .map(|t| {
353 t.split(',')
354 .map(|s| s.trim().to_string())
355 .filter(|s| !s.is_empty())
356 .collect()
357 })
358 .unwrap_or_default()
359 }
360 pub fn to_query_params(&self) -> Vec<(String, String)> {
361 let mut params = Vec::new();
362 if let Some(b) = &self.benchmark {
363 params.push(("benchmark".to_string(), b.clone()));
364 }
365 if let Some(p) = &self.benchmark_prefix {
366 params.push(("benchmark_prefix".to_string(), p.clone()));
367 }
368 if let Some(r) = &self.git_ref {
369 params.push(("git_ref".to_string(), r.clone()));
370 }
371 if let Some(s) = &self.git_sha {
372 params.push(("git_sha".to_string(), s.clone()));
373 }
374 if let Some(t) = &self.tags {
375 params.push(("tags".to_string(), t.clone()));
376 }
377 if let Some(s) = &self.since {
378 params.push(("since".to_string(), s.to_rfc3339()));
379 }
380 if let Some(u) = &self.until {
381 params.push(("until".to_string(), u.to_rfc3339()));
382 }
383 params.push(("limit".to_string(), self.limit.to_string()));
384 params.push(("offset".to_string(), self.offset.to_string()));
385 if self.include_receipt {
386 params.push(("include_receipt".to_string(), "true".to_string()));
387 }
388 params
389 }
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
394pub struct PaginationInfo {
395 pub total: u64,
397 pub offset: u64,
399 pub limit: u32,
401 pub has_more: bool,
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
407pub struct ListBaselinesResponse {
408 pub baselines: Vec<BaselineSummary>,
410 pub pagination: PaginationInfo,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
416pub struct BaselineSummary {
417 pub id: String,
418 pub benchmark: String,
419 pub version: String,
420 pub created_at: DateTime<Utc>,
421 pub git_ref: Option<String>,
422 pub git_sha: Option<String>,
423 pub tags: Vec<String>,
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub receipt: Option<RunReceipt>,
426}
427
428impl From<BaselineRecord> for BaselineSummary {
429 fn from(record: BaselineRecord) -> Self {
430 Self {
431 id: record.id,
432 benchmark: record.benchmark,
433 version: record.version,
434 created_at: record.created_at,
435 git_ref: record.git_ref,
436 git_sha: record.git_sha,
437 tags: record.tags,
438 receipt: Some(record.receipt),
439 }
440 }
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
445pub struct UploadBaselineRequest {
446 pub benchmark: String,
447 pub version: Option<String>,
448 pub git_ref: Option<String>,
449 pub git_sha: Option<String>,
450 pub receipt: RunReceipt,
451 pub metadata: BTreeMap<String, String>,
452 pub tags: Vec<String>,
453 pub normalize: bool,
454}
455
456#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
458pub struct UploadBaselineResponse {
459 pub id: String,
460 pub benchmark: String,
461 pub version: String,
462 pub created_at: DateTime<Utc>,
463 pub etag: String,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
468pub struct PromoteBaselineRequest {
469 pub from_version: String,
470 pub to_version: String,
471 pub git_ref: Option<String>,
472 pub git_sha: Option<String>,
473 pub tags: Vec<String>,
474 #[serde(default)]
475 pub normalize: bool,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
480pub struct PromoteBaselineResponse {
481 pub id: String,
482 pub benchmark: String,
483 pub version: String,
484 pub promoted_from: String,
485 pub promoted_at: DateTime<Utc>,
486 pub created_at: DateTime<Utc>,
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
491pub struct DeleteBaselineResponse {
492 pub deleted: bool,
493 pub id: String,
494 pub benchmark: String,
495 pub version: String,
496 pub deleted_at: DateTime<Utc>,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
505#[serde(rename_all = "snake_case")]
506pub enum AuditAction {
507 Create,
509 Update,
511 Delete,
513 Promote,
515}
516
517impl std::fmt::Display for AuditAction {
518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 match self {
520 AuditAction::Create => write!(f, "create"),
521 AuditAction::Update => write!(f, "update"),
522 AuditAction::Delete => write!(f, "delete"),
523 AuditAction::Promote => write!(f, "promote"),
524 }
525 }
526}
527
528impl std::str::FromStr for AuditAction {
529 type Err = String;
530
531 fn from_str(s: &str) -> Result<Self, Self::Err> {
532 match s {
533 "create" => Ok(AuditAction::Create),
534 "update" => Ok(AuditAction::Update),
535 "delete" => Ok(AuditAction::Delete),
536 "promote" => Ok(AuditAction::Promote),
537 other => Err(format!("Unknown audit action: {}", other)),
538 }
539 }
540}
541
542#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
544#[serde(rename_all = "snake_case")]
545pub enum AuditResourceType {
546 Baseline,
548 Key,
550 Verdict,
552}
553
554impl std::fmt::Display for AuditResourceType {
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556 match self {
557 AuditResourceType::Baseline => write!(f, "baseline"),
558 AuditResourceType::Key => write!(f, "key"),
559 AuditResourceType::Verdict => write!(f, "verdict"),
560 }
561 }
562}
563
564impl std::str::FromStr for AuditResourceType {
565 type Err = String;
566
567 fn from_str(s: &str) -> Result<Self, Self::Err> {
568 match s {
569 "baseline" => Ok(AuditResourceType::Baseline),
570 "key" => Ok(AuditResourceType::Key),
571 "verdict" => Ok(AuditResourceType::Verdict),
572 other => Err(format!("Unknown resource type: {}", other)),
573 }
574 }
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
579pub struct AuditEvent {
580 pub id: String,
582 pub timestamp: DateTime<Utc>,
584 pub actor: String,
586 pub action: AuditAction,
588 pub resource_type: AuditResourceType,
590 pub resource_id: String,
592 pub project: String,
594 #[serde(default)]
596 pub metadata: serde_json::Value,
597}
598
599#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
601pub struct ListAuditEventsQuery {
602 pub project: Option<String>,
604 pub action: Option<String>,
606 pub resource_type: Option<String>,
608 pub actor: Option<String>,
610 pub since: Option<DateTime<Utc>>,
612 pub until: Option<DateTime<Utc>>,
614 #[serde(default = "default_limit")]
616 pub limit: u32,
617 #[serde(default)]
619 pub offset: u64,
620}
621
622impl Default for ListAuditEventsQuery {
623 fn default() -> Self {
624 Self {
625 project: None,
626 action: None,
627 resource_type: None,
628 actor: None,
629 since: None,
630 until: None,
631 limit: default_limit(),
632 offset: 0,
633 }
634 }
635}
636
637#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
639pub struct ListAuditEventsResponse {
640 pub events: Vec<AuditEvent>,
642 pub pagination: PaginationInfo,
644}
645
646#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
648pub struct StorageHealth {
649 pub backend: String,
650 pub status: String,
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
655pub struct PoolMetrics {
656 pub idle: u32,
658 pub active: u32,
660 pub max: u32,
662}
663
664#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
666pub struct HealthResponse {
667 pub status: String,
668 pub version: String,
669 pub storage: StorageHealth,
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub pool: Option<PoolMetrics>,
673}
674
675#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
677pub struct ApiError {
678 pub code: String,
679 pub message: String,
680 #[serde(skip_serializing_if = "Option::is_none")]
681 pub details: Option<serde_json::Value>,
682}
683
684impl ApiError {
685 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
686 Self {
687 code: code.into(),
688 message: message.into(),
689 details: None,
690 }
691 }
692 pub fn unauthorized(msg: &str) -> Self {
693 Self::new("unauthorized", msg)
694 }
695 pub fn forbidden(msg: &str) -> Self {
696 Self::new("forbidden", msg)
697 }
698 pub fn not_found(msg: &str) -> Self {
699 Self::new("not_found", msg)
700 }
701 pub fn bad_request(msg: &str) -> Self {
702 Self::new("bad_request", msg)
703 }
704 pub fn conflict(msg: &str) -> Self {
705 Self::new("conflict", msg)
706 }
707 pub fn internal_error(msg: &str) -> Self {
708 Self::new("internal_error", msg)
709 }
710 pub fn internal(msg: &str) -> Self {
711 Self::internal_error(msg)
712 }
713 pub fn validation(msg: &str) -> Self {
714 Self::new("invalid_input", msg)
715 }
716 pub fn already_exists(msg: &str) -> Self {
717 Self::new("conflict", msg)
718 }
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
725pub struct CreateKeyRequest {
726 pub description: String,
728 pub role: perfgate_auth::Role,
730 pub project: String,
732 #[serde(skip_serializing_if = "Option::is_none")]
734 pub pattern: Option<String>,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 pub expires_at: Option<DateTime<Utc>>,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
742pub struct CreateKeyResponse {
743 pub id: String,
745 pub key: String,
747 pub description: String,
749 pub role: perfgate_auth::Role,
751 pub project: String,
753 #[serde(skip_serializing_if = "Option::is_none")]
755 pub pattern: Option<String>,
756 pub created_at: DateTime<Utc>,
758 #[serde(skip_serializing_if = "Option::is_none")]
760 pub expires_at: Option<DateTime<Utc>>,
761}
762
763#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
765pub struct KeyEntry {
766 pub id: String,
768 pub key_prefix: String,
770 pub description: String,
772 pub role: perfgate_auth::Role,
774 pub project: String,
776 #[serde(skip_serializing_if = "Option::is_none")]
778 pub pattern: Option<String>,
779 pub created_at: DateTime<Utc>,
781 #[serde(skip_serializing_if = "Option::is_none")]
783 pub expires_at: Option<DateTime<Utc>>,
784 #[serde(skip_serializing_if = "Option::is_none")]
786 pub revoked_at: Option<DateTime<Utc>>,
787}
788
789#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
791pub struct ListKeysResponse {
792 pub keys: Vec<KeyEntry>,
794}
795
796#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
798pub struct RevokeKeyResponse {
799 pub id: String,
801 pub revoked_at: DateTime<Utc>,
803}
804
805pub const DEPENDENCY_EVENT_SCHEMA_V1: &str = "perfgate.dependency_event.v1";
811
812pub const FLEET_ALERT_SCHEMA_V1: &str = "perfgate.fleet_alert.v1";
814
815#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
817pub struct DependencyChange {
818 pub name: String,
820 #[serde(skip_serializing_if = "Option::is_none")]
822 pub old_version: Option<String>,
823 #[serde(skip_serializing_if = "Option::is_none")]
825 pub new_version: Option<String>,
826}
827
828#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
830pub struct DependencyEvent {
831 pub schema: String,
833 pub id: String,
835 pub project: String,
837 pub benchmark: String,
839 pub dep_name: String,
841 #[serde(skip_serializing_if = "Option::is_none")]
843 pub old_version: Option<String>,
844 #[serde(skip_serializing_if = "Option::is_none")]
846 pub new_version: Option<String>,
847 pub metric: String,
849 pub delta_pct: f64,
851 pub created_at: DateTime<Utc>,
853}
854
855#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
857pub struct RecordDependencyEventRequest {
858 pub project: String,
860 pub benchmark: String,
862 pub dependency_changes: Vec<DependencyChange>,
864 pub metric: String,
866 pub delta_pct: f64,
868}
869
870#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
872pub struct RecordDependencyEventResponse {
873 pub recorded: usize,
875}
876
877#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
879pub struct AffectedProject {
880 pub project: String,
882 pub benchmark: String,
884 pub metric: String,
886 pub delta_pct: f64,
888}
889
890#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
892pub struct FleetAlert {
893 pub schema: String,
895 pub id: String,
897 pub dependency: String,
899 #[serde(skip_serializing_if = "Option::is_none")]
901 pub old_version: Option<String>,
902 #[serde(skip_serializing_if = "Option::is_none")]
904 pub new_version: Option<String>,
905 pub affected_projects: Vec<AffectedProject>,
907 pub confidence: f64,
909 pub avg_delta_pct: f64,
911 pub first_seen: DateTime<Utc>,
913}
914
915#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
917pub struct ListFleetAlertsQuery {
918 #[serde(default = "default_min_affected")]
920 pub min_affected: usize,
921 pub since: Option<DateTime<Utc>>,
923 #[serde(default = "default_limit")]
925 pub limit: u32,
926}
927
928impl Default for ListFleetAlertsQuery {
929 fn default() -> Self {
930 Self {
931 min_affected: default_min_affected(),
932 since: None,
933 limit: default_limit(),
934 }
935 }
936}
937
938fn default_min_affected() -> usize {
939 2
940}
941
942#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
944pub struct ListFleetAlertsResponse {
945 pub alerts: Vec<FleetAlert>,
946}
947
948#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
950pub struct DependencyImpactQuery {
951 pub since: Option<DateTime<Utc>>,
953 #[serde(default = "default_limit")]
955 pub limit: u32,
956}
957
958impl Default for DependencyImpactQuery {
959 fn default() -> Self {
960 Self {
961 since: None,
962 limit: default_limit(),
963 }
964 }
965}
966
967#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
969pub struct DependencyImpactResponse {
970 pub dependency: String,
972 pub events: Vec<DependencyEvent>,
974 pub project_count: usize,
976 pub avg_delta_pct: f64,
978}
979
980#[cfg(feature = "server")]
981impl axum::response::IntoResponse for ApiError {
982 fn into_response(self) -> axum::response::Response {
983 let status = match self.code.as_str() {
984 "bad_request" | "invalid_input" => http::StatusCode::BAD_REQUEST,
985 "unauthorized" => http::StatusCode::UNAUTHORIZED,
986 "forbidden" => http::StatusCode::FORBIDDEN,
987 "not_found" => http::StatusCode::NOT_FOUND,
988 "conflict" => http::StatusCode::CONFLICT,
989 _ => http::StatusCode::INTERNAL_SERVER_ERROR,
990 };
991 (status, axum::Json(self)).into_response()
992 }
993}