neis_client/client/
mod.rs

1mod response;
2
3use crate::error::Error;
4use crate::types::*;
5use http_body_util::{BodyExt, Empty};
6use hyper::body::{Buf, 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_reader(body.reader())?;
346
347                let (total, row) = T::extract_from_response(data);
348                println!("total: {}, page: {}, page_size: {}", total, page, page_size);
349
350                items.extend(row);
351
352                // 데이터가 없는 경우에도 total 이 0 이므로 loop 를 빠져나감
353                if total > page * page_size {
354                    page += 1;
355                } else {
356                    break; // while loop
357                }
358            } else {
359                return Err(Error::new_unknown(&format!("status: {}", status)));
360            }
361        }
362
363        Ok(items)
364    }
365}