#query-string #parse-url #url #enums #router #query

chemin

基于枚举的路由生成器,支持查询字符串和国际化

1 个不稳定版本

0.1.0 2022年12月11日

#226国际化(i18n)

MIT 许可证

38KB
519

Chemin

Chemin是一个基于枚举的路由生成器,支持查询字符串和国际化。它可以在前端或后端使用,与任何框架或库一起使用。它可以双向使用:将URL解析为路由,以及从构造的路由生成URL。

它不是“闪电般快速”:在这个crate中,代码清晰度始终优先于优化。

示例

请参阅API文档以获取更详细的说明。

#[derive(Chemin)]
enum Route {
    #[route("/")]
    Home,

    #[route(en => "/about")]
    #[route(fr => "/a-propos")]
    About,

    #[route(en => "/hello/:name")]
    #[route(fr => "/bonjour/:name")]
    Hello {
        name: String,
        #[query_param(optional)]
        age: Option<u8>,
    },

    #[route("/sub-route/..")]
    SubRoute(SubRoute),
}

#[derive(Chemin)]
enum SubRoute {
    #[route("/a")]
    A,

    #[route("/b")]
    B,
}

lib.rs:

Chemin是一个基于枚举的路由生成器,支持查询字符串和国际化。它可以在前端或后端使用,与任何框架或库一起使用。它既可以解析URL到路由,也可以从构造的路由生成URL。

它不是“闪电般快速”:在这个crate中,代码清晰度始终优先于优化。

基本用法

您只需将路由定义为枚举的不同变体,并派生[Chemin]特质即可。

use chemin::Chemin;

// `PartialEq`, `Eq` and `Debug` are not necessary to derive `Chemin`, but here they are used to be able to use `assert_eq`.
##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    ##[route("/")]
    Home,

    /// If there is a trailing slash at the end (example: #[route("/about/")]), it is considered
    /// a different route than without the trailing slash.
    ##[route("/about")]
    About,

    /// The character ":" is used for dynamic parameters.
    /// The type of the parameter (in this case `String`), must implement `FromStr` and `Display`.
    ##[route("/hello/:")]
    Hello(String),

    /// You can use named fields by giving a name to the parameters (after ":").
    ##[route("/hello/:name/:age")]
    HelloWithAge {
        name: String,
        age: u8
    }
}

// Url parsing:
let decode_params = true; // Whether or not to percent-decode url parameters (see `Chemin::parse` for documentation).
// `vec![]` is the list of the locales for this route. As we don't use i18n for this router yet, it is therefore empty.
assert_eq!(Route::parse("/", decode_params), Some((Route::Home, vec![])));
assert_eq!(Route::parse("/about", decode_params), Some((Route::About, vec![])));
assert_eq!(Route::parse("/about/", decode_params), None); // Route not found because of the trailing slash
assert_eq!(Route::parse("/hello/John", decode_params), Some((Route::Hello(String::from("John")), vec![])));
assert_eq!(
    Route::parse("/hello/John%20Doe/30", decode_params),
    Some((
        Route::HelloWithAge {
            name: String::from("John Doe"),
            age: 30,
        },
        vec![],
    ))
);

// Url generation
let encode_params = true; // Whether or not to percent-encode url parameters (see `Chemin::generate_url` for documentation).
let locale = None; // The locale for which to generate the url. For now, we don't use i18n yet, so it is `None`.
assert_eq!(Route::Home.generate_url(locale, encode_params), Some(String::from("/"))); // The result is guaranteed to be `Some` if we don't use i18n.
assert_eq!(Route::About.generate_url(locale, encode_params), Some(String::from("/about")));
assert_eq!(Route::Hello(String::from("John")).generate_url(locale, encode_params), Some(String::from("/hello/John")));
assert_eq!(
    Route::HelloWithAge {
        name: String::from("John Doe"),
        age: 30,
    }.generate_url(locale, encode_params),
    Some(String::from("/hello/John%20Doe/30")),
);

子路由

但对于更复杂的路由,您不会将所有内容都放入单个枚举中。您可以使用子路由将其分解

use chemin::Chemin;

