Skip to main content

abi_loader/
file.rs

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