Import upstream v0.16.22, stripped
Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f Enterprise-only files removed or emptied: 63 Enterprise-only snippets removed: 117 in 50 files Dangling module declarations removed: 5 Cargo edits turning enterprise off: 14 Verification: clean Enterprise feature gates left for rebuilt features: 19 in 18 files Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{Error, Event, EventDetails, EventType, Key, MetricType, Value};
|
||||
use ahash::AHashSet;
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use mail_parser::DateTime;
|
||||
use serde::{
|
||||
Serialize, Serializer,
|
||||
ser::{SerializeMap, SerializeSeq},
|
||||
};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static EVENT_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
struct Keys<'x> {
|
||||
keys: &'x [(Key, Value)],
|
||||
span_keys: &'x [(Key, Value)],
|
||||
}
|
||||
|
||||
pub struct JsonEventSerializer<T> {
|
||||
inner: T,
|
||||
with_id: bool,
|
||||
with_spans: bool,
|
||||
with_description: bool,
|
||||
}
|
||||
|
||||
impl<T> JsonEventSerializer<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
with_id: false,
|
||||
with_spans: false,
|
||||
with_description: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_id(mut self) -> Self {
|
||||
self.with_id = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_spans(mut self) -> Self {
|
||||
self.with_spans = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_description(mut self) -> Self {
|
||||
self.with_description = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Event<EventDetails>>> Serialize for JsonEventSerializer<Vec<T>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.inner.len()))?;
|
||||
for event in &self.inner {
|
||||
seq.serialize_element(&JsonEventSerializer {
|
||||
inner: event,
|
||||
with_id: self.with_id,
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
})?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Event<EventDetails>>> Serialize for JsonEventSerializer<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let event = self.inner.as_ref();
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
if self.with_id {
|
||||
map.serialize_entry(
|
||||
"id",
|
||||
&format!(
|
||||
"{}{}{}",
|
||||
event.inner.timestamp,
|
||||
EVENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed),
|
||||
event.inner.typ.to_id()
|
||||
),
|
||||
)?;
|
||||
}
|
||||
if self.with_description {
|
||||
map.serialize_entry("text", event.inner.typ.description())?;
|
||||
}
|
||||
map.serialize_entry(
|
||||
"createdAt",
|
||||
&DateTime::from_timestamp(event.inner.timestamp as i64).to_rfc3339(),
|
||||
)?;
|
||||
map.serialize_entry("type", event.inner.typ.as_str())?;
|
||||
map.serialize_entry(
|
||||
"data",
|
||||
&JsonEventSerializer {
|
||||
inner: Keys {
|
||||
keys: event.keys.as_slice(),
|
||||
span_keys: event
|
||||
.inner
|
||||
.span
|
||||
.as_ref()
|
||||
.map(|s| &s.keys[..])
|
||||
.unwrap_or(&[]),
|
||||
},
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
},
|
||||
)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JsonEventSerializer<Keys<'_>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let keys_len = self.inner.keys.len() + self.inner.span_keys.len();
|
||||
let mut seen_keys = AHashSet::with_capacity(keys_len);
|
||||
let mut keys = serializer.serialize_map(Some(keys_len))?;
|
||||
for (key, value) in self.inner.keys.iter().chain(self.inner.span_keys.iter()) {
|
||||
if !matches!(value, Value::None)
|
||||
&& (self.with_spans || !matches!(key, Key::SpanId))
|
||||
&& seen_keys.insert(*key)
|
||||
{
|
||||
keys.serialize_entry(
|
||||
key.as_str(),
|
||||
&JsonEventSerializer {
|
||||
inner: value,
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
keys.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JsonEventSerializer<&Error> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
map.serialize_entry("type", self.inner.0.inner.as_str())?;
|
||||
if self.with_description {
|
||||
map.serialize_entry("text", self.inner.0.inner.description())?;
|
||||
}
|
||||
map.serialize_entry(
|
||||
"data",
|
||||
&JsonEventSerializer {
|
||||
inner: Keys {
|
||||
keys: self.inner.0.keys.as_slice(),
|
||||
span_keys: &[],
|
||||
},
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
},
|
||||
)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JsonEventSerializer<&Value> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match &self.inner {
|
||||
Value::String(value) => value.serialize(serializer),
|
||||
Value::UInt(value) => value.serialize(serializer),
|
||||
Value::Int(value) => value.serialize(serializer),
|
||||
Value::Float(value) => value.serialize(serializer),
|
||||
Value::Timestamp(value) => DateTime::from_timestamp(*value as i64)
|
||||
.to_rfc3339()
|
||||
.serialize(serializer),
|
||||
Value::Duration(value) => value.serialize(serializer),
|
||||
Value::Bytes(value) => STANDARD.encode(value).serialize(serializer),
|
||||
Value::Bool(value) => value.serialize(serializer),
|
||||
Value::Ipv4(value) => value.serialize(serializer),
|
||||
Value::Ipv6(value) => value.serialize(serializer),
|
||||
Value::Event(value) => JsonEventSerializer {
|
||||
inner: value,
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
}
|
||||
.serialize(serializer),
|
||||
Value::Array(value) => JsonEventSerializer {
|
||||
inner: value,
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
}
|
||||
.serialize(serializer),
|
||||
Value::None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JsonEventSerializer<&Vec<Value>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.inner.len()))?;
|
||||
for value in self.inner {
|
||||
seq.serialize_element(&JsonEventSerializer {
|
||||
inner: value,
|
||||
with_spans: self.with_spans,
|
||||
with_description: self.with_description,
|
||||
with_id: self.with_id,
|
||||
})?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for EventType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for EventType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = <&str>::deserialize(deserializer)?;
|
||||
Self::parse(s).ok_or_else(|| serde::de::Error::unknown_variant(s, &[]))
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for MetricType {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for MetricType {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s = <&str>::deserialize(deserializer)?;
|
||||
Self::parse(s).ok_or_else(|| serde::de::Error::unknown_variant(s, &[]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
pub mod json;
|
||||
pub mod text;
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use mail_parser::DateTime;
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use crate::{Error, Event, EventDetails, Key, Level, Value};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
|
||||
pub struct FmtWriter<T: AsyncWrite + Unpin> {
|
||||
writer: T,
|
||||
ansi: bool,
|
||||
multiline: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum Color {
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + Unpin> FmtWriter<T> {
|
||||
pub fn new(writer: T) -> Self {
|
||||
Self {
|
||||
writer,
|
||||
ansi: false,
|
||||
multiline: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_ansi(self, ansi: bool) -> Self {
|
||||
Self { ansi, ..self }
|
||||
}
|
||||
|
||||
pub fn with_multiline(self, multiline: bool) -> Self {
|
||||
Self { multiline, ..self }
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, event: &Event<EventDetails>) -> std::io::Result<()> {
|
||||
// Write timestamp
|
||||
if self.ansi {
|
||||
self.writer
|
||||
.write_all(Color::White.as_code().as_bytes())
|
||||
.await?;
|
||||
}
|
||||
self.writer
|
||||
.write_all(
|
||||
DateTime::from_timestamp(event.inner.timestamp as i64)
|
||||
.to_rfc3339()
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
if self.ansi {
|
||||
self.writer.write_all(Color::reset().as_bytes()).await?;
|
||||
}
|
||||
self.writer.write_all(" ".as_bytes()).await?;
|
||||
|
||||
// Write level
|
||||
if self.ansi {
|
||||
self.writer
|
||||
.write_all(
|
||||
match event.inner.level {
|
||||
Level::Error => Color::Red,
|
||||
Level::Warn => Color::Yellow,
|
||||
Level::Info => Color::Green,
|
||||
Level::Debug => Color::Blue,
|
||||
Level::Trace => Color::Magenta,
|
||||
Level::Disable => return Ok(()),
|
||||
}
|
||||
.as_code_bold()
|
||||
.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
self.writer
|
||||
.write_all(event.inner.level.as_str().as_bytes())
|
||||
.await?;
|
||||
if self.ansi {
|
||||
self.writer.write_all(Color::reset().as_bytes()).await?;
|
||||
}
|
||||
self.writer.write_all(" ".as_bytes()).await?;
|
||||
|
||||
// Write message
|
||||
if self.ansi {
|
||||
self.writer
|
||||
.write_all(Color::White.as_code_bold().as_bytes())
|
||||
.await?;
|
||||
}
|
||||
self.writer
|
||||
.write_all(event.inner.typ.description().as_bytes())
|
||||
.await?;
|
||||
if self.ansi {
|
||||
self.writer.write_all(Color::reset().as_bytes()).await?;
|
||||
}
|
||||
self.writer.write_all(" (".as_bytes()).await?;
|
||||
self.writer
|
||||
.write_all(event.inner.typ.as_str().as_bytes())
|
||||
.await?;
|
||||
|
||||
self.writer
|
||||
.write_all(if self.multiline { ")\n" } else { ") " }.as_bytes())
|
||||
.await?;
|
||||
|
||||
// Write keys
|
||||
if let Some(parent_event) = &event.inner.span {
|
||||
self.write_keys(&parent_event.keys, &event.keys, 1).await?;
|
||||
} else {
|
||||
self.write_keys(&[], &event.keys, 1).await?;
|
||||
}
|
||||
|
||||
if !self.multiline {
|
||||
self.writer.write_all("\n".as_bytes()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_keys(
|
||||
&mut self,
|
||||
span_keys: &[(Key, Value)],
|
||||
keys: &[(Key, Value)],
|
||||
indent: usize,
|
||||
) -> std::io::Result<()> {
|
||||
Box::pin(async move {
|
||||
let mut is_first = true;
|
||||
for (key, value) in span_keys.iter().chain(keys.iter()) {
|
||||
if matches!(key, Key::SpanId) {
|
||||
continue;
|
||||
} else if is_first {
|
||||
is_first = false;
|
||||
} else if !self.multiline {
|
||||
self.writer.write_all(", ".as_bytes()).await?;
|
||||
}
|
||||
|
||||
// Write key
|
||||
if self.multiline {
|
||||
for _ in 0..indent {
|
||||
self.writer.write_all("\t".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
if self.ansi {
|
||||
self.writer
|
||||
.write_all(Color::Cyan.as_code().as_bytes())
|
||||
.await?;
|
||||
}
|
||||
self.writer.write_all(key.as_str().as_bytes()).await?;
|
||||
if self.ansi {
|
||||
self.writer.write_all(Color::reset().as_bytes()).await?;
|
||||
}
|
||||
|
||||
// Write value
|
||||
self.writer.write_all(" = ".as_bytes()).await?;
|
||||
self.write_value(value, indent).await?;
|
||||
|
||||
if self.multiline && !matches!(value, Value::Event(_)) {
|
||||
self.writer.write_all("\n".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn write_value(&mut self, value: &Value, indent: usize) -> std::io::Result<()> {
|
||||
Box::pin(async move {
|
||||
match value {
|
||||
Value::String(v) => {
|
||||
self.writer.write_all("\"".as_bytes()).await?;
|
||||
for ch in v.as_bytes() {
|
||||
match ch {
|
||||
b'\r' => {
|
||||
self.writer.write_all("\\r".as_bytes()).await?;
|
||||
}
|
||||
b'\n' => {
|
||||
self.writer.write_all("\\n".as_bytes()).await?;
|
||||
}
|
||||
b'\t' => {
|
||||
self.writer.write_all("\\t".as_bytes()).await?;
|
||||
}
|
||||
b'\\' => {
|
||||
self.writer.write_all("\\\\".as_bytes()).await?;
|
||||
}
|
||||
_ => {
|
||||
self.writer.write_all(&[*ch]).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.writer.write_all("\"".as_bytes()).await?;
|
||||
}
|
||||
Value::UInt(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
}
|
||||
Value::Int(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
}
|
||||
Value::Float(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
}
|
||||
Value::Timestamp(v) => {
|
||||
self.writer
|
||||
.write_all(DateTime::from_timestamp(*v as i64).to_rfc3339().as_bytes())
|
||||
.await?;
|
||||
}
|
||||
Value::Duration(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
self.writer.write_all("ms".as_bytes()).await?;
|
||||
}
|
||||
Value::Bytes(bytes) => {
|
||||
self.writer.write_all("base64:".as_bytes()).await?;
|
||||
self.writer
|
||||
.write_all(STANDARD.encode(bytes).as_bytes())
|
||||
.await?;
|
||||
}
|
||||
Value::Bool(true) => {
|
||||
self.writer.write_all("true".as_bytes()).await?;
|
||||
}
|
||||
Value::Bool(false) => {
|
||||
self.writer.write_all("false".as_bytes()).await?;
|
||||
}
|
||||
Value::Ipv4(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
}
|
||||
Value::Ipv6(v) => {
|
||||
self.writer.write_all(v.to_string().as_bytes()).await?;
|
||||
}
|
||||
Value::Event(e) => {
|
||||
self.writer
|
||||
.write_all(e.0.inner.description().as_bytes())
|
||||
.await?;
|
||||
self.writer.write_all(" (".as_bytes()).await?;
|
||||
self.writer.write_all(e.0.inner.as_str().as_bytes()).await?;
|
||||
self.writer.write_all(")".as_bytes()).await?;
|
||||
if !e.0.keys.is_empty() {
|
||||
self.writer
|
||||
.write_all(if self.multiline { "\n" } else { " { " }.as_bytes())
|
||||
.await?;
|
||||
|
||||
self.write_keys(&e.0.keys, &[], indent + 1).await?;
|
||||
|
||||
if !self.multiline {
|
||||
self.writer.write_all(" }".as_bytes()).await?;
|
||||
}
|
||||
} else if self.multiline {
|
||||
self.writer.write_all("\n".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
self.writer.write_all("[".as_bytes()).await?;
|
||||
for (pos, value) in arr.iter().enumerate() {
|
||||
if pos > 0 {
|
||||
self.writer.write_all(", ".as_bytes()).await?;
|
||||
}
|
||||
self.write_value(value, indent).await?;
|
||||
}
|
||||
self.writer.write_all("]".as_bytes()).await?;
|
||||
}
|
||||
Value::None => {
|
||||
self.writer.write_all("(null)".as_bytes()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn flush(&mut self) -> std::io::Result<()> {
|
||||
self.writer.flush().await
|
||||
}
|
||||
|
||||
pub fn update_writer(&mut self, writer: T) {
|
||||
self.writer = writer;
|
||||
}
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn as_code(&self) -> &'static str {
|
||||
match self {
|
||||
Color::Black => "\x1b[30m",
|
||||
Color::Red => "\x1b[31m",
|
||||
Color::Green => "\x1b[32m",
|
||||
Color::Yellow => "\x1b[33m",
|
||||
Color::Blue => "\x1b[34m",
|
||||
Color::Magenta => "\x1b[35m",
|
||||
Color::Cyan => "\x1b[36m",
|
||||
Color::White => "\x1b[37m",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_code_bold(&self) -> &'static str {
|
||||
match self {
|
||||
Color::Black => "\x1b[30;1m",
|
||||
Color::Red => "\x1b[31;1m",
|
||||
Color::Green => "\x1b[32;1m",
|
||||
Color::Yellow => "\x1b[33;1m",
|
||||
Color::Blue => "\x1b[34;1m",
|
||||
Color::Magenta => "\x1b[35;1m",
|
||||
Color::Cyan => "\x1b[36;1m",
|
||||
Color::White => "\x1b[37;1m",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset() -> &'static str {
|
||||
"\x1b[0m"
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::String(value) => value.fmt(f),
|
||||
Value::UInt(value) => value.fmt(f),
|
||||
Value::Int(value) => value.fmt(f),
|
||||
Value::Float(value) => value.fmt(f),
|
||||
Value::Timestamp(value) => value.fmt(f),
|
||||
Value::Duration(value) => value.fmt(f),
|
||||
Value::Bytes(value) => STANDARD.encode(value).fmt(f),
|
||||
Value::Bool(value) => value.fmt(f),
|
||||
Value::Ipv4(value) => value.fmt(f),
|
||||
Value::Ipv6(value) => value.fmt(f),
|
||||
Value::Event(value) => {
|
||||
"{".fmt(f)?;
|
||||
value.fmt(f)?;
|
||||
"}".fmt(f)
|
||||
}
|
||||
Value::Array(value) => {
|
||||
f.write_str("[")?;
|
||||
for (i, value) in value.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
value.fmt(f)?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
Value::None => "(null)".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.inner.description().fmt(f)?;
|
||||
" (".fmt(f)?;
|
||||
self.0.inner.as_str().fmt(f)?;
|
||||
")".fmt(f)?;
|
||||
|
||||
if !self.0.keys.is_empty() {
|
||||
f.write_str(": ")?;
|
||||
for (i, (key, value)) in self.0.keys.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
key.as_str().fmt(f)?;
|
||||
f.write_str(" = ")?;
|
||||
value.fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user