##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    /// You can use a sub-route by using ".." (only at the end of the path). The corresponding type must also implement `Chemin`.
    ///
    /// If you want a route to access "/sub-route" or "/sub-route/", it can't possibly be defined inside the sub-route, so it would
    /// have to be a different additional route here.
    ##[route("/sub-route/..")]
    WithSubRoute(SubRoute),

    /// You can also combine sub-route with url parameters, and use named sub-routes, by adding the name after "..".
    ##[route("/hello/:name/..sub_route")]
    HelloWithSubRoute {
        name: String,
        sub_route: SubRoute,
    },
}

##[derive(Chemin, PartialEq, Eq, Debug)]
enum SubRoute {
    ##[route("/a")]
    A,

    ##[route("/b")]
    B,
}

// Url parsing:
assert_eq!(Route::parse("/sub-route/a", true), Some((Route::WithSubRoute(SubRoute::A), vec![])));
assert_eq!(
    Route::parse("/hello/John/b", true),
    Some((
        Route::HelloWithSubRoute {
            name: String::from("John"),
            sub_route: SubRoute::B,
        },
        vec![],
    )),
);

// Url generation:
assert_eq!(Route::WithSubRoute(SubRoute::A).generate_url(None, true), Some(String::from("/sub-route/a")));

查询字符串参数

支持查询字符串

use chemin::Chemin;

##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    ##[route("/hello/:name")]
    Hello {
        name: String,

        /// This attribute can only be used on named fields
        ##[query_param]
        age: u8,
    }
}

// Url parsing:
assert_eq!(Route::parse("/hello/John", true), None); // Route not found because the "age" query parameter wasn't provided
assert_eq!(
    Route::parse("/hello/John?age=30", true),
    Some((
        Route::Hello {
            name: String::from("John"),
            age: 30,
        },
        vec![],
    ))
);

// Url generation:
assert_eq!(
    Route::Hello {
        name: String::from("John"),
        age: 30,
    }.generate_url(None, true),
    Some(String::from("/hello/John?age=30")),
);

查询参数也可以是可选的

use chemin::Chemin;

##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    ##[route("/hello/:name")]
    Hello {
        name: String,
        ##[query_param(optional)]
        age: Option<u8>,
    }
}

// Url parsing:
assert_eq!(
    Route::parse("/hello/John", true),
    Some((
        Route::Hello {
            name: String::from("John"),
            age: None,
        },
        vec![],
    )),
);
assert_eq!(
    Route::parse("/hello/John?age=30", true),
    Some((
        Route::Hello {
            name: String::from("John"),
            age: Some(30),
        },
        vec![],
    )),
);

// Url generation:
assert_eq!(
    Route::Hello {
        name: String::from("John"),
        age: None,
    }.generate_url(None, true),
    Some(String::from("/hello/John")),
);
assert_eq!(
    Route::Hello {
        name: String::from("John"),
        age: Some(30),
    }.generate_url(None, true),
    Some(String::from("/hello/John?age=30")),
);

查询参数可以有默认值

use chemin::Chemin;

##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    ##[route("/hello/:name")]
    Hello {
        name: String,
        ##[query_param(default = 20)]
        age: u8,
    }
}

// Url parsing:
assert_eq!(
    Route::parse("/hello/John", true),
    Some((
        Route::Hello {
            name: String::from("John"),
            age: 20,
        },
        vec![],
    )),
);
assert_eq!(
    Route::parse("/hello/John?age=30", true),
    Some((
        Route::Hello {
            name: String::from("John"),
            age: 30,
        },
        vec![],
    )),
);

// Url generation:
assert_eq!(
    Route::Hello {
        name: String::from("John"),
        age: 20,
    }.generate_url(None, true),
    Some(String::from("/hello/John")),
);
assert_eq!(
    Route::Hello {
        name: String::from("John"),
        age: 30,
    }.generate_url(None, true),
    Some(String::from("/hello/John?age=30")),
);

如果您使用子路由,则可以在“路由树”的任何级别定义查询参数,并且它们将共享同一个查询字符串。

国际化(i18n)

