Skip to main content

thruput_contract_spec/
view.rs

1//! Borrowed/const equivalents of the model types.
2//!
3//! View types use `&'a str` and `&'a [T]` instead of `String` and `Vec<T>`,
4//! enabling compile-time spec generation via `const` evaluation.
5
6#[cfg(feature = "owned-model")]
7use crate::{
8    ContractEnumDef, ContractEnumVariant, ContractEventDef,
9    ContractExceptionType, ContractField, ContractSignedTokenPreimage,
10    ContractSpec, ContractSpecId, ContractSpecInterface, ContractSpecMethod,
11    ContractSpecString, ContractStructDef, ContractType, ContractTypeDef,
12    ContractUnionCase, ContractUnionCaseType, ContractUnionDef, SpecFragment,
13    SpecInterfaceAnchor, SpecInterfaceFragment, SpecMethodFragment,
14};
15#[cfg(feature = "owned-model")]
16use alloc::{boxed::Box, vec::Vec};
17
18use xdrs::*;
19
20#[cfg(feature = "owned-model")]
21fn to_spec_string(s: &str) -> ContractSpecString {
22    ContractSpecString::try_from(s).expect("spec string conversion")
23}
24
25/// Const-friendly view of a [`ContractSpecId`]: what a user-defined type or
26/// event is called in a spec.
27///
28/// The segments are carried apart because every segment is something the
29/// declaring macro already holds whole, so nothing here is parsed out of
30/// anything. Nor could a joined `CRATE@MAJOR::module::path::Name` form be
31/// relied on to split back: the SDK's macros refuse `::` in `name = "..."`,
32/// but nothing stops a hand-written `ContractTypeName` impl from putting one
33/// in `declared`.
34#[derive(
35    Debug,
36    Clone,
37    Copy,
38    PartialEq,
39    Eq,
40    PartialOrd,
41    Ord,
42    Hash,
43    ConstWriteXDR,
44    WriteXDR,
45)]
46pub struct ContractSpecIdView<'a> {
47    /// Declaring crate, as Cargo names the package: hyphenated.
48    pub crate_name: &'a str,
49    /// Leftmost non-zero component of the declaring crate's semver, or of the
50    /// item's own pinned `version = "..."`.
51    pub major: &'a str,
52    /// `module_path!()` at the declaration, verbatim, leading crate segment
53    /// and all -- so its spelling of the crate is the underscored Rust ident,
54    /// not `crate_name`'s.
55    pub module_path: &'a str,
56    /// The declared name, verbatim. The SDK's macros never put `::` in it,
57    /// but a hand-written `ContractTypeName` impl may.
58    pub declared: &'a str,
59}
60
61#[cfg(feature = "owned-model")]
62impl From<ContractSpecIdView<'_>> for ContractSpecId {
63    fn from(value: ContractSpecIdView<'_>) -> Self {
64        Self {
65            crate_name: to_spec_string(value.crate_name),
66            major: to_spec_string(value.major),
67            module_path: to_spec_string(value.module_path),
68            declared: to_spec_string(value.declared),
69        }
70    }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
74pub struct ContractSpecView<'a> {
75    pub interfaces: &'a [ContractSpecInterfaceView<'a>],
76    pub type_defs: &'a [ContractTypeDefView<'a>],
77    pub events: &'a [ContractEventDefView<'a>],
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
81pub struct ContractSpecInterfaceView<'a> {
82    pub name: &'a str,
83    pub version: &'a str,
84    pub throws: &'a [ContractExceptionTypeView<'a>],
85    pub methods: &'a [ContractSpecMethodView<'a>],
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
89pub struct ContractSpecMethodView<'a> {
90    pub name: &'a str,
91    pub args: &'a [ContractFieldView<'a>],
92    pub return_type: ContractTypeView<'a>,
93    pub effect: Option<ContractMethodEffectView>,
94}
95
96/// Const-friendly author declaration; not a compiler or runtime guarantee.
97/// Covers the complete invocation, including nested calls and event emission.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
99#[repr(i32)]
100pub enum ContractMethodEffectView {
101    /// Arguments and constants only, without state/context reads or effects.
102    Pure = 1,
103    /// May read state and execution context, without application effects.
104    ReadOnly = 2,
105    /// May produce application effects.
106    StateChanging = 3,
107}
108
109#[cfg(feature = "owned-model")]
110impl From<ContractMethodEffectView> for crate::ContractMethodEffect {
111    fn from(value: ContractMethodEffectView) -> Self {
112        Self(value as i32)
113    }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
117pub struct ContractFieldView<'a> {
118    pub name: &'a str,
119    pub ty: ContractTypeView<'a>,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
123#[repr(i32)]
124pub enum ContractTypeView<'a> {
125    Void = 0,
126    Bool = 1,
127    U32 = 2,
128    I32 = 3,
129    U64 = 4,
130    I64 = 5,
131    U128 = 6,
132    I128 = 7,
133    String = 8,
134    Bytes = 9,
135    Address = 10,
136    Option(&'a ContractTypeView<'a>) = 11,
137    Vector(ContractVectorTypeView<'a>) = 12,
138    Tuple(&'a [ContractTypeView<'a>]) = 13,
139    Named(ContractSpecIdView<'a>) = 14,
140    U8 = 15,
141    Array(ContractArrayTypeView<'a>) = 16,
142    /// A signed auth token whose preimage has the referenced type, or
143    /// `None` for a type-erased (opaque) payload. Encodes identically to
144    /// the owned model's `ContractSignedTokenPreimage` wrapper (a single
145    /// optional `ContractType` field). Consumes no calldata bytes; the
146    /// token handle arrives out of band.
147    SignedToken(Option<&'a ContractTypeView<'a>>) = 17,
148    U256 = 18,
149    I256 = 19,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
153pub struct ContractExceptionTypeView<'a> {
154    pub selector: u64,
155    pub ty: ContractTypeView<'a>,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
159pub struct ContractArrayTypeView<'a> {
160    pub element: &'a ContractTypeView<'a>,
161    pub len: u32,
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
165pub struct ContractVectorTypeView<'a> {
166    pub element: &'a ContractTypeView<'a>,
167    pub max_len: u32,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
171#[repr(i32)]
172pub enum ContractTypeDefView<'a> {
173    Struct(ContractStructDefView<'a>) = 0,
174    Enum(ContractEnumDefView<'a>) = 1,
175    Union(ContractUnionDefView<'a>) = 2,
176}
177
178impl<'a> ContractTypeDefView<'a> {
179    pub const fn symbol(self) -> ContractSpecIdView<'a> {
180        match self {
181            Self::Struct(def) => def.name,
182            Self::Enum(def) => def.name,
183            Self::Union(def) => def.name,
184        }
185    }
186
187    pub const fn type_ref(self) -> ContractTypeView<'a> {
188        ContractTypeView::Named(self.symbol())
189    }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
193pub struct ContractStructDefView<'a> {
194    pub name: ContractSpecIdView<'a>,
195    pub fields: &'a [ContractFieldView<'a>],
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
199pub struct ContractEnumDefView<'a> {
200    pub name: ContractSpecIdView<'a>,
201    pub variants: &'a [ContractEnumVariantView<'a>],
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
205pub struct ContractEnumVariantView<'a> {
206    pub name: &'a str,
207    pub value: i32,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
211pub struct ContractUnionDefView<'a> {
212    pub name: ContractSpecIdView<'a>,
213    pub cases: &'a [ContractUnionCaseView<'a>],
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
217pub struct ContractUnionCaseView<'a> {
218    pub name: &'a str,
219    pub discriminant: i32,
220    pub case_type: ContractUnionCaseTypeView<'a>,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
224#[repr(i32)]
225pub enum ContractUnionCaseTypeView<'a> {
226    Void = 0,
227    Tuple(&'a [ContractTypeView<'a>]) = 1,
228    Struct(&'a [ContractFieldView<'a>]) = 2,
229}
230
231/// Describes an event emitted via the `LogEvent` syscall. Mirrors the owned
232/// `ContractEventDef`: `signature` is topic 0, `topics` are the indexed
233/// fields (topics 1..), and `fields` form the XDR-encoded payload.
234#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
235pub struct ContractEventDefView<'a> {
236    pub name: ContractSpecIdView<'a>,
237    pub signature: [u8; 32],
238    pub topics: &'a [ContractFieldView<'a>],
239    pub fields: &'a [ContractFieldView<'a>],
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
243#[repr(i32)]
244pub enum SpecFragmentView<'a> {
245    Interface(SpecInterfaceFragmentView<'a>) = 0,
246    Method(SpecMethodFragmentView<'a>) = 1,
247    TypeDef(ContractTypeDefView<'a>) = 2,
248    ExportedTypeDef(ContractTypeDefView<'a>) = 3,
249    Event(ContractEventDefView<'a>) = 4,
250    ExportedEvent(ContractEventDefView<'a>) = 5,
251    InterfaceAnchor(SpecInterfaceAnchorView<'a>) = 6,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
255pub struct SpecInterfaceFragmentView<'a> {
256    pub interface_name: &'a str,
257    pub interface_version: &'a str,
258    pub declared_by: ContractSpecIdView<'a>,
259    pub throws: &'a [ContractExceptionTypeView<'a>],
260    /// Names of the events this interface declares via `emits(...)`.
261    /// References into the artifact's event-def pool; the post-processor
262    /// retains only event defs referenced by a surviving interface (or
263    /// force-exported via `#[event_spec(export)]`).
264    pub emits: &'a [ContractSpecIdView<'a>],
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
268pub struct SpecMethodFragmentView<'a> {
269    pub interface_name: &'a str,
270    pub interface_version: &'a str,
271    pub declared_by: ContractSpecIdView<'a>,
272    pub method: ContractSpecMethodView<'a>,
273}
274
275/// The "include this interface" marker an impl emits so the post-processor
276/// retains a trait's (otherwise candidate) interface spec. Mirrors
277/// `thruput_spec::SpecInterfaceAnchor`.
278#[derive(Debug, Clone, Copy, PartialEq, Eq, ConstWriteXDR, WriteXDR)]
279pub struct SpecInterfaceAnchorView<'a> {
280    pub interface_name: &'a str,
281    pub interface_version: &'a str,
282    pub declared_by: ContractSpecIdView<'a>,
283}
284
285#[cfg(feature = "owned-model")]
286impl From<ContractSpecView<'_>> for ContractSpec {
287    fn from(value: ContractSpecView<'_>) -> Self {
288        Self {
289            interfaces: value
290                .interfaces
291                .iter()
292                .copied()
293                .map(Into::into)
294                .collect::<Vec<_>>()
295                .try_into()
296                .unwrap(),
297            type_defs: value
298                .type_defs
299                .iter()
300                .copied()
301                .map(Into::into)
302                .collect::<Vec<_>>()
303                .try_into()
304                .unwrap(),
305            events: value
306                .events
307                .iter()
308                .copied()
309                .map(Into::into)
310                .collect::<Vec<_>>()
311                .try_into()
312                .unwrap(),
313        }
314    }
315}
316
317#[cfg(feature = "owned-model")]
318impl From<ContractEventDefView<'_>> for ContractEventDef {
319    fn from(value: ContractEventDefView<'_>) -> Self {
320        Self {
321            name: value.name.into(),
322            signature: value.signature.into(),
323            topics: value
324                .topics
325                .iter()
326                .copied()
327                .map(Into::into)
328                .collect::<Vec<_>>()
329                .try_into()
330                .unwrap(),
331            fields: value
332                .fields
333                .iter()
334                .copied()
335                .map(Into::into)
336                .collect::<Vec<_>>()
337                .try_into()
338                .unwrap(),
339        }
340    }
341}
342
343#[cfg(feature = "owned-model")]
344impl From<ContractExceptionTypeView<'_>> for ContractExceptionType {
345    fn from(value: ContractExceptionTypeView<'_>) -> Self {
346        Self {
347            selector: value.selector,
348            ty: value.ty.into(),
349        }
350    }
351}
352
353#[cfg(feature = "owned-model")]
354impl From<ContractSpecInterfaceView<'_>> for ContractSpecInterface {
355    fn from(value: ContractSpecInterfaceView<'_>) -> Self {
356        Self {
357            name: to_spec_string(value.name),
358            vers: to_spec_string(value.version),
359            throws: value
360                .throws
361                .iter()
362                .copied()
363                .map(Into::into)
364                .collect::<Vec<_>>()
365                .try_into()
366                .unwrap(),
367            methods: value
368                .methods
369                .iter()
370                .copied()
371                .map(Into::into)
372                .collect::<Vec<_>>()
373                .try_into()
374                .unwrap(),
375        }
376    }
377}
378
379#[cfg(feature = "owned-model")]
380impl From<ContractSpecMethodView<'_>> for ContractSpecMethod {
381    fn from(value: ContractSpecMethodView<'_>) -> Self {
382        Self {
383            name: to_spec_string(value.name),
384            args: value
385                .args
386                .iter()
387                .copied()
388                .map(Into::into)
389                .collect::<Vec<_>>()
390                .try_into()
391                .unwrap(),
392            return_type: value.return_type.into(),
393            effect: value.effect.map(Into::into),
394        }
395    }
396}
397
398#[cfg(feature = "owned-model")]
399impl From<ContractFieldView<'_>> for ContractField {
400    fn from(value: ContractFieldView<'_>) -> Self {
401        Self {
402            name: to_spec_string(value.name),
403            ty: value.ty.into(),
404        }
405    }
406}
407
408#[cfg(feature = "owned-model")]
409impl From<ContractTypeView<'_>> for ContractType {
410    fn from(value: ContractTypeView<'_>) -> Self {
411        match value {
412            ContractTypeView::Void => ContractType::Void,
413            ContractTypeView::Bool => ContractType::Bool,
414            ContractTypeView::U32 => ContractType::U32,
415            ContractTypeView::I32 => ContractType::I32,
416            ContractTypeView::U64 => ContractType::U64,
417            ContractTypeView::I64 => ContractType::I64,
418            ContractTypeView::U128 => ContractType::U128,
419            ContractTypeView::I128 => ContractType::I128,
420            ContractTypeView::String => ContractType::String,
421            ContractTypeView::Bytes => ContractType::Bytes,
422            ContractTypeView::Address => ContractType::Address,
423            ContractTypeView::Option(inner) => ContractType::Option {
424                option_inner: Box::new((*inner).into()),
425            },
426            ContractTypeView::Vector(inner) => ContractType::Vector {
427                vector: inner.into(),
428            },
429            ContractTypeView::Tuple(items) => ContractType::Tuple {
430                tuple: items
431                    .iter()
432                    .copied()
433                    .map(Into::into)
434                    .collect::<Vec<_>>()
435                    .try_into()
436                    .unwrap(),
437            },
438            ContractTypeView::Named(name) => {
439                ContractType::Named { named: name.into() }
440            }
441            ContractTypeView::U8 => ContractType::U8,
442            ContractTypeView::Array(array) => ContractType::Array {
443                array: array.into(),
444            },
445            ContractTypeView::SignedToken(inner) => {
446                ContractType::SignedToken {
447                    signed_token_preimage: ContractSignedTokenPreimage {
448                        preimage_type: inner
449                            .map(|inner| Box::new((*inner).into())),
450                    },
451                }
452            }
453            ContractTypeView::U256 => ContractType::U256,
454            ContractTypeView::I256 => ContractType::I256,
455        }
456    }
457}
458
459#[cfg(feature = "owned-model")]
460impl From<ContractArrayTypeView<'_>> for crate::ContractArrayType {
461    fn from(value: ContractArrayTypeView<'_>) -> Self {
462        Self {
463            element: Box::new((*value.element).into()),
464            len: value.len,
465        }
466    }
467}
468
469#[cfg(feature = "owned-model")]
470impl From<ContractVectorTypeView<'_>> for crate::ContractVectorType {
471    fn from(value: ContractVectorTypeView<'_>) -> Self {
472        Self {
473            element: Box::new((*value.element).into()),
474            max_len: value.max_len,
475        }
476    }
477}
478
479#[cfg(feature = "owned-model")]
480impl From<ContractTypeDefView<'_>> for ContractTypeDef {
481    fn from(value: ContractTypeDefView<'_>) -> Self {
482        match value {
483            ContractTypeDefView::Struct(def) => {
484                ContractTypeDef::TypeDefStruct {
485                    struct_def: def.into(),
486                }
487            }
488            ContractTypeDefView::Enum(def) => ContractTypeDef::TypeDefEnum {
489                enum_def: def.into(),
490            },
491            ContractTypeDefView::Union(def) => ContractTypeDef::TypeDefUnion {
492                union_def: def.into(),
493            },
494        }
495    }
496}
497
498#[cfg(feature = "owned-model")]
499impl From<ContractStructDefView<'_>> for ContractStructDef {
500    fn from(value: ContractStructDefView<'_>) -> Self {
501        Self {
502            name: value.name.into(),
503            fields: value
504                .fields
505                .iter()
506                .copied()
507                .map(Into::into)
508                .collect::<Vec<_>>()
509                .try_into()
510                .unwrap(),
511        }
512    }
513}
514
515#[cfg(feature = "owned-model")]
516impl From<ContractEnumDefView<'_>> for ContractEnumDef {
517    fn from(value: ContractEnumDefView<'_>) -> Self {
518        Self {
519            name: value.name.into(),
520            variants: value
521                .variants
522                .iter()
523                .copied()
524                .map(Into::into)
525                .collect::<Vec<_>>()
526                .try_into()
527                .unwrap(),
528        }
529    }
530}
531
532#[cfg(feature = "owned-model")]
533impl From<ContractEnumVariantView<'_>> for ContractEnumVariant {
534    fn from(value: ContractEnumVariantView<'_>) -> Self {
535        Self {
536            name: to_spec_string(value.name),
537            value: value.value,
538        }
539    }
540}
541
542#[cfg(feature = "owned-model")]
543impl From<ContractUnionDefView<'_>> for ContractUnionDef {
544    fn from(value: ContractUnionDefView<'_>) -> Self {
545        Self {
546            name: value.name.into(),
547            cases: value
548                .cases
549                .iter()
550                .copied()
551                .map(Into::into)
552                .collect::<Vec<_>>()
553                .try_into()
554                .unwrap(),
555        }
556    }
557}
558
559#[cfg(feature = "owned-model")]
560impl From<ContractUnionCaseView<'_>> for ContractUnionCase {
561    fn from(value: ContractUnionCaseView<'_>) -> Self {
562        Self {
563            name: to_spec_string(value.name),
564            discriminant: value.discriminant,
565            case_type: value.case_type.into(),
566        }
567    }
568}
569
570#[cfg(feature = "owned-model")]
571impl From<ContractUnionCaseTypeView<'_>> for ContractUnionCaseType {
572    fn from(value: ContractUnionCaseTypeView<'_>) -> Self {
573        match value {
574            ContractUnionCaseTypeView::Void => {
575                ContractUnionCaseType::UnionCaseVoid
576            }
577            ContractUnionCaseTypeView::Tuple(types) => {
578                ContractUnionCaseType::UnionCaseTuple {
579                    tuple: types
580                        .iter()
581                        .copied()
582                        .map(Into::into)
583                        .collect::<Vec<_>>()
584                        .try_into()
585                        .unwrap(),
586                }
587            }
588            ContractUnionCaseTypeView::Struct(fields) => {
589                ContractUnionCaseType::UnionCaseStruct {
590                    struct_fields: fields
591                        .iter()
592                        .copied()
593                        .map(Into::into)
594                        .collect::<Vec<_>>()
595                        .try_into()
596                        .unwrap(),
597                }
598            }
599        }
600    }
601}
602
603#[cfg(feature = "owned-model")]
604impl From<SpecFragmentView<'_>> for SpecFragment {
605    fn from(value: SpecFragmentView<'_>) -> Self {
606        match value {
607            SpecFragmentView::Interface(fragment) => {
608                SpecFragment::SpecInterface {
609                    interface_fragment: fragment.into(),
610                }
611            }
612            SpecFragmentView::Method(fragment) => SpecFragment::SpecMethod {
613                method_fragment: fragment.into(),
614            },
615            SpecFragmentView::TypeDef(def) => SpecFragment::SpecTypeDef {
616                type_def: def.into(),
617            },
618            SpecFragmentView::ExportedTypeDef(def) => {
619                SpecFragment::ExportedSpecTypeDef {
620                    type_def: def.into(),
621                }
622            }
623            SpecFragmentView::Event(def) => SpecFragment::SpecEvent {
624                event_def: def.into(),
625            },
626            SpecFragmentView::ExportedEvent(def) => {
627                SpecFragment::ExportedSpecEvent {
628                    event_def: def.into(),
629                }
630            }
631            SpecFragmentView::InterfaceAnchor(anchor) => {
632                SpecFragment::InterfaceAnchor {
633                    interface_anchor: anchor.into(),
634                }
635            }
636        }
637    }
638}
639
640#[cfg(feature = "owned-model")]
641impl From<SpecInterfaceAnchorView<'_>> for SpecInterfaceAnchor {
642    fn from(value: SpecInterfaceAnchorView<'_>) -> Self {
643        Self {
644            interface_name: to_spec_string(value.interface_name),
645            interface_version: to_spec_string(value.interface_version),
646            declared_by: value.declared_by.into(),
647        }
648    }
649}
650
651#[cfg(feature = "owned-model")]
652impl From<SpecInterfaceFragmentView<'_>> for SpecInterfaceFragment {
653    fn from(value: SpecInterfaceFragmentView<'_>) -> Self {
654        Self {
655            interface_name: to_spec_string(value.interface_name),
656            interface_version: to_spec_string(value.interface_version),
657            declared_by: value.declared_by.into(),
658            throws: value
659                .throws
660                .iter()
661                .copied()
662                .map(Into::into)
663                .collect::<Vec<_>>()
664                .try_into()
665                .unwrap(),
666            emits: value
667                .emits
668                .iter()
669                .copied()
670                .map(Into::into)
671                .collect::<Vec<_>>()
672                .try_into()
673                .unwrap(),
674        }
675    }
676}
677
678#[cfg(feature = "owned-model")]
679impl From<SpecMethodFragmentView<'_>> for SpecMethodFragment {
680    fn from(value: SpecMethodFragmentView<'_>) -> Self {
681        Self {
682            interface_name: to_spec_string(value.interface_name),
683            interface_version: to_spec_string(value.interface_version),
684            declared_by: value.declared_by.into(),
685            method: value.method.into(),
686        }
687    }
688}