idl: Add separate spec crate (#3036)

This commit is contained in:
acheron 2024-06-19 23:06:25 +02:00 committed by GitHub
parent 9c17d65a76
commit cc43e67399
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 65 additions and 41 deletions

View File

@ -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

9
Cargo.lock generated
View File

@ -291,6 +291,7 @@ dependencies = [
name = "anchor-lang-idl"
version = "0.1.0"
dependencies = [
"anchor-lang-idl-spec",
"anyhow",
"heck 0.3.3",
"regex",
@ -299,6 +300,14 @@ dependencies = [
"sha2 0.10.8",
]
[[package]]
name = "anchor-lang-idl-spec"
version = "0.1.0"
dependencies = [
"anyhow",
"serde",
]
[[package]]
name = "anchor-spl"
version = "0.30.0"

View File

@ -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};
@ -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)),
@ -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)?,

View File

@ -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"

16
idl/spec/Cargo.toml Normal file
View File

@ -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"] }

View File

@ -309,6 +309,7 @@ pub enum IdlType {
Generic(String),
}
// TODO: Move to utils crate
impl FromStr for IdlType {
type Err = anyhow::Error;

View File

@ -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)?,
}
}
@ -433,10 +431,11 @@ 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
@ -444,7 +443,9 @@ mod legacy {
.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(),
),
@ -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 {

View File

@ -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;

View File

@ -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};
@ -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 {