Rust设计模式:sealed trait 续篇

2024-04-28 10:07:49 浏览数 (2)

之前写sealed trait时没提他在oauth2-rs中怎么用, 为什么用,这个其实在状态接口设计中很有用,今天展开聊聊。

首先复用上篇的代码,为模拟类库,用mod oauth包起来, 并暴露相关结构体

代码语言:javascript复制
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

代码语言:javascript复制
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刚好就可以

代码语言:javascript复制
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信息

代码语言:javascript复制
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

0 人点赞