Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: Proper error types instead of String #1424

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 89 additions & 26 deletions src/sdl2/common.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,102 @@
use std::error::Error;
use std::fmt;
use std::ffi::{CString, NulError};
use std::{error, fmt, io};

/// A given integer was so big that its representation as a C integer would be
/// negative.
#[derive(Debug, Clone, PartialEq)]
pub enum IntegerOrSdlError {
IntegerOverflows(&'static str, u32),
SdlError(String),
#[derive(Debug)]
pub struct SdlError {
msg: String,
}

impl SdlError {
pub fn from_last_error() -> Self {
let msg = crate::get_error();
Self { msg }
}

pub(crate) fn from_string(msg: String) -> Self {
Self { msg }
}

pub fn message(&self) -> &str {
&self.msg
}
}

impl fmt::Display for SdlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.msg)
}
}

impl error::Error for SdlError {}

#[derive(Debug)]
pub enum Error {
Sdl(SdlError),
Io(io::Error),
/// An integer was larger than [`i32::MAX`] in a parameter, and it can't be converted to a C int
IntOverflow(&'static str, u32),
/// A null byte was found within a parameter, and it can't be sent to SDL
InvalidString(NulError, &'static str),
}

impl Error {
pub fn from_sdl_error() -> Self {
Self::Sdl(SdlError::from_last_error())
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Sdl(msg) => write!(f, "SDL error: {msg}"),
Self::Io(err) => write!(f, "IO error: {err}"),
Self::IntOverflow(name, value) => write!(f, "Integer '{name}' overflows: {value}"),
Self::InvalidString(name, nul) => write!(f, "Invalid string '{name}': {nul}"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the definition the first field is the NulError, not the name.

To prevent these bugs (in this crate but also user code using this crate) I advise to use a struct like variant instead of a tuple like one. This also adds context to the context-less &'static str field. I suggest to change the definition to InvalidString { nul_err: NulError, var_name: &'static str } (or something like this) and roughly the same for IntOverflow.

}
}
}

impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Sdl(..) | Self::IntOverflow(..) => None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source may also be returned for Self::Sdl

Self::Io(err) => Some(err),
Self::InvalidString(nul, _) => Some(nul),
}
}
}

impl From<SdlError> for Error {
fn from(value: SdlError) -> Self {
Self::Sdl(value)
}
}

impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}

pub fn validate_string(str: impl Into<Vec<u8>>, name: &'static str) -> Result<CString, Error> {
match CString::new(str) {
Ok(c) => Ok(c),
Err(nul) => Err(Error::InvalidString(nul, name)),
}
}

/// Validates and converts the given u32 to a positive C integer.
pub fn validate_int(value: u32, name: &'static str) -> Result<::libc::c_int, IntegerOrSdlError> {
use self::IntegerOrSdlError::*;
pub fn validate_int(value: u32, name: &'static str) -> Result<libc::c_int, Error> {
// Many SDL functions will accept `int` values, even if it doesn't make sense
// for the values to be negative.
// In the cases that SDL doesn't check negativity, passing negative values
// could be unsafe.
// For example, `SDL_JoystickGetButton` uses the index argument to access an
// array without checking if it's negative, which could potentially lead to
// segmentation faults.
if value >= 1 << 31 {
Err(IntegerOverflows(name, value))
if value > libc::c_int::MAX as u32 {
Err(Error::IntOverflow(name, value))
} else {
Ok(value as ::libc::c_int)
}
}

impl fmt::Display for IntegerOrSdlError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::IntegerOrSdlError::*;

match *self {
IntegerOverflows(name, value) => write!(f, "Integer '{}' overflows ({})", name, value),
SdlError(ref e) => write!(f, "SDL error: {}", e),
}
Ok(value as libc::c_int)
}
}

impl Error for IntegerOrSdlError {}
45 changes: 20 additions & 25 deletions src/sdl2/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ use crate::sensor::SensorType;
#[cfg(feature = "hidapi")]
use std::convert::TryInto;

