1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/*!

base_url is a thin wrapper around [rust-url](https://github.com/servo/rust-url), which itself
implements the [URL Standard](https://url.spec.whatwg.org/). The goal of base_url is to remove
redundant error checks related to the base-suitability of a given URL.


# Acquiring a BaseUrl object

A BaseUrl object may be acquired by either converting a Url or &str using the TryInto/TryFrom traits.
If a &str cannot be parsed into a Url object a BaseUrlError::ParseError will be returned which wraps the
underlying ParseError type implemented by rust-url.

```
use base_url::{ BaseUrl, BaseUrlError, Url, ParseError };

assert!( BaseUrl::try_from( "http://[:::1]" ) == Err( BaseUrlError::ParseError( ParseError::InvalidIpv6Address ) ) );
```

That's a bit unwieldly, so it's suggested that you prefer first parsing the &str into a Url and
converting that object into a BaseUrl, allowing you to deal with errors related to parsing separately
from errors related to base suitability.

```
use base_url::{ BaseUrl, BaseUrlError, Url };

# fn run( ) -> Result< (), BaseUrlError > {
let url:Url = Url::parse( "data:text/plain,Hello?World#" )?;

assert!( BaseUrl::try_from( url ) == BaseUrlError::CannotBeBase );
# Ok( () )
# }
# run( );
```

Once we have a BaseUrl we can do (almost) anything we could with a normal Url and with fewer functions
admitting potential failures


*/

pub extern crate url;
extern crate try_from;

#[cfg( feature = "_conversion_any" )]
pub mod conversions;

pub use url::{ Url, ParseError };

use url::{ UrlQuery, PathSegmentsMut };
use url::form_urlencoded::{Parse, Serializer};
use try_from::TryFrom;

pub use url::{ Host };

use std::str::Split;
use std::net::IpAddr;
use std::fmt::{Formatter, Display, Result as FormatResult};

/// A representation of the origin of a BaseUrl
pub type OriginTuple = ( String, Host<String>, u16 );

#[derive(Debug)]
pub enum BaseUrlError {
    /// If the Url supplied cannot be a base this error is returned
    CannotBeBase,
    /// If a supplied &str cannot be parsed by the parser in the main Url crate this error is returned
    ParseError( ParseError ),
}

/// Any Url which has a host and so can be supplied as a base url
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BaseUrl {
    url:Url,
}

impl From<BaseUrl> for Url {
    fn from( url:BaseUrl ) -> Url {
        url.url
    }
}

impl TryFrom<Url> for BaseUrl {
    type Err = BaseUrlError;
    fn try_from( url:Url ) -> Result< BaseUrl, Self::Err > {
        if url.cannot_be_a_base( ) {
            Err( BaseUrlError::CannotBeBase )
        } else {
            Ok( BaseUrl{ url:url } )
        }
    }
}

impl<'a> TryFrom<&'a str> for BaseUrl {
    type Err = BaseUrlError;

    fn try_from( url:&'a str ) -> Result< BaseUrl, Self::Err > {
        match Url::parse( url ) {
            Ok( u ) => BaseUrl::try_from( u ),
            Err( e ) => Err( BaseUrlError::ParseError( e ) ),
        }
    }
}

impl BaseUrl {

    /// Return the serialization of this BaseUrl
    ///
    /// This is fast, since internally the Url stores the serialization already
    ///
    /// # Examples
    ///
    /// ```rust
    /// use base_url::{ BaseUrl, Url };
    ///# use base_url::{ BaseUrlError };
    ///# fn run( ) -> Result< (), BaseUrlError > {
    /// let url_str = "https://example.org/"
    /// let host = BaseUrl::try_from( url_str )?;
    /// assert_eq!( host.as_str( ), url_str );
    ///# Ok( () )
    ///# }
    ///# run( );
    /// ```
    pub fn as_str( &self ) -> &str {
        self.url.as_str( )
    }

