Skip to content

Latest commit

 

History

History
165 lines (130 loc) · 5.28 KB

develop_feed_generator.md

File metadata and controls

165 lines (130 loc) · 5.28 KB

订阅源开发指引

预备知识

  • 项目中存在两个重要概念:SourceFeedGenerator
  • 每个Source对应一个网站,如GitHub、V2EX
  • 每个FeedGenerator对应一个类型的RSS Feed,如某个GitHub用户的Repository列表、V2EX每日热帖列表
  • 每个Source对应一个到多个FeedGenerator

目录结构

src
├── atom_hub.rs
├── config.rs
├── database
│   ├── init.sql
│   └── mod.rs
├── errors.rs
├── feed_generator.rs               # Trait FeedGenerator定义
├── feed_worker.rs
├── main.rs
├── responses.rs
├── routes                          # 所有实现订阅源都在该文件夹下
│   ├── crates_io
│   │   ├── crate_versions.rs
│   │   └── mod.rs
│   ├── github
│   │   ├── mod.rs                  # 示例:Github Source
│   │   └── user_repos.rs           # 示例:某GitHub用户repository列表的RSS订阅
│   ├── mod.rs
│   └── v2ex
│       ├── hot_topics.rs           # 示例:V2EX每日热帖的RSS订阅
│       └── mod.rs                  # 示例:V2EX Source
├── source.rs
└── utils.rs

详细指引

实现FeedGenerator

项目中存在一个名为FeedGenerator的Trait,一般RSS源开发者需要关心FeedGenerator定义如下:

pub trait FeedGenerator {                               // 对应一个RSS Feed
    type Info: DeserializeOwned + Serialize + Default;  // 用于规定对Query String的解析方式
    const PATH: &'static str;                           // 用于规定最终该RSS Feed的URL path 
    fn update(info: &Self::Info) -> NabuResult<Feed>;   // 用于接受用户请求、生成对应RSS Feed
}

主要的生成逻辑应实现于update方法中,其接受一个根据用户请求URL的Query String生成的结构体,并返回生成的Feed或生成过程中发生的错误。

FeedGenerator::Info

在当前版本的设计中,每个FeedGenerator对应一个URL Path,且该Path中不能存在任何变量。对于部分RSS源,需要由请求者传入参数,此时可以使用Query String。

当前版本使用serde_qs解析Query String,也就意味着默认情况下,项目将支持在Query String中表达键值对、数组等特性,即其表现力无限接近JSON,如

name=Acme&id=42&phone=12345&address[postcode]=12345&\
    address[city]=Carrot+City&user_ids[0]=1&user_ids[1]=2&\
    user_ids[2]=3&user_ids[3]=4

等价于

{
  "name": "Acme",
  "id": 42,
  "phone": 12345,
  "address": {
    "postcode": 12345,
    "city": "Carrot City"
  },
  "user_ids": [1, 2, 3, 4]
}

开发者可以通过自定义Info实际类型,并在serde文档的帮助下,实现对Query String的解析逻辑,如:

#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct UserRepoInfo {
    username: String,
    #[serde(rename = "type")]
    ty: String,
    sort: String,
    direction: String,
}

impl Default for UserRepoInfo {
    fn default() -> Self {
        UserRepoInfo {
            username: "DCjanus".to_string(),
            ty: "owner".to_string(),
            sort: "full_name".to_string(),
            direction: "desc".to_string(),
        }
    }
}

随后将其类型指定为FeedGenerator::Info的实际类型,如:

pub struct UserRepoGenerator;
impl FeedGenerator for UserRepoGenerator {
    type Info = UserRepoInfo;
    // ... other code
}

FeedGenerator::update

update中包含FeedGenerator的主要实现逻辑。

需要注意的是,受限于版权因素,在Feed中仅应包含摘要信息,避免包含完整内容。

FeedGenerator::PATH

指定该FeedGenerator部分URL Path,同一Source下的所有FeedGenerator该字段不应相同。示例:

impl FeedGenerator for UserRepoGenerator {
    const PATH: &'static str = "user/repos";
}

实现IntoSource

以GitHub为例,每个网站对应的所有RSS源都应注册到同一Source上,在当前版本中,Source是一个预先提供的Struct,对于订阅源开发者,实际应创建一个名称以'Source'结尾的Struct,如GitHubSource,并为其实现名为SourceBuilder的Trait,如:

impl SourceBuilder for GitHubSource {
    fn build_source(self) -> Source {
        Source::new("github")               // 该Source的prefix
        .register(UserRepoGenerator)        // 注册相关FeedGenerator 
        .register(UnimplementedGenerator1)
        .register(UnimplementedGenerator2)
    }
}

注册Source

{PROJECT_ROOT}/src/routes/mod.rs中,定义了名为atom_hub的函数,其实现如下:

pub fn atom_hub() -> AtomHub {
    AtomHub::default()
        .register(::routes::github::GitHubSource)
        .register(::routes::v2ex::V2exSource)
        .register(::routes::crates_io::CratesIoSource)
}

模仿已有代码,将实现了IntoSource的结构体实例注册到AtomHub实例中即可。

其他

每个FeedGenerator对应的URL Path为:/{Source Prefix}/{FeedGenerator path},如GitHubSource的prefix为/githubUserRepoGenerator的path为/user/repos,则最终其URL Path为/github/user/repos