Skip to content

Commit 9a716d4

Browse files
committed
Extract conversion code into mod convert
1 parent cc46bd9 commit 9a716d4

File tree

3 files changed

+186
-176
lines changed

3 files changed

+186
-176
lines changed

src/convert.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
use crate::{
2+
imp::{HostMeta, Meta, RiMaybeRef, RmrRef},
3+
parse::ParseError,
4+
pct_enc::encode_byte,
5+
Iri, IriRef, Uri, UriRef,
6+
};
7+
use alloc::string::String;
8+
use borrow_or_share::Bos;
9+
use core::{num::NonZeroUsize, str};
10+
11+
macro_rules! impl_from {
12+
($($x:ident => $($y:ident),+)*) => {
13+
$($(
14+
impl<T: Bos<str>> From<$x<T>> for $y<T> {
15+
#[doc = concat!("Consumes the `", stringify!($x), "` and creates a new [`", stringify!($y), "`] with the same contents.")]
16+
fn from(value: $x<T>) -> Self {
17+
RiMaybeRef::new(value.val, value.meta)
18+
}
19+
}
20+
)+)*
21+
};
22+
}
23+
24+
impl_from! {
25+
Uri => UriRef, Iri, IriRef
26+
UriRef => IriRef
27+
Iri => IriRef
28+
}
29+
30+
macro_rules! impl_try_from {
31+
($(#[$doc:meta] $x:ident if $($cond:ident)&&+ => $y:ident)*) => {
32+
$(
33+
impl<'a> TryFrom<$x<&'a str>> for $y<&'a str> {
34+
type Error = ParseError;
35+
36+
#[$doc]
37+
fn try_from(value: $x<&'a str>) -> Result<Self, Self::Error> {
38+
let r = value.make_ref();
39+
$(r.$cond()?;)+
40+
Ok((RiMaybeRef::new(value.val, value.meta)))
41+
}
42+
}
43+
44+
impl TryFrom<$x<String>> for $y<String> {
45+
type Error = ParseError<$x<String>>;
46+
47+
#[$doc]
48+
fn try_from(value: $x<String>) -> Result<Self, Self::Error> {
49+
let r = value.make_ref();
50+
$(
51+
if let Err(e) = r.$cond() {
52+
return Err(e.with_input(value));
53+
}
54+
)+
55+
Ok((RiMaybeRef::new(value.val, value.meta)))
56+
}
57+
}
58+
)*
59+
};
60+
}
61+
62+
impl_try_from! {
63+
/// Converts the URI reference to a URI if it has a scheme.
64+
UriRef if ensure_has_scheme => Uri
65+
/// Converts the IRI to a URI if it is ASCII.
66+
Iri if ensure_ascii => Uri
67+
/// Converts the IRI reference to a URI if it has a scheme and is ASCII.
68+
IriRef if ensure_has_scheme && ensure_ascii => Uri
69+
/// Converts the IRI reference to a URI reference if it is ASCII.
70+
IriRef if ensure_ascii => UriRef
71+
/// Converts the IRI reference to an IRI if it has a scheme.
72+
IriRef if ensure_has_scheme => Iri
73+
}
74+
75+
impl<T: Bos<str>> Iri<T> {
76+
/// Converts the IRI to a URI by percent-encoding non-ASCII characters.
77+
///
78+
/// Punycode encoding is **not** performed during conversion.
79+
///
80+
/// # Examples
81+
///
82+
/// ```
83+
/// use fluent_uri::Iri;
84+
///
85+
/// let iri = Iri::parse("http://www.example.org/résumé.html").unwrap();
86+
/// assert_eq!(iri.to_uri(), "http://www.example.org/r%C3%A9sum%C3%A9.html");
87+
///
88+
/// let iri = Iri::parse("http://résumé.example.org").unwrap();
89+
/// assert_eq!(iri.to_uri(), "http://r%C3%A9sum%C3%A9.example.org");
90+
/// ```
91+
pub fn to_uri(&self) -> Uri<String> {
92+
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
93+
}
94+
}
95+
96+
impl<T: Bos<str>> IriRef<T> {
97+
/// Converts the IRI reference to a URI reference by percent-encoding non-ASCII characters.
98+
///
99+
/// Punycode encoding is **not** performed during conversion.
100+
///
101+
/// # Examples
102+
///
103+
/// ```
104+
/// use fluent_uri::IriRef;
105+
///
106+
/// let iri_ref = IriRef::parse("résumé.html").unwrap();
107+
/// assert_eq!(iri_ref.to_uri_ref(), "r%C3%A9sum%C3%A9.html");
108+
///
109+
/// let iri_ref = IriRef::parse("//résumé.example.org").unwrap();
110+
/// assert_eq!(iri_ref.to_uri_ref(), "//r%C3%A9sum%C3%A9.example.org");
111+
/// ```
112+
pub fn to_uri_ref(&self) -> UriRef<String> {
113+
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
114+
}
115+
}
116+
117+
fn encode_non_ascii(r: RmrRef<'_, '_>) -> (String, Meta) {
118+
let mut buf = String::new();
119+
let mut meta = Meta::default();
120+
121+
if let Some(scheme) = r.scheme_opt() {
122+
buf.push_str(scheme.as_str());
123+
meta.scheme_end = NonZeroUsize::new(buf.len());
124+
buf.push(':');
125+
}
126+
127+
if let Some(auth) = r.authority() {
128+
buf.push_str("//");
129+
130+
if let Some(userinfo) = auth.userinfo() {
131+
encode_non_ascii_str(&mut buf, userinfo.as_str());
132+
buf.push('@');
133+
}
134+
135+
let mut auth_meta = auth.meta();
136+
auth_meta.host_bounds.0 = buf.len();
137+
match auth_meta.host_meta {
138+
HostMeta::RegName => encode_non_ascii_str(&mut buf, auth.host()),
139+
_ => buf.push_str(auth.host()),
140+
}
141+
auth_meta.host_bounds.1 = buf.len();
142+
meta.auth_meta = Some(auth_meta);
143+
144+
if let Some(port) = auth.port() {
145+
buf.push(':');
146+
buf.push_str(port.as_str());
147+
}
148+
}
149+
150+
meta.path_bounds.0 = buf.len();
151+
encode_non_ascii_str(&mut buf, r.path().as_str());
152+
meta.path_bounds.1 = buf.len();
153+
154+
if let Some(query) = r.query() {
155+
buf.push('?');
156+
encode_non_ascii_str(&mut buf, query.as_str());
157+
meta.query_end = NonZeroUsize::new(buf.len());
158+
}
159+
160+
if let Some(fragment) = r.fragment() {
161+
buf.push('#');
162+
encode_non_ascii_str(&mut buf, fragment.as_str());
163+
}
164+
165+
(buf, meta)
166+
}
167+
168+
fn encode_non_ascii_str(buf: &mut String, s: &str) {
169+
if s.is_ascii() {
170+
buf.push_str(s);
171+
} else {
172+
for ch in s.chars() {
173+
if ch.is_ascii() {
174+
buf.push(ch);
175+
} else {
176+
for x in ch.encode_utf8(&mut [0; 4]).bytes() {
177+
encode_byte(x, buf);
178+
}
179+
}
180+
}
181+
}
182+
}