    /// Return the serialization of this BaseUrl
    ///
    /// This consumes the BaseUrl and takes ownership of the String
    ///
    /// # Examples
    /// ```rust
    /// use base_url::BaseUrl;
    ///# use base_url::{ ParseError };
    ///# fn run( ) -> Result< (), BaseUrlError > {
    /// let url_str = "https://example.org/"
    /// let host = BaseUrl::try_from( url_str )?;
    /// assert_eq!( host.into_string, url_str );
    ///# Ok( () )
    ///# }
    ///# run( );
    /// ```
    pub fn into_string( self ) -> String {
        self.url.into_string( )
    }


    /// Returns the BaseUrl's scheme, host and port as a tuple
    ///
    /// # Examples
    ///
    /// ```rust
    /// use base_url::{ BaseUrl, OriginTuple, Host };
    ///# use base_url::BaseUrlError;
    ///# fn run( ) -> Result< (), BaseUrlError > {
    /// let url = BaseUrl::try_from( "ftp://example.org/foo" );
    ///
    /// assert_eq!( url.origin( ),
    ///             ( "ftp".into( ),
    ///               Host::Domain( "example.org".into( ) ),
    ///               21 ) );
    ///# Ok( () )
    ///# }
    ///# run( );
    /// ```
    pub fn origin( &self ) -> OriginTuple {
        match self.url.origin( ) {
            url::Origin::Opaque( _ ) => { panic!( "Some sorcery occurred, please raise an issue at https://github.com/bradymcd/rs-baseurl" ) }
            url::Origin::Tuple( scheme, host, port ) => {
                ( scheme, host, port )
            }
        }
    }


    /// Returns the scheme of the given BaseUrl, lower-cased, as an ASCII string without the ':'
    /// delimiter
    ///
    /// # Examples
    ///
    /// ```rust
    /// use base_url::BaseUrl;
    ///# use base_url::BaseUrlError;
    ///# fn run( ) -> Result< (), BaseUrlError > {
    /// let url = BaseUrl::try_from( "https://example.org" )?;
    /// assert_eq!( url.scheme, "https".into( ) );
    ///# Ok( () )
    ///# }
    ///# run( );
    /// ```
    pub fn scheme( &self ) -> &str {
        self.url.scheme( )
    }

    //TODO: Examples below this point

    /// Set the BaseUrl's scheme
    ///
    /// Does nothing and returns Err() if the specified scheme does not match the regular expression
    /// [a-zA-Z][a-zA-Z0-9+.-]+
    ///
    ///
    pub fn set_scheme( &mut self, scheme: &str ) -> Result< (), () > {
        self.url.set_scheme( scheme )
    }

    /// Return the username for this BaseUrl. If no username is set an empty string is returned
    ///
    ///
    pub fn username( &self ) -> &str {
        self.url.username( )
    }

    /// Change the username of this BaseUrl.
    ///
    ///
    pub fn set_username( &mut self, username:&str ) {
        self.url.set_username( username ).expect( "The impossible happened" );
    }

    /// Optionally returns the password associated with this BaseUrl as a percent-encoded ASCII string.
    ///
    ///
    pub fn password( &self ) -> Option< &str > {
        self.url.password( )
    }

    /// Change the password of this BaseUrl. Use None to remove the password field.
    ///
    ///
    pub fn set_password( &mut self, password:Option< &str > ) {
        self.url.set_password( password ).expect( "The impossible happened" );
    }

    /// Returns the domain or IP address for this BaseUrl as a string.
    ///
    /// See also the host() method
    ///
    ///
    pub fn host_str( &self ) -> &str {
        self.url.host_str( ).unwrap( )
    }

    /// Returns the host for this BaseUrl in an enumerated type.
    ///
    ///
    pub fn host( &self ) -> Host< &str > {
        self.url.host( ).unwrap( )
    }

