ipfs_api_prelude/
from_uri.rs

1// Copyright 2022 rust-ipfs-api Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//
8
9use http::uri::{Builder, InvalidUri, PathAndQuery, Scheme, Uri};
10use multiaddr::{Multiaddr, Protocol};
11use std::{
12    fs,
13    net::{SocketAddr, SocketAddrV4, SocketAddrV6},
14    str::FromStr,
15};
16
17const VERSION_PATH_V0: &str = "/api/v0";
18
19/// Builds the base url path for the Ipfs api.
20///
21fn build_base_path(builder: Builder) -> Result<Uri, http::Error> {
22    builder.path_and_query(VERSION_PATH_V0).build()
23}
24
25pub trait TryFromUri: Sized {
26    /// Builds a new client from a base URI to the IPFS API.
27    ///
28    fn build_with_base_uri(uri: Uri) -> Self;
29
30    /// Creates a new client from a str.
31    ///
32    /// Note: This constructor will overwrite the path/query part of the URI.
33    ///
34    fn from_str(uri: &str) -> Result<Self, InvalidUri> {
35        let uri: Uri = uri.parse()?;
36        let mut parts = uri.into_parts();
37
38        parts.path_and_query = Some(PathAndQuery::from_static(VERSION_PATH_V0));
39
40        Ok(Self::build_with_base_uri(Uri::from_parts(parts).unwrap()))
41    }
42
43    /// Creates a new client from a host name and port.
44    ///
45    fn from_host_and_port(scheme: Scheme, host: &str, port: u16) -> Result<Self, http::Error> {
46        let authority = format!("{}:{}", host, port);
47        let builder = Builder::new().scheme(scheme).authority(&authority[..]);
48
49        build_base_path(builder).map(Self::build_with_base_uri)
50    }
51
52    /// Creates a new client from an IPV4 address and port number.
53    ///
54    fn from_ipv4(scheme: Scheme, addr: SocketAddrV4) -> Result<Self, http::Error> {
55        let authority = format!("{}", addr);
56        let builder = Builder::new().scheme(scheme).authority(&authority[..]);
57
58        build_base_path(builder).map(Self::build_with_base_uri)
59    }
60
61    /// Creates a new client from an IPV6 addr and port number.
62    ///
63    fn from_ipv6(scheme: Scheme, addr: SocketAddrV6) -> Result<Self, http::Error> {
64        let authority = format!("{}", addr);
65        let builder = Builder::new().scheme(scheme).authority(&authority[..]);
66
67        build_base_path(builder).map(Self::build_with_base_uri)
68    }
69
70    /// Creates a new client from an IP address and port number.
71    ///
72    fn from_socket(scheme: Scheme, socket_addr: SocketAddr) -> Result<Self, http::Error> {
73        match socket_addr {
74            SocketAddr::V4(addr) => Self::from_ipv4(scheme, addr),
75            SocketAddr::V6(addr) => Self::from_ipv6(scheme, addr),
76        }
77    }
78
79    /// Creates a new client from a multiaddr.
80    ///
81    fn from_multiaddr(multiaddr: Multiaddr) -> Result<Self, multiaddr::Error> {
82        let mut scheme: Option<Scheme> = None;
83        let mut port: Option<u16> = None;
84
85        for addr_component in multiaddr.iter() {
86            match addr_component {
87                Protocol::Tcp(tcpport) => port = Some(tcpport),
88                Protocol::Http => scheme = Some(Scheme::HTTP),
89                Protocol::Https => scheme = Some(Scheme::HTTPS),
90                _ => (),
91            }
92        }
93
94        let scheme = scheme.unwrap_or(Scheme::HTTP);
95
96        if let Some(port) = port {
97            for addr_component in multiaddr.iter() {
98                match addr_component {
99                    Protocol::Tcp(_) | Protocol::Http | Protocol::Https => (),
100                    Protocol::Ip4(v4addr) => {
101                        return Ok(Self::from_ipv4(scheme, SocketAddrV4::new(v4addr, port)).unwrap())
102                    }
103                    Protocol::Ip6(v6addr) => {
104                        return Ok(
105                            Self::from_ipv6(scheme, SocketAddrV6::new(v6addr, port, 0, 0)).unwrap(),
106                        )
107                    }
108                    Protocol::Dns(ref hostname) => {
109                        return Ok(Self::from_host_and_port(scheme, hostname, port).unwrap())
110                    }
111                    Protocol::Dns4(ref v4host) => {
112                        return Ok(Self::from_host_and_port(scheme, v4host, port).unwrap())
113                    }
114                    Protocol::Dns6(ref v6host) => {
115                        return Ok(Self::from_host_and_port(scheme, v6host, port).unwrap())
116                    }
117                    _ => {
118                        return Err(multiaddr::Error::InvalidMultiaddr);
119                    }
120                }
121            }
122        }
123
124        Err(multiaddr::Error::InvalidMultiaddr)
125    }
126
127    /// Creates a new client from a multiaddr.
128    ///
129    fn from_multiaddr_str(multiaddr: &str) -> Result<Self, multiaddr::Error> {
130        multiaddr::from_url(multiaddr)
131            .map_err(|e| multiaddr::Error::ParsingError(Box::new(e)))
132            .or_else(|_| Multiaddr::from_str(multiaddr))
133            .and_then(Self::from_multiaddr)
134    }
135
136    /// Creates a new client connected to the endpoint specified in ~/.ipfs/api.
137    ///
138    #[inline]
139    fn from_ipfs_config() -> Option<Self> {
140        dirs::home_dir()
141            .map(|home_dir| home_dir.join(".ipfs").join("api"))
142            .and_then(|multiaddr_path| fs::read_to_string(&multiaddr_path).ok())
143            .and_then(|multiaddr_str| Self::from_multiaddr_str(&multiaddr_str).ok())
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use crate::TryFromUri;
150    use http::uri::{Scheme, Uri};
151
152    #[derive(Debug)]
153    struct StringWrapper(String);
154
155    impl TryFromUri for StringWrapper {
156        fn build_with_base_uri(uri: Uri) -> Self {
157            StringWrapper(uri.to_string())
158        }
159    }
160
161    macro_rules! test_from_value_fn_ok {
162        ([$method: path]: $($f: ident ($($args: expr),+) => $output: expr),+) => {
163            $(
164                #[test]
165                fn $f() {
166                    let result: Result<StringWrapper, _> = $method($($args),+);
167
168                    assert!(
169                        result.is_ok(),
170                        "should be ok but failed with error: {:?}", result.unwrap_err()
171                    );
172
173                    let StringWrapper(result) = result.unwrap();
174
175                    assert!(
176                        result == $output,
177                        "got: ({}) expected: ({})", result, $output
178                    );
179                }
180            )+
181        };
182    }
183
184    test_from_value_fn_ok!(
185        [TryFromUri::from_str]:
186        test_from_str_0_ok ("http://localhost:5001") => "http://localhost:5001/api/v0",
187        test_from_str_1_ok ("https://ipfs.io:9001") => "https://ipfs.io:9001/api/v0"
188    );
189
190    test_from_value_fn_ok!(
191        [TryFromUri::from_host_and_port]:
192        test_from_host_and_port_0_ok (Scheme::HTTP, "localhost", 5001) => "http://localhost:5001/api/v0",
193        test_from_host_and_port_1_ok (Scheme::HTTP, "ipfs.io", 9001) => "http://ipfs.io:9001/api/v0"
194    );
195
196    test_from_value_fn_ok!(
197        [TryFromUri::from_multiaddr_str]:
198        test_from_multiaddr_str_0_ok ("http://localhost:5001/") => "http://localhost:5001/api/v0",
199        test_from_multiaddr_str_1_ok ("https://ipfs.io:9001/") => "https://ipfs.io:9001/api/v0",
200        test_from_multiaddr_str_2_ok ("/ip4/127.0.0.1/tcp/5001/http") => "http://127.0.0.1:5001/api/v0",
201        test_from_multiaddr_str_3_ok ("/ip6/0:0:0:0:0:0:0:0/tcp/5001/http") => "http://[::]:5001/api/v0"
202    );
203}