use crate::common::{validate_int, IntegerOrSdlError};
use crate::get_error;
use crate::common::validate_int;
use crate::joystick;
use crate::GameControllerSubsystem;
use crate::{get_error, Error};
use std::mem::transmute;

use crate::sys;
Expand Down Expand Up @@ -78,13 +78,12 @@ impl GameControllerSubsystem {
/// Controller IDs are the same as joystick IDs and the maximum number can
/// be retrieved using the `SDL_NumJoysticks` function.
#[doc(alias = "SDL_GameControllerOpen")]
pub fn open(&self, joystick_index: u32) -> Result<GameController, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
pub fn open(&self, joystick_index: u32) -> Result<GameController, Error> {
let joystick_index = validate_int(joystick_index, "joystick_index")?;
let controller = unsafe { sys::SDL_GameControllerOpen(joystick_index) };

if controller.is_null() {
Err(SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(GameController {
subsystem: self.clone(),
Expand All @@ -95,13 +94,12 @@ impl GameControllerSubsystem {

/// Return the name of the controller at index `joystick_index`.
#[doc(alias = "SDL_GameControllerNameForIndex")]
pub fn name_for_index(&self, joystick_index: u32) -> Result<String, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
pub fn name_for_index(&self, joystick_index: u32) -> Result<String, Error> {
let joystick_index = validate_int(joystick_index, "joystick_index")?;
let c_str = unsafe { sys::SDL_GameControllerNameForIndex(joystick_index) };

if c_str.is_null() {
Err(SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(unsafe {
CStr::from_ptr(c_str as *const _)
Expand Down Expand Up @@ -170,9 +168,10 @@ impl GameControllerSubsystem {
use self::AddMappingError::*;

let result = unsafe { sys::SDL_GameControllerAddMappingsFromRW(rw.raw(), 0) };
match result {
-1 => Err(SdlError(get_error())),
_ => Ok(result),
if result == -1 {
Err(SdlError(get_error()))
} else {
Ok(result)
}
}

Expand Down Expand Up @@ -509,7 +508,7 @@ impl GameController {
low_frequency_rumble: u16,
high_frequency_rumble: u16,
duration_ms: u32,
) -> Result<(), IntegerOrSdlError> {
) -> Result<(), Error> {
let result = unsafe {
sys::SDL_GameControllerRumble(
self.raw,
Expand All @@ -520,7 +519,7 @@ impl GameController {
};

if result != 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(())
}
Expand All @@ -533,13 +532,13 @@ impl GameController {
left_rumble: u16,
right_rumble: u16,
duration_ms: u32,
) -> Result<(), IntegerOrSdlError> {
) -> Result<(), Error> {
let result = unsafe {
sys::SDL_GameControllerRumbleTriggers(self.raw, left_rumble, right_rumble, duration_ms)
};

if result != 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(())
}
Expand Down Expand Up @@ -580,11 +579,11 @@ impl GameController {

/// Update a game controller's LED color.
#[doc(alias = "SDL_GameControllerSetLED")]
pub fn set_led(&mut self, red: u8, green: u8, blue: u8) -> Result<(), IntegerOrSdlError> {
pub fn set_led(&mut self, red: u8, green: u8, blue: u8) -> Result<(), Error> {
let result = unsafe { sys::SDL_GameControllerSetLED(self.raw, red, green, blue) };

if result != 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(())
}
Expand Down Expand Up @@ -637,7 +636,7 @@ impl GameController {
&self,
sensor_type: crate::sensor::SensorType,
enabled: bool,
) -> Result<(), IntegerOrSdlError> {
) -> Result<(), Error> {
let result = unsafe {
sys::SDL_GameControllerSetSensorEnabled(
self.raw,
Expand All @@ -651,7 +650,7 @@ impl GameController {
};

if result != 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(())
}
Expand All @@ -668,11 +667,7 @@ impl GameController {
/// The number of data points depends on the sensor. Both Gyroscope and
/// Accelerometer return 3 values, one for each axis.
#[doc(alias = "SDL_GameControllerGetSensorData")]
pub fn sensor_get_data(
&self,
sensor_type: SensorType,
data: &mut [f32],
) -> Result<(), IntegerOrSdlError> {
pub fn sensor_get_data(&self, sensor_type: SensorType, data: &mut [f32]) -> Result<(), Error> {
let result = unsafe {
sys::SDL_GameControllerGetSensorData(
self.raw,
Expand All @@ -683,7 +678,7 @@ impl GameController {
};

if result != 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
Ok(())
}
Expand Down
67 changes: 13 additions & 54 deletions src/sdl2/filesystem.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use crate::get_error;
use libc::c_char;
use crate::{get_error, Error};
use libc::c_void;
use std::error;
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::ffi::CStr;

use crate::sys;

Expand All @@ -23,58 +20,20 @@ pub fn base_path() -> Result<String, String> {
}
}

#[derive(Debug, Clone)]
pub enum PrefPathError {
InvalidOrganizationName(NulError),
InvalidApplicationName(NulError),
SdlError(String),
}

impl fmt::Display for PrefPathError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::PrefPathError::*;

match *self {
InvalidOrganizationName(ref e) => write!(f, "Invalid organization name: {}", e),
InvalidApplicationName(ref e) => write!(f, "Invalid application name: {}", e),
SdlError(ref e) => write!(f, "SDL error: {}", e),
}
}
}

impl error::Error for PrefPathError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::InvalidOrganizationName(err) => Some(err),
Self::InvalidApplicationName(err) => Some(err),
Self::SdlError(_) => None,
}
}
}

// TODO: Change to OsStr or something?
/// Return the preferred directory for the application to write files on this
/// system, based on the given organization and application name.
#[doc(alias = "SDL_GetPrefPath")]
pub fn pref_path(org_name: &str, app_name: &str) -> Result<String, PrefPathError> {
use self::PrefPathError::*;
let result = unsafe {
let org = match CString::new(org_name) {
Ok(s) => s,
Err(err) => return Err(InvalidOrganizationName(err)),
};
let app = match CString::new(app_name) {
Ok(s) => s,
Err(err) => return Err(InvalidApplicationName(err)),
};
let buf =
sys::SDL_GetPrefPath(org.as_ptr() as *const c_char, app.as_ptr() as *const c_char);
CStr::from_ptr(buf as *const _).to_str().unwrap().to_owned()
};

if result.is_empty() {
Err(SdlError(get_error()))
} else {
Ok(result)
pub fn pref_path(org_name: &str, app_name: &str) -> Result<String, Error> {
unsafe {
let buf = sys::SDL_GetPrefPath(
as_cstring!(org_name)?.as_ptr(),
as_cstring!(app_name)?.as_ptr(),
);
if buf.is_null() {
Err(Error::from_sdl_error())
} else {
Ok(CStr::from_ptr(buf as *const _).to_str().unwrap().to_owned())
}
}
}
9 changes: 4 additions & 5 deletions src/sdl2/haptic.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
//! Haptic Functions
use crate::sys;

use crate::common::{validate_int, IntegerOrSdlError};
use crate::get_error;
use crate::common::validate_int;
use crate::Error;
use crate::HapticSubsystem;

impl HapticSubsystem {
/// Attempt to open the joystick at index `joystick_index` and return its haptic device.
#[doc(alias = "SDL_JoystickOpen")]
pub fn open_from_joystick_id(&self, joystick_index: u32) -> Result<Haptic, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
pub fn open_from_joystick_id(&self, joystick_index: u32) -> Result<Haptic, Error> {
let joystick_index = validate_int(joystick_index, "joystick_index")?;

let haptic = unsafe {
Expand All @@ -18,7 +17,7 @@ impl HapticSubsystem {
};

if haptic.is_null() {
Err(SdlError(get_error()))
Err(Error::from_sdl_error())
} else {
unsafe { sys::SDL_HapticRumbleInit(haptic) };
Ok(Haptic {
Expand Down
Loading