1use abi_types::TypeDef;
2use serde_derive::{Deserialize, Serialize};
3
4#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
10#[serde(rename_all = "kebab-case")]
11pub enum OnchainTarget {
12 #[default]
14 Program,
15 AbiMeta,
17 Abi,
19}
20
21#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
23#[serde(untagged)]
24pub enum RevisionSpec {
25 Exact(u64),
27 Specifier(String),
29}
30
31impl RevisionSpec {
32 pub fn is_latest(&self) -> bool {
34 matches!(self, RevisionSpec::Specifier(s) if s == "latest")
35 }
36
37 pub fn is_minimum(&self) -> bool {
39 matches!(self, RevisionSpec::Specifier(s) if s.starts_with(">="))
40 }
41
42 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 pub fn exact_value(&self) -> Option<u64> {
52 match self {
53 RevisionSpec::Exact(v) => Some(*v),
54 _ => None,
55 }
56 }
57
58 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#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
80#[serde(tag = "type", rename_all = "kebab-case")]
81pub enum ImportSource {
82 Path {
84 path: String,
86 },
87
88 Git {
90 url: String,
92 #[serde(rename = "ref")]
94 git_ref: String,
95 path: String,
97 },
98
99 Http {
101 url: String,
103 },
104
105 Onchain {
107 address: String,
109 #[serde(default)]
111 target: OnchainTarget,
112 network: String,
114 #[serde(default)]
116 revision: RevisionSpec,
117 },
118}
119
120impl ImportSource {
121 pub fn is_remote(&self) -> bool {
123 !matches!(self, ImportSource::Path { .. })
124 }
125
126 pub fn is_path(&self) -> bool {
128 matches!(self, ImportSource::Path { .. })
129 }
130
131 pub fn path(&self) -> Option<&str> {
133 match self {
134 ImportSource::Path { path } => Some(path),
135 _ => None,
136 }
137 }
138
139 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#[derive(Serialize, Deserialize, Debug, Clone, Default)]
173#[serde(rename_all = "kebab-case")]
174pub struct RootTypes {
175 #[serde(default)]
177 pub instruction_root: Option<String>,
178
179 #[serde(default)]
181 pub account_root: Option<String>,
182
183 #[serde(default)]
185 pub errors: Option<String>,
186
187 #[serde(default)]
189 pub events: Option<String>,
190}
191
192#[derive(Serialize, Deserialize, Debug, Clone, Default)]
194#[serde(rename_all = "kebab-case")]
195pub struct ProgramMetadata {
196 #[serde(default)]
198 pub root_types: RootTypes,
199}
200
201#[derive(Serialize, Deserialize, Debug, Clone, Default)]
203#[serde(rename_all = "kebab-case")]
204pub struct AbiOptions {
205 #[serde(default)]
207 pub program_metadata: ProgramMetadata,
208}
209
210#[derive(Serialize, Deserialize, Debug, Clone)]
212#[serde(rename_all = "kebab-case")]
213pub struct AbiMetadata {
214 pub package: String,
216
217 #[serde(default)]
219 pub name: Option<String>,
220
221 pub abi_version: u32,
223
224 pub package_version: String,
226
227 pub description: String,
229
230 #[serde(default)]
232 pub imports: Vec<ImportSource>,
233
234 #[serde(default)]
236 pub options: AbiOptions,
237}
238
239#[derive(Serialize, Deserialize, Debug, Clone)]
241#[serde(rename_all = "kebab-case")]
242pub struct AbiFile {
243 pub abi: AbiMetadata,
245
246 #[serde(default)]
248 pub types: Vec<TypeDef>,
249}
250
251impl AbiFile {
252 pub fn new(metadata: AbiMetadata) -> Self {
254 Self {
255 abi: metadata,
256 types: Vec::new(),
257 }
258 }
259
260 pub fn add_type(&mut self, typedef: TypeDef) {
262 self.types.push(typedef);
263 }
264
265 pub fn get_types(&self) -> &[TypeDef] {
267 &self.types
268 }
269
270 pub fn package(&self) -> &str {
272 &self.abi.package
273 }
274
275 pub fn name(&self) -> Option<&str> {
277 self.abi.name.as_deref()
278 }
279
280 pub fn imports(&self) -> &[ImportSource] {
282 &self.abi.imports
283 }
284
285 pub fn has_remote_imports(&self) -> bool {
287 self.abi.imports.iter().any(|i| i.is_remote())
288 }
289
290 pub fn has_local_imports(&self) -> bool {
292 self.abi.imports.iter().any(|i| i.is_path())
293 }
294
295 pub fn abi_version(&self) -> u32 {
297 self.abi.abi_version
298 }
299
300 pub fn package_version(&self) -> &str {
302 &self.abi.package_version
303 }
304
305 pub fn description(&self) -> &str {
307 &self.abi.description
308 }
309
310 pub fn root_types(&self) -> &RootTypes {
312 &self.abi.options.program_metadata.root_types
313 }
314
315 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 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 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 pub fn options(&self) -> &AbiOptions {
347 &self.abi.options
348 }
349
350 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}