itsi-scheduler 0.1.5 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of itsi-scheduler might be problematic. Click here for more details.

@@ -19,11 +19,12 @@ parking_lot = "0.12.3"
19
19
  rustls-pemfile = "2.2.0"
20
20
  tokio-rustls = "0.26.2"
21
21
  bytes = "1.3"
22
+ tokio-rustls-acme = "0.6.0"
22
23
  rcgen = { version = "0.13.2", features = ["x509-parser", "pem"] }
23
24
  base64 = "0.22.1"
24
25
  http-body-util = "0.1.2"
25
26
  hyper = { version = "1.5.0", features = ["full", "server", "http1", "http2"] }
26
- tokio = { version = "1", features = ["full"] }
27
+ tokio = { version = "1.44.1", features = ["full"] }
27
28
  hyper-util = { version = "0.1.10", features = ["full"] }
28
29
  derive_more = { version = "2.0.1", features = ["debug"] }
29
30
  http = "1.3.1"
@@ -39,8 +40,11 @@ httparse = "1.10.1"
39
40
  async-channel = "2.3.1"
40
41
  tempfile = "3.18.0"
41
42
  sysinfo = "0.33.1"
42
- tokio-rustls-acme = "0.6.0"
43
43
  rustls = "0.23.23"
44
44
  fs2 = "0.4.3"
45
45
  ring = "0.17.14"
46
46
  async-trait = "0.1.87"
47
+ dirs = "6.0.0"
48
+ regex = "1.11.1"
49
+ route-recognizer = "0.3.1"
50
+ fnv = "1.0.7"
@@ -0,0 +1,43 @@
1
+ use std::{
2
+ env::{var, VarError},
3
+ path::PathBuf,
4
+ sync::LazyLock,
5
+ };
6
+
7
+ type StringVar = LazyLock<String>;
8
+ type MaybeStringVar = LazyLock<Result<String, VarError>>;
9
+ type PathVar = LazyLock<PathBuf>;
10
+
11
+ /// ACME Configuration for auto-generating production certificates
12
+ /// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
13
+ /// so that these are not regenerated every time the server starts
14
+ pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
15
+ var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
16
+ });
17
+
18
+ /// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
19
+ pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
20
+ LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
21
+
22
+ /// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
23
+ pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
24
+
25
+ /// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
26
+ pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
27
+ var("ITSI_ACME_DIRECTORY_URL")
28
+ .unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
29
+ });
30
+
31
+ /// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
32
+ pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
33
+ LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
34
+
35
+ pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
36
+ var("ITSI_LOCAL_CA_DIR")
37
+ .map(PathBuf::from)
38
+ .unwrap_or_else(|_| {
39
+ dirs::home_dir()
40
+ .expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
41
+ .join(".itsi")
42
+ })
43
+ });
@@ -1,11 +1,15 @@
1
1
  use body_proxy::itsi_body_proxy::ItsiBodyProxy;
2
- use magnus::{error::Result, function, method, value::Lazy, Module, Object, RClass, RModule, Ruby};
2
+ use magnus::{
3
+ error::Result, function, method, value::Lazy, Module, Object, RClass, RHash, RModule, Ruby,
4
+ };
5
+ use regex::{Regex, RegexSet};
3
6
  use request::itsi_request::ItsiRequest;
4
7
  use response::itsi_response::ItsiResponse;
5
8
  use server::{itsi_server::Server, signal::reset_signal_handlers};
6
9
  use tracing::*;
7
10
 
8
11
  pub mod body_proxy;
12
+ pub mod env;
9
13
  pub mod request;
10
14
  pub mod response;
11
15
  pub mod server;
@@ -53,6 +57,70 @@ pub fn log_error(msg: String) {
53
57
  error!(msg);
54
58
  }
55
59
 
