bigml/resource/execution/
mod.rs

1//! An execution of a WhizzML script.
2
3use serde::de;
4use serde::de::DeserializeOwned;
5use serde::ser::SerializeSeq;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use std::fmt;
8use url::Url;
9
10use super::id::*;
11use super::status::*;
12use super::{Library, Script};
13use super::{Resource, ResourceCommon};
14use crate::client::Client;
15use crate::errors::*;
16
17mod args;
18mod execution_status;
19
20pub use self::args::*;
21pub use self::execution_status::*;
22
23/// An execution of a WhizzML script.
24///
25/// TODO: Still lots of missing fields.
26#[derive(Clone, Debug, Deserialize, Resource, Serialize)]
27#[api_name = "execution"]
28#[non_exhaustive]
29pub struct Execution {
30    /// Common resource information. These fields will be serialized at the
31    /// top-level of this structure by `serde`.
32    #[serde(flatten)]
33    pub common: ResourceCommon,
34
35    /// The ID of this resource.
36    pub resource: Id<Execution>,
37
38    /// The current status of this execution.
39    pub status: ExecutionStatus,
40
41    /// Further information about this execution.
42    pub execution: Data,
43}
44
45/// Data about a script execution.
46///
47/// TODO: Lots of missing fields.
48#[derive(Clone, Debug, Deserialize, Serialize)]
49#[non_exhaustive]
50pub struct Data {
51    /// Outputs from this script.
52    #[serde(default)]
53    pub outputs: Vec<Output>,
54
55    /// Result values from the script.  This is literally whatever value is
56    /// returned at the end of the WhizzML script.
57    pub result: Option<serde_json::Value>,
58
59    /// Log entries generated by the script.
60    #[serde(default)]
61    pub logs: Vec<LogEntry>,
62
63    /// BigML resources created by the script.
64    #[serde(default)]
65    pub output_resources: Vec<OutputResource>,
66
67    /// Source files used as inputs to this execution.
68    #[serde(default)]
69    pub sources: Vec<Source>,
70}
71
72impl Data {
73    /// Get a named output of this execution.
74    pub fn get<D: DeserializeOwned>(&self, name: &str) -> Result<D> {
75        for output in &self.outputs {
76            if output.name == name {
77                return output.get();
78            }
79        }
80        Err(Error::could_not_get_output(
81            name,
82            Error::OutputNotAvailable {},
83        ))
84    }
85}
86
87/// Information about a source code resource.
88#[derive(Clone, Debug)]
89#[non_exhaustive]
90pub struct Source {
91    /// The script or library associated with this source.
92    pub id: SourceId,
93    /// The description associated with this source.
94    pub description: String,
95}
96
97impl<'de> Deserialize<'de> for Source {
98    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99    where
100        D: Deserializer<'de>,
101    {
102        struct Visitor;
103
104        // Do a whole bunch of annoying work needed to deserialize mixed-type
105        // arrays.
106        impl<'de> de::Visitor<'de> for Visitor {
107            type Value = Source;
108
109            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110                write!(f, "a list with a source ID and a description")
111            }
112
113            fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
114            where
115                V: de::SeqAccess<'de>,
116            {
117                use serde::de::Error;
118
119                let id = visitor
120                    .next_element()?
121                    .ok_or_else(|| V::Error::custom("no id field in source"))?;
122                let description = visitor.next_element()?.ok_or_else(|| {
123                    V::Error::custom("no description field in source")
124                })?;
125
126                Ok(Source { id, description })
127            }
128        }
129
130        deserializer.deserialize_seq(Visitor)
131    }
132}
133
134impl Serialize for Source {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: Serializer,
138    {
139        let mut seq = serializer.serialize_seq(Some(2))?;
140        seq.serialize_element(&self.id)?;
141        seq.serialize_element(&self.description)?;
142        seq.end()
143    }
144}
145
146/// Either a script or library ID.
147#[derive(Clone, Debug)]
148#[non_exhaustive]
149pub enum SourceId {
150    /// A library ID.
151    Library(Id<Library>),
152    /// A script ID.
153    Script(Id<Script>),
154}
155
156impl SourceId {
157    /// Build a URL pointing to the BigML dashboard view for this script.
158    pub fn dashboard_url(&self) -> Url {
159        match self {
160            SourceId::Library(id) => id.dashboard_url(),
161            SourceId::Script(id) => id.dashboard_url(),
162        }
163    }
164
165    /// Download the corresponding source code.
166    pub async fn fetch_source_code(&self, client: &Client) -> Result<String> {
167        match *self {
168            SourceId::Library(ref id) => Ok(client.fetch(id).await?.source_code),
169            SourceId::Script(ref id) => Ok(client.fetch(id).await?.source_code),
170        }
171    }
172}
173
174impl fmt::Display for SourceId {
175    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match *self {
177            SourceId::Library(ref id) => id.fmt(fmt),
178            SourceId::Script(ref id) => id.fmt(fmt),
179        }
180    }
181}
182
183impl<'de> Deserialize<'de> for SourceId {
184    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185    where
186        D: Deserializer<'de>,
187    {
188        struct Visitor;
189
190        // Do a whole bunch of annoying work needed to deserialize mixed-type
191        // arrays.
192        impl<'de> de::Visitor<'de> for Visitor {
193            type Value = SourceId;
194
195            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196                write!(f, "a script or library ID")
197            }
198
199            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
200            where
201                E: de::Error,
202            {
203                if value.starts_with(Library::id_prefix()) {
204                    let id = value
205                        .parse()
206                        .map_err(|e| de::Error::custom(format!("{}", e)))?;
207                    Ok(SourceId::Library(id))
208                } else if value.starts_with(Script::id_prefix()) {
209                    let id = value
210                        .parse()
211                        .map_err(|e| de::Error::custom(format!("{}", e)))?;
212                    Ok(SourceId::Script(id))
213                } else {
214                    Err(de::Error::custom("expected script or library ID"))
215                }
216            }
217        }
218
219        deserializer.deserialize_str(Visitor)
220    }
221}
222
223impl Serialize for SourceId {
224    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
225    where
226        S: Serializer,
227    {
228        match *self {
229            SourceId::Library(ref id) => id.serialize(serializer),
230            SourceId::Script(ref id) => id.serialize(serializer),
231        }
232    }
233}