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