use std::borrow::Cow;
use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use crate::protocol::JsonMessage;
use crate::protocol::schema::{self, Schema};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename = "advertiseServices", rename_all = "camelCase")]
pub struct AdvertiseServices<'a> {
#[serde(borrow)]
pub services: Vec<Service<'a>>,
}
impl<'a> AdvertiseServices<'a> {
pub fn new(services: impl IntoIterator<Item = Service<'a>>) -> Self {
Self {
services: services.into_iter().collect(),
}
}
pub fn into_owned(self) -> AdvertiseServices<'static> {
AdvertiseServices {
services: self.services.into_iter().map(|s| s.into_owned()).collect(),
}
}
}
impl JsonMessage for AdvertiseServices<'_> {}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Service<'a> {
pub id: u32,
#[serde(borrow)]
pub name: Cow<'a, str>,
#[serde(borrow)]
pub r#type: Cow<'a, str>,
#[serde(borrow, skip_serializing_if = "Option::is_none")]
pub request: Option<MessageSchema<'a>>,
#[serde(borrow, skip_serializing_if = "Option::is_none")]
pub request_schema: Option<Cow<'a, str>>,
#[serde(borrow, skip_serializing_if = "Option::is_none")]
pub response: Option<MessageSchema<'a>>,
#[serde(borrow, skip_serializing_if = "Option::is_none")]
pub response_schema: Option<Cow<'a, str>>,
}
impl<'a> Service<'a> {
pub fn new(id: u32, name: impl Into<Cow<'a, str>>, r#type: impl Into<Cow<'a, str>>) -> Self {
Self {
id,
name: name.into(),
r#type: r#type.into(),
request: None,
request_schema: Some("".into()),
response: None,
response_schema: Some("".into()),
}
}
pub fn with_request(
mut self,
encoding: impl Into<Cow<'a, str>>,
schema: Schema<'a>,
) -> Result<Self, schema::EncodeError> {
let schema_data = schema::encode_schema_data(&schema.encoding, schema.data)?;
self.request = Some(MessageSchema::new(
encoding,
schema.name,
schema.encoding,
schema_data,
));
self.request_schema = None;
Ok(self)
}
pub fn with_response(
mut self,
encoding: impl Into<Cow<'a, str>>,
schema: Schema<'a>,
) -> Result<Self, schema::EncodeError> {
let schema_data = schema::encode_schema_data(&schema.encoding, schema.data)?;
self.response = Some(MessageSchema::new(
encoding,
schema.name,
schema.encoding,
schema_data,
));
self.response_schema = None;
Ok(self)
}
#[must_use]
pub fn with_request_schema(mut self, schema_name: impl Into<Cow<'a, str>>) -> Self {
self.request = None;
self.request_schema = Some(schema_name.into());
self
}
#[must_use]
pub fn with_response_schema(mut self, schema_name: impl Into<Cow<'a, str>>) -> Self {
self.response = None;
self.response_schema = Some(schema_name.into());
self
}
pub fn into_owned(self) -> Service<'static> {
Service {
id: self.id,
name: self.name.into_owned().into(),
r#type: self.r#type.into_owned().into(),
request: self.request.map(|r| r.into_owned()),
request_schema: self.request_schema.map(|r| r.into_owned().into()),
response: self.response.map(|r| r.into_owned()),
response_schema: self.response_schema.map(|r| r.into_owned().into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageSchema<'a> {
#[serde(borrow)]
pub encoding: Cow<'a, str>,
#[serde(borrow)]
pub schema_name: Cow<'a, str>,
#[serde(borrow)]
pub schema_encoding: Cow<'a, str>,
#[serde(borrow)]
pub schema: Cow<'a, str>,
}
impl<'a> MessageSchema<'a> {
pub fn new(
encoding: impl Into<Cow<'a, str>>,
schema_name: impl Into<Cow<'a, str>>,
schema_encoding: impl Into<Cow<'a, str>>,
schema: impl Into<Cow<'a, str>>,
) -> Self {
Self {
encoding: encoding.into(),
schema_name: schema_name.into(),
schema_encoding: schema_encoding.into(),
schema: schema.into(),
}
}
pub fn schema(&self) -> Result<Vec<u8>, schema::DecodeError> {
schema::decode_schema_data(&self.schema_encoding, &self.schema)
}
pub fn into_owned(self) -> MessageSchema<'static> {
MessageSchema {
encoding: self.encoding.into_owned().into(),
schema_name: self.schema_name.into_owned().into(),
schema_encoding: self.schema_encoding.into_owned().into(),
schema: Cow::Owned(self.schema.into_owned()),
}
}
}
impl<'a> TryFrom<&'a crate::remote_common::service::Service> for Service<'a> {
type Error = schema::EncodeError;
fn try_from(s: &'a crate::remote_common::service::Service) -> Result<Self, Self::Error> {
let svc_schema = s.schema();
let mut service = Self::new(s.id().into(), s.name(), svc_schema.name());
if let Some(request) = svc_schema.request() {
service = service.with_request(&request.encoding, (&request.schema).into())?;
}
if let Some(response) = svc_schema.response() {
service = service.with_response(&response.encoding, (&response.schema).into())?;
}
Ok(service)
}
}
impl<'a> TryFrom<MessageSchema<'a>> for Schema<'a> {
type Error = schema::DecodeError;
fn try_from(value: MessageSchema<'a>) -> Result<Self, Self::Error> {
let schema = value.schema()?;
Ok(Schema::new(
value.schema_name,
value.schema_encoding,
schema,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn message() -> AdvertiseServices<'static> {
AdvertiseServices::new([
Service::new(10, "/s1", "my_type")
.with_request(
"json",
Schema::new("request-schema", "jsonschema", br#"{"type": "object"}"#),
)
.unwrap()
.with_response(
"json",
Schema::new("response-schema", "jsonschema", br#"{"type": "object"}"#),
)
.unwrap(),
Service::new(20, "/s2", "other_type")
.with_request(
"protobuf",
Schema::new("request-schema", "protobuf", &[0xde, 0xad, 0xbe, 0xef]),
)
.unwrap(),
Service::new(30, "/s3", "old_type")
.with_request_schema("request-schema")
.with_response_schema("response-schema"),
Service::new(40, "/s4", "mixed_type")
.with_request(
"json",
Schema::new("request-schema", "jsonschema", br#"{"type": "object"}"#),
)
.unwrap()
.with_response_schema("response-schema"),
Service::new(50, "/s5", "mixed_type")
.with_request_schema("request-schema")
.with_response(
"json",
Schema::new("response-schema", "jsonschema", br#"{"type": "object"}"#),
)
.unwrap(),
Service::new(60, "/s6", "override_type")
.with_request(
"json",
Schema::new("request-schema", "jsonschema", br#"{"type": "object"}"#),
)
.unwrap()
.with_request_schema("new-request-schema")
.with_response_schema("response-schema")
.with_response(
"json",
Schema::new(
"new-response-schema",
"jsonschema",
br#"{"type": "object"}"#,
),
)
.unwrap(),
Service::new(70, "/s7", "default_schemas"),
])
}
#[test]
fn test_encode() {
insta::assert_json_snapshot!(message());
}
#[test]
fn test_roundtrip() {
let orig = message();
let buf = orig.to_string();
let parsed: AdvertiseServices = serde_json::from_str(&buf).unwrap();
assert_eq!(parsed, orig);
}
}