Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 138 additions & 13 deletions idl/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::str::FromStr;
use anyhow::anyhow;
use serde::{Deserialize, Serialize};

/// IDL specification Semantic Version
pub const IDL_SPEC: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -314,6 +313,10 @@ impl FromStr for IdlType {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(anyhow!("Type string cannot be empty"));
}

let mut s = s.to_owned();
s.retain(|c| !c.is_whitespace());

Expand Down Expand Up @@ -341,7 +344,7 @@ impl FromStr for IdlType {
let inner_ty = Self::from_str(
inner
.strip_suffix('>')
.ok_or_else(|| anyhow!("Invalid Option"))?,
.ok_or_else(|| anyhow!("Invalid Option syntax: missing '>'"))?,
)?;
return Ok(IdlType::Option(Box::new(inner_ty)));
}
Expand All @@ -350,37 +353,73 @@ impl FromStr for IdlType {
let inner_ty = Self::from_str(
inner
.strip_suffix('>')
.ok_or_else(|| anyhow!("Invalid Vec"))?,
.ok_or_else(|| anyhow!("Invalid Vec syntax: missing '>'"))?,
)?;
return Ok(IdlType::Vec(Box::new(inner_ty)));
}

if s.starts_with('[') {
fn array_from_str(inner: &str) -> IdlType {
fn array_from_str(inner: &str) -> Result<IdlType, anyhow::Error> {
match inner.strip_suffix(']') {
Some(nested_inner) => array_from_str(&nested_inner[1..]),
Some(nested_inner) => {
if nested_inner.len() <= 1 {
return Err(anyhow!("Invalid nested array syntax"));
}

array_from_str(&nested_inner[1..])
}
None => {
let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
let ty = IdlType::from_str(raw_type).unwrap();
let (raw_type, raw_length) = inner
.rsplit_once(';')
.ok_or_else(|| anyhow!(
"Invalid array syntax: expected '[type; length]', found '[{}]'",
inner
))?;

let raw_type = raw_type.trim();
if raw_type.is_empty() {
return Err(anyhow!("Array type cannot be empty"));
}

let ty = IdlType::from_str(raw_type).map_err(|e| {
anyhow!("Invalid array element type '{}': {}", raw_type, e)
})?;

let raw_length = raw_length.trim();
if raw_length.is_empty() {
return Err(anyhow!("Array length cannot be empty"));
}

let len = match raw_length.replace('_', "").parse::<usize>() {
Ok(len) => IdlArrayLen::Value(len),
Err(_) => IdlArrayLen::Generic(raw_length.to_owned()),
Err(_) => {
if !raw_length
.chars()
.all(|c| c.is_alphanumeric() || c == '_')
{
return Err(anyhow!(
"Invalid array length or generic name: '{}'",
raw_length
));
}
IdlArrayLen::Generic(raw_length.to_owned())
}
};
IdlType::Array(Box::new(ty), len)

Ok(IdlType::Array(Box::new(ty), len))
}
}
}
return Ok(array_from_str(&s));
return array_from_str(&s);
}

// Defined
let (name, generics) = if let Some(i) = s.find('<') {
(
s.get(..i).unwrap().to_owned(),
s.get(i + 1..)
.unwrap()
.strip_suffix('>')
.unwrap()
.ok_or_else(|| anyhow!("Invalid generic syntax: missing '>'"))?
.split(',')
.map(|g| g.trim().to_owned())
.map(|g| {
Expand Down Expand Up @@ -409,7 +448,6 @@ impl FromStr for IdlType {

pub type IdlDiscriminator = Vec<u8>;

/// Get whether the given data is the default of its type.
fn is_default<T: Default + PartialEq>(it: &T) -> bool {
*it == T::default()
}
Expand Down Expand Up @@ -499,4 +537,91 @@ mod tests {
}
)
}

#[test]
fn array_missing_semicolon_error() {
let result = IdlType::from_str("[u8 32]");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid array syntax"));
}

#[test]
fn array_malformed_colon_error() {
let result = IdlType::from_str("[u8:32]");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid array syntax"));
}

#[test]
fn array_empty_type_error() {
let result = IdlType::from_str("[; 32]");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Array type cannot be empty"));
}

#[test]
fn array_empty_length_error() {
let result = IdlType::from_str("[u8; ]");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Array length cannot be empty"));
}

#[test]
fn array_invalid_generic_name_error() {
let result = IdlType::from_str("[u8; @invalid]");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid array length or generic name"));
}

#[test]
fn empty_string_error() {
let result = IdlType::from_str("");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Type string cannot be empty"));
}

#[test]
fn array_numeric_parsing_edge_cases() {
assert!(IdlType::from_str("[u8; 1_000]").is_ok());
assert!(IdlType::from_str("[u8; 1_000_000]").is_ok());
assert!(IdlType::from_str("[u8; 1.5]").is_err());
}

#[test]
fn nested_array_malformed_error() {
let result = IdlType::from_str("[[u8 32]; 16]");
assert!(result.is_err());
}

#[test]
fn valid_nested_array() {
assert_eq!(
IdlType::from_str("[[u8; 16]; 32]").unwrap(),
IdlType::Array(
Box::new(IdlType::Array(
Box::new(IdlType::U8),
IdlArrayLen::Value(16)
)),
IdlArrayLen::Value(32)
)
);
}
}
Loading