1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
 * Copyright (C) 2018 Kubos Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use failure::format_err;
use kubos_system::Config as ServiceConfig;
use serde_json;
use std::net::UdpSocket;
use std::time::Duration;

/// The result type used by `query`
type AppResult<T> = Result<T, failure::Error>;

/// Execute a GraphQL query against a running KubOS Service using UDP.
///
/// Returns the parsed JSON result as a serde_json::Value on success
///
/// # Arguments
///
/// * `config` - The configuration information for the service which should be queried
/// * `query` - The raw GraphQL query as a string
/// * `timeout` - The timeout provided to the UDP socket. Note: This function will block when `None`
///               is provided here
///
/// # Examples
///
/// ```
/// # use failure;
/// use kubos_app::*;
/// use std::time::Duration;
///
/// # fn func() -> Result<(), failure::Error> {
/// let request = r#"{
/// 		ping
/// 	}"#;
///
/// let result = query(&ServiceConfig::new_from_path("radio-service", "/home/kubos/config.toml".to_owned()), request, Some(Duration::from_secs(1)))?;
///
/// let data = result.get("ping").unwrap().as_str();
///
/// assert_eq!(data, Some("pong"));
/// # Ok(())
/// # }
/// ```
///
/// ```
/// # use failure;
/// use kubos_app::*;
/// use std::time::Duration;
///
/// # fn func() -> Result<(), failure::Error> {
/// let request = r#"{
/// 		power
/// 	}"#;
///
/// let result = query(&ServiceConfig::new("antenna-service"), request, Some(Duration::from_secs(1)))?;
///
/// let data = result.get("power").unwrap().as_str();
///
/// assert_eq!(data, Some("ON"));
/// # Ok(())
/// # }
/// ```
///
pub fn query(
    config: &ServiceConfig,
    query: &str,
    timeout: Option<Duration>,
) -> AppResult<serde_json::Value> {
    let socket = UdpSocket::bind("0.0.0.0:0")?;
    socket.connect(config.hosturl())?;
    socket.send(query.as_bytes())?;

    // Allow the caller to set a read timeout on the socket
    socket.set_read_timeout(timeout).unwrap();

    let mut buf = [0; 4096];
    let (amt, _) = socket.recv_from(&mut buf)?;

    let v: serde_json::Value = serde_json::from_slice(&buf[0..(amt)])?;

    if let Some(errs) = v.get("errors") {
        if errs.is_string() {
            let errs_str = errs.as_str().unwrap();
            if !errs_str.is_empty() {
                return Err(format_err!("{}", errs_str.to_string()));
            }
        } else if !errs.is_null() {
            match errs.get("message") {
                Some(message) => {
                    return Err(format_err!("{}", message.as_str().unwrap().to_string()));
                }
                None => {
                    return Err(format_err!("{}", serde_json::to_string(errs).unwrap()));
                }
            }
        }
    }

    match v.get(0) {
        Some(err) if err.get("message").is_some() => {
            return Err(format_err!(
                "{}",
                err["message"].as_str().unwrap().to_string(),
            ));
        }
        _ => {}
    }

    match v.get("data") {
        Some(result) => Ok(result.clone()),
        None => Err(format_err!(
            "No result returned in 'data' key: {}",
            serde_json::to_string(&v).unwrap()
        )),
    }
}