之前写sealed trait
时没提他在oauth2-rs
中怎么用, 为什么用,这个其实在状态接口设计中很有用,今天展开聊聊。
首先复用上篇的代码,为模拟类库,用mod oauth
包起来, 并暴露相关结构体
mod oauth {
use std::marker::PhantomData;
pub struct EndointSet {}
pub struct EndpointNotSet {}
pub trait EndpointState {}
impl EndpointState for EndointSet {}
impl EndpointState for EndpointNotSet {}
pub struct Client<HasAuthUrl = EndpointNotSet, HasTokenUrl = EndpointNotSet>
where
HasAuthUrl: EndpointState,
HasTokenUrl: EndpointState,
{
auth_url: Option<String>,
token_url: Option<String>,
phantom: std::marker::PhantomData<(HasAuthUrl, HasTokenUrl)>,
}
impl<HasAuthUrl: EndpointState, HasTokenUrl: EndpointState> Client<HasAuthUrl, HasTokenUrl> {
pub fn new() -> Self {
Client {
auth_url: None,
token_url: None,
phantom: PhantomData,
}
}
pub fn set_auth_url(self, auth_url: &str) -> Client<EndointSet, HasTokenUrl> {
Client {
auth_url: Some(auth_url.to_string()),
token_url: self.token_url,
phantom: PhantomData,
}
}
pub fn set_token_url(self, token_url: &str) -> Client<HasAuthUrl, EndointSet> {
Client {
auth_url: self.auth_url,
token_url: Some(token_url.to_string()),
phantom: PhantomData,
}
}
}
impl<HasTokenUrl: EndpointState> Client<EndointSet, HasTokenUrl> {
pub fn get_auth_url(&self) -> &str {
self.auth_url.as_ref().unwrap()
}
}
impl<HasAuthUrl: EndpointState> Client<HasAuthUrl, EndointSet> {
pub fn get_token_url(&self) -> &str {
self.token_url.as_ref().unwrap()
}
}
}
hack
这样的话,其实不设置token_url
也可以绕过编译器检查,直接调用get_token_url
比如自己实现一个带有get_token_url
方法的HackEndpointSet
:
use oauth::*;
struct HackEndpointSet {}
impl EndpointState for HackEndpointSet {}
impl Client<EndointSet, HackEndpointSet> {
pub fn get_token_url(&self) -> &str {
"Hacked!"
}
}
let client =
Client::<EndointSet, HackEndpointSet>::new().set_auth_url("https://auth.example.com");
// .set_token_url("https://token.example.com");
println!("Auth URL: {}", client.get_auth_url());
println!("Token URL: {}", client.get_token_url());
防止
怎么防止呢?sealed trait
刚好就可以
mod oauth {
// ...
// sealed trait
mod private {
pub trait EndpointStateSealed {}
}
pub trait EndpointState: private::EndpointStateSealed {}
impl private::EndpointStateSealed for EndointSet {}
impl private::EndpointStateSealed for EndpointNotSet {}
// ...
}
这样的话,HackEndpointSet
就没法实现EndpointState trait
报错就会有sealed trait
信息
error[E0277]: the trait bound `HackEndpointSet: EndpointStateSealed` is not satisfied
--> src/main.rs:71:28
|
71 | impl EndpointState for HackEndpointSet {}
| ^^^^^^^^^^^^^^^ the trait `EndpointStateSealed` is not implemented for `HackEndpointSet`
|
= help: the following other types implement trait `EndpointStateSealed`:
oauth::EndointSet
EndpointNotSet
note: required by a bound in `oauth::EndpointState`
--> src/main.rs:12:30
|
12 | pub trait EndpointState: private::EndpointStateSealed {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `EndpointState`
= note: `EndpointState` is a "sealed trait", because to implement it you also need to implement `oauth::private::EndpointStateSealed`, which is not accessible; this is usually done to force you to use one of the provided types that already implement it
= help: the following types implement the trait:
oauth::EndointSet
oauth::EndpointNotSet
最后附上oauth2-rs
相关代码[1]地址, 感兴趣的可以去看下。
参考资料
[1]
代码: https://github.com/ramosbugs/oauth2-rs/blob/main/src/client.rs