refactor: clean serializers

This commit is contained in:
Clément DOUIN
2026-03-31 16:55:21 +02:00
parent 8b868f6e0e
commit 6cde5dfe38
17 changed files with 159 additions and 486 deletions
+7 -21
View File
@@ -12,7 +12,7 @@ use io_imap::{
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use serde::Serialize;
use crate::imap::{
account::ImapAccount, envelope::search::parse_query, mailbox::arg::MailboxNameOptionalArg,
@@ -116,22 +116,15 @@ fn parse_sort_key(s: &str) -> Result<SortKey> {
}
#[derive(Clone, Debug, Serialize)]
pub struct SortResult {
pub id: u32,
}
pub struct SortResultsTable {
results: Vec<SortResult>,
ids: Vec<u32>,
uid_mode: bool,
}
impl SortResultsTable {
pub fn new(ids: Vec<std::num::NonZeroU32>, uid_mode: bool) -> Self {
let results = ids
.into_iter()
.map(|id| SortResult { id: id.get() })
.collect();
Self { results, uid_mode }
let ids = ids.into_iter().map(|id| id.get()).collect();
Self { ids, uid_mode }
}
}
@@ -146,20 +139,13 @@ impl fmt::Display for SortResultsTable {
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
.set_header(Row::from([Cell::new(id_header)]));
for result in &self.results {
table.add_row(Row::from([Cell::new(result.id)]));
for id in &self.ids {
table.add_row(Row::from([Cell::new(id)]));
}
writeln!(f)?;
write!(f, "{table}")?;
writeln!(f)?;
writeln!(f, "Found {} message(s)", self.results.len())?;
Ok(())
}
}
impl Serialize for SortResultsTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.results.serialize(serializer)
writeln!(f, "Found {} message(s)", self.ids.len())
}
}
+4 -2
View File
@@ -12,7 +12,7 @@ use io_imap::{
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::{stream::imap::ImapSession, terminal::printer::Printer};
use serde::{Serialize, Serializer};
use serde::{ser::SerializeStruct, Serialize, Serializer};
use crate::imap::{
account::ImapAccount,
@@ -320,6 +320,8 @@ impl ThreadResultsTable {
impl Serialize for ThreadResultsTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.build_entries().serialize(serializer)
let mut s = serializer.serialize_struct("ThreadResultsTable", 1)?;
s.serialize_field("threads", &self.build_entries())?;
s.end()
}
}
+5 -5
View File
@@ -74,14 +74,14 @@ impl IdCommand {
server_id: params
.unwrap_or_default()
.into_iter()
.map(|(key, val)| {
(
String::from_utf8(key.into_inner().into_owned()).unwrap(),
.filter_map(|(key, val)| {
Some((
String::from_utf8(key.into_inner().into_owned()).ok()?,
match val.into_option() {
Some(val) => Some(String::from_utf8(val.into_owned()).unwrap()),
Some(val) => Some(String::from_utf8(val.into_owned()).ok()?),
None => None,
},
)
))
})
.collect(),
};
+6 -11
View File
@@ -9,7 +9,7 @@ use io_imap::{
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use serde::Serialize;
use crate::imap::account::ImapAccount;
@@ -65,17 +65,18 @@ impl ListMailboxesCommand {
let table = MailboxesTable {
preset: account.table_preset,
rows: mailboxes.into_iter().map(From::from).collect(),
mailboxes: mailboxes.into_iter().map(From::from).collect(),
};
printer.out(table)
}
}
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Serialize)]
pub struct MailboxesTable {
#[serde(skip)]
pub preset: String,
pub rows: Vec<MailboxRow>,
pub mailboxes: Vec<MailboxRow>,
}
impl fmt::Display for MailboxesTable {
@@ -89,7 +90,7 @@ impl fmt::Display for MailboxesTable {
Cell::new("DELIMITER"),
Cell::new("ATTRIBUTES"),
]))
.add_rows(self.rows.iter().map(|mbox| {
.add_rows(self.mailboxes.iter().map(|mbox| {
let mut row = Row::new();
row.max_height(1)
@@ -107,12 +108,6 @@ impl fmt::Display for MailboxesTable {
}
}
impl Serialize for MailboxesTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.rows.serialize(serializer)
}
}
#[derive(Clone, Debug, Serialize)]
pub struct MailboxRow {
pub name: String,
-4
View File
@@ -38,10 +38,6 @@ pub struct ReadMessageCommand {
/// Show HTML content instead of plain text.
#[arg(long)]
pub html: bool,
/// Terminal width for text wrapping.
#[arg(long, short = 'w', default_value = "80")]
pub width: usize,
}
impl ReadMessageCommand {
-378
View File
@@ -1,378 +0,0 @@
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::{
fs,
io::{self, Read, Write},
net::TcpStream,
sync::Arc,
};
use anyhow::{bail, Result};
use io_imap::{
context::ImapContext,
coroutines::{
authenticate::*, authenticate_anonymous::ImapAuthenticateAnonymousParams,
authenticate_plain::ImapAuthenticatePlainParams, capability::*,
greeting_with_capability::*, login::ImapLoginParams, starttls::*,
},
types::{auth::AuthMechanism, response::Capability},
};
use io_stream::runtimes::std::handle;
use log::{debug, info};
#[cfg(feature = "native-tls")]
use native_tls::TlsConnector;
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
use rustls::{
crypto::{self, CryptoProvider},
pki_types::{pem::PemObject, CertificateDer},
ClientConfig, ClientConnection, StreamOwned,
};
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
use rustls_platform_verifier::{ConfigVerifierExt, Verifier};
#[cfg(windows)]
use uds_windows::UnixStream;
use crate::config::{ImapConfig, RustlsCryptoConfig, SaslMechanismConfig, TlsProviderConfig};
pub enum Stream {
Tcp(TcpStream),
Unix(UnixStream),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Rustls(StreamOwned<ClientConnection, TcpStream>),
#[cfg(feature = "native-tls")]
NativeTls(native_tls::TlsStream<TcpStream>),
}
pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
info!("connecting to IMAP server using {}", config.url);
let mut context = ImapContext::new();
let host = config.url.host_str().unwrap_or("127.0.0.1");
let (mut context, mut stream) = match config.url.scheme() {
scheme if scheme.eq_ignore_ascii_case("imap") => {
let port = config.url.port().unwrap_or(143);
let mut stream = TcpStream::connect((host, port))?;
let mut coroutine = GetImapGreetingWithCapability::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetImapGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetImapGreetingWithCapabilityResult::Ok { context: c } => break context = c,
GetImapGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
(context, Stream::Tcp(stream))
}
scheme if scheme.eq_ignore_ascii_case("imaps") => {
let port = config.url.port().unwrap_or(993);
let mut stream = TcpStream::connect((host, port))?;
if config.starttls {
let mut coroutine = ImapStartTls::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
ImapStartTlsResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapStartTlsResult::Ok { context: c } => break context = c,
ImapStartTlsResult::Err { err, .. } => Err(err)?,
}
}
}
let tls_provider = match config.tls.provider {
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Some(TlsProviderConfig::Rustls) => TlsProviderConfig::Rustls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
Some(TlsProviderConfig::Rustls) => {
bail!("Required cargo feature: `rustls-aws` or `rustls-ring`")
}
#[cfg(feature = "native-tls")]
Some(TlsProviderConfig::NativeTls) => TlsProviderConfig::NativeTls,
#[cfg(not(feature = "native-tls"))]
Some(TlsProviderConfig::NativeTls) => {
bail!("Required cargo feature: `native-tls`")
}
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
None => TlsProviderConfig::Rustls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
#[cfg(feature = "native-tls")]
None => TlsProviderConfig::NativeTls,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
#[cfg(not(feature = "native-tls"))]
None => {
bail!("Required cargo feature: `rustls-aws`, `rustls-ring` or `native-tls`")
}
};
debug!("using TLS provider: {tls_provider:?}");
let mut stream = match tls_provider {
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
TlsProviderConfig::Rustls => {
let crypto_provider = match config.tls.rustls.crypto {
#[cfg(feature = "rustls-aws")]
Some(RustlsCryptoConfig::Aws) => RustlsCryptoConfig::Aws,
#[cfg(not(feature = "rustls-aws"))]
Some(RustlsCryptoConfig::Aws) => {
bail!("Required cargo feature: `rustls-aws`");
}
#[cfg(feature = "rustls-ring")]
Some(RustlsCryptoConfig::Ring) => RustlsCryptoConfig::Ring,
#[cfg(not(feature = "rustls-ring"))]
Some(RustlsCryptoConfig::Ring) => {
bail!("Required cargo feature: `rustls-ring`");
}
#[cfg(feature = "rustls-ring")]
None => RustlsCryptoConfig::Ring,
#[cfg(not(feature = "rustls-ring"))]
#[cfg(feature = "rustls-aws")]
None => RustlsCryptoConfig::Aws,
#[cfg(not(feature = "rustls-aws"))]
#[cfg(not(feature = "rustls-ring"))]
None => {
bail!("Required cargo feature: `rustls-aws` or `rustls-ring`");
}
};
debug!("using rustls crypto provider: {crypto_provider:?}");
let crypto_provider = match crypto_provider {
#[cfg(feature = "rustls-aws")]
RustlsCryptoConfig::Aws => crypto::aws_lc_rs::default_provider(),
#[cfg(feature = "rustls-ring")]
RustlsCryptoConfig::Ring => crypto::ring::default_provider(),
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
let crypto_provider = match crypto_provider.install_default() {
Ok(()) => CryptoProvider::get_default().unwrap().clone(),
Err(crypto_provider) => crypto_provider,
};
let mut config = if let Some(pem_path) = &config.tls.cert {
debug!("using TLS cert at {}", pem_path.display());
let pem = fs::read(pem_path)?;
let Some(cert) = CertificateDer::pem_slice_iter(&pem).next() else {
bail!("empty TLS cert at {}", pem_path.display())
};
let verifier =
Verifier::new_with_extra_roots(vec![cert?], crypto_provider)?;
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(verifier))
.with_no_client_auth()
} else {
debug!("using OS TLS certs");
ClientConfig::with_platform_verifier()?
};
config.alpn_protocols = vec![b"imap".to_vec()];
let server_name = host.to_string().try_into()?;
let conn = ClientConnection::new(Arc::new(config), server_name)?;
Stream::Rustls(StreamOwned::new(conn, stream))
}
#[cfg(feature = "native-tls")]
TlsProviderConfig::NativeTls => {
let mut builder = TlsConnector::builder();
if let Some(pem_path) = &config.tls.cert {
debug!("using TLS cert at {}", pem_path.display());
let pem = fs::read(pem_path)?;
let cert = native_tls::Certificate::from_pem(&pem)?;
builder.add_root_certificate(cert);
}
let connector = builder.build()?;
Stream::NativeTls(connector.connect(host, stream)?)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
};
if config.starttls {
let mut coroutine = GetImapCapability::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetImapCapabilityResult::Io { io } => arg = Some(handle(&mut stream, io)?),
GetImapCapabilityResult::Ok { context: c } => break context = c,
GetImapCapabilityResult::Err { err, .. } => Err(err)?,
}
}
} else {
let mut coroutine = GetImapGreetingWithCapability::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetImapGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetImapGreetingWithCapabilityResult::Ok { context: c } => {
break context = c
}
GetImapGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
}
(context, stream)
}
scheme if scheme.eq_ignore_ascii_case("unix") => {
let sock_path = config.url.path();
let mut stream = UnixStream::connect(&sock_path)?;
let mut coroutine = GetImapGreetingWithCapability::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetImapGreetingWithCapabilityResult::Io { io } => {
arg = Some(handle(&mut stream, io)?)
}
GetImapGreetingWithCapabilityResult::Ok { context: c } => break context = c,
GetImapGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
(context, Stream::Unix(stream))
}
scheme => {
bail!("Unknown scheme `{scheme}`, expected imap, imaps or unix");
}
};
if !context.authenticated {
let mut candidates = vec![];
let ir = context.capability.contains(&Capability::SaslIr);
for mechanism in config.sasl.mechanisms {
match mechanism {
SaslMechanismConfig::Login => {
let Some(auth) = config.sasl.login.take() else {
debug!("missing SASL LOGIN configuration, skipping it");
continue;
};
if context.capability.contains(&Capability::LoginDisabled) {
debug!("SASL LOGIN disabled by the server, skipping it");
continue;
}
let login = Capability::Auth(AuthMechanism::Login);
if !context.capability.contains(&login) {
debug!("SASL LOGIN disabled by the server, skipping it");
continue;
}
candidates.push(ImapAuthenticateCandidate::Login(ImapLoginParams::new(
auth.username,
auth.password.get()?,
)?));
}
SaslMechanismConfig::Plain => {
let Some(auth) = config.sasl.plain.take() else {
debug!("missing SASL PLAIN configuration, skipping it");
continue;
};
let plain = Capability::Auth(AuthMechanism::Plain);
if !context.capability.contains(&plain) {
debug!("SASL PLAIN disabled by the server, skipping it");
continue;
}
candidates.push(ImapAuthenticateCandidate::Plain(
ImapAuthenticatePlainParams::new(
auth.authzid,
auth.authcid,
auth.passwd.get()?,
ir,
),
));
}
SaslMechanismConfig::Anonymous => {
// TODO: check if capability available
let message = config
.sasl
.anonymous
.take()
.and_then(|auth| auth.message)
.unwrap_or_default();
candidates.push(ImapAuthenticateCandidate::Anonymous(
ImapAuthenticateAnonymousParams::new(message, ir),
));
}
};
}
let mut arg = None;
let mut coroutine = ImapAuthenticate::new(context, candidates);
loop {
match coroutine.resume(arg.take()) {
ImapAuthenticateResult::Io { io } => arg = Some(handle(&mut stream, io)?),
ImapAuthenticateResult::Ok { context: c, .. } => break context = c,
ImapAuthenticateResult::Err { err, .. } => bail!(err),
}
}
}
Ok((context, stream))
}
impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.read(buf),
Self::Unix(s) => s.read(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.read(buf),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.read(buf),
}
}
}
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Tcp(s) => s.write(buf),
Self::Unix(s) => s.write(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.write(buf),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
Self::Tcp(s) => s.flush(),
Self::Unix(s) => s.flush(),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.flush(),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.flush(),
}
}
}
+5 -3
View File
@@ -1,8 +1,10 @@
use anyhow::{anyhow, bail, Result};
use clap::Parser;
use io_jmap::{
rfc8620::coroutines::blob_download::{JmapBlobDownload, JmapBlobDownloadResult},
rfc8620::types::session::capabilities,
rfc8620::{
coroutines::blob_download::{JmapBlobDownload, JmapBlobDownloadResult},
types::session::capabilities::{self, MAIL},
},
rfc8621::coroutines::email_get::{JmapEmailGet, JmapEmailGetResult},
};
use io_stream::runtimes::std::handle;
@@ -52,7 +54,7 @@ impl ExportEmailCommand {
let account_id = jmap
.session
.primary_accounts
.get(capabilities::MAIL)
.get(MAIL)
.map(|s| s.as_str())
.unwrap_or("");
let blob_id = emails
+9 -5
View File
@@ -6,10 +6,14 @@ use std::{
use anyhow::{bail, Result};
use clap::Parser;
use io_jmap::{
rfc8620::coroutines::blob_upload::{JmapBlobUpload, JmapBlobUploadResult},
rfc8620::types::session::capabilities,
rfc8621::coroutines::email_import::{JmapEmailImport, JmapEmailImportResult},
rfc8621::types::email::EmailImport,
rfc8620::{
coroutines::blob_upload::{JmapBlobUpload, JmapBlobUploadResult},
types::session::capabilities::{self, MAIL},
},
rfc8621::{
coroutines::email_import::{JmapEmailImport, JmapEmailImportResult},
types::email::EmailImport,
},
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
@@ -64,7 +68,7 @@ impl ImportEmailCommand {
let account_id = jmap
.session
.primary_accounts
.get(capabilities::MAIL)
.get(MAIL)
.map(|s| s.as_str())
.unwrap_or("");
let url: Url = jmap
+18 -1
View File
@@ -4,6 +4,7 @@ use io_jmap::rfc8621::coroutines::email_parse::{JmapEmailParse, JmapEmailParseRe
use io_stream::runtimes::std::handle;
use log::warn;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::Serialize;
use crate::jmap::account::JmapAccount;
@@ -49,13 +50,15 @@ impl ParseEmailCommand {
warn!("blob `{id}` not valid MIME message, ignoring it");
}
let mut bodies = Vec::new();
for (_blob_id, email) in parsed {
if let Some(body_values) = &email.body_values {
if let Some(text_parts) = &email.text_body {
for part in text_parts {
if let Some(part_id) = &part.part_id {
if let Some(body_value) = body_values.get(part_id) {
printer.out(&body_value.value)?;
bodies.push(body_value.value.clone());
}
}
}
@@ -63,6 +66,20 @@ impl ParseEmailCommand {
}
}
printer.out(ParsedBodies { bodies })
}
}
#[derive(Serialize)]
struct ParsedBodies {
bodies: Vec<String>,
}
impl std::fmt::Display for ParsedBodies {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for body in &self.bodies {
write!(f, "{body}")?;
}
Ok(())
}
}
-1
View File
@@ -171,7 +171,6 @@ impl JmapEmailQueryCommand {
}
#[derive(Clone, Debug, Serialize)]
#[serde(transparent)]
pub struct EmailsTable {
#[serde(skip)]
pub preset: String,
-1
View File
@@ -58,7 +58,6 @@ impl GetIdentityCommand {
}
#[derive(Clone, Debug, Serialize)]
#[serde(transparent)]
pub struct IdentitiesTable {
#[serde(skip)]
pub preset: String,
-1
View File
@@ -118,7 +118,6 @@ impl JmapMailboxQueryCommand {
}
#[derive(Clone, Debug, Default, Serialize)]
#[serde(transparent)]
pub struct MailboxesTable {
#[serde(skip)]
pub preset: String,
+23 -16
View File
@@ -7,11 +7,12 @@ use anyhow::{bail, Context, Result};
use clap::Parser;
use io_jmap::rfc8620::{
coroutines::send::{JmapRequest, JmapSend, JmapSendResult},
types::session::capabilities,
types::session::capabilities::{CORE, MAIL},
};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::Serialize;
use serde_json::Value;
use crate::jmap::account::JmapAccount;
@@ -53,50 +54,52 @@ impl QueryCommand {
self.method_calls.join(" ")
};
let calls_value: serde_json::Value =
let calls_value: Value =
serde_json::from_str(&raw).context("METHOD_CALLS is not valid JSON")?;
let serde_json::Value::Array(calls_arr) = calls_value else {
let Value::Array(calls_arr) = calls_value else {
bail!("METHOD_CALLS must be a JSON array");
};
let account_id = jmap
.session
.primary_accounts
.get(capabilities::MAIL)
.get(MAIL)
.cloned()
.unwrap_or_default();
// Parse and inject accountId into each call's args.
let mut method_calls = Vec::with_capacity(calls_arr.len());
for (i, call) in calls_arr.into_iter().enumerate() {
let serde_json::Value::Array(mut tuple) = call else {
let Value::Array(mut tuple) = call else {
bail!("method call #{i} must be a JSON array [name, args, callId]");
};
if tuple.len() != 3 {
bail!("method call #{i} must have exactly 3 elements [name, args, callId]");
}
let call_id = match tuple.remove(2) {
serde_json::Value::String(s) => s,
Value::String(s) => s,
v => bail!("method call #{i} callId must be a string, got {v}"),
};
let mut args = tuple.remove(1);
let name = match tuple.remove(0) {
serde_json::Value::String(s) => s,
Value::String(s) => s,
v => bail!("method call #{i} name must be a string, got {v}"),
};
// Inject accountId if the args object doesn't already have it.
if let serde_json::Value::Object(ref mut map) = args {
if let Value::Object(ref mut map) = args {
map.entry("accountId")
.or_insert_with(|| serde_json::Value::String(account_id.clone()));
.or_insert_with(|| Value::String(account_id.clone()));
}
method_calls.push((name, args, call_id));
}
let mut using = vec![
capabilities::CORE.to_string(),
capabilities::MAIL.to_string(),
];
let mut using = vec![CORE.to_string(), MAIL.to_string()];
for extra in self.using {
if !using.contains(&extra) {
using.push(extra);
@@ -122,17 +125,21 @@ impl QueryCommand {
}
};
printer.out(RawResponse(response.method_responses))
printer.out(RawResponse {
method_responses: response.method_responses,
})
}
}
/// Wraps the raw method_responses for display.
#[derive(Serialize)]
struct RawResponse(Vec<(String, serde_json::Value, String)>);
struct RawResponse {
method_responses: Vec<(String, Value, String)>,
}
impl fmt::Display for RawResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match serde_json::to_string_pretty(&self.0) {
match serde_json::to_string_pretty(&self.method_responses) {
Ok(s) => write!(f, "{s}"),
Err(e) => write!(f, "<serialization error: {e}>"),
}
-1
View File
@@ -108,7 +108,6 @@ impl QuerySubmissionCommand {
}
#[derive(Clone, Debug, Serialize)]
#[serde(transparent)]
pub struct SubmissionsTable {
#[serde(skip)]
pub preset: String,
-1
View File
@@ -53,7 +53,6 @@ impl GetThreadCommand {
}
#[derive(Clone, Debug, Serialize)]
#[serde(transparent)]
pub struct ThreadsTable {
#[serde(skip)]
pub preset: String,