1use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7#[cfg(feature = "schemars")]
8use schemars::JsonSchema;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::{
14 ArtifactRef, BundleId, CollectionId, ComponentRef, DistributorRef, EnvironmentRef,
15 MetadataRecordRef, PackId, PackRef, SemverReq, StoreFrontId, StorePlanId, StoreProductId,
16 SubscriptionId, TenantCtx,
17};
18
19#[derive(Clone, Debug, PartialEq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[cfg_attr(feature = "schemars", derive(JsonSchema))]
23pub struct Theme {
24 pub primary_color: String,
26 pub secondary_color: String,
28 pub accent_color: String,
30 pub background_color: String,
32 pub text_color: String,
34 pub font_family: String,
36 #[cfg_attr(
38 feature = "serde",
39 serde(default, skip_serializing_if = "Option::is_none")
40 )]
41 pub logo_url: Option<String>,
42 #[cfg_attr(
44 feature = "serde",
45 serde(default, skip_serializing_if = "Option::is_none")
46 )]
47 pub favicon_url: Option<String>,
48 #[cfg_attr(
50 feature = "serde",
51 serde(default, skip_serializing_if = "Option::is_none")
52 )]
53 pub hero_image_url: Option<String>,
54 #[cfg_attr(
56 feature = "serde",
57 serde(default, skip_serializing_if = "Option::is_none")
58 )]
59 pub hero_title: Option<String>,
60 #[cfg_attr(
62 feature = "serde",
63 serde(default, skip_serializing_if = "Option::is_none")
64 )]
65 pub hero_subtitle: Option<String>,
66 #[cfg_attr(
68 feature = "serde",
69 serde(default, skip_serializing_if = "Option::is_none")
70 )]
71 pub card_radius: Option<u8>,
72 #[cfg_attr(
74 feature = "serde",
75 serde(default, skip_serializing_if = "Option::is_none")
76 )]
77 pub card_elevation: Option<u8>,
78 #[cfg_attr(
80 feature = "serde",
81 serde(default, skip_serializing_if = "Option::is_none")
82 )]
83 pub button_style: Option<String>,
84}
85
86impl Default for Theme {
87 fn default() -> Self {
88 Self {
89 primary_color: "#0f766e".into(),
90 secondary_color: "#134e4a".into(),
91 accent_color: "#10b981".into(),
92 background_color: "#ffffff".into(),
93 text_color: "#0f172a".into(),
94 font_family: "Inter, sans-serif".into(),
95 logo_url: None,
96 favicon_url: None,
97 hero_image_url: None,
98 hero_title: None,
99 hero_subtitle: None,
100 card_radius: None,
101 card_elevation: None,
102 button_style: None,
103 }
104 }
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, Hash)]
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
111#[cfg_attr(feature = "schemars", derive(JsonSchema))]
112pub enum LayoutSectionKind {
113 Hero,
115 FeaturedCollection,
117 Grid,
119 Cta,
121 Custom(String),
123}
124
125#[derive(Clone, Debug, PartialEq)]
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
128#[cfg_attr(feature = "schemars", derive(JsonSchema))]
129pub struct LayoutSection {
130 pub id: String,
132 pub kind: LayoutSectionKind,
134 #[cfg_attr(
136 feature = "serde",
137 serde(default, skip_serializing_if = "Option::is_none")
138 )]
139 pub collection_id: Option<CollectionId>,
140 #[cfg_attr(
142 feature = "serde",
143 serde(default, skip_serializing_if = "Option::is_none")
144 )]
145 pub title: Option<String>,
146 #[cfg_attr(
148 feature = "serde",
149 serde(default, skip_serializing_if = "Option::is_none")
150 )]
151 pub subtitle: Option<String>,
152 pub sort_order: i32,
154 #[cfg_attr(feature = "serde", serde(default))]
156 pub metadata: BTreeMap<String, Value>,
157}
158
159#[derive(Clone, Debug, PartialEq)]
161#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
162#[cfg_attr(feature = "schemars", derive(JsonSchema))]
163pub struct Collection {
164 pub id: CollectionId,
166 pub storefront_id: StoreFrontId,
168 pub title: String,
170 #[cfg_attr(
172 feature = "serde",
173 serde(default, skip_serializing_if = "Vec::is_empty")
174 )]
175 pub product_ids: Vec<StoreProductId>,
176 #[cfg_attr(
178 feature = "serde",
179 serde(default, skip_serializing_if = "Option::is_none")
180 )]
181 pub slug: Option<String>,
182 #[cfg_attr(
184 feature = "serde",
185 serde(default, skip_serializing_if = "Option::is_none")
186 )]
187 pub description: Option<String>,
188 pub sort_order: i32,
190 #[cfg_attr(feature = "serde", serde(default))]
192 pub metadata: BTreeMap<String, Value>,
193}
194
195#[derive(Clone, Debug, PartialEq)]
197#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
198#[cfg_attr(feature = "schemars", derive(JsonSchema))]
199pub struct ProductOverride {
200 pub storefront_id: StoreFrontId,
202 pub product_id: StoreProductId,
204 #[cfg_attr(
206 feature = "serde",
207 serde(default, skip_serializing_if = "Option::is_none")
208 )]
209 pub display_name: Option<String>,
210 #[cfg_attr(
212 feature = "serde",
213 serde(default, skip_serializing_if = "Option::is_none")
214 )]
215 pub short_description: Option<String>,
216 #[cfg_attr(
218 feature = "serde",
219 serde(default, skip_serializing_if = "Vec::is_empty")
220 )]
221 pub badges: Vec<String>,
222 #[cfg_attr(feature = "serde", serde(default))]
224 pub metadata: BTreeMap<String, Value>,
225}
226
227#[derive(Clone, Debug, PartialEq)]
229#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
230#[cfg_attr(feature = "schemars", derive(JsonSchema))]
231pub struct StoreFront {
232 pub id: StoreFrontId,
234 pub slug: String,
236 pub name: String,
238 #[cfg_attr(feature = "serde", serde(default))]
240 pub theme: Theme,
241 #[cfg_attr(
243 feature = "serde",
244 serde(default, skip_serializing_if = "Vec::is_empty")
245 )]
246 pub sections: Vec<LayoutSection>,
247 #[cfg_attr(
249 feature = "serde",
250 serde(default, skip_serializing_if = "Vec::is_empty")
251 )]
252 pub collections: Vec<Collection>,
253 #[cfg_attr(
255 feature = "serde",
256 serde(default, skip_serializing_if = "Vec::is_empty")
257 )]
258 pub overrides: Vec<ProductOverride>,
259 #[cfg_attr(
261 feature = "serde",
262 serde(default, skip_serializing_if = "Option::is_none")
263 )]
264 pub worker_id: Option<String>,
265 #[cfg_attr(feature = "serde", serde(default))]
267 pub metadata: BTreeMap<String, Value>,
268}
269
270#[derive(Clone, Debug, PartialEq, Eq, Hash)]
272#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
273#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
274#[cfg_attr(feature = "schemars", derive(JsonSchema))]
275pub enum StoreProductKind {
276 Component,
278 Flow,
280 Pack,
282}
283
284#[derive(Clone, Debug, PartialEq, Eq, Hash)]
286#[cfg_attr(feature = "schemars", derive(JsonSchema))]
287pub enum VersionStrategy {
288 Latest,
290 Pinned {
292 requirement: SemverReq,
294 },
295 Lts,
297 Custom(String),
299 Fixed {
301 version: String,
303 },
304 Range {
306 range: String,
308 },
309 Channel {
311 channel: String,
313 },
314 CustomTagged {
316 value: String,
318 },
319}
320
321#[cfg(feature = "serde")]
322impl Serialize for VersionStrategy {
323 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
324 where
325 S: serde::Serializer,
326 {
327 #[derive(Serialize)]
328 #[serde(rename_all = "snake_case")]
329 enum Legacy<'a> {
330 Latest,
331 Pinned { requirement: &'a SemverReq },
332 Lts,
333 Custom(&'a str),
334 }
335
336 #[derive(Serialize)]
337 struct Tagged<'a> {
338 #[serde(rename = "kind")]
339 kind: &'static str,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 version: Option<&'a String>,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 range: Option<&'a String>,
344 #[serde(skip_serializing_if = "Option::is_none")]
345 channel: Option<&'a String>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 value: Option<&'a String>,
348 }
349
350 match self {
351 VersionStrategy::Latest => Legacy::Latest.serialize(serializer),
352 VersionStrategy::Pinned { requirement } => {
353 Legacy::Pinned { requirement }.serialize(serializer)
354 }
355 VersionStrategy::Lts => Legacy::Lts.serialize(serializer),
356 VersionStrategy::Custom(value) => Legacy::Custom(value).serialize(serializer),
357 VersionStrategy::Fixed { version } => Tagged {
358 kind: "fixed",
359 version: Some(version),
360 range: None,
361 channel: None,
362 value: None,
363 }
364 .serialize(serializer),
365 VersionStrategy::Range { range } => Tagged {
366 kind: "range",
367 version: None,
368 range: Some(range),
369 channel: None,
370 value: None,
371 }
372 .serialize(serializer),
373 VersionStrategy::Channel { channel } => Tagged {
374 kind: "channel",
375 version: None,
376 range: None,
377 channel: Some(channel),
378 value: None,
379 }
380 .serialize(serializer),
381 VersionStrategy::CustomTagged { value } => Tagged {
382 kind: "custom",
383 version: None,
384 range: None,
385 channel: None,
386 value: Some(value),
387 }
388 .serialize(serializer),
389 }
390 }
391}
392
393#[cfg(feature = "serde")]
394impl<'de> Deserialize<'de> for VersionStrategy {
395 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
396 where
397 D: serde::Deserializer<'de>,
398 {
399 #[derive(Deserialize)]
400 #[serde(rename_all = "snake_case")]
401 enum Legacy {
402 Latest,
403 Pinned { requirement: SemverReq },
404 Lts,
405 Custom(String),
406 }
407
408 #[derive(Deserialize)]
409 #[serde(tag = "kind", rename_all = "snake_case")]
410 enum Tagged {
411 Latest,
412 Fixed { version: String },
413 Range { range: String },
414 Channel { channel: String },
415 Custom { value: String },
416 }
417
418 #[derive(Deserialize)]
419 #[serde(untagged)]
420 enum Wrapper {
421 Tagged(Tagged),
422 Legacy(Legacy),
423 }
424
425 match Wrapper::deserialize(deserializer)? {
426 Wrapper::Tagged(tagged) => match tagged {
427 Tagged::Latest => Ok(VersionStrategy::Latest),
428 Tagged::Fixed { version } => Ok(VersionStrategy::Fixed { version }),
429 Tagged::Range { range } => Ok(VersionStrategy::Range { range }),
430 Tagged::Channel { channel } => Ok(VersionStrategy::Channel { channel }),
431 Tagged::Custom { value } => Ok(VersionStrategy::CustomTagged { value }),
432 },
433 Wrapper::Legacy(legacy) => match legacy {
434 Legacy::Latest => Ok(VersionStrategy::Latest),
435 Legacy::Pinned { requirement } => Ok(VersionStrategy::Pinned { requirement }),
436 Legacy::Lts => Ok(VersionStrategy::Lts),
437 Legacy::Custom(value) => Ok(VersionStrategy::Custom(value)),
438 },
439 }
440 }
441}
442
443pub type CapabilityMap = BTreeMap<String, Vec<String>>;
445
446#[derive(Clone, Debug, PartialEq)]
448#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
449#[cfg_attr(feature = "schemars", derive(JsonSchema))]
450pub struct StoreProduct {
451 pub id: StoreProductId,
453 pub kind: StoreProductKind,
455 pub name: String,
457 pub slug: String,
459 pub description: String,
461 pub source_repo: crate::RepoRef,
463 #[cfg_attr(
465 feature = "serde",
466 serde(default, skip_serializing_if = "Option::is_none")
467 )]
468 pub component_ref: Option<ComponentRef>,
469 #[cfg_attr(
471 feature = "serde",
472 serde(default, skip_serializing_if = "Option::is_none")
473 )]
474 pub pack_ref: Option<PackId>,
475 #[cfg_attr(
477 feature = "serde",
478 serde(default, skip_serializing_if = "Option::is_none")
479 )]
480 pub category: Option<String>,
481 #[cfg_attr(
483 feature = "serde",
484 serde(default, skip_serializing_if = "Vec::is_empty")
485 )]
486 pub tags: Vec<String>,
487 #[cfg_attr(feature = "serde", serde(default))]
489 pub capabilities: CapabilityMap,
490 pub version_strategy: VersionStrategy,
492 #[cfg_attr(
494 feature = "serde",
495 serde(default, skip_serializing_if = "Option::is_none")
496 )]
497 pub default_plan_id: Option<StorePlanId>,
498 pub is_free: bool,
500 #[cfg_attr(feature = "serde", serde(default))]
502 pub metadata: BTreeMap<String, Value>,
503}
504
505#[derive(Clone, Debug, PartialEq, Eq, Hash)]
507#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
508#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
509#[cfg_attr(feature = "schemars", derive(JsonSchema))]
510pub enum PriceModel {
511 Free,
513 Flat {
515 amount_micro: u64,
517 period_days: u16,
519 },
520 Metered {
522 included_units: u64,
524 overage_rate_micro: u64,
526 unit_label: String,
528 },
529 Enterprise {
531 description: String,
533 },
534}
535
536#[derive(Clone, Debug, PartialEq, Eq, Default)]
538#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
539#[cfg_attr(feature = "schemars", derive(JsonSchema))]
540pub struct PlanLimits {
541 #[cfg_attr(
543 feature = "serde",
544 serde(default, skip_serializing_if = "Option::is_none")
545 )]
546 pub max_environments: Option<u32>,
547 #[cfg_attr(
549 feature = "serde",
550 serde(default, skip_serializing_if = "Option::is_none")
551 )]
552 pub max_subscriptions: Option<u32>,
553 #[cfg_attr(
555 feature = "serde",
556 serde(default, skip_serializing_if = "Option::is_none")
557 )]
558 pub monthly_units_included: Option<u64>,
559 #[cfg_attr(feature = "serde", serde(default))]
561 pub metadata: BTreeMap<String, Value>,
562}
563
564#[derive(Clone, Debug, PartialEq)]
566#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
567#[cfg_attr(feature = "schemars", derive(JsonSchema))]
568pub struct StorePlan {
569 pub id: StorePlanId,
571 pub name: String,
573 pub description: String,
575 pub price_model: PriceModel,
577 #[cfg_attr(feature = "serde", serde(default))]
579 pub limits: PlanLimits,
580 #[cfg_attr(
582 feature = "serde",
583 serde(default, skip_serializing_if = "Vec::is_empty")
584 )]
585 pub tags: Vec<String>,
586 #[cfg_attr(feature = "serde", serde(default))]
588 pub metadata: BTreeMap<String, Value>,
589}
590
591#[derive(Clone, Debug, PartialEq, Eq, Hash)]
593#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
594#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
595#[cfg_attr(feature = "schemars", derive(JsonSchema))]
596pub enum SubscriptionStatus {
597 Draft,
599 Active,
601 Paused,
603 Cancelled,
605 Error,
607}
608
609#[derive(Clone, Debug, PartialEq)]
611#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
612#[cfg_attr(feature = "schemars", derive(JsonSchema))]
613pub struct Subscription {
614 pub id: SubscriptionId,
616 pub tenant_ctx: TenantCtx,
618 pub product_id: StoreProductId,
620 pub plan_id: StorePlanId,
622 #[cfg_attr(
624 feature = "serde",
625 serde(default, skip_serializing_if = "Option::is_none")
626 )]
627 pub environment_ref: Option<EnvironmentRef>,
628 #[cfg_attr(
630 feature = "serde",
631 serde(default, skip_serializing_if = "Option::is_none")
632 )]
633 pub distributor_ref: Option<DistributorRef>,
634 pub status: SubscriptionStatus,
636 #[cfg_attr(feature = "serde", serde(default))]
638 pub metadata: BTreeMap<String, Value>,
639}
640
641#[derive(Clone, Debug, PartialEq, Eq, Hash)]
643#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
644#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
645#[cfg_attr(feature = "schemars", derive(JsonSchema))]
646pub enum PackOrComponentRef {
647 Component(ComponentRef),
649 Pack(PackId),
651}
652
653#[derive(Clone, Debug, PartialEq, Eq)]
655#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
656#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
657#[cfg_attr(feature = "schemars", derive(JsonSchema))]
658pub enum ArtifactSelector {
659 Component(ComponentRef),
661 Pack(PackRef),
663}
664
665#[derive(Clone, Debug, PartialEq)]
667#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
668#[cfg_attr(feature = "schemars", derive(JsonSchema))]
669pub struct DesiredSubscriptionEntry {
670 pub selector: ArtifactSelector,
672 pub version_strategy: VersionStrategy,
674 #[cfg_attr(feature = "serde", serde(default))]
676 pub config_overrides: BTreeMap<String, Value>,
677 #[cfg_attr(
679 feature = "serde",
680 serde(default, skip_serializing_if = "Vec::is_empty")
681 )]
682 pub policy_tags: Vec<String>,
683 #[cfg_attr(feature = "serde", serde(default))]
685 pub metadata: BTreeMap<String, Value>,
686}
687
688#[derive(Clone, Debug, PartialEq)]
690#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
691#[cfg_attr(feature = "schemars", derive(JsonSchema))]
692pub struct DesiredState {
693 pub tenant: TenantCtx,
695 pub environment_ref: EnvironmentRef,
697 #[cfg_attr(
699 feature = "serde",
700 serde(default, skip_serializing_if = "Vec::is_empty")
701 )]
702 pub entries: Vec<DesiredSubscriptionEntry>,
703 pub version: u64,
705 #[cfg_attr(feature = "serde", serde(default))]
707 pub metadata: BTreeMap<String, Value>,
708}
709
710#[derive(Clone, Debug, PartialEq, Eq, Hash)]
712#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
713#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
714#[cfg_attr(feature = "schemars", derive(JsonSchema))]
715pub enum ConnectionKind {
716 Online,
718 Offline,
720}
721
722#[derive(Clone, Debug, PartialEq)]
724#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
725#[cfg_attr(feature = "schemars", derive(JsonSchema))]
726pub struct Environment {
727 pub id: EnvironmentRef,
729 pub tenant: TenantCtx,
731 pub name: String,
733 #[cfg_attr(feature = "serde", serde(default))]
735 pub labels: BTreeMap<String, String>,
736 pub distributor_ref: DistributorRef,
738 pub connection_kind: ConnectionKind,
740 #[cfg_attr(feature = "serde", serde(default))]
742 pub metadata: BTreeMap<String, Value>,
743}
744
745impl Environment {
746 pub fn new(
748 id: EnvironmentRef,
749 tenant: TenantCtx,
750 distributor_ref: DistributorRef,
751 connection_kind: ConnectionKind,
752 name: impl Into<String>,
753 ) -> Self {
754 Self {
755 id,
756 tenant,
757 name: name.into(),
758 distributor_ref,
759 connection_kind,
760 labels: BTreeMap::new(),
761 metadata: BTreeMap::new(),
762 }
763 }
764}
765
766#[derive(Clone, Debug, PartialEq, Eq, Hash)]
768#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
769#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
770#[cfg_attr(feature = "schemars", derive(JsonSchema))]
771pub enum RolloutState {
772 Pending,
774 Planning,
776 InProgress,
778 Succeeded,
780 Failed,
782 Blocked,
784}
785
786#[derive(Clone, Debug, PartialEq)]
788#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
789#[cfg_attr(feature = "schemars", derive(JsonSchema))]
790pub struct RolloutStatus {
791 pub environment_ref: EnvironmentRef,
793 #[cfg_attr(
795 feature = "serde",
796 serde(default, skip_serializing_if = "Option::is_none")
797 )]
798 pub desired_state_version: Option<u64>,
799 pub state: RolloutState,
801 #[cfg_attr(
803 feature = "serde",
804 serde(default, skip_serializing_if = "Option::is_none")
805 )]
806 pub bundle_id: Option<BundleId>,
807 #[cfg_attr(
809 feature = "serde",
810 serde(default, skip_serializing_if = "Option::is_none")
811 )]
812 pub message: Option<String>,
813 #[cfg_attr(feature = "serde", serde(default))]
815 pub metadata: BTreeMap<String, Value>,
816}
817
818#[derive(Clone, Debug, PartialEq)]
820#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
821#[cfg_attr(feature = "schemars", derive(JsonSchema))]
822pub struct BundleSpec {
823 pub bundle_id: BundleId,
825 pub tenant: TenantCtx,
827 pub environment_ref: EnvironmentRef,
829 pub desired_state_version: u64,
831 #[cfg_attr(
833 feature = "serde",
834 serde(default, skip_serializing_if = "Vec::is_empty")
835 )]
836 pub artifact_refs: Vec<ArtifactRef>,
837 #[cfg_attr(
839 feature = "serde",
840 serde(default, skip_serializing_if = "Vec::is_empty")
841 )]
842 pub metadata_refs: Vec<MetadataRecordRef>,
843 #[cfg_attr(feature = "serde", serde(default))]
845 pub additional_metadata: BTreeMap<String, Value>,
846}
847
848#[derive(Clone, Debug, PartialEq)]
850#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
851#[cfg_attr(feature = "schemars", derive(JsonSchema))]
852pub struct DesiredStateExportSpec {
853 pub tenant: TenantCtx,
855 pub environment_ref: EnvironmentRef,
857 pub desired_state_version: u64,
859 #[cfg_attr(feature = "serde", serde(default))]
861 pub include_artifacts: bool,
862 #[cfg_attr(feature = "serde", serde(default))]
864 pub include_metadata: bool,
865 #[cfg_attr(feature = "serde", serde(default))]
867 pub metadata: BTreeMap<String, Value>,
868}