Skip to content

Commit 2da1886

Browse files
committed
Use enum_dispatch instead of dyn traits
1 parent 4e773cc commit 2da1886

File tree

13 files changed

+106
-67
lines changed

13 files changed

+106
-67
lines changed

Cargo.lock

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
pkgs.rustc
1515
# pkgs.rustup
1616
pkgs.rustfmt
17+
pkgs.rust-analyzer
1718
pkgs.cargo-edit
1819
pkgs.go-task
1920
# needed for linker to work with tokio

integrations/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ shellexpand = "2.1.0"
1919
reqwest = { version = "0.11", default-features = false, features = [ "json", "rustls-tls" ] }
2020
tracing = "0.1.35"
2121
url = { version = "2.2.2", features = ["serde"] }
22+
enum_dispatch = "0.3.8"

integrations/src/integrations/airplay.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ pub struct IntegrationConfig {
4343

4444
#[async_trait]
4545
impl integration::IntegrationConfig for IntegrationConfig {
46-
async fn to_integration(&self, name: Option<String>) -> integration::IntegrationResult {
46+
async fn into_integration(&self, name: Option<String>) -> integration::IntegrationResult {
4747
let i = Integration::new(
4848
name.unwrap_or("airplay".to_string()),
4949
&self.api_endpoint,
5050
&self.devices,
5151
)?;
52-
return Ok(Box::new(i));
52+
return Ok(i.into());
5353
}
5454
}
5555

integrations/src/integrations/homebridge.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct IntegrationConfig {
1717

1818
#[async_trait]
1919
impl integration::IntegrationConfig for IntegrationConfig {
20-
async fn to_integration(&self, name: Option<String>) -> integration::IntegrationResult {
20+
async fn into_integration(&self, name: Option<String>) -> integration::IntegrationResult {
2121
let username = shellexpand::env(&self.username)?.to_string();
2222
let password = shellexpand::env(&self.password)?.to_string();
2323

@@ -28,7 +28,7 @@ impl integration::IntegrationConfig for IntegrationConfig {
2828
&password,
2929
)
3030
.await?;
31-
return Ok(Box::new(i));
31+
return Ok(i.into());
3232
}
3333
}
3434

integrations/src/integrations/http.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ pub struct IntegrationConfig {}
88

99
#[async_trait]
1010
impl integration::IntegrationConfig for IntegrationConfig {
11-
async fn to_integration(&self, name: Option<String>) -> integration::IntegrationResult {
12-
return Ok(Box::new(Integration::new(
13-
name.unwrap_or("http".to_string()),
14-
)));
11+
async fn into_integration(&self, name: Option<String>) -> integration::IntegrationResult {
12+
let integration = Integration::new(name.unwrap_or("http".to_string()));
13+
return Ok(integration.into());
1514
}
1615
}
1716

integrations/src/integrations/hue.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ pub struct IntegrationConfig {
1515

1616
#[async_trait]
1717
impl integration::IntegrationConfig for IntegrationConfig {
18-
async fn to_integration(&self, name: Option<String>) -> integration::IntegrationResult {
18+
async fn into_integration(&self, name: Option<String>) -> integration::IntegrationResult {
1919
let auth = shellexpand::env(&self.auth)?.to_string();
2020
let i = Integration::new(name.unwrap_or("hue".to_string()).as_ref(), &auth).await?;
21-
return Ok(Box::new(i));
21+
return Ok(i.into());
2222
}
2323
}
2424

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,50 @@
1+
use crate::integrations::IntegrationEnum;
2+
13
use anyhow::Result;
24
use async_trait::async_trait;
5+
use enum_dispatch::enum_dispatch;
36

4-
pub type IntegrationResult = Result<Box<dyn Integration + Send + Sync>>;
7+
pub type IntegrationResult = Result<IntegrationEnum>;
58

9+
// IntegrationConfig is implemented by the integration, to convert some configuration (on the struct) to an integration in the ingegration enum
610
#[async_trait]
711
pub trait IntegrationConfig {
8-
async fn to_integration(&self, name: Option<String>) -> IntegrationResult;
12+
async fn into_integration(&self, name: Option<String>) -> IntegrationResult;
913
}
1014

15+
// Intregration is the core logic of an integration
1116
#[async_trait]
17+
#[enum_dispatch]
1218
pub trait Integration {
1319
fn name(&self) -> &str;
14-
// fn actions(&self) -> Vec<&str>;
1520
async fn execute_action(&self, action: String, options: serde_json::value::Value)
1621
-> Result<()>;
22+
}
23+
24+
// IntoIntegration is a helper trait for converting an integration into an integration result
25+
// can't use the normal into trait since converting to an integration can fail, so this returns a result
26+
// I think this can be try_into trait but not sure how to convert it
27+
#[async_trait]
28+
#[enum_dispatch]
29+
pub trait IntoIntegration {
30+
async fn into_integration(&self) -> IntegrationResult;
31+
}
32+
33+
// IntegrationConfiguration implements IntoIntegration, by calling IntegrationConfig, with the embeded name
34+
// This struct is used for the configuration of integrations, and is what is exposed to the end user
35+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
36+
pub struct IntegrationConfiguration<T: IntegrationConfig> {
37+
name: Option<String>,
38+
#[serde(flatten)]
39+
options: T,
40+
}
1741

18-
// fn resync(&self);
42+
#[async_trait]
43+
impl<T> IntoIntegration for IntegrationConfiguration<T>
44+
where
45+
T: IntegrationConfig + Sync + Send,
46+
{
47+
async fn into_integration(&self) -> IntegrationResult {
48+
return self.options.into_integration(self.name.clone()).await;
49+
}
1950
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::integrations::{airplay, homebridge, http, hue};
2+
use crate::integrations::{
3+
Integration, IntegrationConfiguration, IntegrationResult, IntoIntegration,
4+
};
5+
6+
use anyhow::Result;
7+
use enum_dispatch::enum_dispatch;
8+
9+
#[enum_dispatch(Integration)]
10+
pub enum IntegrationEnum {
11+
Hue(hue::Integration),
12+
Homebridge(homebridge::Integration),
13+
Http(http::Integration),
14+
Airplay(airplay::Integration),
15+
}
16+
17+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
18+
#[serde(tag = "type")]
19+
#[serde(rename_all = "snake_case")]
20+
#[enum_dispatch(IntoIntegration)]
21+
pub enum IntegrationsConfigurationEnum {
22+
Hue(IntegrationConfiguration<hue::IntegrationConfig>),
23+
Homebridge(IntegrationConfiguration<homebridge::IntegrationConfig>),
24+
Airplay(IntegrationConfiguration<airplay::IntegrationConfig>),
25+
Http(IntegrationConfiguration<http::IntegrationConfig>),
26+
}
27+
28+
impl std::fmt::Display for IntegrationsConfigurationEnum {
29+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
30+
match self {
31+
IntegrationsConfigurationEnum::Hue(_) => write!(f, "hue"),
32+
IntegrationsConfigurationEnum::Homebridge(_) => write!(f, "homebridge"),
33+
IntegrationsConfigurationEnum::Airplay(_) => write!(f, "airplay"),
34+
IntegrationsConfigurationEnum::Http(_) => write!(f, "http"),
35+
}
36+
}
37+
}

integrations/src/integrations/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pub mod homebridge;
33
pub mod http;
44
pub mod hue;
55
mod integration;
6+
mod integration_enum;
67
pub(crate) mod utils;
78

89
pub use integration::*;
10+
pub use integration_enum::*;

server/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ reqwest = { version = "0.11", default-features = false, features = [ "json", "ru
2525
shellexpand = "2.1.0"
2626
tracing = "0.1.35"
2727
tracing-subscriber = "0.3.14"
28+
enum_dispatch = "0.3.8"

server/src/main.rs

+5-51
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::{anyhow, Result};
2-
use integrations::Integration;
2+
use integrations::{Integration, IntegrationEnum, IntegrationsConfigurationEnum, IntoIntegration};
33
use sdc_core::types::{Actions, ExecuteActionReq, Profiles};
44
use std::collections::HashMap;
55
use std::env;
@@ -15,55 +15,10 @@ mod ws_api;
1515

1616
const ACTION_SPLIT_CHARS: [char; 2] = [':', ':'];
1717

18-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
19-
struct IntegrationConfiguration<T: integrations::IntegrationConfig> {
20-
name: Option<String>,
21-
#[serde(flatten)]
22-
options: T,
23-
}
24-
25-
impl<T: integrations::IntegrationConfig> IntegrationConfiguration<T> {
26-
async fn as_integration(&self) -> integrations::IntegrationResult {
27-
return self.options.to_integration(self.name.clone()).await;
28-
}
29-
}
30-
31-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
32-
#[serde(tag = "type")]
33-
#[serde(rename_all = "snake_case")]
34-
enum IntegrationsConfiguration {
35-
Hue(IntegrationConfiguration<integrations::hue::IntegrationConfig>),
36-
Homebridge(IntegrationConfiguration<integrations::integrations::homebridge::IntegrationConfig>),
37-
Airplay(IntegrationConfiguration<integrations::airplay::IntegrationConfig>),
38-
Http(IntegrationConfiguration<integrations::http::IntegrationConfig>),
39-
}
40-
41-
impl IntegrationsConfiguration {
42-
async fn as_integration(&self) -> integrations::IntegrationResult {
43-
match self {
44-
IntegrationsConfiguration::Hue(c) => c.as_integration().await,
45-
IntegrationsConfiguration::Homebridge(c) => c.as_integration().await,
46-
IntegrationsConfiguration::Airplay(c) => c.as_integration().await,
47-
IntegrationsConfiguration::Http(c) => c.as_integration().await,
48-
}
49-
}
50-
}
51-
52-
impl std::fmt::Display for IntegrationsConfiguration {
53-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54-
match self {
55-
IntegrationsConfiguration::Hue(_) => write!(f, "hue"),
56-
IntegrationsConfiguration::Homebridge(_) => write!(f, "homebridge"),
57-
IntegrationsConfiguration::Airplay(_) => write!(f, "airplay"),
58-
IntegrationsConfiguration::Http(_) => write!(f, "http"),
59-
}
60-
}
61-
}
62-
6318
#[derive(Debug, serde::Serialize, serde::Deserialize)]
6419
#[serde(rename_all = "snake_case")]
6520
pub struct Config {
66-
integrations: Vec<IntegrationsConfiguration>,
21+
integrations: Vec<IntegrationsConfigurationEnum>,
6722
profiles: Profiles,
6823
}
6924

@@ -132,7 +87,7 @@ async fn populat_image_cache(config_ref: Arc<Config>, image_cache: ws_api::Image
13287
}
13388

13489
struct IntegrationManager {
135-
integrations: HashMap<String, Box<dyn Integration + Send + Sync>>,
90+
integrations: HashMap<String, IntegrationEnum>,
13691
rx: Receiver<ExecuteActionReq>,
13792
ws_clients: ws_api::Clients,
13893
}
@@ -155,7 +110,7 @@ impl IntegrationManager {
155110
integration = integration.to_string(),
156111
"setting up integration"
157112
);
158-
let i = integration.as_integration().await.map_err(|err| {
113+
let i = integration.into_integration().await.map_err(|err| {
159114
error!(?integration, error = ?err, "failed to create integration");
160115
anyhow!(
161116
"failed to create integration {:?} with {:?}",
@@ -175,7 +130,7 @@ impl IntegrationManager {
175130
return Ok((manager, tx));
176131
}
177132

178-
fn add_integration(&mut self, integration: Box<dyn Integration + Send + Sync>) {
133+
fn add_integration(&mut self, integration: IntegrationEnum) {
179134
self.integrations
180135
.insert(integration.name().to_string(), integration);
181136
}
@@ -238,7 +193,6 @@ impl IntegrationManager {
238193
match integration_option {
239194
Some(integration) => {
240195
integration
241-
.as_ref()
242196
.execute_action(action_name.to_string(), options)
243197
.await?;
244198
}

server/src/ws_api.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use futures_util::StreamExt;
66
use image::{self, Pixel};
77
use sdc_core::types::{ExecuteActionReq, ProfileButtonPressed, SetButtonUI, WsActions};
88
use std::collections::HashMap;
9-
use std::ops::ControlFlow;
109
use std::sync::Arc;
1110
use tokio::sync::mpsc;
1211
use tokio::sync::mpsc::UnboundedSender;
@@ -156,7 +155,7 @@ async fn handle_msg(
156155
Some(id),
157156
)
158157
.await
159-
.map_err(|e| anyhow!("button pressed handler rejected the request"))?;
158+
.map_err(|err| anyhow!("button pressed handler rejected the request: {:?}", err))?;
160159

161160
// todo: this is pretty silly, since every button press will trigger a full ui resyn, really
162161
// this should be smart and only resync if there are changes

0 commit comments

Comments
 (0)