    /// Changes the host for this BaseUrl. If there is any error parsing the provided string no action
    /// is taken and Err() is returned
    ///
    ///
    pub fn set_host( &mut self, host:&str ) -> Result< (), () > {
        match self.url.set_host( Some( host ) ) {
            Ok( _ ) => Ok( () ),
            Err( _ ) => Err( () ),
        }
    }

    /// Change this BaseUrl's host to the given Ip address.
    ///
    /// This skips the parsing step compared to calling set_host()
    ///
    ///
    pub fn set_ip_host( &mut self, address:IpAddr ) {
        self.url.set_ip_host( address ).expect( "The impossible occurred" );
    }

    /// Return's the domain string of this BaseUrl. Returns None if the host is an Ip address rather
    /// than a domain name.
    ///
    pub fn domain( &self ) -> Option< &str > {
        self.url.domain( )
    }

    /// Optionally return's the port number of this BaseUrl.
    ///
    pub fn port( &self ) -> Option< u16 > {
        self.url.port( )
    }

    /// Return's the port number of this BaseUrl. If no port number is present a guess is made based
    /// on the scheme, if no guess can be made None is returned.
    ///
    pub fn port_or_known_default( &self ) -> Option< u16 > {
        self.url.port_or_known_default( )
    }

    /// Change this BaseUrl's port.
    ///
    pub fn set_port( &mut self, port:Option< u16 > ) {
        self.url.set_port( port ).expect( "The impossible happened" )
    }

    /// Return's the path of this BaseUrl, percent-encoded. Path strings will start with '/' and
    /// continue with '/' separated path segments.
    ///
    pub fn path( &self ) -> &str {
        self.url.path( )
    }

    /// Return's an iterator through each of this BaseUrl's path segments. Path segments do not contain
    /// the separating '/' characters and may be empty, often on the last entry.
    ///
    pub fn path_segments( &self ) -> Split<char> {
        self.url.path_segments( ).unwrap( )
    }

    /// Change this BaseUrl's path
    ///
    ///
    pub fn set_path( &mut self, path:&str ) {
        self.url.set_path( path )
    }


    /// Returns an object with methods to manipulate this BaseUrl's path segments.
    ///
    ///
    pub fn path_segments_mut( &mut self ) -> PathSegmentsMut {
        self.url.path_segments_mut( ).unwrap( )
    }

    /// Optionally return's this BaseUrl's percent-encoded query string.
    ///
    ///
    pub fn query( &self ) -> Option< &str > {
        self.url.query( )
    }

    /// Parse the BaseUrl's query string and return an iterator over all found (key, value) pairs.
    ///
    ///
    pub fn query_pairs( &self ) -> Parse {
        self.url.query_pairs( )
    }

    /// Change this BaseUrl's query string.
    ///
    ///
    pub fn set_query( &mut self, query:Option<&str> ) {
        self.url.set_query( query )
    }

    /// Returns an object with a method chaining API. These methods manipulate the query string of the
    /// BaseUrl as a sequence of (key, value) pairs.
    ///
    ///
    pub fn query_pairs_mut( &mut self ) -> Serializer< UrlQuery > {
        self.url.query_pairs_mut( )
    }

    /// Optionally returns this BaseUrl's fragment identifier.
    ///
    ///
    pub fn fragment( &self ) -> Option< &str > {
        self.url.fragment( )
    }

    /// Change this BaseUrl's fragment identifier.
    ///
    /// The fragment is any text placed after a `#` symbol in the Url. It is meant to refer to a
    /// secondary resource.
    ///
    /// This is often not sent to the server where it is used in http: and similar schemes.
    ///
    pub fn set_fragment( &mut self, fragment:Option<&str> ) {
        self.url.set_fragment( fragment )
    }

    /* TODO: possibly
    pub fn with_default_port <F> ( &self, f:F ) -> Result<HostAndPort<&str>>
     */



}

impl Display for BaseUrl {
    fn fmt( &self, formatter: &mut Formatter ) -> FormatResult {
        self.url.fmt( formatter )
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}