Skip to main content

iota_json_rpc_types/
iota_primitives.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! JSON Schema and serialization adapter types for the IOTA JSON-RPC surface,
5//! applied at field sites via `#[schemars(with = "...")]` and
6//! `#[serde_as(as = "...")]`. Each adapter owns both the `schemars::JsonSchema`
7//! layer and the JSON serialization for its type, so the JSON-RPC wire format
8//! is defined in this crate rather than relying on the serde impls of the
9//! external `iota-sdk-types` crate.
10//!
11//! To add a new adapter, prefer a unit marker struct with a manual `JsonSchema`
12//! impl (for explicit control over description, format, and shape) plus
13//! `SerializeAs` / `DeserializeAs` impls for the target type(s). String-like
14//! types reuse `serde_with::DisplayFromStr` so the format matches the type's
15//! `Display`/`FromStr`; byte payloads reuse the `fastcrypto` encoders. The Move
16//! tag adapters reuse the shared, IOTA-specific formatting/parsing helpers from
17//! `iota_types` (which many other crates depend on) rather than duplicating
18//! that logic. Newtype wrappers (e.g. `SequenceNumberString(u64)`) are only
19//! appropriate when the wrapper itself is the serialised value.
20
21use fastcrypto::{
22    encoding::{Base58 as FastCryptoBase58, Base64 as FastCryptoBase64},
23    traits::EncodeDecodeBase64,
24};
25use iota_sdk_types::{
26    Address as NativeAddress, CertificateDigest, CheckpointContentsDigest, CheckpointDigest,
27    ConsensusCommitDigest, Digest, EffectsAuxDataDigest, Identifier as NativeIdentifier,
28    MisbehaviorReportDigest, MoveAuthenticatorDigest, ObjectDigest, ObjectId as NativeObjectId,
29    SenderSignedDataDigest, StructTag as NativeStructTag, TransactionDigest,
30    TransactionEffectsDigest, TransactionEventsDigest, TypeTag as NativeTypeTag, Version,
31};
32use iota_types::{
33    iota_serde::{to_iota_struct_tag_string, to_iota_type_tag_string},
34    parse_iota_struct_tag, parse_iota_type_tag,
35    signature::GenericSignature as NativeGenericSignature,
36};
37use schemars::{
38    JsonSchema,
39    schema::{InstanceType, Metadata, NumberValidation, SchemaObject},
40};
41use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error as _};
42use serde_with::{DeserializeAs, DisplayFromStr, SerializeAs, serde_as};
43
44/// A schema type that defines the JSON representation of the
45/// [`Address`](iota_sdk_types::Address) type.
46pub struct Address;
47
48impl JsonSchema for Address {
49    fn schema_name() -> String {
50        "Address".to_owned()
51    }
52
53    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
54        SchemaObject {
55            metadata: Some(Box::new(Metadata {
56                description: Some("IOTA address as a hex string".to_owned()),
57                ..Default::default()
58            })),
59            instance_type: Some(InstanceType::String.into()),
60            format: Some("hex".to_owned()),
61            ..Default::default()
62        }
63        .into()
64    }
65}
66
67impl SerializeAs<NativeAddress> for Address {
68    fn serialize_as<S>(value: &NativeAddress, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: Serializer,
71    {
72        DisplayFromStr::serialize_as(value, serializer)
73    }
74}
75
76impl<'de> DeserializeAs<'de, NativeAddress> for Address {
77    fn deserialize_as<D>(deserializer: D) -> Result<NativeAddress, D::Error>
78    where
79        D: Deserializer<'de>,
80    {
81        DisplayFromStr::deserialize_as(deserializer)
82    }
83}
84
85/// A schema type that defines the JSON representation of the
86/// [`ObjectId`](iota_sdk_types::ObjectId) type.
87pub struct ObjectId;
88
89impl JsonSchema for ObjectId {
90    fn schema_name() -> String {
91        "ObjectID".to_owned()
92    }
93
94    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
95        SchemaObject {
96            metadata: Some(Box::new(Metadata {
97                description: Some("Object ID as a hex string".to_owned()),
98                ..Default::default()
99            })),
100            instance_type: Some(InstanceType::String.into()),
101            format: Some("hex".to_owned()),
102            ..Default::default()
103        }
104        .into()
105    }
106}
107
108impl SerializeAs<NativeObjectId> for ObjectId {
109    fn serialize_as<S>(value: &NativeObjectId, serializer: S) -> Result<S::Ok, S::Error>
110    where
111        S: Serializer,
112    {
113        DisplayFromStr::serialize_as(value, serializer)
114    }
115}
116
117impl<'de> DeserializeAs<'de, NativeObjectId> for ObjectId {
118    fn deserialize_as<D>(deserializer: D) -> Result<NativeObjectId, D::Error>
119    where
120        D: Deserializer<'de>,
121    {
122        DisplayFromStr::deserialize_as(deserializer)
123    }
124}
125
126/// A schema type that defines the JSON representation of the
127/// [`Version`] type as a string
128/// and provides an alternate serialization usable via `#[serde_as]`.
129#[serde_as]
130#[derive(Serialize, Deserialize)]
131pub struct SequenceNumberString(#[serde_as(as = "DisplayFromStr")] u64);
132
133impl JsonSchema for SequenceNumberString {
134    fn schema_name() -> String {
135        "SequenceNumberString".to_owned()
136    }
137
138    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
139        SchemaObject {
140            metadata: Some(Box::new(Metadata {
141                description: Some("Sequence number as a string".to_owned()),
142                ..Default::default()
143            })),
144            instance_type: Some(InstanceType::String.into()),
145            ..Default::default()
146        }
147        .into()
148    }
149}
150
151impl SerializeAs<Version> for SequenceNumberString {
152    fn serialize_as<S>(source: &Version, serializer: S) -> Result<S::Ok, S::Error>
153    where
154        S: Serializer,
155    {
156        SequenceNumberString(source.as_u64()).serialize(serializer)
157    }
158}
159
160impl<'de> DeserializeAs<'de, Version> for SequenceNumberString {
161    fn deserialize_as<D>(deserializer: D) -> Result<Version, D::Error>
162    where
163        D: Deserializer<'de>,
164    {
165        let schema = SequenceNumberString::deserialize(deserializer)?;
166        Ok(Version::from_u64(schema.0))
167    }
168}
169
170/// JSON representation of a [`Version`] as a u64 integer.
171///
172/// This serializes to a number as opposed to the SDK type that serializes
173/// as a string.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
175pub struct SequenceNumberU64(Version);
176
177impl From<Version> for SequenceNumberU64 {
178    fn from(value: Version) -> Self {
179        Self(value)
180    }
181}
182
183impl From<SequenceNumberU64> for Version {
184    fn from(value: SequenceNumberU64) -> Self {
185        value.0
186    }
187}
188
189impl std::fmt::Display for SequenceNumberU64 {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        self.0.fmt(f)
192    }
193}
194
195impl Serialize for SequenceNumberU64 {
196    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197    where
198        S: Serializer,
199    {
200        self.0.as_u64().serialize(serializer)
201    }
202}
203
204impl<'de> Deserialize<'de> for SequenceNumberU64 {
205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
206    where
207        D: Deserializer<'de>,
208    {
209        Ok(Self(Version::from_u64(u64::deserialize(deserializer)?)))
210    }
211}
212
213impl JsonSchema for SequenceNumberU64 {
214    fn schema_name() -> String {
215        "SequenceNumberU64".to_owned()
216    }
217
218    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
219        SchemaObject {
220            metadata: Some(Box::new(Metadata {
221                description: Some("Sequence number as a u64 integer".to_owned()),
222                ..Default::default()
223            })),
224            format: Some("uint64".to_owned()),
225            number: Some(Box::new(NumberValidation {
226                minimum: Some(0.0),
227                ..Default::default()
228            })),
229            instance_type: Some(InstanceType::Integer.into()),
230            ..Default::default()
231        }
232        .into()
233    }
234}
235
236/// A schema type that defines the JSON representation of the
237/// [`ProtocolVersion`](iota_protocol_config::ProtocolVersion) type as a string
238/// and provides an alternate serialization usable via `#[serde_as]`.
239#[serde_as]
240#[derive(Serialize, Deserialize, JsonSchema)]
241pub struct ProtocolVersion(
242    #[schemars(with = "String")]
243    #[serde_as(as = "DisplayFromStr")]
244    u64,
245);
246
247impl SerializeAs<iota_protocol_config::ProtocolVersion> for ProtocolVersion {
248    fn serialize_as<S>(
249        source: &iota_protocol_config::ProtocolVersion,
250        serializer: S,
251    ) -> Result<S::Ok, S::Error>
252    where
253        S: Serializer,
254    {
255        ProtocolVersion(source.as_u64()).serialize(serializer)
256    }
257}
258
259impl<'de> DeserializeAs<'de, iota_protocol_config::ProtocolVersion> for ProtocolVersion {
260    fn deserialize_as<D>(deserializer: D) -> Result<iota_protocol_config::ProtocolVersion, D::Error>
261    where
262        D: Deserializer<'de>,
263    {
264        let schema = ProtocolVersion::deserialize(deserializer)?;
265        Ok(iota_protocol_config::ProtocolVersion::new(schema.0))
266    }
267}
268
269/// A schema type that defines the JSON representation of a Base58 encoded
270/// string. A custom JsonSchema impl is necessary to add the "base58" format to
271/// the schema.
272pub struct Base58;
273
274impl JsonSchema for Base58 {
275    fn schema_name() -> String {
276        "Base58".to_owned()
277    }
278
279    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
280        SchemaObject {
281            metadata: Some(Box::new(Metadata {
282                description: Some("Base58 encoded data".to_owned()),
283                ..Default::default()
284            })),
285            instance_type: Some(InstanceType::String.into()),
286            format: Some("base58".to_owned()),
287            ..Default::default()
288        }
289        .into()
290    }
291}
292
293/// Implements the `Base58` serde adapter for a digest type by delegating to its
294/// `Display`/`FromStr` (Base58) representation.
295macro_rules! impl_base58_for_digest {
296    ($($t:ty),* $(,)?) => {
297        $(
298            impl SerializeAs<$t> for Base58 {
299                fn serialize_as<S>(value: &$t, serializer: S) -> Result<S::Ok, S::Error>
300                where
301                    S: Serializer,
302                {
303                    DisplayFromStr::serialize_as(value, serializer)
304                }
305            }
306
307            impl<'de> DeserializeAs<'de, $t> for Base58 {
308                fn deserialize_as<D>(deserializer: D) -> Result<$t, D::Error>
309                where
310                    D: Deserializer<'de>,
311                {
312                    DisplayFromStr::deserialize_as(deserializer)
313                }
314            }
315        )*
316    };
317}
318
319impl_base58_for_digest!(
320    Digest,
321    CheckpointDigest,
322    CheckpointContentsDigest,
323    CertificateDigest,
324    SenderSignedDataDigest,
325    TransactionDigest,
326    TransactionEffectsDigest,
327    TransactionEventsDigest,
328    EffectsAuxDataDigest,
329    ObjectDigest,
330    ConsensusCommitDigest,
331    MoveAuthenticatorDigest,
332    MisbehaviorReportDigest,
333);
334
335impl SerializeAs<Vec<u8>> for Base58 {
336    fn serialize_as<S>(value: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
337    where
338        S: Serializer,
339    {
340        FastCryptoBase58::serialize_as(value, serializer)
341    }
342}
343
344impl<'de> DeserializeAs<'de, Vec<u8>> for Base58 {
345    fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
346    where
347        D: Deserializer<'de>,
348    {
349        FastCryptoBase58::deserialize_as(deserializer)
350    }
351}
352
353/// A schema type that defines the JSON representation of a Base64 encoded
354/// string. A custom JsonSchema impl is necessary to add the "base64" format to
355/// the schema.
356pub struct Base64;
357
358impl JsonSchema for Base64 {
359    fn schema_name() -> String {
360        "Base64".to_owned()
361    }
362
363    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
364        SchemaObject {
365            metadata: Some(Box::new(Metadata {
366                description: Some("Base64 encoded data".to_owned()),
367                ..Default::default()
368            })),
369            instance_type: Some(InstanceType::String.into()),
370            format: Some("base64".to_owned()),
371            ..Default::default()
372        }
373        .into()
374    }
375}
376
377impl SerializeAs<Vec<u8>> for Base64 {
378    fn serialize_as<S>(value: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
379    where
380        S: Serializer,
381    {
382        FastCryptoBase64::serialize_as(value, serializer)
383    }
384}
385
386impl<'de> DeserializeAs<'de, Vec<u8>> for Base64 {
387    fn deserialize_as<D>(deserializer: D) -> Result<Vec<u8>, D::Error>
388    where
389        D: Deserializer<'de>,
390    {
391        FastCryptoBase64::deserialize_as(deserializer)
392    }
393}
394
395/// A schema type that defines the JSON representation of a Base64 encoded
396/// signature.
397pub struct GenericSignature;
398
399impl JsonSchema for GenericSignature {
400    fn schema_name() -> String {
401        "GenericSignature".to_owned()
402    }
403
404    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
405        SchemaObject {
406            metadata: Some(Box::new(Metadata {
407                description: Some("Base64 encoded signature".to_owned()),
408                ..Default::default()
409            })),
410            instance_type: Some(InstanceType::String.into()),
411            format: Some("base64".to_owned()),
412            ..Default::default()
413        }
414        .into()
415    }
416}
417
418impl SerializeAs<NativeGenericSignature> for GenericSignature {
419    fn serialize_as<S>(value: &NativeGenericSignature, serializer: S) -> Result<S::Ok, S::Error>
420    where
421        S: Serializer,
422    {
423        value.encode_base64().serialize(serializer)
424    }
425}
426
427impl<'de> DeserializeAs<'de, NativeGenericSignature> for GenericSignature {
428    fn deserialize_as<D>(deserializer: D) -> Result<NativeGenericSignature, D::Error>
429    where
430        D: Deserializer<'de>,
431    {
432        let s = String::deserialize(deserializer)?;
433        NativeGenericSignature::decode_base64(&s).map_err(D::Error::custom)
434    }
435}
436
437/// A schema type that defines the JSON representation of a Move
438/// [`StructTag`](iota_sdk_types::StructTag) as a string, and
439/// provides a string serialization usable via `#[serde_as]`.
440pub struct StructTag;
441
442impl JsonSchema for StructTag {
443    fn schema_name() -> String {
444        "StructTag".to_owned()
445    }
446
447    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
448        SchemaObject {
449            metadata: Some(Box::new(Metadata {
450                description: Some(
451                    "Move struct tag, in the format 'address::module::name<type_params>'"
452                        .to_owned(),
453                ),
454                ..Default::default()
455            })),
456            instance_type: Some(InstanceType::String.into()),
457            ..Default::default()
458        }
459        .into()
460    }
461}
462
463impl SerializeAs<NativeStructTag> for StructTag {
464    fn serialize_as<S>(value: &NativeStructTag, serializer: S) -> Result<S::Ok, S::Error>
465    where
466        S: Serializer,
467    {
468        to_iota_struct_tag_string(value)
469            .map_err(S::Error::custom)?
470            .serialize(serializer)
471    }
472}
473
474impl<'de> DeserializeAs<'de, NativeStructTag> for StructTag {
475    fn deserialize_as<D>(deserializer: D) -> Result<NativeStructTag, D::Error>
476    where
477        D: Deserializer<'de>,
478    {
479        let s = String::deserialize(deserializer)?;
480        parse_iota_struct_tag(&s).map_err(D::Error::custom)
481    }
482}
483
484/// A schema type that defines the JSON representation of a Move
485/// [`TypeTag`](iota_sdk_types::TypeTag) as a string, and
486/// provides a string serialization usable via `#[serde_as]`.
487pub struct TypeTag;
488
489impl JsonSchema for TypeTag {
490    fn schema_name() -> String {
491        "TypeTag".to_owned()
492    }
493
494    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
495        SchemaObject {
496            metadata: Some(Box::new(Metadata {
497                description: Some("Move type tag as a string".to_owned()),
498                ..Default::default()
499            })),
500            instance_type: Some(InstanceType::String.into()),
501            ..Default::default()
502        }
503        .into()
504    }
505}
506
507impl SerializeAs<NativeTypeTag> for TypeTag {
508    fn serialize_as<S>(value: &NativeTypeTag, serializer: S) -> Result<S::Ok, S::Error>
509    where
510        S: Serializer,
511    {
512        to_iota_type_tag_string(value)
513            .map_err(S::Error::custom)?
514            .serialize(serializer)
515    }
516}
517
518impl<'de> DeserializeAs<'de, NativeTypeTag> for TypeTag {
519    fn deserialize_as<D>(deserializer: D) -> Result<NativeTypeTag, D::Error>
520    where
521        D: Deserializer<'de>,
522    {
523        let s = String::deserialize(deserializer)?;
524        parse_iota_type_tag(&s).map_err(D::Error::custom)
525    }
526}
527
528/// A schema type that defines the JSON representation of a Move identifier,
529/// and provides a string serialization usable via `#[serde_as]`.
530pub struct Identifier;
531
532impl JsonSchema for Identifier {
533    fn schema_name() -> String {
534        "Identifier".to_owned()
535    }
536
537    fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
538        SchemaObject {
539            metadata: Some(Box::new(Metadata {
540                description: Some("Move identifier".to_owned()),
541                ..Default::default()
542            })),
543            instance_type: Some(InstanceType::String.into()),
544            ..Default::default()
545        }
546        .into()
547    }
548}
549
550impl SerializeAs<NativeIdentifier> for Identifier {
551    fn serialize_as<S>(value: &NativeIdentifier, serializer: S) -> Result<S::Ok, S::Error>
552    where
553        S: Serializer,
554    {
555        DisplayFromStr::serialize_as(value, serializer)
556    }
557}
558
559impl<'de> DeserializeAs<'de, NativeIdentifier> for Identifier {
560    fn deserialize_as<D>(deserializer: D) -> Result<NativeIdentifier, D::Error>
561    where
562        D: Deserializer<'de>,
563    {
564        DisplayFromStr::deserialize_as(deserializer)
565    }
566}