neis_client/client/mod.rs
1mod response;
2
3use crate::error::Error;
4use crate::types::*;
5use http_body_util::{BodyExt, Empty};
6use hyper::body::Bytes;
7use hyper_tls::HttpsConnector;
8use hyper_util::{
9 client::legacy::{Client, connect::HttpConnector},
10 rt::TokioExecutor,
11};
12use response::{ExtractFromResponse, ResponseBody};
13
14pub struct NeisClient {
15 api_key: String,
16 client: Client<HttpsConnector<HttpConnector>, Empty<Bytes>>,
17}
18
19impl NeisClient {
20 pub fn new(api_key: &str) -> Self {
21 let https = HttpsConnector::new();
22 let client = Client::builder(TokioExecutor::new()).build(https);
23
24 Self {
25 api_key: api_key.to_owned(),
26 client,
27 }
28 }
29
30 /// 학교기본정보
31 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN17020190531110010104913&infSeq=2
32 ///
33 /// # Example
34 ///
35 /// ```rust
36 /// use neis_client::{types::SchoolInfoParams, Error, NeisClient};
37 ///
38 /// # async fn foo() -> Result<(), Error> {
39 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
40 /// let client = NeisClient::new(&api_key);
41 ///
42 /// let params = SchoolInfoParams {
43 /// SD_SCHUL_CODE: Some(String::from("7010959")),
44 /// ..Default::default()
45 /// };
46 /// let items = client.school_info(params).await?;
47 /// # Ok(())
48 /// # }
49 /// ```
50 pub async fn school_info(
51 &self,
52 params: SchoolInfoParams,
53 ) -> Result<Vec<SchoolInfoItem>, Error> {
54 self.request("schoolInfo", params).await
55 }
56
57 /// 학급정보
58 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN15320190408174919197546&infSeq=2
59 ///
60 /// # Example
61 ///
62 /// ```rust
63 /// use neis_client::{types::ClassInfoParams, Error, NeisClient};
64 ///
65 /// # async fn foo() -> Result<(), Error> {
66 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
67 /// let client = NeisClient::new(&api_key);
68 ///
69 /// let params = ClassInfoParams::new("B10", "7010959");
70 /// let items = client.class_info(params).await?;
71 /// # Ok(())
72 /// # }
73 /// ```
74 pub async fn class_info(&self, params: ClassInfoParams) -> Result<Vec<ClassInfoItem>, Error> {
75 self.request("classInfo", params).await
76 }
77
78 /// 학교학과정보
79 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN14020190311111456561190&infSeq=2
80 ///
81 /// # Example
82 ///
83 /// ```rust
84 /// use neis_client::{types::SchoolMajorInfoParams, Error, NeisClient};
85 ///
86 /// # async fn foo() -> Result<(), Error> {
87 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
88 /// let client = NeisClient::new(&api_key);
89 ///
90 /// let params = SchoolMajorInfoParams::new("B10").school_code("7010959");
91 /// let items = client.school_major_info(params).await?;
92 /// # Ok(())
93 /// # }
94 /// ```
95 pub async fn school_major_info(
96 &self,
97 params: SchoolMajorInfoParams,
98 ) -> Result<Vec<SchoolMajorInfoItem>, Error> {
99 self.request("schoolMajorinfo", params).await
100 }
101
102 /// 학교계열정보
103 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN13920190311110530306647&infSeq=2
104 ///
105 /// # Example
106 ///
107 /// ```rust
108 /// use neis_client::{types::SchoolAflcoInfoParams, Error, NeisClient};
109 ///
110 /// # async fn foo() -> Result<(), Error> {
111 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
112 /// let client = NeisClient::new(&api_key);
113 ///
114 /// let params = SchoolAflcoInfoParams::new("B10").school_code("7010959");
115 /// let items = client.school_aflco_info(params).await?;
116 /// # Ok(())
117 /// # }
118 /// ```
119 pub async fn school_aflco_info(
120 &self,
121 params: SchoolAflcoInfoParams,
122 ) -> Result<Vec<SchoolAflcoInfoItem>, Error> {
123 self.request("schulAflcoinfo", params).await
124 }
125
126 /// 학사일정
127 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN17220190722175038389180&infSeq=2
128 ///
129 /// # Example
130 ///
131 /// ```rust
132 /// use neis_client::{types::SchoolScheduleParams, Error, NeisClient};
133 ///
134 /// # async fn foo() -> Result<(), Error> {
135 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
136 /// let client = NeisClient::new(&api_key);
137 ///
138 /// let params = SchoolScheduleParams::new("B10", "7010959");
139 /// let items = client.school_schedule(params).await?;
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub async fn school_schedule(
144 &self,
145 params: SchoolScheduleParams,
146 ) -> Result<Vec<SchoolScheduleItem>, Error> {
147 self.request("SchoolSchedule", params).await
148 }
149
150 /// 초등학교시간표
151 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN15020190408160341416743&infSeq=2
152 ///
153 /// # Example
154 ///
155 /// ```rust
156 /// use neis_client::{types::ElsTimetableParams, Error, NeisClient};
157 ///
158 /// # async fn foo() -> Result<(), Error> {
159 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
160 /// let client = NeisClient::new(&api_key);
161 ///
162 /// let params = ElsTimetableParams::new("B10", "7130126").grade(6).sem(2);
163 /// let items = client.els_timetable(params).await?;
164 /// # Ok(())
165 /// # }
166 /// ```
167 pub async fn els_timetable(
168 &self,
169 params: ElsTimetableParams,
170 ) -> Result<Vec<ElsTimetableItem>, Error> {
171 self.request("elsTimetable", params).await
172 }
173
174 /// 중학교시간표
175 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN15120190408165334348844&infSeq=2
176 ///
177 /// # Example
178 ///
179 /// ```rust
180 /// use neis_client::{types::MisTimetableParams, Error, NeisClient};
181 ///
182 /// # async fn foo() -> Result<(), Error> {
183 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
184 /// let client = NeisClient::new(&api_key);
185 ///
186 /// let params = MisTimetableParams::new("B10", "7130177").grade(3).sem(2);
187 /// let items = client.mis_timetable(params).await?;
188 /// # Ok(())
189 /// # }
190 /// ```
191 pub async fn mis_timetable(
192 &self,
193 params: MisTimetableParams,
194 ) -> Result<Vec<MisTimetableItem>, Error> {
195 self.request("misTimetable", params).await
196 }
197
198 /// 고등학교시간표
199 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN18620200826103326268120&infSeq=2
200 ///
201 /// # Example
202 ///
203 /// ```rust
204 /// use neis_client::{types::HisTimetableParams, Error, NeisClient};
205 ///
206 /// # async fn foo() -> Result<(), Error> {
207 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
208 /// let client = NeisClient::new(&api_key);
209 ///
210 /// let params = HisTimetableParams::new("B10", "7010959").grade(3).sem(2);
211 /// let items = client.his_timetable(params).await?;
212 /// # Ok(())
213 /// # }
214 /// ```
215 pub async fn his_timetable(
216 &self,
217 params: HisTimetableParams,
218 ) -> Result<Vec<HisTimetableItem>, Error> {
219 self.request("hisTimetable", params).await
220 }
221
222 /// 특수학교시간표
223 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN18520200826093359591792&infSeq=2
224 ///
225 /// # Example
226 ///
227 /// ```rust
228 /// use neis_client::{types::SpsTimetableParams, Error, NeisClient};
229 ///
230 /// # async fn foo() -> Result<(), Error> {
231 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
232 /// let client = NeisClient::new(&api_key);
233 ///
234 /// let params = SpsTimetableParams::new("B10", "7010575");
235 /// let items = client.sps_timetable(params).await?;
236 /// # Ok(())
237 /// # }
238 /// ```
239 pub async fn sps_timetable(
240 &self,
241 params: SpsTimetableParams,
242 ) -> Result<Vec<SpsTimetableItem>, Error> {
243 self.request("spsTimetable", params).await
244 }
245
246 /// 시간표강의실정보
247 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=2&rows=10&sortColumn=&sortDirection=&infId=OPEN14120190311112536362172&infSeq=2
248 ///
249 /// # Example
250 ///
251 /// ```rust
252 /// use neis_client::{types::ClassRoomInfoParams, Error, NeisClient};
253 ///
254 /// # async fn foo() -> Result<(), Error> {
255 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
256 /// let client = NeisClient::new(&api_key);
257 ///
258 /// let params = ClassRoomInfoParams::new("B10", "7010959");
259 /// let items = client.class_room_info(params).await?;
260 /// # Ok(())
261 /// # }
262 /// ```
263 pub async fn class_room_info(
264 &self,
265 params: ClassRoomInfoParams,
266 ) -> Result<Vec<ClassRoomInfoItem>, Error> {
267 self.request("tiClrminfo", params).await
268 }
269
270 /// 학원교습소정보
271 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN19220231012134453534385&infSeq=2
272 ///
273 /// # Example
274 ///
275 /// ```rust
276 /// use neis_client::{types::AcademyInfoParams, Error, NeisClient};
277 ///
278 /// # async fn foo() -> Result<(), Error> {
279 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
280 /// let client = NeisClient::new(&api_key);
281 ///
282 /// let params = AcademyInfoParams::new("B10");
283 /// let items = client.academy_info(params).await?;
284 /// # Ok(())
285 /// # }
286 /// ```
287 pub async fn academy_info(
288 &self,
289 params: AcademyInfoParams,
290 ) -> Result<Vec<AcademyInfoItem>, Error> {
291 self.request("acaInsTiInfo", params).await
292 }
293
294 /// 급식식단정보
295 /// https://open.neis.go.kr/portal/data/service/selectServicePage.do?page=1&rows=10&sortColumn=&sortDirection=&infId=OPEN17320190722180924242823&infSeq=2
296 ///
297 /// # Example
298 ///
299 /// ```rust
300 /// use neis_client::{types::MealServiceParams, Error, NeisClient};
301 ///
302 /// # async fn foo() -> Result<(), Error> {
303 /// let api_key = std::env::var("NEIS_API_KEY").unwrap();
304 /// let client = NeisClient::new(&api_key);
305 ///
306 /// let params = MealServiceParams::new("B10", "7031115").from_ymd(2025, 1, 1);
307 /// let items = client.meal_service(params).await?;
308 /// # Ok(())
309 /// # }
310 /// ```
311 pub async fn meal_service(
312 &self,
313 params: MealServiceParams,
314 ) -> Result<Vec<MealServiceItem>, Error> {
315 self.request("mealServiceDietInfo", params).await
316 }
317
318 pub async fn request<P, T>(&self, resouce: &str, params: P) -> Result<Vec<T>, Error>
319 where
320 P: ToQueryString,
321 T: ExtractFromResponse,
322 {
323 let mut page = 1;
324 let page_size = 1000;
325 let mut items = Vec::new();
326
327 loop {
328 let common_params = format!(
329 "KEY={}&Type=json&pIndex={}&pSize={}",
330 self.api_key, page, page_size
331 );
332 let url = format!(
333 "https://open.neis.go.kr/hub/{}?{}&{}",
334 resouce,
335 common_params,
336 params.to_query_string()
337 );
338
339 let res = self.client.get(url.try_into().unwrap()).await?;
340 let status = res.status();
341 let body = res.collect().await?.to_bytes();
342 tracing::trace!(?body);
343
344 if status.is_success() {
345 let data: ResponseBody = serde_json::from_slice(&body)?;
346
347 let (total, row) = T::extract_from_response(data);
348
349 items.extend(row);
350
351 // 데이터가 없는 경우에도 total 이 0 이므로 loop 를 빠져나감
352 if total > page * page_size {
353 page += 1;
354 } else {
355 break; // while loop
356 }
357 } else {
358 return Err(Error::new_unknown(&format!("status: {}", status)));
359 }
360 }
361
362 Ok(items)
363 }
364}