Skip to main content

arminius_lib_events/
conversions.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Helper functions for converting events between different formats.
6//!
7//! This module provides utilities for:
8//! - Building event JSON values
9//! - Serializing event references
10//! - Extracting signatures from canonical JSON
11//! - Converting between PDU formats
12//!
13//! This module is package-private; Things marked as `pub` are not actually
14//! public, unless re-exported elsewhere.
15
16use ruma::{CanonicalJsonObject, CanonicalJsonValue};
17use serde_json::value::RawValue as RawJsonValue;
18
19use crate::{
20	errors::SigningError,
21	types::{ClientContext, HashedEventRef},
22};
23
24/// Build the unsigned section for client events.
25pub fn build_unsigned(ctx: ClientContext) -> Option<serde_json::Value> {
26	let mut unsigned = serde_json::Map::new();
27	if let Some(age) = ctx.age {
28		unsigned.insert("age".to_owned(), serde_json::Value::Number(age.into()));
29	}
30	if let Some(transaction_id) = ctx.transaction_id {
31		unsigned.insert(
32			"transaction_id".to_owned(),
33			serde_json::Value::String(transaction_id.to_string()),
34		);
35	}
36	if let Some(prev_content) = ctx.prev_content {
37		unsigned.insert("prev_content".to_owned(), prev_content);
38	}
39
40	(!unsigned.is_empty()).then_some(serde_json::Value::Object(unsigned))
41}
42
43/// Serialize a hashed event reference to JSON.
44pub fn serialize_hashed_event_ref(event: &HashedEventRef) -> serde_json::Value {
45	let hashes = event.hashes.to_json_value();
46	serde_json::Value::Array(vec![serde_json::Value::String(event.event_id.to_string()), hashes])
47}
48
49/// Extract signature maps from a canonical event object.
50pub fn extract_signatures(
51	object: &CanonicalJsonObject,
52) -> Result<ruma::ServerSignatures, SigningError> {
53	let signatures = object.get("signatures").ok_or_else(|| {
54		SigningError::SignatureCreation("Missing signatures in canonical event".to_owned())
55	})?;
56
57	let CanonicalJsonValue::Object(servers) = signatures else {
58		return Err(SigningError::SignatureCreation("signatures must be a JSON object".to_owned()));
59	};
60
61	let mut parsed = ruma::ServerSignatures::new();
62	for (server, keys) in servers {
63		let server_name = ruma::ServerName::parse(server).map_err(|err| {
64			SigningError::InvalidContent(format!("Error parsing server name: {err}"))
65		})?;
66		let CanonicalJsonValue::Object(keys) = keys else {
67			return Err(SigningError::SignatureCreation(
68				"signatures must map servers to key sets".to_owned(),
69			));
70		};
71
72		for (key_id, signature) in keys {
73			let CanonicalJsonValue::String(signature) = signature else {
74				return Err(SigningError::SignatureCreation(
75					"signature values must be strings".to_owned(),
76				));
77			};
78			parsed.insert_signature(
79				server_name.clone(),
80				key_id.parse().map_err(|err| {
81					SigningError::InvalidContent(format!("Error parsing key id: {err}"))
82				})?,
83				signature.to_owned(),
84			);
85		}
86	}
87
88	Ok(parsed)
89}
90
91/// Serialize a JSON value into a raw JSON box.
92pub fn raw_json(value: &serde_json::Value) -> Box<RawJsonValue> {
93	let serialized = value.to_string();
94	#[expect(
95		clippy::expect_used,
96		reason = "RawValue::from_string can fail if the input is not valid JSON. But we know it's valid JSON, we just serialized it"
97	)]
98	RawJsonValue::from_string(serialized)
99	.expect("RawValue::from_string can fail if the input is not valid JSON. But we know it's valid JSON, we just serialized it")
100}
101
102mod tests;