此crate允许您通过在枚举的每个变体上定义多个路径并将每个路径与一个或多个区域设置代码(如https://mdn.org.cn/en-US/docs/Web/API/Navigator/language)相关联来实现不同语言的路线翻译。

use chemin::Chemin;

##[derive(Chemin, PartialEq, Eq, Debug)]
enum Route {
    ##[route("/")]
    Home,

    // Notice that the hyphens normally used in locale codes are here replaced by an underscore, to be valid rust identifiers
    ##[route(en, en_US, en_UK => "/about")]
    ##[route(fr, fr_FR => "/a-propos")]
    About,

    ##[route(en, en_US, en_UK => "/select/..")]
    ##[route(fr, fr_FR => "/selectionner/..")]
    Select(SelectRoute),
}

##[derive(Chemin, PartialEq, Eq, Debug)]
enum SelectRoute {
    ##[route(en, en_US => "/color/:/:/:")]
    ##[route(en_UK => "/colour/:/:/:")]
    ##[route(fr, fr_FR => "/couleur/:/:/:")]
    RgbColor(u8, u8, u8),
}

// Url parsing:
assert_eq!(Route::parse("/", true), Some((Route::Home, vec![])));

let about_english = Route::parse("/about", true).unwrap();
assert_eq!(about_english.0, Route::About);
// The `Vec<String>` of locales has to be asserted that way, because the order isn't guaranteed
assert_eq!(about_english.1.len(), 3);
assert!(about_english.1.contains(&"en"));
assert!(about_english.1.contains(&"en-US")); // Notice that returned locale codes use hyphens and not underscores
assert!(about_english.1.contains(&"en-UK"));

let about_french = Route::parse("/a-propos", true).unwrap();
assert_eq!(about_french.0, Route::About);
assert_eq!(about_french.1.len(), 2);
assert!(about_french.1.contains(&"fr"));
assert!(about_french.1.contains(&"fr-FR"));

let select_color_us_english = Route::parse("/select/color/0/255/0", true).unwrap();
assert_eq!(select_color_us_english.0, Route::Select(SelectRoute::RgbColor(0, 255, 0)));
assert_eq!(select_color_us_english.1.len(), 2); // The `Vec<String>` has to be asserted that way, because the order isn't guaranteed
assert!(select_color_us_english.1.contains(&"en"));
assert!(select_color_us_english.1.contains(&"en-US"));

assert_eq!(
    Route::parse("/select/colour/0/255/0", true),
    Some((Route::Select(SelectRoute::RgbColor(0, 255, 0)), vec!["en-UK"])),
);

let select_color_french = Route::parse("/selectionner/couleur/0/255/0", true).unwrap();
assert_eq!(select_color_french.0, Route::Select(SelectRoute::RgbColor(0, 255, 0)));
assert_eq!(select_color_french.1.len(), 2); // The `Vec<String>` has to be asserted that way, because the order isn't guaranteed
assert!(select_color_french.1.contains(&"fr"));
assert!(select_color_french.1.contains(&"fr-FR"));

assert_eq!(Route::parse("/select/couleur/0/255/0", true), None);

// Url generation:
assert_eq!(Route::Home.generate_url(Some("es"), true), Some(String::from("/")));
assert_eq!(Route::Home.generate_url(None, true), Some(String::from("/")));

// Notice that you have to use hyphens and not underscores in locale codes
assert_eq!(Route::About.generate_url(Some("en"), true), Some(String::from("/about")));
assert_eq!(Route::About.generate_url(Some("fr-FR"), true), Some(String::from("/a-propos")));
assert_eq!(Route::About.generate_url(Some("es"), true), None);
assert_eq!(Route::About.generate_url(None, true), None);

assert_eq!(
    Route::Select(SelectRoute::RgbColor(0, 255, 0)).generate_url(Some("en-UK"), true),
    Some(String::from("/select/colour/0/255/0")),
);
assert_eq!(
    Route::Select(SelectRoute::RgbColor(0, 255, 0)).generate_url(Some("fr-FR"), true),
    Some(String::from("/selectionner/couleur/0/255/0")),
);

依赖关系

~6MB
~118K SLoC