bigml 1.0.0

Unofficial BigML client bindings
Documentation
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

use crate::resource::status::*;

/// Execution-specific status information.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct ExecutionStatus {
    /// Status code.
    pub code: StatusCode,

    /// Human-readable status message.
    pub message: String,

    /// Number of milliseconds which were needed to create this resource.
    pub elapsed: Option<u64>,

    /// Number between 0.0 and 1.0 representing the progress of creating
    /// this resource.
    pub progress: Option<f32>,

    /// The call stack, if one is present.
    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        with = "call_stack_repr"
    )]
    pub call_stack: Option<Vec<Option<SourceLocation>>>,

    /// The cause of the error.
    pub cause: Option<Cause>,

    /// The number of milliseconds elapsed during different phases of execution.
    #[serde(default)]
    pub elapsed_times: HashMap<String, u64>,

    /// Extra information about errors, typically mapping field names to
    /// field-specific error messages.
    #[serde(default)]
    pub extra: HashMap<String, String>,

    /// The instruction at which an error occurred.
    pub instruction: Option<Instruction>,

    /// (Undocumented) Where are we in the script's execution? This is
    /// particularly useful when an error occurs.
    pub source_location: Option<SourceLocation>,
}

impl ExecutionStatus {
    /// The `message` for this status, plus the `cause` and any other useful
    /// information that might be present.
    pub fn full_message(&self) -> String {
        if let Some(ref cause) = self.cause {
            format!("{} ({})", self.message, cause)
        } else {
            self.message.clone()
        }
    }
}

impl Status for ExecutionStatus {
    fn code(&self) -> StatusCode {
        self.code
    }

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

    fn elapsed(&self) -> Option<u64> {
        self.elapsed
    }

    fn progress(&self) -> Option<f32> {
        self.progress
    }
}

/// Functions for (de)serializing WhizzML call stacks.
pub(crate) mod call_stack_repr {
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    use super::*;

    pub(crate) fn deserialize<'de, D>(
        deserializer: D,
    ) -> Result<Option<Vec<Option<SourceLocation>>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[allow(clippy::type_complexity)]
        let raw: Option<Vec<Option<(usize, (u64, u64), (u64, u64))>>> =
            Deserialize::deserialize(deserializer)?;

        Ok(raw.map(|vec| {
            vec.into_iter()
                .map(|opt| {
                    opt.map(|(origin, lines, columns)| SourceLocation {
                        origin,
                        columns,
                        lines,
                    })
                })
                .collect()
        }))
    }

    pub(crate) fn serialize<S>(
        stack: &Option<Vec<Option<SourceLocation>>>,
        serializer: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let raw: Option<Vec<_>> = stack.as_ref().map(|vec| {
            vec.iter()
                .map(|opt_sloc| {
                    opt_sloc
                        .as_ref()
                        .map(|sloc| (sloc.origin, sloc.lines, sloc.columns))
                })
                .collect()
        });

        raw.serialize(serializer)
    }
}

/// A location in an execution's source code.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SourceLocation {
    /// Start and end column.
    pub columns: (u64, u64),

    /// Start and end line.
    pub lines: (u64, u64),

    /// File in which the error occurred, probably as a position in the
    /// `sources` array?
    pub origin: usize,
}

/// The cause of an error.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Cause {
    /// The error code of the underlying error.
    pub code: i64,

    /// Extra information about the underlying error (may be a list or
    /// hash, possibly other things).
    #[serde(default)]
    pub extra: serde_json::Value,

    /// The HTTP status related to the underlying error.
    pub http_status: Option<u16>,
}

impl fmt::Display for Cause {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "code: {}", self.code)?;
        if let Some(http_status) = self.http_status {
            write!(f, ", HTTP status: {}", http_status)?;
        }
        write!(f, ", extra: {}", self.extra,)
    }
}

/// Information on the instruction where an error occurred.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Instruction {
    /// The name of the instruction.
    pub instruction: String,

    /// The source location where the error occurred.
    pub source: Option<SourceLocation>,
}

#[test]
fn deserialize_error_status() {
    let json = r#"{"call_stack": [[1, [109, 109], [14, 65]], [1, [109, 109], [15, 17]]], "code": -1, "elapsed": 62321, "elapsed_times": {"in-progress": 62265, "queued": 140, "started": 56}, "error": -8200, "instruction": {"instruction": "push-procedure", "source": {"columns": [14, 65], "lines": [109, 109], "origin": 1}}, "message": "Problem while executing script:  'get' expects 2 or 3 arguments, 4 given", "progress": 0.195, "source_location": {"columns": [0, 34], "lines": [97, 97], "origin": 1}}"#;
    let _status: ExecutionStatus = serde_json::from_str(json).unwrap();

    let json = r#"{"call_stack": [[1, [32, 47], [15, 1]]], "cause": {"code": -1206, "extra": {"all_fields": "Must be true or false", "fields": "Must be an object"}, "http_status": 400}, "code": -1, "elapsed": 8896, "elapsed_times": {"in-progress": 8834, "queued": 22, "started": 62}, "error": -8200, "instruction": {"instruction": "apply", "source": {"columns": [15, 1], "lines": [32, 47], "origin": 1}}, "message": "Problem while executing script: Error handling resource (Validation error)", "progress": 0.195, "source_location": {"columns": [15, 1], "lines": [32, 47], "origin": 1}}"#;
    let status: ExecutionStatus = serde_json::from_str(json).unwrap();
    assert_eq!(status.cause.unwrap().code, -1206);
}