use std::{borrow::Cow, convert::Infallible, fmt, str::FromStr};
use phf::phf_map;
use zenoh_buffers::ZSlice;
use zenoh_protocol::core::EncodingId;
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Encoding(zenoh_protocol::core::Encoding);
impl Encoding {
const SCHEMA_SEP: char = ';';
const CUSTOM_ENCODING_ID: u16 = 0xFFFFu16;
pub const ZENOH_BYTES: Encoding = Self(zenoh_protocol::core::Encoding {
id: 0,
schema: None,
});
pub const ZENOH_STRING: Encoding = Self(zenoh_protocol::core::Encoding {
id: 1,
schema: None,
});
pub const ZENOH_SERIALIZED: Encoding = Self(zenoh_protocol::core::Encoding {
id: 2,
schema: None,
});
pub const APPLICATION_OCTET_STREAM: Encoding = Self(zenoh_protocol::core::Encoding {
id: 3,
schema: None,
});
pub const TEXT_PLAIN: Encoding = Self(zenoh_protocol::core::Encoding {
id: 4,
schema: None,
});
pub const APPLICATION_JSON: Encoding = Self(zenoh_protocol::core::Encoding {
id: 5,
schema: None,
});
pub const TEXT_JSON: Encoding = Self(zenoh_protocol::core::Encoding {
id: 6,
schema: None,
});
pub const APPLICATION_CDR: Encoding = Self(zenoh_protocol::core::Encoding {
id: 7,
schema: None,
});
pub const APPLICATION_CBOR: Encoding = Self(zenoh_protocol::core::Encoding {
id: 8,
schema: None,
});
pub const APPLICATION_YAML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 9,
schema: None,
});
pub const TEXT_YAML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 10,
schema: None,
});
pub const TEXT_JSON5: Encoding = Self(zenoh_protocol::core::Encoding {
id: 11,
schema: None,
});
pub const APPLICATION_PYTHON_SERIALIZED_OBJECT: Encoding =
Self(zenoh_protocol::core::Encoding {
id: 12,
schema: None,
});
pub const APPLICATION_PROTOBUF: Encoding = Self(zenoh_protocol::core::Encoding {
id: 13,
schema: None,
});
pub const APPLICATION_JAVA_SERIALIZED_OBJECT: Encoding = Self(zenoh_protocol::core::Encoding {
id: 14,
schema: None,
});
pub const APPLICATION_OPENMETRICS_TEXT: Encoding = Self(zenoh_protocol::core::Encoding {
id: 15,
schema: None,
});
pub const IMAGE_PNG: Encoding = Self(zenoh_protocol::core::Encoding {
id: 16,
schema: None,
});
pub const IMAGE_JPEG: Encoding = Self(zenoh_protocol::core::Encoding {
id: 17,
schema: None,
});
pub const IMAGE_GIF: Encoding = Self(zenoh_protocol::core::Encoding {
id: 18,
schema: None,
});
pub const IMAGE_BMP: Encoding = Self(zenoh_protocol::core::Encoding {
id: 19,
schema: None,
});
pub const IMAGE_WEBP: Encoding = Self(zenoh_protocol::core::Encoding {
id: 20,
schema: None,
});
pub const APPLICATION_XML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 21,
schema: None,
});
pub const APPLICATION_X_WWW_FORM_URLENCODED: Encoding = Self(zenoh_protocol::core::Encoding {
id: 22,
schema: None,
});
pub const TEXT_HTML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 23,
schema: None,
});
pub const TEXT_XML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 24,
schema: None,
});
pub const TEXT_CSS: Encoding = Self(zenoh_protocol::core::Encoding {
id: 25,
schema: None,
});
pub const TEXT_JAVASCRIPT: Encoding = Self(zenoh_protocol::core::Encoding {
id: 26,
schema: None,
});
pub const TEXT_MARKDOWN: Encoding = Self(zenoh_protocol::core::Encoding {
id: 27,
schema: None,
});
pub const TEXT_CSV: Encoding = Self(zenoh_protocol::core::Encoding {
id: 28,
schema: None,
});
pub const APPLICATION_SQL: Encoding = Self(zenoh_protocol::core::Encoding {
id: 29,
schema: None,
});
pub const APPLICATION_COAP_PAYLOAD: Encoding = Self(zenoh_protocol::core::Encoding {
id: 30,
schema: None,
});
pub const APPLICATION_JSON_PATCH_JSON: Encoding = Self(zenoh_protocol::core::Encoding {
id: 31,
schema: None,
});
pub const APPLICATION_JSON_SEQ: Encoding = Self(zenoh_protocol::core::Encoding {
id: 32,
schema: None,
});
pub const APPLICATION_JSONPATH: Encoding = Self(zenoh_protocol::core::Encoding {
id: 33,
schema: None,
});
pub const APPLICATION_JWT: Encoding = Self(zenoh_protocol::core::Encoding {
id: 34,
schema: None,
});
pub const APPLICATION_MP4: Encoding = Self(zenoh_protocol::core::Encoding {
id: 35,
schema: None,
});
pub const APPLICATION_SOAP_XML: Encoding = Self(zenoh_protocol::core::Encoding {
id: 36,
schema: None,
});
pub const APPLICATION_YANG: Encoding = Self(zenoh_protocol::core::Encoding {
id: 37,
schema: None,
});
pub const AUDIO_AAC: Encoding = Self(zenoh_protocol::core::Encoding {
id: 38,
schema: None,
});
pub const AUDIO_FLAC: Encoding = Self(zenoh_protocol::core::Encoding {
id: 39,
schema: None,
});
pub const AUDIO_MP4: Encoding = Self(zenoh_protocol::core::Encoding {
id: 40,
schema: None,
});
pub const AUDIO_OGG: Encoding = Self(zenoh_protocol::core::Encoding {
id: 41,
schema: None,
});
pub const AUDIO_VORBIS: Encoding = Self(zenoh_protocol::core::Encoding {
id: 42,
schema: None,
});
pub const VIDEO_H261: Encoding = Self(zenoh_protocol::core::Encoding {
id: 43,
schema: None,
});
pub const VIDEO_H263: Encoding = Self(zenoh_protocol::core::Encoding {
id: 44,
schema: None,
});
pub const VIDEO_H264: Encoding = Self(zenoh_protocol::core::Encoding {
id: 45,
schema: None,
});
pub const VIDEO_H265: Encoding = Self(zenoh_protocol::core::Encoding {
id: 46,
schema: None,
});
pub const VIDEO_H266: Encoding = Self(zenoh_protocol::core::Encoding {
id: 47,
schema: None,
});
pub const VIDEO_MP4: Encoding = Self(zenoh_protocol::core::Encoding {
id: 48,
schema: None,
});
pub const VIDEO_OGG: Encoding = Self(zenoh_protocol::core::Encoding {
id: 49,
schema: None,
});
pub const VIDEO_RAW: Encoding = Self(zenoh_protocol::core::Encoding {
id: 50,
schema: None,
});
pub const VIDEO_VP8: Encoding = Self(zenoh_protocol::core::Encoding {
id: 51,
schema: None,
});
pub const VIDEO_VP9: Encoding = Self(zenoh_protocol::core::Encoding {
id: 52,
schema: None,
});
const ID_TO_STR: phf::Map<EncodingId, &'static str> = phf_map! {
0u16 => "zenoh/bytes",
1u16 => "zenoh/string",
2u16 => "zenoh/serialized",
3u16 => "application/octet-stream",
4u16 => "text/plain",
5u16 => "application/json",
6u16 => "text/json",
7u16 => "application/cdr",
8u16 => "application/cbor",
9u16 => "application/yaml",
10u16 => "text/yaml",
11u16 => "text/json5",
12u16 => "application/python-serialized-object",
13u16 => "application/protobuf",
14u16 => "application/java-serialized-object",
15u16 => "application/openmetrics-text",
16u16 => "image/png",
17u16 => "image/jpeg",
18u16 => "image/gif",
19u16 => "image/bmp",
20u16 => "image/webp",
21u16 => "application/xml",
22u16 => "application/x-www-form-urlencoded",
23u16 => "text/html",
24u16 => "text/xml",
25u16 => "text/css",
26u16 => "text/javascript",
27u16 => "text/markdown",
28u16 => "text/csv",
29u16 => "application/sql",
30u16 => "application/coap-payload",
31u16 => "application/json-patch+json",
32u16 => "application/json-seq",
33u16 => "application/jsonpath",
34u16 => "application/jwt",
35u16 => "application/mp4",
36u16 => "application/soap+xml",
37u16 => "application/yang",
38u16 => "audio/aac",
39u16 => "audio/flac",
40u16 => "audio/mp4",
41u16 => "audio/ogg",
42u16 => "audio/vorbis",
43u16 => "video/h261",
44u16 => "video/h263",
45u16 => "video/h264",
46u16 => "video/h265",
47u16 => "video/h266",
48u16 => "video/mp4",
49u16 => "video/ogg",
50u16 => "video/raw",
51u16 => "video/vp8",
52u16 => "video/vp9",
0xFFFFu16 => "",
};
const STR_TO_ID: phf::Map<&'static str, EncodingId> = phf_map! {
"zenoh/bytes" => 0u16,
"zenoh/string" => 1u16,
"zenoh/serialized" => 2u16,
"application/octet-stream" => 3u16,
"text/plain" => 4u16,
"application/json" => 5u16,
"text/json" => 6u16,
"application/cdr" => 7u16,
"application/cbor" => 8u16,
"application/yaml" => 9u16,
"text/yaml" => 10u16,
"text/json5" => 11u16,
"application/python-serialized-object" => 12u16,
"application/protobuf" => 13u16,
"application/java-serialized-object" => 14u16,
"application/openmetrics-text" => 15u16,
"image/png" => 16u16,
"image/jpeg" => 17u16,
"image/gif" => 18u16,
"image/bmp" => 19u16,
"image/webp" => 20u16,
"application/xml" => 21u16,
"application/x-www-form-urlencoded" => 22u16,
"text/html" => 23u16,
"text/xml" => 24u16,
"text/css" => 25u16,
"text/javascript" => 26u16,
"text/markdown" => 27u16,
"text/csv" => 28u16,
"application/sql" => 29u16,
"application/coap-payload" => 30u16,
"application/json-patch+json" => 31u16,
"application/json-seq" => 32u16,
"application/jsonpath" => 33u16,
"application/jwt" => 34u16,
"application/mp4" => 35u16,
"application/soap+xml" => 36u16,
"application/yang" => 37u16,
"audio/aac" => 38u16,
"audio/flac" => 39u16,
"audio/mp4" => 40u16,
"audio/ogg" => 41u16,
"audio/vorbis" => 42u16,
"video/h261" => 43u16,
"video/h263" => 44u16,
"video/h264" => 45u16,
"video/h265" => 46u16,
"video/h266" => 47u16,
"video/mp4" => 48u16,
"video/ogg" => 49u16,
"video/raw" => 50u16,
"video/vp8" => 51u16,
"video/vp9" => 52u16,
};
pub const fn default() -> Self {
Self::ZENOH_BYTES
}
pub fn with_schema<S>(mut self, s: S) -> Self
where
S: Into<String>,
{
let s: String = s.into();
let schema_str = match self.0.id {
Self::CUSTOM_ENCODING_ID => {
let schema_str = self
.0
.schema
.as_ref()
.map(|schema| std::str::from_utf8(schema).unwrap_or(""))
.unwrap_or("");
let (encoding, _) = schema_str
.split_once(Self::SCHEMA_SEP)
.unwrap_or((schema_str, ""));
match (encoding.is_empty(), s.is_empty()) {
(true, _) => s,
(false, true) => encoding.to_string(),
(false, false) => format!("{encoding}{}{s}", Self::SCHEMA_SEP),
}
}
_ => s,
};
self.0.schema = Some(schema_str.into_boxed_str().into_boxed_bytes().into());
self
}
}
impl Default for Encoding {
fn default() -> Self {
Self::default()
}
}
impl From<&str> for Encoding {
fn from(t: &str) -> Self {
let mut inner = zenoh_protocol::core::Encoding::empty();
if t.is_empty() {
return Encoding(inner);
}
let (id, mut schema) = t.split_once(Encoding::SCHEMA_SEP).unwrap_or((t, ""));
if let Some(id) = Encoding::STR_TO_ID.get(id).copied() {
inner.id = id;
} else {
inner.id = Self::CUSTOM_ENCODING_ID;
schema = t;
}
if !schema.is_empty() {
inner.schema = Some(ZSlice::from(schema.to_string().into_bytes()));
}
Encoding(inner)
}
}
impl From<String> for Encoding {
fn from(value: String) -> Self {
Self::from(value.as_str())
}
}
impl FromStr for Encoding {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl From<&Encoding> for Cow<'static, str> {
fn from(encoding: &Encoding) -> Self {
fn su8_to_str(schema: &[u8]) -> &str {
std::str::from_utf8(schema).unwrap_or("unknown(non-utf8)")
}
match (
Encoding::ID_TO_STR.get(&encoding.0.id).copied(),
encoding.0.schema.as_ref(),
) {
(Some(i), None) => Cow::Borrowed(i),
(Some(""), Some(s)) => Cow::Owned(su8_to_str(s).into()),
(Some(i), Some(s)) => {
Cow::Owned(format!("{}{}{}", i, Encoding::SCHEMA_SEP, su8_to_str(s)))
}
(None, Some(s)) => Cow::Owned(format!(
"unknown({}){}{}",
encoding.0.id,
Encoding::SCHEMA_SEP,
su8_to_str(s)
)),
(None, None) => Cow::Owned(format!("unknown({})", encoding.0.id)),
}
}
}
impl From<Encoding> for Cow<'static, str> {
fn from(encoding: Encoding) -> Self {
Self::from(&encoding)
}
}
impl From<Encoding> for String {
fn from(encoding: Encoding) -> Self {
encoding.to_string()
}
}
impl From<Encoding> for zenoh_protocol::core::Encoding {
fn from(value: Encoding) -> Self {
value.0
}
}
impl From<zenoh_protocol::core::Encoding> for Encoding {
fn from(value: zenoh_protocol::core::Encoding) -> Self {
Self(value)
}
}
impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
let s = Cow::from(self);
f.write_str(s.as_ref())
}
}
impl Encoding {
#[zenoh_macros::internal]
pub fn id(&self) -> EncodingId {
self.0.id
}
#[zenoh_macros::internal]
pub fn schema(&self) -> Option<&ZSlice> {
self.0.schema.as_ref()
}
#[zenoh_macros::internal]
pub fn new(id: EncodingId, schema: Option<ZSlice>) -> Self {
Encoding(zenoh_protocol::core::Encoding { id, schema })
}
}
#[cfg(test)]
mod tests {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use super::*;
#[test]
fn hash() {
let encodings = [
Encoding::ZENOH_BYTES,
Encoding::ZENOH_STRING,
Encoding::ZENOH_STRING.with_schema("utf-8"),
Encoding::ZENOH_STRING.with_schema("utf-8"),
];
let hashes = encodings.map(|e| {
let mut hasher = DefaultHasher::new();
e.hash(&mut hasher);
hasher.finish()
});
assert_ne!(hashes[0], hashes[1]);
assert_ne!(hashes[0], hashes[2]);
assert_ne!(hashes[1], hashes[2]);
assert_eq!(hashes[2], hashes[3]);
}
#[test]
fn test_default_encoding() {
let default = Encoding::default();
assert_eq!(default, Encoding::ZENOH_BYTES);
assert_eq!(default.to_string(), "zenoh/bytes");
}
#[test]
fn test_from_str() {
assert_eq!(Encoding::from("zenoh/bytes"), Encoding::ZENOH_BYTES);
assert_eq!(Encoding::from("zenoh/string"), Encoding::ZENOH_STRING);
assert_eq!(Encoding::from("text/plain"), Encoding::TEXT_PLAIN);
assert_eq!(
Encoding::from("application/json"),
Encoding::APPLICATION_JSON
);
}
#[test]
fn test_to_string() {
assert_eq!(Encoding::ZENOH_BYTES.to_string(), "zenoh/bytes");
assert_eq!(Encoding::ZENOH_STRING.to_string(), "zenoh/string");
assert_eq!(Encoding::TEXT_PLAIN.to_string(), "text/plain");
assert_eq!(Encoding::APPLICATION_JSON.to_string(), "application/json");
}
#[test]
fn test_schema() {
let with_schema = Encoding::TEXT_PLAIN.with_schema("utf-8");
assert_eq!(with_schema.to_string(), "text/plain;utf-8");
let from_str = Encoding::from("text/plain;utf-8");
assert_eq!(from_str.to_string(), "text/plain;utf-8");
let unknown_with_schema = Encoding::from("custom/format;v1.0");
assert_eq!(unknown_with_schema.to_string(), "custom/format;v1.0");
let unknown_with_schema = unknown_with_schema.with_schema("v2.0");
assert_eq!(unknown_with_schema.to_string(), "custom/format;v2.0");
}
}