use abi_types::TypeDef;
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum OnchainTarget {
#[default]
Program,
AbiMeta,
Abi,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum RevisionSpec {
Exact(u64),
Specifier(String),
}
impl RevisionSpec {
pub fn is_latest(&self) -> bool {
matches!(self, RevisionSpec::Specifier(s) if s == "latest")
}
pub fn is_minimum(&self) -> bool {
matches!(self, RevisionSpec::Specifier(s) if s.starts_with(">="))
}
pub fn minimum_value(&self) -> Option<u64> {
match self {
RevisionSpec::Specifier(s) if s.starts_with(">=") => {
s[2..].parse().ok()
}
_ => None,
}
}
pub fn exact_value(&self) -> Option<u64> {
match self {
RevisionSpec::Exact(v) => Some(*v),
_ => None,
}
}
pub fn satisfies(&self, revision: u64) -> bool {
match self {
RevisionSpec::Exact(v) => revision == *v,
RevisionSpec::Specifier(s) if s == "latest" => true,
RevisionSpec::Specifier(s) if s.starts_with(">=") => {
s[2..].parse::<u64>().map(|min| revision >= min).unwrap_or(false)
}
_ => false,
}
}
}
impl Default for RevisionSpec {
fn default() -> Self {
RevisionSpec::Specifier("latest".to_string())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ImportSource {
Path {
path: String,
},
Git {
url: String,
#[serde(rename = "ref")]
git_ref: String,
path: String,
},
Http {
url: String,
},
Onchain {
address: String,
#[serde(default)]
target: OnchainTarget,
network: String,
#[serde(default)]
revision: RevisionSpec,
},
}
impl ImportSource {
pub fn is_remote(&self) -> bool {
!matches!(self, ImportSource::Path { .. })
}
pub fn is_path(&self) -> bool {
matches!(self, ImportSource::Path { .. })
}
pub fn path(&self) -> Option<&str> {
match self {
ImportSource::Path { path } => Some(path),
_ => None,
}
}
pub fn canonical_id(&self) -> String {
match self {
ImportSource::Path { path } => format!("path:{}", path),
ImportSource::Git { url, git_ref, path } => {
format!("git:{}@{}:{}", url, git_ref, path)
}
ImportSource::Http { url } => format!("http:{}", url),
ImportSource::Onchain { address, target, network, revision } => {
let target_str = match target {
OnchainTarget::Program => "program",
OnchainTarget::AbiMeta => "abi-meta",
OnchainTarget::Abi => "abi",
};
let rev_str = match revision {
RevisionSpec::Exact(v) => format!("{}", v),
RevisionSpec::Specifier(s) => s.clone(),
};
format!("onchain:{}:{}@{}?rev={}", network, target_str, address, rev_str)
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct RootTypes {
#[serde(default)]
pub instruction_root: Option<String>,
#[serde(default)]
pub account_root: Option<String>,
#[serde(default)]
pub errors: Option<String>,
#[serde(default)]
pub events: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct ProgramMetadata {
#[serde(default)]
pub root_types: RootTypes,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "kebab-case")]
pub struct AbiOptions {
#[serde(default)]
pub program_metadata: ProgramMetadata,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct AbiMetadata {
pub package: String,
#[serde(default)]
pub name: Option<String>,
pub abi_version: u32,
pub package_version: String,
pub description: String,
#[serde(default)]
pub imports: Vec<ImportSource>,
#[serde(default)]
pub options: AbiOptions,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct AbiFile {
pub abi: AbiMetadata,
#[serde(default)]
pub types: Vec<TypeDef>,
}
impl AbiFile {
pub fn new(metadata: AbiMetadata) -> Self {
Self {
abi: metadata,
types: Vec::new(),
}
}
pub fn add_type(&mut self, typedef: TypeDef) {
self.types.push(typedef);
}
pub fn get_types(&self) -> &[TypeDef] {
&self.types
}
pub fn package(&self) -> &str {
&self.abi.package
}
pub fn name(&self) -> Option<&str> {
self.abi.name.as_deref()
}
pub fn imports(&self) -> &[ImportSource] {
&self.abi.imports
}
pub fn has_remote_imports(&self) -> bool {
self.abi.imports.iter().any(|i| i.is_remote())
}
pub fn has_local_imports(&self) -> bool {
self.abi.imports.iter().any(|i| i.is_path())
}
pub fn abi_version(&self) -> u32 {
self.abi.abi_version
}
pub fn package_version(&self) -> &str {
&self.abi.package_version
}
pub fn description(&self) -> &str {
&self.abi.description
}
pub fn root_types(&self) -> &RootTypes {
&self.abi.options.program_metadata.root_types
}
pub fn instruction_root(&self) -> Option<&str> {
self.abi.options.program_metadata.root_types.instruction_root.as_deref()
}
pub fn account_root(&self) -> Option<&str> {
self.abi.options.program_metadata.root_types.account_root.as_deref()
}
pub fn errors_type(&self) -> Option<&str> {
self.abi.options.program_metadata.root_types.errors.as_deref()
}
pub fn options(&self) -> &AbiOptions {
&self.abi.options
}
pub fn events_type(&self) -> Option<&str> {
self.abi.options.program_metadata.root_types.events.as_deref()
}
}