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(">=") => {
46 s[2..].parse().ok()
47 }
48 _ => None,
49 }
50 }
51
52 pub fn exact_value(&self) -> Option<u64> {
54 match self {
55 RevisionSpec::Exact(v) => Some(*v),
56 _ => None,
57 }
58 }
59
60 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#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
81#[serde(tag = "type", rename_all = "kebab-case")]
82pub enum ImportSource {
83 Path {
85 path: String,
87 },
88
89 Git {
91 url: String,
93 #[serde(rename = "ref")]
95 git_ref: String,
96 path: String,
98 },
99
100 Http {
102 url: String,
104 },
105
106 Onchain {
108 address: String,
110 #[serde(default)]
112 target: OnchainTarget,
113 network: String,
115 #[serde(default)]
117 revision: RevisionSpec,
118 },
119}
120
121impl ImportSource {
122 pub fn is_remote(&self) -> bool {
124 !matches!(self, ImportSource::Path { .. })
125 }
126
127 pub fn is_path(&self) -> bool {
129 matches!(self, ImportSource::Path { .. })
130 }
131
132 pub fn path(&self) -> Option<&str> {
134 match self {
135 ImportSource::Path { path } => Some(path),
136 _ => None,
137 }
138 }
139
140 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#[derive(Serialize, Deserialize, Debug, Clone, Default)]
166#[serde(rename_all = "kebab-case")]
167pub struct RootTypes {
168 #[serde(default)]
170 pub instruction_root: Option<String>,
171
172 #[serde(default)]
174 pub account_root: Option<String>,
175
176 #[serde(default)]
178 pub errors: Option<String>,
179
180 #[serde(default)]
182 pub events: Option<String>,
183}
184
185#[derive(Serialize, Deserialize, Debug, Clone, Default)]
187#[serde(rename_all = "kebab-case")]
188pub struct ProgramMetadata {
189 #[serde(default)]
191 pub root_types: RootTypes,
192}
193
194#[derive(Serialize, Deserialize, Debug, Clone, Default)]
196#[serde(rename_all = "kebab-case")]
197pub struct AbiOptions {
198 #[serde(default)]
200 pub program_metadata: ProgramMetadata,
201}
202
203#[derive(Serialize, Deserialize, Debug, Clone)]
205#[serde(rename_all = "kebab-case")]
206pub struct AbiMetadata {
207 pub package: String,
209
210 #[serde(default)]
212 pub name: Option<String>,
213
214 pub abi_version: u32,
216
217 pub package_version: String,
219
220 pub description: String,
222
223 #[serde(default)]
225 pub imports: Vec<ImportSource>,
226
227 #[serde(default)]
229 pub options: AbiOptions,
230}
231
232#[derive(Serialize, Deserialize, Debug, Clone)]
234#[serde(rename_all = "kebab-case")]
235pub struct AbiFile {
236 pub abi: AbiMetadata,
238
239 #[serde(default)]
241 pub types: Vec<TypeDef>,
242}
243
244impl AbiFile {
245 pub fn new(metadata: AbiMetadata) -> Self {
247 Self {
248 abi: metadata,
249 types: Vec::new(),
250 }
251 }
252
253 pub fn add_type(&mut self, typedef: TypeDef) {
255 self.types.push(typedef);
256 }
257
258 pub fn get_types(&self) -> &[TypeDef] {
260 &self.types
261 }
262
263 pub fn package(&self) -> &str {
265 &self.abi.package
266 }
267
268 pub fn name(&self) -> Option<&str> {
270 self.abi.name.as_deref()
271 }
272
273 pub fn imports(&self) -> &[ImportSource] {
275 &self.abi.imports
276 }
277
278 pub fn has_remote_imports(&self) -> bool {
280 self.abi.imports.iter().any(|i| i.is_remote())
281 }
282
283 pub fn has_local_imports(&self) -> bool {
285 self.abi.imports.iter().any(|i| i.is_path())
286 }
287
288 pub fn abi_version(&self) -> u32 {
290 self.abi.abi_version
291 }
292
293 pub fn package_version(&self) -> &str {
295 &self.abi.package_version
296 }
297
298 pub fn description(&self) -> &str {
300 &self.abi.description
301 }
302
303 pub fn root_types(&self) -> &RootTypes {
305 &self.abi.options.program_metadata.root_types
306 }
307
308 pub fn instruction_root(&self) -> Option<&str> {
310 self.abi.options.program_metadata.root_types.instruction_root.as_deref()
311 }
312
313 pub fn account_root(&self) -> Option<&str> {
315 self.abi.options.program_metadata.root_types.account_root.as_deref()
316 }
317
318 pub fn errors_type(&self) -> Option<&str> {
320 self.abi.options.program_metadata.root_types.errors.as_deref()
321 }
322
323 pub fn options(&self) -> &AbiOptions {
325 &self.abi.options
326 }
327
328 pub fn events_type(&self) -> Option<&str> {
330 self.abi.options.program_metadata.root_types.events.as_deref()
331 }
332}