Skip to main content

abi_loader/
file.rs

1use abi_types::TypeDef;
2use serde_derive::{Deserialize, Serialize};
3
4/* ============================================================================
5Import Source Types
6============================================================================ */
7
8/* Target type for on-chain ABI imports */
9#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
10#[serde(rename_all = "kebab-case")]
11pub enum OnchainTarget {
12    /* Official ABI derived from a program account */
13    #[default]
14    Program,
15    /* ABI derived from an ABI meta account */
16    AbiMeta,
17    /* ABI account address provided directly */
18    Abi,
19}
20
21/* Revision specifier for on-chain imports */
22#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
23#[serde(untagged)]
24pub enum RevisionSpec {
25    /* Exact revision number (e.g., revision: 5) */
26    Exact(u64),
27    /* String specifier: minimum (e.g., ">=5") or "latest" */
28    Specifier(String),
29}
30
31impl RevisionSpec {
32    /* Check if this is a "latest" specifier */
33    pub fn is_latest(&self) -> bool {
34        matches!(self, RevisionSpec::Specifier(s) if s == "latest")
35    }
36
37    /* Check if this is a minimum specifier (e.g., ">=5") */
38    pub fn is_minimum(&self) -> bool {
39        matches!(self, RevisionSpec::Specifier(s) if s.starts_with(">="))
40    }
41
42    /* Parse minimum value from ">=N" specifier */
43    pub fn minimum_value(&self) -> Option<u64> {
44        match self {
45            RevisionSpec::Specifier(s) if s.starts_with(">=") => s[2..].parse().ok(),
46            _ => None,
47        }
48    }
49
50    /* Get exact value if this is an exact specifier */
51    pub fn exact_value(&self) -> Option<u64> {
52        match self {
53            RevisionSpec::Exact(v) => Some(*v),
54            _ => None,
55        }
56    }
57
58    /* Check if a given revision satisfies this spec */
59    pub fn satisfies(&self, revision: u64) -> bool {
60        match self {
61            RevisionSpec::Exact(v) => revision == *v,
62            RevisionSpec::Specifier(s) if s == "latest" => true,
63            RevisionSpec::Specifier(s) if s.starts_with(">=") => s[2..]
64                .parse::<u64>()
65                .map(|min| revision >= min)
66                .unwrap_or(false),
67            _ => false,
68        }
69    }
70}
71
72impl Default for RevisionSpec {
73    fn default() -> Self {
74        RevisionSpec::Specifier("latest".to_string())
75    }
76}
77
78/* Import source specification */
79#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
80#[serde(tag = "type", rename_all = "kebab-case")]
81pub enum ImportSource {
82    /* Local file path import */
83    Path {
84        /* Relative or absolute path to the ABI file */
85        path: String,
86    },
87
88    /* Git repository import */
89    Git {
90        /* Repository URL (https:// or ssh://) */
91        url: String,
92        /* Git reference: branch name, tag, or commit hash */
93        #[serde(rename = "ref")]
94        git_ref: String,
95        /* Path within the repository to the ABI file */
96        path: String,
97    },
98
99    /* HTTP/HTTPS URL import */
100    Http {
101        /* Direct URL to the ABI YAML file */
102        url: String,
103    },
104
105    /* On-chain ABI import */
106    Onchain {
107        /* Thru address (ta-prefixed; .thru names are not yet supported) */
108        address: String,
109        /* Whether this is a program meta-derived ABI, ABI meta-derived ABI, or direct ABI */
110        #[serde(default)]
111        target: OnchainTarget,
112        /* Network name (e.g., "mainnet", "testnet") or chain ID */
113        network: String,
114        /* Revision specifier: exact number, ">=N" minimum, or "latest" */
115        #[serde(default)]
116        revision: RevisionSpec,
117    },
118}
119
120impl ImportSource {
121    /* Check if this is a remote import (not local path) */
122    pub fn is_remote(&self) -> bool {
123        !matches!(self, ImportSource::Path { .. })
124    }
125
126    /* Check if this is a local path import */
127    pub fn is_path(&self) -> bool {
128        matches!(self, ImportSource::Path { .. })
129    }
130
131    /* Get the path for path imports */
132    pub fn path(&self) -> Option<&str> {
133        match self {
134            ImportSource::Path { path } => Some(path),
135            _ => None,
136        }
137    }
138
139    /* Get a canonical identifier for this import source (for cycle detection) */
140    pub fn canonical_id(&self) -> String {
141        match self {
142            ImportSource::Path { path } => format!("path:{}", path),
143            ImportSource::Git { url, git_ref, path } => {
144                format!("git:{}@{}:{}", url, git_ref, path)
145            }
146            ImportSource::Http { url } => format!("http:{}", url),
147            ImportSource::Onchain {
148                address,
149                target,
150                network,
151                revision,
152            } => {
153                let target_str = match target {
154                    OnchainTarget::Program => "program",
155                    OnchainTarget::AbiMeta => "abi-meta",
156                    OnchainTarget::Abi => "abi",
157                };
158                let rev_str = match revision {
159                    RevisionSpec::Exact(v) => format!("{}", v),
160                    RevisionSpec::Specifier(s) => s.clone(),
161                };
162                format!(
163                    "onchain:{}:{}@{}?rev={}",
164                    network, target_str, address, rev_str
165                )
166            }
167        }
168    }
169}
170
171/* Root type names for program reflection */
172#[derive(Serialize, Deserialize, Debug, Clone, Default)]
173#[serde(rename_all = "kebab-case")]
174pub struct RootTypes {
175    /* Type name for the instruction envelope */
176    #[serde(default)]
177    pub instruction_root: Option<String>,
178
179    /* Type name for account state */
180    #[serde(default)]
181    pub account_root: Option<String>,
182
183    /* Type name for program errors */
184    #[serde(default)]
185    pub errors: Option<String>,
186
187    /* Type name for program events */
188    #[serde(default)]
189    pub events: Option<String>,
190}
191
192/* Program-specific metadata */
193#[derive(Serialize, Deserialize, Debug, Clone, Default)]
194#[serde(rename_all = "kebab-case")]
195pub struct ProgramMetadata {
196    /* Root type names for the program */
197    #[serde(default)]
198    pub root_types: RootTypes,
199}
200
201/* ABI file options */
202#[derive(Serialize, Deserialize, Debug, Clone, Default)]
203#[serde(rename_all = "kebab-case")]
204pub struct AbiOptions {
205    /* Program-specific metadata */
206    #[serde(default)]
207    pub program_metadata: ProgramMetadata,
208}
209
210/* Metadata for an ABI file */
211#[derive(Serialize, Deserialize, Debug, Clone)]
212#[serde(rename_all = "kebab-case")]
213pub struct AbiMetadata {
214    /* Fully qualified domain name package identifier (e.g., "thru.ammdex") */
215    pub package: String,
216
217    /* Optional human-readable display name (e.g., "Token Program") */
218    #[serde(default)]
219    pub name: Option<String>,
220
221    /* ABI specification version */
222    pub abi_version: u32,
223
224    /* This package's semantic version */
225    pub package_version: String,
226
227    /* File description */
228    pub description: String,
229
230    /* List of imported ABI sources */
231    #[serde(default)]
232    pub imports: Vec<ImportSource>,
233
234    /* Optional configuration options */
235    #[serde(default)]
236    pub options: AbiOptions,
237}
238
239/* Complete ABI file structure with metadata and type definitions */
240#[derive(Serialize, Deserialize, Debug, Clone)]
241#[serde(rename_all = "kebab-case")]
242pub struct AbiFile {
243    /* ABI file metadata */
244    pub abi: AbiMetadata,
245
246    /* Type definitions */
247    #[serde(default)]
248    pub types: Vec<TypeDef>,
249}
250
251impl AbiFile {
252    /* Create a new ABI file with the given metadata */
253    pub fn new(metadata: AbiMetadata) -> Self {
254        Self {
255            abi: metadata,
256            types: Vec::new(),
257        }
258    }
259
260    /* Add a type definition to this ABI file */
261    pub fn add_type(&mut self, typedef: TypeDef) {
262        self.types.push(typedef);
263    }
264
265    /* Get all type definitions */
266    pub fn get_types(&self) -> &[TypeDef] {
267        &self.types
268    }
269
270    /* Get the package identifier */
271    pub fn package(&self) -> &str {
272        &self.abi.package
273    }
274
275    /* Get the human-readable display name */
276    pub fn name(&self) -> Option<&str> {
277        self.abi.name.as_deref()
278    }
279
280    /* Get the imports */
281    pub fn imports(&self) -> &[ImportSource] {
282        &self.abi.imports
283    }
284
285    /* Check if this file has any remote imports */
286    pub fn has_remote_imports(&self) -> bool {
287        self.abi.imports.iter().any(|i| i.is_remote())
288    }
289
290    /* Check if this file has any local (path) imports */
291    pub fn has_local_imports(&self) -> bool {
292        self.abi.imports.iter().any(|i| i.is_path())
293    }
294
295    /* Get the ABI version */
296    pub fn abi_version(&self) -> u32 {
297        self.abi.abi_version
298    }
299
300    /* Get the package version */
301    pub fn package_version(&self) -> &str {
302        &self.abi.package_version
303    }
304
305    /* Get the description */
306    pub fn description(&self) -> &str {
307        &self.abi.description
308    }
309
310    /* Get the root types configuration */
311    pub fn root_types(&self) -> &RootTypes {
312        &self.abi.options.program_metadata.root_types
313    }
314
315    /* Get the instruction root type name */
316    pub fn instruction_root(&self) -> Option<&str> {
317        self.abi
318            .options
319            .program_metadata
320            .root_types
321            .instruction_root
322            .as_deref()
323    }
324
325    /* Get the account root type name */
326    pub fn account_root(&self) -> Option<&str> {
327        self.abi
328            .options
329            .program_metadata
330            .root_types
331            .account_root
332            .as_deref()
333    }
334
335    /* Get the errors type name */
336    pub fn errors_type(&self) -> Option<&str> {
337        self.abi
338            .options
339            .program_metadata
340            .root_types
341            .errors
342            .as_deref()
343    }
344
345    /* Get the options */
346    pub fn options(&self) -> &AbiOptions {
347        &self.abi.options
348    }
349
350    /* Get the events type name */
351    pub fn events_type(&self) -> Option<&str> {
352        self.abi
353            .options
354            .program_metadata
355            .root_types
356            .events
357            .as_deref()
358    }
359}