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