aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorenz Leitner <lrnz.ltnr@gmail.com>2021-10-10 15:12:41 +0200
committerLorenz Leitner <lrnz.ltnr@gmail.com>2021-10-12 12:06:57 +0200
commited1c228094188d872ceb8407fb6f46ff698937c2 (patch)
tree7d97b610220ff57cfcbf874b45936a88571de8a7
parentBasic (re)implementation of gonzobot wolfram alpha (diff)
Add test
Put WA api response JSON into test resource file Add short url, increase concurrency Move shorten_url to util dir
Diffstat (limited to '')
-rw-r--r--Cargo.lock61
-rw-r--r--Cargo.toml5
-rw-r--r--src/hooks/wa.rs107
-rw-r--r--src/lib.rs1
-rw-r--r--src/util/mod.rs1
-rw-r--r--src/util/web.rs20
-rw-r--r--tests/resources/wolfram_alpha_api_response.json244
7 files changed, 404 insertions, 35 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8dd3d26..24907dd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,9 +22,30 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.40"
+version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
+checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
+
+[[package]]
+name = "assert-json-diff"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f1c3703dd33532d7f0ca049168930e9099ecac238e23cf932f3a69c42f06da"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
[[package]]
name = "autocfg"
@@ -81,6 +102,7 @@ dependencies = [
"futures",
"irc",
"irc-proto",
+ "mockito",
"rand",
"regex",
"reqwest",
@@ -137,6 +159,17 @@ dependencies = [
]
[[package]]
+name = "colored"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
+dependencies = [
+ "atty",
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -172,6 +205,12 @@ dependencies = [
]
[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
+[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -680,6 +719,24 @@ dependencies = [
]
[[package]]
+name = "mockito"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d10030163d67f681db11810bc486df3149e6d91c8b4f3f96fa8b62b546c2cef8"
+dependencies = [
+ "assert-json-diff",
+ "colored",
+ "difference",
+ "httparse",
+ "lazy_static",
+ "log",
+ "rand",
+ "regex",
+ "serde_json",
+ "serde_urlencoded",
+]
+
+[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index bbf6c64..1f31a40 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.5"
-anyhow = "1"
+anyhow = "1.0.44"
futures = "0.3"
tokio = { version = "1.5.0", features = ["full", "rt-multi-thread"] }
@@ -32,6 +32,9 @@ rand = "0.8.3"
reqwest = "0.11"
+[dev-dependencies]
+mockito = "0.30.0"
+
[features]
default = []
diff --git a/src/hooks/wa.rs b/src/hooks/wa.rs
index b6d4eff..7d087ea 100644
--- a/src/hooks/wa.rs
+++ b/src/hooks/wa.rs
@@ -1,11 +1,12 @@
-use anyhow::{bail, Context, Error, Result};
+use crate::util::web::shorten_url;
+use anyhow::{Context, Error, Result};
+use futures::try_join;
use reqwest::{get, Url};
use serde::{Deserialize, Serialize};
use serde_json::Result as SerdeJsonResult;
-use tracing::trace;
#[derive(Serialize, Deserialize, Debug)]
-struct WaResult {
+struct WaResponse {
queryresult: QueryResult,
}
@@ -18,41 +19,28 @@ struct QueryResult {
struct Pod {
title: String,
id: String,
+ primary: Option<bool>,
subpods: Vec<SubPod>,
}
#[derive(Serialize, Deserialize, Debug)]
struct SubPod {
- title: String,
plaintext: String,
}
-fn parse_json(str_data: &str) -> SerdeJsonResult<WaResult> {
- let w: WaResult = serde_json::from_str(str_data)?;
+fn parse_json(str_data: &str) -> SerdeJsonResult<WaResponse> {
+ let w: WaResponse = serde_json::from_str(str_data)?;
Ok(w)
}
-#[tracing::instrument]
-async fn wa_query(query_str: &str) -> Result<String, Error> {
- let app_id = "XXX"; // TODO: Get from env
- let api_url = format!(
- "http://api.wolframalpha.com/v2/query?input={}&appid={}&output=json",
- query_str, app_id
- );
-
- let body = get(Url::parse(&api_url).context("Failed to parse url")?)
- .await
- .context("Failed to make request")?
- .text()
- .await
- .context("failed to get request response text")?;
-
- let full_wa_res = parse_json(&body)?;
- trace!("got full_wa_res: {:?}", full_wa_res);
-
- let pod_plaintexts = full_wa_res.queryresult.pods
+/// Reduces all 'pod' plaintexts to a single string.
+/// Same as gonzobot does it.
+fn to_single_string(wa_res: WaResponse) -> String {
+ wa_res
+ .queryresult
+ .pods
.iter()
- .filter(|it| it.id.to_lowercase() != "input")
+ .filter(|it| it.id.to_lowercase() != "input" && it.primary.is_some())
.map(|pod| {
let subpod_texts = pod
.subpods
@@ -64,9 +52,55 @@ async fn wa_query(query_str: &str) -> Result<String, Error> {
format!("{}: {}", &pod.title, subpod_texts)
})
.collect::<Vec<String>>()
- .join(" - ");
+ .join(" - ")
+}
- Ok(pod_plaintexts)
+fn get_url(query_str: &str, base_url: Option<&str>) -> String {
+ let app_id = "XXX"; // TODO: Get from env
+ let wa_url = "http://api.wolframalpha.com";
+ let api_url = format!(
+ "{}/v2/query?input={}&appid={}&output=json",
+ base_url.unwrap_or(wa_url),
+ query_str,
+ app_id
+ );
+ api_url
+}
+
+async fn send_wa_req(url: &str) -> Result<String, Error> {
+ let body = get(Url::parse(url).context("Failed to parse url")?)
+ .await
+ .context("Failed to make request")?
+ .text()
+ .await
+ .context("failed to get request response text")?;
+ Ok(body)
+}
+
+async fn handle_wa_req(url: &str) -> Result<WaResponse, Error> {
+ let res_body = send_wa_req(url).await?;
+ let parsed = parse_json(&res_body)?;
+ Ok(parsed)
+}
+
+/// Sends a request to the Wolfram Alpha API, returns a plain text response.
+#[tracing::instrument]
+async fn wa_query(query_str: &str, base_url: Option<&str>) -> Result<String, Error> {
+ let user_url = format!("http://www.wolframalpha.com/input/?i={}", query_str);
+ let user_url_shortened_fut = shorten_url(&user_url);
+
+ let url = get_url(query_str, base_url);
+ let wa_res_fut = handle_wa_req(&url);
+
+ // Can't just (foo.await, bar.await), smh
+ // https://rust-lang.github.io/async-book/06_multiple_futures/02_join.html
+ let (wa_res, user_url_shortened) = try_join!(wa_res_fut, user_url_shortened_fut)?;
+
+ Ok(format!(
+ "{} - {}",
+ &user_url_shortened,
+ to_single_string(wa_res)
+ ))
}
#[cfg(test)]
@@ -74,11 +108,20 @@ mod tests {
use super::wa_query;
use anyhow::{Error, Result};
+ use mockito::{self, Matcher};
#[tokio::test]
- // async fn test_query_result_json_parsing() -> Result<(), Error> {
- async fn test_query_result_json_parsing() {
- let res = wa_query("weather graz").await.unwrap();
- assert_eq!(res, "asdf");
+ async fn test_query_result_json_parsing() -> Result<(), Error> {
+ let body = include_str!("../../tests/resources/wolfram_alpha_api_response.json");
+ let _m = mockito::mock("GET", Matcher::Any)
+ // Trimmed down version of a full WA response:
+ .with_body(body)
+ .create();
+ mockito::start();
+
+ let res = wa_query("5/10", Some(&mockito::server_url())).await?;
+ let res_without_link = res.splitn(2, "-").collect::<Vec<&str>>()[1].trim();
+ assert_eq!(res_without_link, "Exact result: 1/2 - Decimal form: 0.5");
+ Ok(())
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 220c3af..0aba61c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,6 +10,7 @@ use tracing::info;
pub mod config;
pub mod hooks;
+pub mod util;
pub use macros::catinator;
diff --git a/src/util/mod.rs b/src/util/mod.rs
new file mode 100644
index 0000000..fdd4603
--- /dev/null
+++ b/src/util/mod.rs
@@ -0,0 +1 @@
+pub mod web;
diff --git a/src/util/web.rs b/src/util/web.rs
new file mode 100644
index 0000000..77e20a3
--- /dev/null
+++ b/src/util/web.rs
@@ -0,0 +1,20 @@
+use anyhow::{Context, Error, Result};
+use reqwest::{get, Url};
+
+// TODO: Either catinator should have a URL shortening utility module,
+// or we should start our own service
+pub(crate) async fn shorten_url(url: &str) -> Result<String, Error> {
+ // This just uses the first service gonzobot uses too
+ let short_url = get(Url::parse(&format!(
+ "https://is.gd/create.php?format=simple&url={}",
+ url
+ ))
+ .context("Failed to parse url")?)
+ .await
+ .context("Failed to make request")?
+ .text()
+ .await
+ .context("failed to get request response text")?;
+
+ Ok(short_url)
+}
diff --git a/tests/resources/wolfram_alpha_api_response.json b/tests/resources/wolfram_alpha_api_response.json
new file mode 100644
index 0000000..c18b30b
--- /dev/null
+++ b/tests/resources/wolfram_alpha_api_response.json
@@ -0,0 +1,244 @@
+{
+ "queryresult": {
+ "success": true,
+ "error": false,
+ "numpods": 7,
+ "datatypes": "Math",
+ "timedout": "",
+ "timedoutpods": "",
+ "timing": 0.934,
+ "parsetiming": 0.181,
+ "parsetimedout": false,
+ "recalculate": "",
+ "id": "MSP621911bg8chadg539i550000308ea58ih11600i1",
+ "host": "https://www4c.wolframalpha.com",
+ "server": "49",
+ "related": "https://www4c.wolframalpha.com/api/v1/relatedQueries.jsp?id=MSPa622011bg8chadg539i5500004eidb30h8gh28f0f5993178497346123851",
+ "version": "2.6",
+ "inputstring": "5/10",
+ "pods": [
+ {
+ "title": "Input",
+ "scanner": "Identity",
+ "id": "Input",
+ "position": 100,
+ "error": false,
+ "numsubpods": 1,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622111bg8chadg539i5500000he65h3ca6bci37f?MSPStoreType=image/gif&s=49",
+ "alt": "5/10",
+ "title": "5/10",
+ "width": 21,
+ "height": 37,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": "5/10"
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ }
+ },
+ {
+ "title": "Exact result",
+ "scanner": "Rational",
+ "id": "Result",
+ "position": 200,
+ "error": false,
+ "numsubpods": 1,
+ "primary": true,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622211bg8chadg539i55000062chh995bc3f9362?MSPStoreType=image/gif&s=49",
+ "alt": "1/2",
+ "title": "1/2",
+ "width": 13,
+ "height": 37,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": "1/2"
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ },
+ "states": [
+ {
+ "name": "Step-by-step solution",
+ "input": "Result__Step-by-step solution",
+ "stepbystep": true
+ }
+ ]
+ },
+ {
+ "title": "Decimal form",
+ "scanner": "Numeric",
+ "id": "DecimalApproximation",
+ "position": 300,
+ "error": false,
+ "numsubpods": 1,
+ "primary": true,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622311bg8chadg539i55000069fi57493b7cabb6?MSPStoreType=image/gif&s=49",
+ "alt": "0.5",
+ "title": "0.5",
+ "width": 30,
+ "height": 21,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": "0.5"
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ }
+ },
+ {
+ "title": "Number line",
+ "scanner": "NumberLine",
+ "id": "NumberLine",
+ "position": 400,
+ "error": false,
+ "numsubpods": 1,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622411bg8chadg539i5500004ddhf07f5g058fcc?MSPStoreType=image/gif&s=49",
+ "alt": "Number line",
+ "title": "",
+ "width": 330,
+ "height": 59,
+ "type": "1DMathPlot_1",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": ""
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ }
+ },
+ {
+ "title": "Percentage",
+ "scanner": "Numeric",
+ "id": "Percentage",
+ "position": 500,
+ "error": false,
+ "numsubpods": 1,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622511bg8chadg539i550000612853id44f7fdg0?MSPStoreType=image/gif&s=49",
+ "alt": "50%",
+ "title": "50%",
+ "width": 28,
+ "height": 19,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": "50%"
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ }
+ },
+ {
+ "title": "Pie chart",
+ "scanner": "Rational",
+ "id": "PieChart",
+ "position": 600,
+ "error": false,
+ "numsubpods": 1,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622611bg8chadg539i55000021861gdbbi1a4fhb?MSPStoreType=image/gif&s=49",
+ "alt": "Pie chart",
+ "title": "",
+ "width": 60,
+ "height": 62,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": ""
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ }
+ },
+ {
+ "title": "Continued fraction",
+ "scanner": "ContinuedFraction",
+ "id": "ContinuedFraction",
+ "position": 700,
+ "error": false,
+ "numsubpods": 1,
+ "subpods": [
+ {
+ "title": "",
+ "img": {
+ "src": "https://www4c.wolframalpha.com/Calculate/MSP/MSP622711bg8chadg539i550000405cheh4e44a1eb0?MSPStoreType=image/gif&s=49",
+ "alt": "[0; 2]",
+ "title": "[0; 2]",
+ "width": 548,
+ "height": 19,
+ "type": "Default",
+ "themes": "1,2,3,4,5,6,7,8,9,10,11,12",
+ "colorinvertable": true
+ },
+ "plaintext": "[0; 2]"
+ }
+ ],
+ "expressiontypes": {
+ "name": "Default"
+ },
+ "states": [
+ {
+ "name": "Fraction form",
+ "input": "ContinuedFraction__Fraction form"
+ }
+ ]
+ }
+ ],
+ "assumptions": {
+ "type": "Clash",
+ "word": "5/10",
+ "template": "Assuming \"${word}\" is ${desc1}. Use as ${desc2} instead",
+ "count": 2,
+ "values": [
+ {
+ "name": "FracNumber",
+ "desc": " referring to math",
+ "input": "*C.5%2F10-_*FracNumber-"
+ },
+ {
+ "name": "DateObject",
+ "desc": "a date",
+ "input": "*C.5%2F10-_*DateObject-"
+ }
+ ]
+ }
+ }
+}