1extern crate reqwest;
34extern crate serde;
35extern crate snafu;
36
37use serde::Deserialize;
38use snafu::{ResultExt, Snafu};
39
40pub mod types {
42 use serde::Deserialize;
43
44 #[derive(Debug, Deserialize)]
45 #[serde(rename_all = "camelCase")]
46 pub struct Software {
47 pub name: String,
48 pub version: String,
49 pub api_version: u8,
50 pub premium: bool,
51 pub premium_hint: String,
52 pub status: String,
53 }
54
55 #[derive(Debug, Deserialize)]
56 #[serde(rename_all = "camelCase")]
57 pub struct Warnings {
58 pub incomplete_results: bool,
59 }
60
61 #[derive(Debug, Deserialize)]
62 #[serde(rename_all = "camelCase")]
63 pub struct Language {
64 pub name: String,
65 pub code: String,
66 pub detected_language: DetectedLanguage,
67 }
68
69 #[derive(Debug, Deserialize)]
70 #[serde(rename_all = "camelCase")]
71 pub struct DetectedLanguage {
72 pub name: String,
73 pub code: String,
74 }
75
76 #[derive(Debug, Deserialize)]
77 #[serde(rename_all = "camelCase")]
78 pub struct Match {
79 pub message: String,
80 pub short_message: String,
81 pub replacements: Vec<Replacement>,
82 pub offset: u32,
83 pub length: u32,
84 pub context: Context,
85 pub sentence: String,
86 pub r#type: Type,
87 pub rule: Rule,
88 }
89
90 #[derive(Debug, Deserialize)]
91 #[serde(rename_all = "camelCase")]
92 pub struct Replacement {
93 pub value: String,
94 }
95
96 #[derive(Debug, Deserialize)]
97 #[serde(rename_all = "camelCase")]
98 pub struct Context {
99 pub text: String,
100 pub offset: u32,
101 pub length: u32,
102 }
103
104 #[derive(Debug, Deserialize)]
105 #[serde(rename_all = "camelCase")]
106 pub struct Type {
107 pub type_name: String,
108 }
109
110 #[derive(Debug, Deserialize)]
111 #[serde(rename_all = "camelCase")]
112 pub struct Rule {
113 pub id: String,
114 pub description: String,
115 pub issue_type: String,
116 pub category: Category,
117 }
118
119 #[derive(Debug, Deserialize)]
120 #[serde(rename_all = "camelCase")]
121 pub struct Category {
122 pub id: String,
124 pub name: String,
126 }
127}
128
129#[derive(Debug, Deserialize)]
131pub struct Response {
132 pub software: types::Software,
133 pub warnings: types::Warnings,
134 pub language: types::Language,
135 pub matches: Vec<types::Match>,
136}
137
138pub struct Client {
140 api_key: String,
141 language: String,
142 base: reqwest::Url,
143 client: reqwest::Client,
144}
145
146impl Client {
147 pub fn new(api_key: &str) -> Self {
159 Self {
160 api_key: api_key.to_string(),
161 language: "en-US".to_string(),
162 base: reqwest::Url::parse("http://api.grammarbot.io").unwrap(),
163 client: reqwest::Client::new(),
164 }
165 }
166
167 pub fn check(&self, text: &str) -> Result<Response> {
168 self.request(reqwest::Method::GET, "/v2/check", &[("text", text)])?
169 .json()
170 .context(InvalidJSON)
171 }
172
173 pub fn api_key(&mut self, api_key: &str) -> &mut Self {
182 self.api_key = api_key.to_string();
183 self
184 }
185
186 pub fn language(&mut self, language: &str) -> &mut Self {
194 self.language = language.to_string();
195 self
196 }
197
198 pub fn base(&mut self, base: &str) -> Result<&mut Self> {
206 self.base = reqwest::Url::parse(base).context(InvalidUrl)?;
207 Ok(self)
208 }
209
210 fn request(
211 &self,
212 method: reqwest::Method,
213 path: &str,
214 query: &[(&str, &str)],
215 ) -> Result<reqwest::Response> {
216 self.client
217 .request(method, self.base.join(path).context(InvalidUrl)?)
218 .query(&query)
219 .send()
220 .context(RequestFailed)
221 }
222}
223
224#[derive(Debug, Snafu)]
226pub enum Error {
227 #[snafu(display("request failed: {}", source))]
229 RequestFailed {
230 source: reqwest::Error,
232 },
233 #[snafu(display("invalid URL: {}", source))]
235 InvalidUrl {
236 source: reqwest::UrlError,
238 },
239 #[snafu(display("invalid JSON: {}", source))]
241 InvalidJSON {
242 source: reqwest::Error,
244 },
245}
246
247pub type Result<T, E = Error> = std::result::Result<T, E>;