60
+ const ROUTES: [&str; 39] = [
61
+ r"(?-u)^/organisations/(?<organisation_id>\d+)/users/(?<user_id>\d+)$",
62
+ r"(?-u)^/projects/(?<project_id>\d+)/tasks/(?<task_id>\d+)$",
63
+ r"(?-u)^/products/(?<product_id>\d+)(?:/reviews/(?<review_id>\d+))?$",
64
+ r"(?-u)^/orders/(?<order_id>\d+)/items(?:/(?<item_id>\d+))?$",
65
+ r"(?-u)^/posts/(?<post_id>\d+)/comments(?:/(?<comment_id>\d+))?$",
66
+ r"(?-u)^/teams/(?<team_id>\d+)(?:/members/(?<member_id>\d+))?$",
67
+ r"(?-u)^/categories/(?<category_id>\d+)/subcategories(?:/(?<subcategory_id>\d+))?$",
68
+ r"(?-u)^/departments/(?<department_id>\d+)/employees/(?<employee_id>\d+)$",
69
+ r"(?-u)^/events/(?<event_id>\d+)(?:/sessions/(?<session_id>\d+))?$",
70
+ r"(?-u)^/invoices/(?<invoice_id>\d+)/payments(?:/(?<payment_id>\d+))?$",
71
+ r"(?-u)^/tickets/(?<ticket_id>\d+)(?:/responses/(?<response_id>\d+))?$",
72
+ r"(?-u)^/forums/(?<forum_id>\d+)(?:/threads/(?<thread_id>\d+))?$",
73
+ r"(?-u)^/subscriptions/(?<subscription_id>\d+)/plans(?:/(?<plan_id>\d+))?$",
74
+ r"(?-u)^/profiles/(?<profile_id>\d+)/settings$",
75
+ r"(?-u)^/organizations/(?<organization_id>\d+)/billing(?:/(?<billing_id>\d+))?$",
76
+ r"(?-u)^/vendors/(?<vendor_id>\d+)/products(?:/(?<product_id>\d+))?$",
77
+ r"(?-u)^/courses/(?<course_id>\d+)/modules(?:/(?<module_id>\d+))?$",
78
+ r"(?-u)^/accounts/(?<account_id>\d+)(?:/transactions/(?<transaction_id>\d+))?$",
79
+ r"(?-u)^/warehouses/(?<warehouse_id>\d+)/inventory(?:/(?<inventory_id>\d+))?$",
80
+ r"(?-u)^/campaigns/(?<campaign_id>\d+)/ads(?:/(?<ad_id>\d+))?$",
81
+ r"(?-u)^/applications/(?<application_id>\d+)/stages(?:/(?<stage_id>\d+))?$",
82
+ r"(?-u)^/notifications/(?<notification_id>\d+)$",
83
+ r"(?-u)^/albums/(?<album_id>\d+)/photos(?:/(?<photo_id>\d+))?$",
84
+ r"(?-u)^/news/(?<news_id>\d+)/articles(?:/(?<article_id>\d+))?$",
85
+ r"(?-u)^/libraries/(?<library_id>\d+)/books(?:/(?<book_id>\d+))?$",
86
+ r"(?-u)^/universities/(?<university_id>\d+)/students(?:/(?<student_id>\d+))?$",
87
+ r"(?-u)^/banks/(?<bank_id>\d+)/branches(?:/(?<branch_id>\d+))?$",
88
+ r"(?-u)^/vehicles/(?<vehicle_id>\d+)/services(?:/(?<service_id>\d+))?$",
89
+ r"(?-u)^/hotels/(?<hotel_id>\d+)/rooms(?:/(?<room_id>\d+))?$",
90
+ r"(?-u)^/doctors/(?<doctor_id>\d+)/appointments(?:/(?<appointment_id>\d+))?$",
91
+ r"(?-u)^/gyms/(?<gym_id>\d+)/memberships(?:/(?<membership_id>\d+))?$",
92
+ r"(?-u)^/restaurants/(?<restaurant_id>\d+)/menus(?:/(?<menu_id>\d+))?$",
93
+ r"(?-u)^/parks/(?<park_id>\d+)/events(?:/(?<event_id>\d+))?$",
94
+ r"(?-u)^/theaters/(?<theater_id>\d+)/shows(?:/(?<show_id>\d+))?$",
95
+ r"(?-u)^/museums/(?<museum_id>\d+)/exhibits(?:/(?<exhibit_id>\d+))?$",
96
+ r"(?-u)^/stadiums/(?<stadium_id>\d+)/games(?:/(?<game_id>\d+))?$",
97
+ r"(?-u)^/schools/(?<school_id>\d+)/classes(?:/(?<class_id>\d+))?$",
98
+ r"(?-u)^/clubs/(?<club_id>\d+)/events(?:/(?<event_id>\d+))?$",
99
+ r"(?-u)^/festivals/(?<festival_id>\d+)/tickets(?:/(?<ticket_id>\d+))?$",
100
+ ];
101
+ use std::sync::LazyLock;
102
+
103
+ static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| RegexSet::new(ROUTES).unwrap());
104
+ static REGEXES: LazyLock<Vec<Regex>> =
105
+ LazyLock::new(|| ROUTES.iter().map(|&r| Regex::new(r).unwrap()).collect());
106
+
107
+ fn match_route(input: String) -> Result<Option<(usize, Option<RHash>)>> {
108
+ if let Some(index) = REGEX_SET.matches(&input).iter().next() {
109
+ let regex = &REGEXES[index];
110
+ if let Some(captures) = regex.captures(&input) {
111
+ let params = RHash::with_capacity(captures.len());
112
+ for name in regex.capture_names().flatten() {
113
+ if let Some(value) = captures.name(name) {
114
+ params.aset(name, value.as_str()).ok();
115
+ }
116
+ }
117
+ return Ok(Some((index, Some(params))));
118
+ }
119
+ return Ok(Some((index, None)));
120
+ }
121
+ Ok(None)
122
+ }
123
+
56
124
  #[magnus::init]