src/imp.rs

Lines changed: 3 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
component::{Authority, IAuthority, Scheme},
99
normalize,
1010
parse::{self, ParseError},
11-
pct_enc::{encode_byte, encoder::*, EStr, Encoder},
11+
pct_enc::{encoder::*, EStr, Encoder},
1212
resolve::{self, ResolveError},
1313
};
1414
use alloc::{borrow::ToOwned, string::String};
@@ -258,10 +258,10 @@ macro_rules! ri_maybe_ref {
258258
#[derive(Clone, Copy)]
259259
pub struct $Ty<T> {
260260
/// Value of the URI/IRI (reference).
261-
val: T,
261+
pub(crate) val: T,
262262
/// Metadata of the URI/IRI (reference).
263263
/// Should be identical to parser output with `val` as input.
264-
meta: Meta,
264+
pub(crate) meta: Meta,
265265
}
266266

267267
impl<T> RiMaybeRef for $Ty<T> {
@@ -1166,176 +1166,3 @@ ri_maybe_ref! {
11661166
QueryEncoderType = IQuery,
11671167
FragmentEncoderType = IFragment,
11681168
}
1169-
1170-
macro_rules! impl_from {
1171-
($($x:ident => $($y:ident),+)*) => {
1172-
$($(
1173-
impl<T: Bos<str>> From<$x<T>> for $y<T> {
1174-
#[doc = concat!("Consumes the `", stringify!($x), "` and creates a new [`", stringify!($y), "`] with the same contents.")]
1175-
fn from(value: $x<T>) -> Self {
1176-
RiMaybeRef::new(value.val, value.meta)
1177-
}
1178-
}
1179-
)+)*
1180-
};
1181-
}
1182-
1183-
impl_from! {
1184-
Uri => UriRef, Iri, IriRef
1185-
UriRef => IriRef
1186-
Iri => IriRef
1187-
}
1188-
1189-
macro_rules! impl_try_from {
1190-
($(#[$doc:meta] $x:ident if $($cond:ident)&&+ => $y:ident)*) => {
1191-
$(
1192-
impl<'a> TryFrom<$x<&'a str>> for $y<&'a str> {
1193-
type Error = ParseError;
1194-
1195-
#[$doc]
1196-
fn try_from(value: $x<&'a str>) -> Result<Self, Self::Error> {
1197-
let r = value.make_ref();
1198-
$(r.$cond()?;)+
1199-
Ok((RiMaybeRef::new(value.val, value.meta)))
1200-
}
1201-
}
1202-
1203-
impl TryFrom<$x<String>> for $y<String> {
1204-
type Error = ParseError<$x<String>>;
1205-
1206-
#[$doc]
1207-
fn try_from(value: $x<String>) -> Result<Self, Self::Error> {
1208-
let r = value.make_ref();
1209-
$(
1210-
if let Err(e) = r.$cond() {
1211-
return Err(e.with_input(value));
1212-
}
1213-
)+
1214-
Ok((RiMaybeRef::new(value.val, value.meta)))
1215-
}
1216-
}
1217-
)*
1218-
};
1219-
}
1220-
1221-
impl_try_from! {
1222-
/// Converts the URI reference to a URI if it has a scheme.
1223-
UriRef if ensure_has_scheme => Uri
1224-
/// Converts the IRI to a URI if it is ASCII.
1225-
Iri if ensure_ascii => Uri
1226-
/// Converts the IRI reference to a URI if it has a scheme and is ASCII.
1227-
IriRef if ensure_has_scheme && ensure_ascii => Uri
1228-
/// Converts the IRI reference to a URI reference if it is ASCII.
1229-
IriRef if ensure_ascii => UriRef
1230-
/// Converts the IRI reference to an IRI if it has a scheme.
1231-
IriRef if ensure_has_scheme => Iri
1232-
}
1233-
1234-
impl<T: Bos<str>> Iri<T> {
1235-
/// Converts the IRI to a URI by percent-encoding non-ASCII characters.
1236-
///
1237-
/// Punycode encoding is **not** performed during conversion.
1238-
///
1239-
/// # Examples
1240-
///
1241-
/// ```
1242-
/// use fluent_uri::Iri;
1243-
///
1244-
/// let iri = Iri::parse("http://www.example.org/résumé.html").unwrap();
1245-
/// assert_eq!(iri.to_uri(), "http://www.example.org/r%C3%A9sum%C3%A9.html");
1246-
///
1247-
/// let iri = Iri::parse("http://résumé.example.org").unwrap();
1248-
/// assert_eq!(iri.to_uri(), "http://r%C3%A9sum%C3%A9.example.org");
1249-
/// ```
1250-
pub fn to_uri(&self) -> Uri<String> {
1251-
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
1252-
}
1253-
}
1254-
1255-
impl<T: Bos<str>> IriRef<T> {
1256-
/// Converts the IRI reference to a URI reference by percent-encoding non-ASCII characters.
1257-
///
1258-
/// Punycode encoding is **not** performed during conversion.
1259-
///
1260-
/// # Examples
1261-
///
1262-
/// ```
1263-
/// use fluent_uri::IriRef;
1264-
///
1265-
/// let iri_ref = IriRef::parse("résumé.html").unwrap();
1266-
/// assert_eq!(iri_ref.to_uri_ref(), "r%C3%A9sum%C3%A9.html");
1267-
///
1268-
/// let iri_ref = IriRef::parse("//résumé.example.org").unwrap();
1269-
/// assert_eq!(iri_ref.to_uri_ref(), "//r%C3%A9sum%C3%A9.example.org");
1270-
/// ```
1271-
pub fn to_uri_ref(&self) -> UriRef<String> {
1272-
RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
1273-
}
1274-
}
1275-
1276-
fn encode_non_ascii(r: RmrRef<'_, '_>) -> (String, Meta) {
1277-
let mut buf = String::new();
1278-
let mut meta = Meta::default();
1279-
1280-
if let Some(scheme) = r.scheme_opt() {
1281-
buf.push_str(scheme.as_str());
1282-
meta.scheme_end = NonZeroUsize::new(buf.len());
1283-
buf.push(':');
1284-
}
1285-
1286-
if let Some(auth) = r.authority() {
1287-
buf.push_str("//");
1288-
1289-
if let Some(userinfo) = auth.userinfo() {
1290-
encode_non_ascii_str(&mut buf, userinfo.as_str());
1291-
buf.push('@');
1292-
}
1293-
1294-
let mut auth_meta = auth.meta();
1295-
auth_meta.host_bounds.0 = buf.len();
1296-
match auth_meta.host_meta {
1297-
HostMeta::RegName => encode_non_ascii_str(&mut buf, auth.host()),
1298-
_ => buf.push_str(auth.host()),
1299-
}
1300-
auth_meta.host_bounds.1 = buf.len();
1301-
meta.auth_meta = Some(auth_meta);
1302-
1303-
if let Some(port) = auth.port() {
1304-
buf.push(':');
1305-
buf.push_str(port.as_str());
1306-
}
1307-
}
1308-
1309-
meta.path_bounds.0 = buf.len();
1310-
encode_non_ascii_str(&mut buf, r.path().as_str());
1311-
meta.path_bounds.1 = buf.len();
1312-
1313-
if let Some(query) = r.query() {
1314-
buf.push('?');
1315-
encode_non_ascii_str(&mut buf, query.as_str());
1316-
meta.query_end = NonZeroUsize::new(buf.len());
1317-
}
1318-
1319-
if let Some(fragment) = r.fragment() {
1320-
buf.push('#');
1321-
encode_non_ascii_str(&mut buf, fragment.as_str());
1322-
}
1323-
1324-
(buf, meta)
1325-
}
1326-
1327-
fn encode_non_ascii_str(buf: &mut String, s: &str) {
1328-
if s.is_ascii() {
1329-
buf.push_str(s);
1330-
} else {
1331-
for ch in s.chars() {
1332-
if ch.is_ascii() {
1333-
buf.push(ch);
1334-
} else {
1335-
for x in ch.encode_utf8(&mut [0; 4]).bytes() {
1336-
encode_byte(x, buf);
1337-
}
1338-
}
1339-
}
1340-
}
1341-
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
8282
pub mod build;
8383
pub mod component;
84+
mod convert;
8485
mod fmt;
8586
mod imp;
8687
mod normalize;

0 commit comments

Comments
 (0)