我们使用了几种不同的API(在示例中,operator::Api
和supervisor::Api
)。这些API的有效载荷通常用不同的样式(ApiWrapper
)进行包装,并且它们的使用方式也相似但略有不同。
我们的API客户端的方法是根据元数据(tool_b
,tool_x
等)生成的。
它们返回一个实现特征Reporter
的结构,而不是返回有效载荷的有趣部分,该特征包装数据并提供其他方法。
实际上,为了能够存储有效负载的有趣部分(该部分将由tool
方法返回),使用了PhantomData
字段(最初我们添加了一个字段和Option<T>
)。
为减少样板,我们创建了特征Reporter
,该特征定义了应该由我们的API客户端实现的接口,但也实现了某些功能。不幸的是,即使采用这种方法,我们也有一些重复。看起来部分重复内容无法删除,但我们希望减少重复。我已经在代码中添加了一些对此的注释。
由于我们无法提供每个API Reporter
的特定实现(Operator
上的operator::Api
和Supervisor
上的supervisor::Api
),因此我们决定使用{{ 1}}来指定将使用实现该特征的结构。
整个示例在https://github.com/foochi/duplicated-impl-traits
上impl Reporter
如果可能的话,我们希望减少使用特征并为该特征的每个实现创建结构的重复,这似乎有点难看。
但是,我们最大的问题是,由于我们使用mod report {
use crate::Result;
use serde::de::DeserializeOwned;
use std::io::{Error, ErrorKind};
#[derive(Debug)]
pub struct Report {
/// The entire report, as text
pub text: String,
}
pub trait Reporter<T>
where
T: DeserializeOwned,
{
fn new(report: Report) -> Self;
/// The report associated to the reporter
fn report(&self) -> &Report;
fn deserialize<U>(&self, text: &String) -> Result<U>
where
U: DeserializeOwned,
{
serde_json::from_str::<U>(text)
.map_err(|_err| Error::new(ErrorKind::Other, "Failed when deserializing"))
}
/// The tool is inferred from the report
fn tool(&self) -> Result<T>;
}
}
mod supervisor {
use crate::report::{Report, Reporter};
use crate::Result;
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
#[derive(Debug, Deserialize)]
pub struct ToolX {
pub tool_x: String,
pub number: u8,
}
#[derive(Debug, Deserialize)]
pub struct ToolY {
pub tool_y: String,
}
#[derive(Debug, Deserialize)]
pub struct ApiWrapper<T> {
pub supervisor: T,
}
#[derive(Debug)]
pub struct Supervisor<T>
where
T: DeserializeOwned,
{
report: Report,
report_tool: PhantomData<T>,
}
impl<T> Reporter<T> for Supervisor<T>
where
T: DeserializeOwned,
{
fn new(report: Report) -> Supervisor<T> {
Self {
report,
report_tool: PhantomData,
}
}
// NOTE 1: This cannot be implemented inside the trait because it does not have any associated field
fn report(&self) -> &Report {
&self.report
}
fn tool(&self) -> Result<T> {
let text = &self.report.text;
let reporter = self.deserialize::<ApiWrapper<T>>(&text)?;
let reporter = reporter.supervisor;
match &reporter {
ToolX => println!("INFO: a report of X has been received"),
};
Ok(reporter)
}
}
impl<T> Supervisor<T>
where
T: DeserializeOwned,
{
// NOTE 2: This method cannot be accessed
pub fn review() {
println!("REVIEWED");
}
}
pub struct Api {}
impl Api {
fn process<T>(&self, message: String) -> Result<Supervisor<T>>
where
T: DeserializeOwned,
{
let report = Report { text: message };
println!("Incoming report");
Ok(Supervisor::new(report))
}
// This methods is generated, so it cannot use a specific implementation of Reporter
pub fn tool_x(&self, message: String) -> Result<impl Reporter<ToolX>> {
self.process::<ToolX>(message)
}
// This methods is generated, so it cannot use a specific implementation of Reporter
pub fn tool_y(&self, message: String) -> Result<impl Reporter<ToolY>> {
println!("WARNING: Y tool is deprecated!");
self.process::<ToolY>(message)
}
}
}
mod operator {
use crate::report::{Report, Reporter};
use crate::Result;
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
#[derive(Debug, Deserialize)]
pub struct ToolA {
pub tool_a: String,
}
#[derive(Debug, Deserialize)]
pub struct ToolB {
pub tool_b: String,
pub scale: u16,
}
#[derive(Debug, Deserialize)]
pub struct ApiWrapper<T> {
pub operator: T,
}
#[derive(Debug)]
pub struct Operator<T>
where
T: DeserializeOwned,
{
report: Report,
report_tool: PhantomData<T>,
}
impl<T> Reporter<T> for Operator<T>
where
T: DeserializeOwned,
{
fn new(report: Report) -> Operator<T> {
Self {
report,
report_tool: PhantomData,
}
}
// NOTE 1: This cannot be implemented inside the trait because it does not have any associated field
fn report(&self) -> &Report {
&self.report
}
fn tool(&self) -> Result<T> {
let text = &self.report.text;
let reporter = self.deserialize::<ApiWrapper<T>>(&text)?;
Ok(reporter.operator)
}
}
pub struct Api {}
impl Api {
fn process<T>(&self, message: String) -> Result<Operator<T>>
where
T: DeserializeOwned,
{
let report = Report { text: message };
Ok(Operator::new(report))
}
// This methods is generated, so it cannot use a specific implementation of Reporter
pub fn tool_a(&self, message: String) -> Result<impl Reporter<ToolA>> {
println!("WARNING: A tool is unstable!");
self.process::<ToolA>(message)
}
// This methods is generated, so it cannot use a specific implementation of Reporter
pub fn tool_b(&self, message: String) -> Result<impl Reporter<ToolB>> {
self.process::<ToolB>(message)
}
}
}
#[macro_use]
extern crate serde_derive;
use report::Reporter;
pub type Result<T> = ::std::result::Result<T, ::std::io::Error>;
fn main() {
// Operator
let _message_a = r#"
{ "operator": { "tool_a": "a111a" } }
"#
.to_string();
let message_b = r#"
{ "operator": { "tool_b": "2b2bb", "scale": 400 } }
"#
.to_string();
let api_operator = operator::Api {};
let reporter_b = api_operator
.tool_b(message_b)
.expect("Error while processing report b");
let report_b = reporter_b.report();
let tool_b = reporter_b.tool().expect("Error while processing tool b");
println!("Operator report: {} => {}", report_b.text, tool_b.tool_b);
// Supervisor
let message_x = r#"
{ "supervisor": { "tool_x": "xXxxxX", "number": 5 } }
"#
.to_string();
let _message_y = r#"
{ "supervisor": { "tool_y": "yYYYy" } }
"#
.to_string();
let api_supervisor = supervisor::Api {};
let reporter_x = api_supervisor
.tool_x(message_x)
.expect("Error while processing report x");
// FIXME ideally this would work
// reporter_x.preview();
let report_x = reporter_x.report();
let tool_x = reporter_x.tool().expect("Error while processing tool x");
println!("Supervisor report: {} => {}", report_x.text, tool_x.tool_x);
}
,因此找不到具体实现(impl Reporter
)的preview
方法。
理想情况下,我们只能有1个结构(Supervisor
)而不是当前的Reporter
特性和2个结构(Reporter
和Operator
),并实现这些方法对于使用泛型或类似方法的每个API来说都是唯一的,但我们还没有找到一种方法来做到这一点,而又不会丢失我们包装和存储 客户方法的返回类型的技术({{1} },Supervisor
等。