kubos_app/query.rs
1/*
2 * Copyright (C) 2018 Kubos Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use failure::format_err;
18use kubos_system::Config as ServiceConfig;
19
20use std::time::Duration;
21
22/// The result type used by `query`
23type AppResult<T> = Result<T, failure::Error>;
24
25/// Execute a GraphQL query against a running KubOS Service using UDP.
26///
27/// Returns the parsed JSON result as a serde_json::Value on success
28///
29/// # Arguments
30///
31/// * `config` - The configuration information for the service which should be queried
32/// * `query` - The raw GraphQL query as a string
33/// * `timeout` - The timeout provided to the UDP socket. Note: This function will block when `None`
34/// is provided here
35///
36/// # Examples
37///
38/// ```
39/// # use failure;
40/// use kubos_app::*;
41/// use std::time::Duration;
42///
43/// # fn func() -> Result<(), failure::Error> {
44/// let request = r#"{
45/// ping
46/// }"#;
47///
48/// let result = query(&ServiceConfig::new_from_path("radio-service", "/home/kubos/config.toml".to_owned())?, request, Some(Duration::from_secs(1)))?;
49///
50/// let data = result.get("ping").unwrap().as_str();
51///
52/// assert_eq!(data, Some("pong"));
53/// # Ok(())
54/// # }
55/// ```
56///
57/// ```
58/// # use failure;
59/// use kubos_app::*;
60/// use std::time::Duration;
61///
62/// # fn func() -> Result<(), failure::Error> {
63/// let request = r#"{
64/// power
65/// }"#;
66///
67/// let result = query(&ServiceConfig::new("antenna-service")?, request, Some(Duration::from_secs(1)))?;
68///
69/// let data = result.get("power").unwrap().as_str();
70///
71/// assert_eq!(data, Some("ON"));
72/// # Ok(())
73/// # }
74/// ```
75///
76pub fn query(
77 config: &ServiceConfig,
78 query: &str,
79 timeout: Option<Duration>,
80) -> AppResult<serde_json::Value> {
81 let client = match timeout {
82 Some(time) => reqwest::Client::builder().timeout(time).build()?,
83 None => reqwest::Client::builder().build()?,
84 };
85
86 let uri = format!(
87 "http://{}",
88 config
89 .hosturl()
90 .ok_or_else(|| format_err!("Unable to fetch addr for service"))?
91 );
92
93 let mut map = ::std::collections::HashMap::new();
94 map.insert("query", query);
95
96 let response: serde_json::Value = client.post(&uri).json(&map).send()?.json()?;
97
98 if let Some(errs) = response.get("errors") {
99 if errs.is_string() {
100 let errs_str = errs.as_str().unwrap();
101 if !errs_str.is_empty() {
102 return Err(format_err!("{}", errs_str.to_string()));
103 }
104 } else if !errs.is_null() {
105 match errs.get("message") {
106 Some(message) => {
107 return Err(format_err!("{}", message.as_str().unwrap().to_string()));
108 }
109 None => {
110 return Err(format_err!("{}", serde_json::to_string(errs).unwrap()));
111 }
112 }
113 }
114 }
115
116 match response.get(0) {
117 Some(err) if err.get("message").is_some() => {
118 return Err(format_err!(
119 "{}",
120 err["message"].as_str().unwrap().to_string(),
121 ));
122 }
123 _ => {}
124 }
125
126 match response.get("data") {
127 Some(result) => Ok(result.clone()),
128 None => Err(format_err!(
129 "No result returned in 'data' key: {}",
130 serde_json::to_string(&response).unwrap()
131 )),
132 }
133}