57
125
  fn init(ruby: &Ruby) -> Result<()> {
58
126
  itsi_tracing::init();
@@ -61,6 +129,7 @@ fn init(ruby: &Ruby) -> Result<()> {
61
129
  .ok();
62
130
 
63
131
  let itsi = ruby.get_inner(&ITSI_MODULE);
132
+ itsi.define_singleton_method("match_route", function!(match_route, 1))?;
64
133
  itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
65
134
  itsi.define_singleton_method("log_info", function!(log_info, 1))?;
66
135
  itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
@@ -70,6 +139,7 @@ fn init(ruby: &Ruby) -> Result<()> {
70
139
  server.define_singleton_method("new", function!(Server::new, -1))?;
71
140
  server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
72
141
  server.define_method("start", method!(Server::start, 0))?;
142
+ server.define_method("stop", method!(Server::stop, 0))?;
73
143
 
74
144
  let request = ruby.get_inner(&ITSI_REQUEST);
75
145
  request.define_method("path", method!(ItsiRequest::path, 0))?;
@@ -85,6 +155,8 @@ fn init(ruby: &Ruby) -> Result<()> {
85
155
  request.define_method("port", method!(ItsiRequest::port, 0))?;
86
156
  request.define_method("body", method!(ItsiRequest::body, 0))?;
87
157
  request.define_method("response", method!(ItsiRequest::response, 0))?;
158
+ request.define_method("json?", method!(ItsiRequest::is_json, 0))?;
159
+ request.define_method("html?", method!(ItsiRequest::is_html, 0))?;
88
160
 
89
161
  let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
90
162
  body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
@@ -101,6 +173,8 @@ fn init(ruby: &Ruby) -> Result<()> {
101
173
  response.define_method("close_read", method!(ItsiResponse::close_read, 0))?;
102
174
  response.define_method("close", method!(ItsiResponse::close, 0))?;
103
175
  response.define_method("hijack", method!(ItsiResponse::hijack, 1))?;
176
+ response.define_method("json?", method!(ItsiResponse::is_json, 0))?;
177
+ response.define_method("html?", method!(ItsiResponse::is_html, 0))?;
104
178
 
105
179
  Ok(())
106
180
  }
@@ -6,17 +6,18 @@ use crate::{
6
6
  response::itsi_response::ItsiResponse,
7
7
  server::{
8
8
  itsi_server::{RequestJob, Server},
9
- listener::{SockAddr, TokioListener},
9
+ listener::{ListenerInfo, SockAddr},
10
10
  serve_strategy::single_mode::RunningPhase,
11
11
  },
12
12
  };
13
13
  use bytes::Bytes;
14
14
  use derive_more::Debug;
15
15
  use futures::StreamExt;
16
- use http::{request::Parts, Response, StatusCode};
16
+ use http::{request::Parts, HeaderValue, Response, StatusCode};
17
17
  use http_body_util::{combinators::BoxBody, BodyExt, Empty};
18
18
  use hyper::{body::Incoming, Request};
19
19
  use itsi_error::from::CLIENT_CONNECTION_CLOSED;
20
+ use itsi_rb_helpers::print_rb_backtrace;
20
21
  use itsi_tracing::{debug, error};
21
22
  use magnus::{
22
23
  error::{ErrorType, Result as MagnusResult},
@@ -33,7 +34,6 @@ use tokio::sync::{
33
34
  };
34
35
  static ID_CALL: LazyId = LazyId::new("call");
35
36
  static ID_MESSAGE: LazyId = LazyId::new("message");
36
- static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
37
37
 
38
38
  #[derive(Debug)]
39
39
  #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
@@ -44,11 +44,12 @@ pub struct ItsiRequest {
44
44
  pub remote_addr: String,
45
45
  pub version: String,
46
46
  #[debug(skip)]
47
- pub(crate) listener: Arc<TokioListener>,
47
+ pub(crate) listener: Arc<ListenerInfo>,
48
48
  #[debug(skip)]
49
49
  pub server: Arc<Server>,
50
50
  pub response: ItsiResponse,
51
51
  pub start: Instant,
52
+ pub content_type: String,
52
53
  }
53
54
 
54
55
  impl fmt::Display for ItsiRequest {
@@ -82,6 +83,14 @@ impl ItsiRequest {
82
83
  }
83
84
  }
84
85
 
86
+ pub fn is_json(&self) -> bool {
87
+ self.content_type.eq("application/json")
88
+ }
89
+
90
+ pub fn is_html(&self) -> bool {
91
+ self.content_type.eq("text/html")
92
+ }
93
+
85
94
  pub fn process(
86
95
  self,
87
96
  ruby: &Ruby,
@@ -106,14 +115,7 @@ impl ItsiRequest {
106
115
  debug!("Connection closed by client");
107
116
  response.close();
108
117
  } else if let Some(rb_err) = err.value() {
109
- let backtrace = rb_err
110
- .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
111
- .unwrap_or_default();
112
-
113
- error!("Error occurred in Handler: {:?}", rb_err);
114
- for line in backtrace {
115
- error!("{}", line);
116
- }
118
+ print_rb_backtrace(rb_err);
117
119
  response.internal_server_error(err.to_string());
118
120
  } else {
119
121
  response.internal_server_error(err.to_string());
@@ -128,7 +130,7 @@ impl ItsiRequest {
128
130
  hyper_request: Request<Incoming>,
129
131
  sender: async_channel::Sender<RequestJob>,
130
132
  server: Arc<Server>,
131
- listener: Arc<TokioListener>,
133
+ listener: Arc<ListenerInfo>,
132
134
  addr: SockAddr,
133
135
  shutdown_rx: watch::Receiver<RunningPhase>,
134
136
  ) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
@@ -153,7 +155,7 @@ impl ItsiRequest {
153
155
  request: Request<Incoming>,
154
156
  sock_addr: SockAddr,
155
157
  server: Arc<Server>,
156
- listener: Arc<TokioListener>,
158
+ listener: Arc<ListenerInfo>,
157
159
  ) -> (ItsiRequest, mpsc::Receiver<Option<Bytes>>) {
158
160
  let (parts, body) = request.into_parts();
159
161
  let body = if server.stream_body.is_some_and(|f| f) {
@@ -175,8 +177,27 @@ impl ItsiRequest {
175
177
  server,
176
178
  listener,
177
179
  version: format!("{:?}", &parts.version),
178
- response: ItsiResponse::new(parts.clone(), response_channel.0),
180
+ response: ItsiResponse::new(
181
+ parts.clone(),
182
+ response_channel.0,
183
+ parts
184
+ .headers
185
+ .get("Accept")
186
+ .unwrap_or(&HeaderValue::from_static("text/html"))
187
+ .to_str()
188
+ .unwrap()
189
+ .to_string(),
190
+ ),
179
191
  start: Instant::now(),
192
+ content_type: parts
193
+ .headers
194
+ .get("Content-Type")
195
+ .unwrap_or(&HeaderValue::from_static(
196
+ "application/x-www-form-urlencoded",
197
+ ))
198
+ .to_str()
199
+ .unwrap()
200
+ .to_string(),
180
201
  parts,
181
202
  },
182
203
  response_channel.1,
@@ -231,7 +252,7 @@ impl ItsiRequest {
231
252
  .uri
232
253
  .host()
233
254
  .map(|host| host.to_string())
234
- .unwrap_or_else(|| self.listener.host()))
255
+ .unwrap_or_else(|| self.listener.host.clone()))
235
256
  }
236
257
 
237
258
  pub(crate) fn scheme(&self) -> MagnusResult<String> {
@@ -240,7 +261,7 @@ impl ItsiRequest {
240
261
  .uri
241
262
  .scheme()
242
263
  .map(|scheme| scheme.to_string())
243
- .unwrap_or_else(|| self.listener.scheme()))
264
+ .unwrap_or_else(|| self.listener.scheme.clone()))
244
265
  }
245
266
 
246
267
  pub(crate) fn headers(&self) -> MagnusResult<Vec<(String, &str)>> {
@@ -264,7 +285,7 @@ impl ItsiRequest {
264
285
  }
265
286
 
266
287
  pub(crate) fn port(&self) -> MagnusResult<u16> {
267
- Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port()))
288
+ Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port))
268
289
  }
269
290
 
270
291
  pub(crate) fn body(&self) -> MagnusResult<Value> {
@@ -37,6 +37,7 @@ use crate::server::serve_strategy::single_mode::RunningPhase;
37
37
  #[derive(Debug, Clone)]
38
38
  pub struct ItsiResponse {
39
39
  pub data: Arc<ResponseData>,
40
+ pub accept: String,
40
41
  }
41
42
 
42
43
  #[derive(Debug)]
@@ -76,7 +77,6 @@ impl ItsiResponse {
76
77
  (ReceiverStream::new(receiver), shutdown_rx),
77
78
  |(mut receiver, mut shutdown_rx)| async move {
78
79
  if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
79
- warn!("Disconnecting streaming client.");
80
80
  return None;
81
81
  }
82
82
  loop {
@@ -279,7 +279,8 @@ impl ItsiResponse {
279
279
  if let Some(writer) = writer.write().as_ref() {
280
280
  writer
281
281
  .blocking_send(Some(frame))
282
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
282
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)
283
+ .ok();
283
284
  }
284
285
  Ok(0)
285
286
  }
@@ -293,11 +294,19 @@ impl ItsiResponse {
293
294
  Ok(true)
294
295
  }
295
296
 
297
+ pub fn is_html(&self) -> bool {
298
+ self.accept.starts_with("text/html")
299
+ }
300
+
301
+ pub fn is_json(&self) -> bool {
302
+ self.accept.starts_with("application/json")
303
+ }
304
+
296
305
  pub fn close_read(&self) -> MagnusResult<bool> {
297
- todo!();
306
+ Ok(true)
298
307
  }
299
308
 
300
- pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>) -> Self {
309
+ pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>, accept: String) -> Self {
301
310
  Self {
302
311
  data: Arc::new(ResponseData {
303
312
  response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
@@ -306,6 +315,7 @@ impl ItsiResponse {
306
315
  hijacked_socket: RwLock::new(None),
307
316
  parts,
308
317
  }),
318
+ accept,
309
319
  }
310
320
  }
311
321
 
@@ -9,6 +9,7 @@ use std::{
9
9
  path::PathBuf,
10
10
  str::FromStr,
11
11
  };
12
+
12
13
  #[derive(Debug, Clone)]
13
14
  pub enum BindAddress {
14
15
  Ip(IpAddr),
@@ -100,7 +101,7 @@ impl FromStr for Bind {
100
101
  "IPv6 addresses must use [ ] when specifying a port".to_owned(),
101
102
  ));
102
103
  } else {
103
- (h, None) // Treat as a hostname
104
+ (h, p.parse::<u16>().ok()) // Treat as a hostname
104
105
  }
105
106
  } else {
106
107
  (url, None)
@@ -109,18 +110,22 @@ impl FromStr for Bind {
109
110
  let address = if let Ok(ip) = host.parse::<IpAddr>() {
110
111
  BindAddress::Ip(ip)
111
112
  } else {
112
- resolve_hostname(host)
113
- .map(BindAddress::Ip)
114
- .ok_or(ItsiError::ArgumentError(format!(
115
- "Failed to resolve hostname {}",
116
- host
117
- )))?
113
+ match protocol {
114
+ BindProtocol::Https | BindProtocol::Http => resolve_hostname(host)
115
+ .map(BindAddress::Ip)
116
+ .ok_or(ItsiError::ArgumentError(format!(
117
+ "Failed to resolve hostname {}",
118
+ host
119
+ )))?,
120
+ BindProtocol::Unix | BindProtocol::Unixs => BindAddress::UnixSocket(host.into()),
121
+ }
118
122
  };
119
- let (port, address) = match protocol {
120
- BindProtocol::Http => (port.or(Some(80)), address),
121
- BindProtocol::Https => (port.or(Some(443)), address),
122
- BindProtocol::Unix => (None, BindAddress::UnixSocket(host.into())),
123
- BindProtocol::Unixs => (None, BindAddress::UnixSocket(host.into())),
123
+
124
+ let port = match protocol {
125
+ BindProtocol::Http => port.or(Some(80)),
126
+ BindProtocol::Https => port.or(Some(443)),
127
+ BindProtocol::Unix => None,
128
+ BindProtocol::Unixs => None,
124
129
  };
125
130
 
126
131
  let tls_config = match protocol {
@@ -129,13 +134,13 @@ impl FromStr for Bind {
129
134
  BindProtocol::Unix => None,
130
135
  BindProtocol::Unixs => Some(configure_tls(host, &options)?),
131
136
  };
132
-
133
- Ok(Self {
137
+ let bind = Self {
134
138
  address,
135
139
  port,
136
140
  protocol,
137
141
  tls_config,
138
- })
142
+ };
143
+ Ok(bind)
139
144
  }
140
145
  }
141
146