Skip to content

Commit

Permalink
idl: Add separate spec crate (#3036)
Browse files Browse the repository at this point in the history
  • Loading branch information
acheroncrypto authored Jun 19, 2024
1 parent 9c17d65 commit cc43e67
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- cli: Sync program ids on the initial build ([#3023](https://github.com/coral-xyz/anchor/pull/3023)).
- idl: Remove `anchor-syn` dependency ([#3030](https://github.com/coral-xyz/anchor/pull/3030)).
- lang: Add `const` of program ID to `declare_id!` and `declare_program!` ([#3019](https://github.com/coral-xyz/anchor/pull/3019)).
- idl: Add separate spec crate ([#3036](https://github.com/coral-xyz/anchor/pull/3036)).

### Fixes

Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::config::{
use anchor_client::Cluster;
use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
use anchor_lang_idl::convert::convert_idl;
use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
use anyhow::{anyhow, Context, Result};
use checks::{check_anchor_version, check_overflow};
Expand Down Expand Up @@ -2734,7 +2735,7 @@ fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>

fn idl_convert(path: String, out: Option<String>) -> Result<()> {
let idl = fs::read(path)?;
let idl = Idl::from_slice_with_conversion(&idl)?;
let idl = convert_idl(&idl)?;
let out = match out {
None => OutFile::Stdout,
Some(out) => OutFile::File(PathBuf::from(out)),
Expand All @@ -2744,7 +2745,7 @@ fn idl_convert(path: String, out: Option<String>) -> Result<()> {

fn idl_type(path: String, out: Option<String>) -> Result<()> {
let idl = fs::read(path)?;
let idl = Idl::from_slice_with_conversion(&idl)?;
let idl = convert_idl(&idl)?;
let types = idl_ts(&idl)?;
match out {
Some(out) => fs::write(out, types)?,
Expand Down
1 change: 1 addition & 0 deletions idl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ build = ["regex"]
convert = ["heck", "sha2"]

[dependencies]
anchor-lang-idl-spec = { path = "./spec", version = "0.1.0" }
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
16 changes: 16 additions & 0 deletions idl/spec/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "anchor-lang-idl-spec"
version = "0.1.0"
authors = ["Anchor Maintainers <accounts@200ms.io>"]
repository = "https://github.com/coral-xyz/anchor"
edition = "2021"
license = "Apache-2.0"
description = "Anchor framework IDL spec"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
1 change: 1 addition & 0 deletions idl/src/types.rs → idl/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ pub enum IdlType {
Generic(String),
}

// TODO: Move to utils crate
impl FromStr for IdlType {
type Err = anyhow::Error;

Expand Down
65 changes: 30 additions & 35 deletions idl/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,28 @@ use anyhow::{anyhow, Result};

use crate::types::Idl;

impl Idl {
/// Create an [`Idl`] value with additional support for older specs based on the
/// `idl.metadata.spec` field.
///
/// If `spec` field is not specified, the conversion will fallback to the legacy IDL spec
/// (pre Anchor v0.30.0).
///
/// **Note:** For legacy IDLs, `idl.metadata.address` field is required to be populated with
/// program's address otherwise an error will be returned.
pub fn from_slice_with_conversion(idl: &[u8]) -> Result<Self> {
let value = serde_json::from_slice::<serde_json::Value>(idl)?;
let spec = value
.get("metadata")
.and_then(|m| m.get("spec"))
.and_then(|spec| spec.as_str());
match spec {
// New standard
Some(spec) => match spec {
"0.1.0" => serde_json::from_value(value).map_err(Into::into),
_ => Err(anyhow!("IDL spec not supported: `{spec}`")),
},
// Legacy
None => serde_json::from_value::<legacy::Idl>(value).map(TryInto::try_into)?,
}
/// Create an [`Idl`] value with additional support for older specs based on the
/// `idl.metadata.spec` field.
///
/// If `spec` field is not specified, the conversion will fallback to the legacy IDL spec
/// (pre Anchor v0.30.0).
///
/// **Note:** For legacy IDLs, `idl.metadata.address` field is required to be populated with
/// program's address otherwise an error will be returned.
pub fn convert_idl(idl: &[u8]) -> Result<Idl> {
let value = serde_json::from_slice::<serde_json::Value>(idl)?;
let spec = value
.get("metadata")
.and_then(|m| m.get("spec"))
.and_then(|spec| spec.as_str());
match spec {
// New standard
Some(spec) => match spec {
"0.1.0" => serde_json::from_value(value).map_err(Into::into),
_ => Err(anyhow!("IDL spec not supported: `{spec}`")),
},
// Legacy
None => serde_json::from_value::<legacy::Idl>(value).map(TryInto::try_into)?,
}
}

Expand Down Expand Up @@ -433,18 +431,21 @@ mod legacy {
fn from(value: IdlTypeDefinitionTy) -> Self {
match value {
IdlTypeDefinitionTy::Struct { fields } => Self::Struct {
fields: fields
.is_empty()
.then(|| None)
.unwrap_or_else(|| Some(fields.into())),
fields: fields.is_empty().then(|| None).unwrap_or_else(|| {
Some(t::IdlDefinedFields::Named(
fields.into_iter().map(Into::into).collect(),
))
}),
},
IdlTypeDefinitionTy::Enum { variants } => Self::Enum {
variants: variants
.into_iter()
.map(|variant| t::IdlEnumVariant {
name: variant.name,
fields: variant.fields.map(|fields| match fields {
EnumFields::Named(fields) => fields.into(),
EnumFields::Named(fields) => t::IdlDefinedFields::Named(
fields.into_iter().map(Into::into).collect(),
),
EnumFields::Tuple(tys) => t::IdlDefinedFields::Tuple(
tys.into_iter().map(Into::into).collect(),
),
Expand All @@ -469,12 +470,6 @@ mod legacy {
}
}

impl From<Vec<IdlField>> for t::IdlDefinedFields {
fn from(value: Vec<IdlField>) -> Self {
Self::Named(value.into_iter().map(Into::into).collect())
}
}

impl From<IdlType> for t::IdlType {
fn from(value: IdlType) -> Self {
match value {
Expand Down
4 changes: 2 additions & 2 deletions idl/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! Anchor IDL.

pub mod types;

#[cfg(feature = "build")]
pub mod build;

#[cfg(feature = "convert")]
pub mod convert;

pub use anchor_lang_idl_spec as types;

#[cfg(feature = "build")]
pub use serde_json;
4 changes: 2 additions & 2 deletions lang/attribute/program/src/declare_program/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod common;
mod mods;

use anchor_lang_idl::types::Idl;
use anchor_lang_idl::{convert::convert_idl, types::Idl};
use anyhow::anyhow;
use quote::{quote, ToTokens};
use syn::parse::{Parse, ParseStream};
Expand Down Expand Up @@ -45,7 +45,7 @@ fn get_idl(name: &syn::Ident) -> anyhow::Result<Idl> {
.map(|idl_dir| idl_dir.join(name.to_string()).with_extension("json"))
.map(std::fs::read)?
.map_err(|e| anyhow!("Failed to read IDL `{name}`: {e}"))
.map(|buf| Idl::from_slice_with_conversion(&buf))?
.map(|buf| convert_idl(&buf))?
}

fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream {
Expand Down

0 comments on commit cc43e67

Please sign in to comment.