1use crate::{BackendError, BackendErrorKind};
2use std::collections::BTreeMap;
3
4pub type MetadataMap = BTreeMap<String, String>;
5pub type Headers = BTreeMap<String, String>;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct CustomerPluginDescriptor {
9 pub id: String,
10 pub display_name: String,
11 pub version: String,
12 pub documentation_url: Option<String>,
13}
14
15impl CustomerPluginDescriptor {
16 pub fn new(
17 id: impl Into<String>,
18 display_name: impl Into<String>,
19 version: impl Into<String>,
20 ) -> Self {
21 Self {
22 id: id.into(),
23 display_name: display_name.into(),
24 version: version.into(),
25 documentation_url: None,
26 }
27 }
28
29 pub fn with_documentation_url(mut self, documentation_url: impl Into<String>) -> Self {
30 self.documentation_url = Some(documentation_url.into());
31 self
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct CustomerAppContext {
37 pub app_id: String,
38 pub environment: String,
39 pub site_id: Option<String>,
40 pub locale: Option<String>,
41}
42
43impl CustomerAppContext {
44 pub fn new(app_id: impl Into<String>, environment: impl Into<String>) -> Self {
45 Self {
46 app_id: app_id.into(),
47 environment: environment.into(),
48 site_id: None,
49 locale: None,
50 }
51 }
52
53 pub fn with_site_id(mut self, site_id: impl Into<String>) -> Self {
54 self.site_id = Some(site_id.into());
55 self
56 }
57
58 pub fn with_locale(mut self, locale: impl Into<String>) -> Self {
59 self.locale = Some(locale.into());
60 self
61 }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum PrincipalKind {
66 Anonymous,
67 User,
68 ServiceAccount,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct PrincipalContext {
73 pub kind: PrincipalKind,
74 pub id: Option<String>,
75}
76
77impl PrincipalContext {
78 pub fn anonymous() -> Self {
79 Self {
80 kind: PrincipalKind::Anonymous,
81 id: None,
82 }
83 }
84
85 pub fn user(id: impl Into<String>) -> Self {
86 Self {
87 kind: PrincipalKind::User,
88 id: Some(id.into()),
89 }
90 }
91
92 pub fn service_account(id: impl Into<String>) -> Self {
93 Self {
94 kind: PrincipalKind::ServiceAccount,
95 id: Some(id.into()),
96 }
97 }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct TraceContext {
102 pub trace_id: String,
103 pub request_id: Option<String>,
104}
105
106impl TraceContext {
107 pub fn new(trace_id: impl Into<String>) -> Self {
108 Self {
109 trace_id: trace_id.into(),
110 request_id: None,
111 }
112 }
113
114 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
115 self.request_id = Some(request_id.into());
116 self
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct RequestContext {
122 pub customer_app: CustomerAppContext,
123 pub principal: PrincipalContext,
124 pub trace: TraceContext,
125}
126
127impl RequestContext {
128 pub fn new(
129 customer_app: CustomerAppContext,
130 principal: PrincipalContext,
131 trace: TraceContext,
132 ) -> Self {
133 Self {
134 customer_app,
135 principal,
136 trace,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct MoneyAmount {
143 pub currency_code: String,
144 pub minor_units: i64,
145}
146
147impl MoneyAmount {
148 pub fn new(currency_code: impl Into<String>, minor_units: i64) -> Self {
149 Self {
150 currency_code: currency_code.into(),
151 minor_units,
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct OrderLineDraft {
158 pub sku: String,
159 pub title: String,
160 pub quantity: u32,
161 pub unit_price: MoneyAmount,
162 pub product_kind: String,
163 pub collection_handle: Option<String>,
164 pub entitlement_key: Option<String>,
165 pub metadata: MetadataMap,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct OrderDraft {
170 pub order_id: String,
171 pub currency_code: String,
172 pub subtotal: MoneyAmount,
173 pub total: MoneyAmount,
174 pub lines: Vec<OrderLineDraft>,
175 pub metadata: MetadataMap,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct OrderRejection {
180 pub code: String,
181 pub message: String,
182}
183
184impl OrderRejection {
185 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
186 Self {
187 code: code.into(),
188 message: message.into(),
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct OrderAdjustment {
195 pub reason: String,
196 pub metadata: MetadataMap,
197}
198
199impl OrderAdjustment {
200 pub fn new(reason: impl Into<String>) -> Self {
201 Self {
202 reason: reason.into(),
203 metadata: MetadataMap::new(),
204 }
205 }
206
207 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
208 self.metadata.insert(key.into(), value.into());
209 self
210 }
211
212 pub fn with_metadata_entries<I, K, V>(mut self, entries: I) -> Self
213 where
214 I: IntoIterator<Item = (K, V)>,
215 K: Into<String>,
216 V: Into<String>,
217 {
218 for (key, value) in entries {
219 self.metadata.insert(key.into(), value.into());
220 }
221 self
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub enum OrderReviewDecision {
227 Approved,
228 Rejected(OrderRejection),
229 Adjusted(OrderAdjustment),
230}
231
232impl OrderReviewDecision {
233 pub const fn approved() -> Self {
234 Self::Approved
235 }
236
237 pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
238 Self::Rejected(OrderRejection::new(code, message))
239 }
240
241 pub fn adjusted(reason: impl Into<String>) -> Self {
242 Self::Adjusted(OrderAdjustment::new(reason))
243 }
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct CmsPageDraft {
248 pub page_id: String,
249 pub slug: String,
250 pub title: String,
251 pub summary: String,
252 pub body_html: String,
253 pub locale: Option<String>,
254 pub metadata: MetadataMap,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum CmsPublishDecision {
259 Allow,
260 Reject { code: String, message: String },
261}
262
263impl CmsPublishDecision {
264 pub fn reject(code: impl Into<String>, message: impl Into<String>) -> Self {
265 Self::Reject {
266 code: code.into(),
267 message: message.into(),
268 }
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
273pub struct VerifiedWebhook {
274 pub source: String,
275 pub event: String,
276 pub headers: Headers,
277 pub content_type: Option<String>,
278 pub payload: Vec<u8>,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
282pub enum WebhookHandlingResult {
283 Accepted { detail: Option<String> },
284 Rejected { code: String, message: String },
285}
286
287impl WebhookHandlingResult {
288 pub fn accepted(detail: Option<String>) -> Self {
289 Self::Accepted { detail }
290 }
291
292 pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
293 Self::Rejected {
294 code: code.into(),
295 message: message.into(),
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct JobRequest {
302 pub queue: String,
303 pub job_name: String,
304 pub idempotency_key: Option<String>,
305 pub payload_description: String,
306 pub metadata: MetadataMap,
307}
308
309impl JobRequest {
310 pub fn new(
311 queue: impl Into<String>,
312 job_name: impl Into<String>,
313 payload_description: impl Into<String>,
314 ) -> Self {
315 Self {
316 queue: queue.into(),
317 job_name: job_name.into(),
318 idempotency_key: None,
319 payload_description: payload_description.into(),
320 metadata: MetadataMap::new(),
321 }
322 }
323
324 pub fn with_idempotency_key(mut self, idempotency_key: impl Into<String>) -> Self {
325 self.idempotency_key = Some(idempotency_key.into());
326 self
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub struct JobReceipt {
332 pub queue: String,
333 pub job_id: String,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct RepositoryQuery {
338 pub repository: String,
339 pub key: Option<String>,
340 pub filters: MetadataMap,
341}
342
343impl RepositoryQuery {
344 pub fn new(repository: impl Into<String>) -> Self {
345 Self {
346 repository: repository.into(),
347 key: None,
348 filters: MetadataMap::new(),
349 }
350 }
351
352 pub fn with_key(mut self, key: impl Into<String>) -> Self {
353 self.key = Some(key.into());
354 self
355 }
356
357 pub fn with_filter(mut self, field: impl Into<String>, value: impl Into<String>) -> Self {
358 self.filters.insert(field.into(), value.into());
359 self
360 }
361}
362
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct RepositoryRecord {
365 pub id: String,
366 pub fields: MetadataMap,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq)]
370pub struct RepositoryRecordSet {
371 pub repository: String,
372 pub records: Vec<RepositoryRecord>,
373}
374
375#[derive(Debug, Clone, PartialEq, Eq)]
376pub struct RepositoryWrite {
377 pub repository: String,
378 pub record_id: String,
379 pub fields: MetadataMap,
380}
381
382#[derive(Debug, Clone, PartialEq, Eq)]
383pub struct RepositoryWriteReceipt {
384 pub repository: String,
385 pub record_id: String,
386 pub version: Option<String>,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq)]
390pub struct CmsPageRecord {
391 pub page_id: String,
392 pub title: String,
393 pub slug: String,
394 pub summary: String,
395 pub body_html: String,
396 pub status: String,
397 pub live_path: Option<String>,
398}
399
400impl CmsPageRecord {
401 pub const REPOSITORY: &'static str = "cms.pages";
402
403 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
404 Ok(Self {
405 page_id: record.id.clone(),
406 title: required_repository_field(record, "title")?,
407 slug: required_repository_field(record, "slug")?,
408 summary: required_repository_field(record, "summary")?,
409 body_html: required_repository_field(record, "body_html")?,
410 status: required_repository_field(record, "status")?,
411 live_path: optional_repository_field(record, "live_path"),
412 })
413 }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq)]
417pub struct CmsPageUpdate {
418 pub page_id: String,
419 pub title: String,
420 pub slug: String,
421 pub summary: String,
422 pub body_html: String,
423}
424
425impl CmsPageUpdate {
426 pub fn new(
427 page_id: impl Into<String>,
428 title: impl Into<String>,
429 slug: impl Into<String>,
430 summary: impl Into<String>,
431 body_html: impl Into<String>,
432 ) -> Self {
433 Self {
434 page_id: page_id.into(),
435 title: title.into(),
436 slug: slug.into(),
437 summary: summary.into(),
438 body_html: body_html.into(),
439 }
440 }
441}
442
443#[derive(Debug, Clone, PartialEq, Eq)]
444pub struct CmsNavigationRecord {
445 pub record_id: usize,
446 pub label: String,
447 pub href: String,
448}
449
450impl CmsNavigationRecord {
451 pub const REPOSITORY: &'static str = "cms.navigation";
452
453 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
454 Ok(Self {
455 record_id: record.id.parse::<usize>().map_err(|_| {
456 BackendError::new(
457 BackendErrorKind::Conflict,
458 "repository.record.invalid_navigation_id",
459 format!(
460 "Repository record `{}` was not a valid CMS navigation record id.",
461 record.id
462 ),
463 )
464 })?,
465 label: required_repository_field(record, "label")?,
466 href: required_repository_field(record, "href")?,
467 })
468 }
469}
470
471#[derive(Debug, Clone, PartialEq, Eq)]
472pub struct CmsNavigationUpdate {
473 pub record_id: usize,
474 pub label: String,
475 pub href: String,
476}
477
478impl CmsNavigationUpdate {
479 pub fn new(record_id: usize, label: impl Into<String>, href: impl Into<String>) -> Self {
480 Self {
481 record_id,
482 label: label.into(),
483 href: href.into(),
484 }
485 }
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
489pub struct CmsNavigationAppend {
490 pub label: String,
491 pub href: String,
492}
493
494impl CmsNavigationAppend {
495 pub fn new(label: impl Into<String>, href: impl Into<String>) -> Self {
496 Self {
497 label: label.into(),
498 href: href.into(),
499 }
500 }
501}
502
503#[derive(Debug, Clone, PartialEq, Eq)]
504pub struct CmsRedirectRecord {
505 pub record_id: usize,
506 pub from: String,
507 pub to: String,
508 pub permanent: bool,
509}
510
511impl CmsRedirectRecord {
512 pub const REPOSITORY: &'static str = "cms.redirects";
513
514 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
515 Ok(Self {
516 record_id: record.id.parse::<usize>().map_err(|_| {
517 BackendError::new(
518 BackendErrorKind::Conflict,
519 "repository.record.invalid_redirect_id",
520 format!(
521 "Repository record `{}` was not a valid CMS redirect record id.",
522 record.id
523 ),
524 )
525 })?,
526 from: required_repository_field(record, "from")?,
527 to: required_repository_field(record, "to")?,
528 permanent: required_repository_bool_field(record, "permanent")?,
529 })
530 }
531}
532
533#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct CmsRedirectUpdate {
535 pub record_id: usize,
536 pub from: String,
537 pub to: String,
538 pub permanent: bool,
539}
540
541impl CmsRedirectUpdate {
542 pub fn new(
543 record_id: usize,
544 from: impl Into<String>,
545 to: impl Into<String>,
546 permanent: bool,
547 ) -> Self {
548 Self {
549 record_id,
550 from: from.into(),
551 to: to.into(),
552 permanent,
553 }
554 }
555}
556
557#[derive(Debug, Clone, PartialEq, Eq)]
558pub struct CmsRedirectAppend {
559 pub from: String,
560 pub to: String,
561 pub permanent: bool,
562}
563
564impl CmsRedirectAppend {
565 pub fn new(from: impl Into<String>, to: impl Into<String>, permanent: bool) -> Self {
566 Self {
567 from: from.into(),
568 to: to.into(),
569 permanent,
570 }
571 }
572}
573
574#[derive(Debug, Clone, PartialEq, Eq)]
575pub struct CommerceCatalogProductRecord {
576 pub handle: String,
577 pub sku: String,
578 pub title: String,
579 pub summary: String,
580 pub price_minor: i64,
581 pub currency: String,
582 pub collection_handle: String,
583 pub is_visible: bool,
584 pub product_kind: String,
585 pub entitlement_key: Option<String>,
586}
587
588impl CommerceCatalogProductRecord {
589 pub const REPOSITORY: &'static str = "commerce.catalog.products";
590
591 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
592 Ok(Self {
593 handle: required_repository_field(record, "handle")?,
594 sku: required_repository_field(record, "sku")?,
595 title: required_repository_field(record, "title")?,
596 summary: required_repository_field(record, "summary")?,
597 price_minor: required_repository_i64_field(record, "price_minor")?,
598 currency: required_repository_field(record, "currency")?,
599 collection_handle: required_repository_field(record, "collection_handle")?,
600 is_visible: required_repository_bool_field(record, "is_visible")?,
601 product_kind: required_repository_field(record, "product_kind")?,
602 entitlement_key: optional_repository_field(record, "entitlement_key"),
603 })
604 }
605}
606
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub struct CommerceCatalogProductUpdate {
609 pub handle: String,
610 pub title: String,
611 pub summary: String,
612 pub price_minor: i64,
613 pub collection_handle: String,
614 pub is_visible: bool,
615}
616
617impl CommerceCatalogProductUpdate {
618 pub fn new(
619 handle: impl Into<String>,
620 title: impl Into<String>,
621 summary: impl Into<String>,
622 price_minor: i64,
623 collection_handle: impl Into<String>,
624 is_visible: bool,
625 ) -> Self {
626 Self {
627 handle: handle.into(),
628 title: title.into(),
629 summary: summary.into(),
630 price_minor,
631 collection_handle: collection_handle.into(),
632 is_visible,
633 }
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq)]
638pub struct CommerceCatalogCollectionRecord {
639 pub handle: String,
640 pub title: String,
641 pub label: String,
642 pub summary: String,
643 pub is_visible: bool,
644}
645
646impl CommerceCatalogCollectionRecord {
647 pub const REPOSITORY: &'static str = "commerce.catalog.collections";
648
649 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
650 Ok(Self {
651 handle: required_repository_field(record, "handle")?,
652 title: required_repository_field(record, "title")?,
653 label: required_repository_field(record, "label")?,
654 summary: required_repository_field(record, "summary")?,
655 is_visible: required_repository_bool_field(record, "is_visible")?,
656 })
657 }
658}
659
660#[derive(Debug, Clone, PartialEq, Eq)]
661pub struct CommerceCatalogCollectionUpdate {
662 pub handle: String,
663 pub title: String,
664 pub label: String,
665 pub summary: String,
666 pub is_visible: bool,
667}
668
669impl CommerceCatalogCollectionUpdate {
670 pub fn new(
671 handle: impl Into<String>,
672 title: impl Into<String>,
673 label: impl Into<String>,
674 summary: impl Into<String>,
675 is_visible: bool,
676 ) -> Self {
677 Self {
678 handle: handle.into(),
679 title: title.into(),
680 label: label.into(),
681 summary: summary.into(),
682 is_visible,
683 }
684 }
685}
686
687#[derive(Debug, Clone, PartialEq, Eq)]
688pub struct CommerceOrderRecord {
689 pub order_id: String,
690 pub status: String,
691 pub payment_status: String,
692 pub payment_reference: Option<String>,
693 pub payment_method: Option<String>,
694 pub checkout_email: Option<String>,
695 pub principal_id: Option<String>,
696 pub currency: String,
697 pub total_minor: i64,
698 pub line_count: usize,
699}
700
701impl CommerceOrderRecord {
702 pub const REPOSITORY: &'static str = "commerce.orders";
703
704 pub fn from_repository_record(record: &RepositoryRecord) -> Result<Self, BackendError> {
705 Ok(Self {
706 order_id: record.id.clone(),
707 status: required_repository_field(record, "status")?,
708 payment_status: required_repository_field(record, "payment_status")?,
709 payment_reference: optional_repository_field(record, "payment_reference"),
710 payment_method: optional_repository_field(record, "payment_method"),
711 checkout_email: optional_repository_field(record, "checkout_email"),
712 principal_id: optional_repository_field(record, "principal_id"),
713 currency: required_repository_field(record, "currency")?,
714 total_minor: required_repository_i64_field(record, "total_minor")?,
715 line_count: required_repository_usize_field(record, "line_count")?,
716 })
717 }
718}
719
720fn required_repository_field(
721 record: &RepositoryRecord,
722 field: &str,
723) -> Result<String, BackendError> {
724 record.fields.get(field).cloned().ok_or_else(|| {
725 BackendError::new(
726 BackendErrorKind::Conflict,
727 "repository.record.missing_field",
728 format!(
729 "Repository record `{}` did not expose required field `{field}`.",
730 record.id
731 ),
732 )
733 })
734}
735
736fn optional_repository_field(record: &RepositoryRecord, field: &str) -> Option<String> {
737 record
738 .fields
739 .get(field)
740 .cloned()
741 .and_then(|value| (!value.trim().is_empty()).then_some(value))
742}
743
744fn required_repository_i64_field(
745 record: &RepositoryRecord,
746 field: &str,
747) -> Result<i64, BackendError> {
748 let raw = required_repository_field(record, field)?;
749 raw.parse::<i64>().map_err(|_| {
750 BackendError::new(
751 BackendErrorKind::Conflict,
752 "repository.record.invalid_i64",
753 format!(
754 "Repository record `{}` field `{field}` was not a valid integer.",
755 record.id
756 ),
757 )
758 })
759}
760
761fn required_repository_usize_field(
762 record: &RepositoryRecord,
763 field: &str,
764) -> Result<usize, BackendError> {
765 let raw = required_repository_field(record, field)?;
766 raw.parse::<usize>().map_err(|_| {
767 BackendError::new(
768 BackendErrorKind::Conflict,
769 "repository.record.invalid_usize",
770 format!(
771 "Repository record `{}` field `{field}` was not a valid positive count.",
772 record.id
773 ),
774 )
775 })
776}
777
778fn required_repository_bool_field(
779 record: &RepositoryRecord,
780 field: &str,
781) -> Result<bool, BackendError> {
782 let raw = required_repository_field(record, field)?;
783 match raw.trim().to_ascii_lowercase().as_str() {
784 "true" | "1" | "yes" | "on" => Ok(true),
785 "false" | "0" | "no" | "off" => Ok(false),
786 _ => Err(BackendError::new(
787 BackendErrorKind::Conflict,
788 "repository.record.invalid_bool",
789 format!(
790 "Repository record `{}` field `{field}` was not a valid boolean.",
791 record.id
792 ),
793 )),
794 }
795}
796
797#[derive(Debug, Clone, PartialEq, Eq)]
798pub struct AuthCheckRequest {
799 pub capability: String,
800 pub object: String,
801}
802
803#[derive(Debug, Clone, PartialEq, Eq)]
804pub struct AuthCheckResult {
805 pub allowed: bool,
806 pub explanation: Option<String>,
807}
808
809#[derive(Debug, Clone, PartialEq, Eq)]
810pub struct AuthExplainRequest {
811 pub capability: String,
812 pub object: String,
813}
814
815#[derive(Debug, Clone, PartialEq, Eq)]
816pub struct AuthExplanation {
817 pub summary: String,
818 pub traces: Vec<String>,
819}
820
821#[derive(Debug, Clone, PartialEq, Eq)]
822pub struct AuditEntry {
823 pub action: String,
824 pub resource_kind: String,
825 pub resource_id: String,
826 pub outcome: String,
827 pub detail: Option<String>,
828 pub metadata: MetadataMap,
829}
830
831impl AuditEntry {
832 pub fn new(
833 action: impl Into<String>,
834 resource_kind: impl Into<String>,
835 resource_id: impl Into<String>,
836 outcome: impl Into<String>,
837 ) -> Self {
838 Self {
839 action: action.into(),
840 resource_kind: resource_kind.into(),
841 resource_id: resource_id.into(),
842 outcome: outcome.into(),
843 detail: None,
844 metadata: MetadataMap::new(),
845 }
846 }
847
848 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
849 self.detail = Some(detail.into());
850 self
851 }
852}
853
854#[derive(Debug, Clone, PartialEq, Eq)]
855pub struct OutboundHttpRequest {
856 pub integration: String,
857 pub method: String,
858 pub url: String,
859 pub headers: Headers,
860 pub body: Vec<u8>,
861}
862
863#[derive(Debug, Clone, PartialEq, Eq)]
864pub struct OutboundHttpResponse {
865 pub status: u16,
866 pub headers: Headers,
867 pub body: Vec<u8>,
868}
869
870#[derive(Debug, Clone, PartialEq, Eq)]
871pub struct ManagedAsset {
872 pub logical_path: String,
873 pub storage_class: String,
874 pub public_url: Option<String>,
875}
876
877#[derive(Debug, Clone, PartialEq, Eq)]
878pub struct AssetWriteRequest {
879 pub logical_path: String,
880 pub storage_class: String,
881 pub content_type: Option<String>,
882 pub bytes: Vec<u8>,
883 pub metadata: MetadataMap,
884}
885
886#[derive(Debug, Clone, PartialEq, Eq)]
887pub struct AssetWriteReceipt {
888 pub logical_path: String,
889 pub storage_path: String,
890 pub bytes_written: u64,
891}
892
893#[derive(Debug, Clone, PartialEq, Eq)]
894pub struct CommerceProduct {
895 pub sku: String,
896 pub handle: String,
897 pub title: String,
898 pub current_price: MoneyAmount,
899 pub collection_handle: Option<String>,
900 pub metadata: MetadataMap,
901}