Skip to content

Commit

Permalink
feat: async_graphql support
Browse files Browse the repository at this point in the history
  • Loading branch information
meskill committed Jul 3, 2022
1 parent 0b8123c commit fea4613
Show file tree
Hide file tree
Showing 8 changed files with 768 additions and 5 deletions.
484 changes: 484 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ maintenance = { status = "passively-maintained" }

[package.metadata.docs.rs]
default-target = "x86_64-pc-windows-gnu"
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
async-graphql = { version="4.0.1", optional=true }
custom_error = "1.9.2"
libloading = "0.7.3"
oaidl = "0.2.1"
Expand All @@ -33,6 +36,7 @@ copy_dir = "0.1.2"

[features]
serde = ["dep:serde"]
async-graphql = ["dep:async-graphql"]

[[example]]
name = "serde_serialization"
Expand Down
22 changes: 22 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(docsrs, feature(doc_cfg))]

//! Rust SDK wrapper for the [Mystic Light SDK](https://www.msi.com/Landing/mystic-light-rgb-gaming-pc/download)
//!
//! # Requirements
Expand Down Expand Up @@ -55,6 +57,26 @@
//!
//! Enables [serde](https://crates.io/crates/serde) serialization/deserialization for some of the sdk structs
//!
//! ## async-graphql
//!
//! Enables [async-graphql](https://crates.io/crates/async-graphql) support for sdk entities
//!
//! When this feature is enabled you can use [MysticLightSDK] as async_graphql::Query and [MysticLightSDKMutation] as async_graphql::Mutation
//!
//! ```
//! use async_graphql::{EmptySubscription, Schema};
//! use mystic_light_sdk::{MysticLightSDK, MysticLightSDKMutation};
//!
//! pub type MysticLightSchema = Schema<MysticLightSDK, MysticLightSDKMutation, EmptySubscription>;
//!
//! pub fn create_qraphql_schema(sdk: MysticLightSDK) -> MysticLightSchema {
//! let mutation = MysticLightSDKMutation(sdk.clone());
//!
//! Schema::build(sdk, mutation, EmptySubscription).finish()
//! }
//!
//! ```
//!
//! # Troubleshooting
//!
//! ## Timeout error on initialization
Expand Down
5 changes: 5 additions & 0 deletions src/sdk/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ pub type SingleColor = u32;
/// Represent RGB color
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "async-graphql",
derive(async_graphql::SimpleObject, async_graphql::InputObject),
graphql(input_name = "ColorInput")
)]
pub struct Color {
pub red: SingleColor,
pub green: SingleColor,
Expand Down
79 changes: 77 additions & 2 deletions src/sdk/device.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
use std::fmt::Debug;
use std::rc::Rc;
use std::sync::{Arc, Mutex};

#[cfg(feature = "async-graphql")]
use crate::DeviceLedMutation;

use super::led::DeviceLed;
use super::types::Result;
use super::types::{Filter, Result};
use libloading::Library;

/// used for filtering device's leds.
/// Currently, supports only filtering by name
#[derive(Default)]
#[cfg_attr(feature = "async-graphql", derive(async_graphql::InputObject))]
struct DeviceLedFilter {
names: Option<Vec<String>>,
}

impl Filter<&DeviceLed> for DeviceLedFilter {
fn predicate(&self, led: &DeviceLed) -> bool {
match &self.names {
Some(names) => {
if names.is_empty() {
return true;
}

names.iter().any(|name| name == led.name())
}
None => true,
}
}
}

/// Represents single hardware MysticLight Device
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Device {
Expand All @@ -16,6 +42,44 @@ pub struct Device {
led_count: u32,
}

/// Represents single hardware MysticLight Device
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl Device {
#[graphql(name = "name")]
async fn async_graphql_name(&self) -> &str {
self.name()
}

#[graphql(name = "leds")]
async fn async_graphql_leds(
&self,
#[graphql(default)] filter: DeviceLedFilter,
) -> Result<Vec<DeviceLed>> {
self.leds_with_filter(filter)
}
}

/// Mutation wrapper for a device
#[cfg(feature = "async-graphql")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-graphql")))]
pub struct DeviceMutation(pub Device);

/// Mutation wrapper for a device
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl DeviceMutation {
async fn leds(
&self,
ctx: &async_graphql::Context<'_>,
#[graphql(default)] filter: DeviceLedFilter,
) -> Result<Vec<DeviceLedMutation>> {
let leds = self.0.async_graphql_leds(ctx, filter).await?;

Ok(leds.into_iter().map(DeviceLedMutation::new).collect())
}
}

impl Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
Expand All @@ -40,9 +104,20 @@ impl Device {

/// returns vec of device's leds
pub fn leds(&self) -> Result<Vec<DeviceLed>> {
self.leds_with_filter(DeviceLedFilter::default())
}

pub fn leds_with_filter<F>(&self, filter: F) -> Result<Vec<DeviceLed>>
where
F: for<'a> Filter<&'a DeviceLed>,
{
let leds = (0..self.led_count)
.into_iter()
.map(|led_index| DeviceLed::new(Arc::clone(&self.library), &self.name, led_index))
.filter(|led| match led {
Ok(led) => filter.predicate(led),
Err(_) => true,
})
.collect::<Result<Vec<_>>>()?;

Ok(leds)
Expand Down
101 changes: 101 additions & 0 deletions src/sdk/led.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::collections::HashSet;
use std::fmt::Debug;
use std::ptr::null_mut;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use libloading::{Library, Symbol};

Expand All @@ -19,6 +21,7 @@ use super::{CommonError, LedStyles, MysticLightSDKError};
/// Represents state of the single led
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "async-graphql", derive(async_graphql::SimpleObject))]
pub struct DeviceLedState {
/// current style of the led
pub style: String,
Expand All @@ -30,6 +33,46 @@ pub struct DeviceLedState {
pub speed: u32,
}

/// Represents state of the single led
#[cfg(feature = "async-graphql")]
#[cfg_attr(feature = "async-graphql", derive(async_graphql::InputObject))]
#[cfg_attr(docsrs, doc(cfg(feature = "async-graphql")))]
pub struct DeviceLedStateInput {
/// current style of the led
pub style: Option<String>,
/// current color of the led (some of the styles do not support this, so there will be fake data in this case)
pub color: Option<Color>,
/// current brightness of the led (some of the styles do not support this, so there will be fake data in this case)
pub bright: Option<u32>,
/// current speed of the led (some of the styles do not support this, so there will be fake data in this case)
pub speed: Option<u32>,
}

#[cfg(feature = "async-graphql")]
impl DeviceLedStateInput {
pub fn merge_with_state(self, current_state: DeviceLedState) -> DeviceLedState {
let mut state = DeviceLedState { ..current_state };

if let Some(style) = self.style {
state.style = style;
}

if let Some(color) = self.color {
state.color = color;
}

if let Some(bright) = self.bright {
state.bright = bright;
}

if let Some(speed) = self.speed {
state.speed = speed;
}

state
}
}

/// Represents single led of the device
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct DeviceLed {
Expand All @@ -50,6 +93,64 @@ pub struct DeviceLed {
led_index: u32,
}

/// Represents single led of the device
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl DeviceLed {
#[graphql(name = "name")]
async fn async_graphql_name(&self) -> &str {
self.name()
}

#[graphql(name = "supportedStyles")]
async fn async_graphql_supported_styles(&self) -> &HashSet<String> {
self.supported_styles()
}

#[graphql(name = "maxBright")]
async fn async_graphql_max_bright(&self) -> u32 {
self.max_bright()
}

#[graphql(name = "maxSpeed")]
async fn async_graphql_max_speed(&self) -> u32 {
self.max_speed()
}

#[graphql(name = "state")]
async fn async_graphql_get_state(&self) -> Result<DeviceLedState> {
self.get_state()
}
}

/// Mutation wrapper for a device led
#[cfg(feature = "async-graphql")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-graphql")))]
pub struct DeviceLedMutation(Mutex<DeviceLed>);

#[cfg(feature = "async-graphql")]
impl DeviceLedMutation {
pub fn new(device_led: DeviceLed) -> Self {
Self(Mutex::new(device_led))
}
}

/// Mutation wrapper for a device led
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl DeviceLedMutation {
async fn set_state(&self, state: DeviceLedStateInput) -> Result<DeviceLedState> {
let mut led = self.0.lock()?;
let current_state = led.get_state()?;

let new_state = state.merge_with_state(current_state);

led.set_state(&new_state)?;

Ok(new_state)
}
}

impl Debug for DeviceLed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceLed")
Expand Down
73 changes: 70 additions & 3 deletions src/sdk/mystic_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,72 @@ use std::{

use libloading::{Library, Symbol};

#[cfg(feature = "async-graphql")]
use crate::DeviceMutation;
use crate::{winapi::FromSafeArray, DeviceTypes, LedCounts, MysticLightSdkResult};

use super::{device::Device, error::MysticLightSDKError, types::Result};
use super::{
device::Device,
error::MysticLightSDKError,
types::{Filter, Result},
};

/// used for filtering devices.
/// Currently, supports only filtering by name
#[derive(Default)]
#[cfg_attr(feature = "async-graphql", derive(async_graphql::InputObject))]
struct DeviceFilter {
names: Option<Vec<String>>,
}

impl Filter<&str> for DeviceFilter {
fn predicate(&self, device_name: &str) -> bool {
match &self.names {
Some(names) => {
if names.is_empty() {
return true;
}

names.iter().any(|name| name == device_name)
}
None => true,
}
}
}

/// Rust Wrapper for the underlying Mystic Light SDK
#[derive(Clone)]
pub struct MysticLightSDK {
library: Rc<Library>,
library: Arc<Mutex<Library>>,
}

/// Rust Wrapper for the underlying Mystic Light SDK
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl MysticLightSDK {
async fn devices(&self, #[graphql(default)] filter: DeviceFilter) -> Result<Vec<Device>> {
self.get_devices_with_filter(filter)
}
}

/// Mutation wrapper for sdk
#[cfg(feature = "async-graphql")]
#[cfg_attr(docsrs, doc(cfg(feature = "async-graphql")))]
pub struct MysticLightSDKMutation(pub MysticLightSDK);

/// Mutation wrapper for sdk
#[cfg(feature = "async-graphql")]
#[async_graphql::Object]
impl MysticLightSDKMutation {
async fn devices(
&self,
ctx: &async_graphql::Context<'_>,
#[graphql(default)] filter: DeviceFilter,
) -> Result<Vec<DeviceMutation>> {
let devices = self.0.devices(ctx, filter).await?;

Ok(devices.into_iter().map(DeviceMutation).collect())
}
}

impl MysticLightSDK {
Expand Down Expand Up @@ -52,8 +111,15 @@ impl MysticLightSDK {
})
}

/// Return list of the currently available devices
pub fn get_devices(&self) -> Result<Vec<Device>> {
self.get_devices_with_filter(DeviceFilter::default())
}

/// Return list of the currently available devices
pub fn get_devices_with_filter<F>(&self, filter: F) -> Result<Vec<Device>>
where
F: for<'a> Filter<&'a str>,
{
let mut dev_type: DeviceTypes = null_mut();
let mut led_count: LedCounts = null_mut();

Expand All @@ -79,6 +145,7 @@ impl MysticLightSDK {
Ok(devices
.into_iter()
.zip(leds)
.filter(|(device_name, _)| filter.predicate(device_name))
.map(|(device_name, led_count)| {
let led_count: u32 = led_count.parse().expect("Cannot parse led count str");

Expand Down
Loading

0 comments on commit fea4613

Please sign in to comment.