Skip to content

Commit 6bcb787

Browse files
chore(pubkey): (may be) proper serde json support (encode as base59 string, not as array) and json schema
1 parent 4e30766 commit 6bcb787

File tree

3 files changed

+146
-8
lines changed

3 files changed

+146
-8
lines changed

Cargo.lock

Lines changed: 66 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pubkey/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "solana-pubkey"
33
description = "Solana account addresses"
44
documentation = "https://docs.rs/solana-pubkey"
5-
version = "2.2.1"
5+
version = "2.3.0"
66
authors = { workspace = true }
77
repository = { workspace = true }
88
homepage = { workspace = true }
@@ -19,6 +19,7 @@ bytemuck_derive = { workspace = true, optional = true }
1919
five8_const = { workspace = true }
2020
num-traits = { workspace = true }
2121
rand = { workspace = true, optional = true }
22+
schemars = { version = "0.8", default-features = false, optional = true, features = ["derive", "preserve_order"] }
2223
serde = { workspace = true, optional = true }
2324
serde_derive = { workspace = true, optional = true }
2425
solana-atomic-u64 = { workspace = true }
@@ -48,6 +49,7 @@ wasm-bindgen = { workspace = true }
4849
anyhow = { workspace = true }
4950
arbitrary = { workspace = true, features = ["derive"] }
5051
bs58 = { workspace = true, features = ["alloc"] }
52+
serde_json = { workspace = true }
5153
# circular dev deps need to be path deps for `cargo publish` to be happy,
5254
# and for now the doc tests need solana-program
5355
solana-program = { path = "../program" }
@@ -72,7 +74,8 @@ frozen-abi = [
7274
"std",
7375
]
7476
rand = ["dep:rand", "std"]
75-
serde = ["dep:serde", "dep:serde_derive"]
77+
schemars = ["dep:schemars", "serde", "std"]
78+
serde = ["dep:serde", "dep:serde_derive", "bs58/alloc"]
7679
sha2 = ["dep:solana-sha256-hasher", "solana-sha256-hasher/sha2"]
7780
std = []
7881

pubkey/src/lib.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
55
#![allow(clippy::arithmetic_side_effects)]
66

7+
#[cfg(all(feature = "serde", not(feature = "std")))]
8+
extern crate alloc;
9+
710
#[cfg(any(feature = "std", target_arch = "wasm32"))]
811
extern crate std;
912
#[cfg(feature = "dev-context-only-utils")]
@@ -156,11 +159,27 @@ impl From<u64> for PubkeyError {
156159
borsh(crate = "borsh")
157160
)]
158161
#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
162+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
159163
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
160164
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
161165
#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
162166
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
163-
pub struct Pubkey(pub(crate) [u8; 32]);
167+
#[cfg_attr(feature = "serde", serde(transparent))]
168+
pub struct Pubkey(
169+
#[cfg_attr(
170+
feature = "serde",
171+
serde(serialize_with = "serialize", deserialize_with = "deserialize")
172+
)]
173+
#[cfg_attr(
174+
feature = "schemars",
175+
schemars(
176+
with = "std::string::String",
177+
title = "SolanaPubkey",
178+
range(min = 32, max = 44)
179+
)
180+
)]
181+
pub(crate) [u8; 32],
182+
);
164183

165184
impl solana_sanitize::Sanitize for Pubkey {}
166185

@@ -894,6 +913,35 @@ macro_rules! impl_borsh_serialize {
894913
#[cfg(feature = "borsh")]
895914
impl_borsh_serialize!(borsh0_10);
896915

916+
#[cfg(feature = "serde")]
917+
fn serialize<S>(bytes: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
918+
where
919+
S: serde::Serializer,
920+
{
921+
let encoded = bs58::encode(bytes).into_string();
922+
serializer.serialize_str(&encoded)
923+
}
924+
925+
#[cfg(feature = "serde")]
926+
fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
927+
where
928+
D: serde::Deserializer<'de>,
929+
{
930+
#[cfg(feature = "std")]
931+
use std::{format, string::String};
932+
933+
#[cfg(not(feature = "std"))]
934+
use alloc::{format, string::String};
935+
936+
let s: String = serde::Deserialize::deserialize(deserializer)
937+
.map_err(|_| serde::de::Error::custom("Failed to read String"))?;
938+
let b = bs58::decode(s)
939+
.into_vec()
940+
.map_err(|x| serde::de::Error::custom(format!("Failed to decode base58 {x}")))?;
941+
b.try_into()
942+
.map_err(|_| serde::de::Error::custom("Invalid length"))
943+
}
944+
897945
#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
898946
fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
899947
let vec_vec_u8 = array_of_uint8_arrays
@@ -1416,4 +1464,30 @@ mod tests {
14161464
// Sanity check: ensure the pointer is the same.
14171465
assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
14181466
}
1467+
1468+
#[test]
1469+
#[cfg(feature = "serde")]
1470+
fn json() {
1471+
let pubkey = Pubkey::new_unique();
1472+
let json = serde_json::to_string(&pubkey).unwrap();
1473+
let display = pubkey.to_string();
1474+
let back = serde_json::from_str::<Pubkey>(&json).unwrap();
1475+
1476+
assert_eq!(json, std::format!("\"{display}\""));
1477+
assert_eq!(pubkey, back);
1478+
}
1479+
1480+
#[test]
1481+
#[cfg(feature = "schemars")]
1482+
fn schemars() {
1483+
use schemars::{
1484+
schema::{InstanceType, SingleOrVec},
1485+
schema_for,
1486+
};
1487+
let schema = schema_for!(Pubkey);
1488+
assert_eq!(
1489+
schema.schema.instance_type.unwrap(),
1490+
SingleOrVec::Single(std::boxed::Box::new(InstanceType::String))
1491+
);
1492+
}
14191493
}

0 commit comments

Comments
 (0)