use serde::Deserialize;
use serde_json::Value;
use crate::decoder::{event_discriminator, DecodeError};
#[derive(Deserialize, Clone, Debug, Default)]
pub struct Idl {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub address: Option<String>,
#[serde(default)]
pub metadata: Option<IdlMetadata>,
#[serde(default)]
pub events: Vec<IdlEvent>,
#[serde(default)]
pub errors: Vec<IdlErrorCode>,
#[serde(default)]
pub types: Vec<IdlTypeDef>,
}
impl Idl {
pub fn from_json(json: &str) -> Result<Self, DecodeError> {
serde_json::from_str(json).map_err(|e| DecodeError::InvalidIdl(e.to_string()))
}
pub fn program_name(&self) -> &str {
self.name
.as_deref()
.or_else(|| self.metadata.as_ref().and_then(|m| m.name.as_deref()))
.unwrap_or("")
}
pub fn find_type(&self, name: &str) -> Option<&IdlTypeDef> {
self.types.iter().find(|t| t.name == name)
}
pub fn lookup_error(&self, code: u32) -> Option<&IdlErrorCode> {
self.errors.iter().find(|e| e.code == code)
}
}
#[derive(Deserialize, Clone, Debug, Default)]
pub struct IdlMetadata {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub spec: Option<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct IdlEvent {
pub name: String,
#[serde(default)]
pub discriminator: Option<Vec<u8>>,
#[serde(default)]
pub fields: Option<Vec<IdlField>>,
}
impl IdlEvent {
pub fn discriminator_bytes(&self) -> [u8; 8] {
match &self.discriminator {
Some(explicit) if explicit.len() == 8 => {
let mut out = [0u8; 8];
out.copy_from_slice(explicit);
out
}
_ => event_discriminator(&self.name),
}
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct IdlErrorCode {
pub code: u32,
pub name: String,
#[serde(default)]
pub msg: Option<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct IdlTypeDef {
pub name: String,
#[serde(rename = "type")]
pub ty: IdlTypeDefTy,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum IdlTypeDefTy {
Struct {
#[serde(default)]
fields: Option<IdlDefinedFields>,
},
Enum {
variants: Vec<IdlEnumVariant>,
},
Alias {
value: IdlType,
},
}
#[derive(Deserialize, Clone, Debug)]
pub struct IdlEnumVariant {
pub name: String,
#[serde(default)]
pub fields: Option<IdlDefinedFields>,
}
#[derive(Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum IdlDefinedFields {
Named(Vec<IdlField>),
Tuple(Vec<IdlType>),
}
#[derive(Deserialize, Clone, Debug)]
pub struct IdlField {
pub name: String,
#[serde(rename = "type")]
pub ty: IdlType,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IdlType {
Bool,
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
U128,
I128,
F32,
F64,
Bytes,
String,
Pubkey,
Vec(Box<IdlType>),
Option(Box<IdlType>),
Array(Box<IdlType>, usize),
Defined(std::string::String),
Unsupported(std::string::String),
}
impl<'de> Deserialize<'de> for IdlType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;
Ok(IdlType::from_value(&value))
}
}
impl IdlType {
fn from_value(value: &Value) -> IdlType {
match value {
Value::String(s) => match s.as_str() {
"bool" => IdlType::Bool,
"u8" => IdlType::U8,
"i8" => IdlType::I8,
"u16" => IdlType::U16,
"i16" => IdlType::I16,
"u32" => IdlType::U32,
"i32" => IdlType::I32,
"u64" => IdlType::U64,
"i64" => IdlType::I64,
"u128" => IdlType::U128,
"i128" => IdlType::I128,
"f32" => IdlType::F32,
"f64" => IdlType::F64,
"bytes" => IdlType::Bytes,
"string" => IdlType::String,
"pubkey" | "publicKey" => IdlType::Pubkey,
other => IdlType::Unsupported(other.to_string()),
},
Value::Object(map) => {
if let Some(inner) = map.get("vec") {
IdlType::Vec(Box::new(IdlType::from_value(inner)))
} else if let Some(inner) = map.get("option") {
IdlType::Option(Box::new(IdlType::from_value(inner)))
} else if let Some(arr) = map.get("array").and_then(|a| a.as_array()) {
match (arr.first(), arr.get(1).and_then(|l| l.as_u64())) {
(Some(elem), Some(len)) => {
IdlType::Array(Box::new(IdlType::from_value(elem)), len as usize)
}
_ => IdlType::Unsupported(value.to_string()),
}
} else if let Some(defined) = map.get("defined") {
if let Some(name) = defined.as_str() {
IdlType::Defined(name.to_string())
} else if let Some(name) = defined.get("name").and_then(|n| n.as_str()) {
let has_generics = defined
.get("generics")
.and_then(|g| g.as_array())
.is_some_and(|g| !g.is_empty());
if has_generics {
IdlType::Unsupported(value.to_string())
} else {
IdlType::Defined(name.to_string())
}
} else {
IdlType::Unsupported(value.to_string())
}
} else {
IdlType::Unsupported(value.to_string())
}
}
other => IdlType::Unsupported(other.to_string()),
}
}
}