wild_doc_client_lib/
lib.rs

1use std::{
2    io::{BufRead, BufReader, Read, Write},
3    net::TcpStream,
4    path::{Path, PathBuf},
5};
6
7use hashbrown::HashMap;
8
9pub struct WildDocResult {
10    body: Vec<u8>,
11    options_json: String,
12}
13impl WildDocResult {
14    pub fn body(&self) -> &[u8] {
15        &self.body
16    }
17    pub fn options_json(&self) -> &str {
18        &self.options_json
19    }
20}
21
22pub struct WildDocClient {
23    document_root: PathBuf,
24    sock: TcpStream,
25}
26impl WildDocClient {
27    pub fn new<P: AsRef<Path>>(host: &str, port: &str, document_root: P, dbname: &str) -> Self {
28        let mut sock =
29            TcpStream::connect(&(host.to_owned() + ":" + port)).expect("failed to connect server");
30        sock.set_nonblocking(false).expect("out of service");
31        sock.write_all(dbname.as_bytes()).unwrap();
32        sock.write_all(&[0]).unwrap();
33
34        let mut sig = Vec::new();
35        let mut reader = BufReader::new(&sock);
36        reader.read_until(0, &mut sig).unwrap();
37
38        Self {
39            document_root: {
40                let mut path = document_root.as_ref().to_path_buf();
41                path.push(dbname);
42                path
43            },
44            sock,
45        }
46    }
47    pub fn exec(&mut self, xml: &str, input_json: &str) -> std::io::Result<WildDocResult> {
48        let mut include_cache = HashMap::new();
49
50        if input_json.len() > 0 {
51            self.sock.write_all(input_json.as_bytes())?;
52        }
53        self.sock.write_all(&[0])?;
54
55        self.sock.write_all(xml.as_bytes())?;
56        self.sock.write_all(&[0])?;
57
58        let mut reader = BufReader::new(self.sock.try_clone().unwrap());
59        loop {
60            let mut recv_include = Vec::new();
61            if reader.read_until(0, &mut recv_include)? > 0 {
62                if recv_include.starts_with(b"include:") {
63                    recv_include.remove(recv_include.len() - 1);
64                    let mut exists = false;
65                    if let Ok(str) = std::str::from_utf8(&recv_include) {
66                        let s: Vec<_> = str.split("include:/").collect();
67                        if s.len() >= 2 {
68                            let mut path = self.document_root.clone();
69                            path.push(s[1]);
70                            if let Some(include_xml) =
71                                include_cache.entry(path).or_insert_with_key(|path| {
72                                    match std::fs::File::open(path) {
73                                        Ok(mut f) => {
74                                            let mut contents = Vec::new();
75                                            let _ = f.read_to_end(&mut contents);
76                                            Some(contents)
77                                        }
78                                        _ => None,
79                                    }
80                                })
81                            {
82                                exists = true;
83                                let exists: [u8; 1] = [1];
84                                self.sock.write_all(&exists)?;
85
86                                let len = include_xml.len() as u64;
87                                self.sock.write_all(&len.to_be_bytes())?;
88                                self.sock.write_all(&include_xml)?;
89                            }
90                        }
91                    }
92                    if !exists {
93                        let exists: [u8; 1] = [0];
94                        self.sock.write_all(&exists)?;
95                    }
96                } else {
97                    break;
98                }
99            } else {
100                break;
101            }
102        }
103
104        let mut len: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
105        reader.read_exact(&mut len)?;
106        let len = u64::from_be_bytes(len) as usize;
107
108        let mut recv_body = Vec::<u8>::with_capacity(len);
109        unsafe {
110            recv_body.set_len(len);
111        }
112        reader.read_exact(recv_body.as_mut_slice())?;
113
114        let mut recv_options = Vec::new();
115        reader.read_until(0, &mut recv_options)?;
116        recv_options.remove(recv_options.len() - 1);
117
118        Ok(WildDocResult {
119            body: recv_body,
120            options_json: String::from_utf8(recv_options).unwrap_or("".to_owned()),
121        })
122    }
123}
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn it_works() {
130        let mut client = WildDocClient::new("localhost", "51818", "./test/", "test");
131        client
132            .exec(
133                r#"<wd:session name="hoge">
134            <wd:update commit="true">
135                <collection name="person">
136                    <field name="name">Noah</field>
137                    <field name="country">US</field>
138                </collection>
139                <collection name="person">
140                    <field name="name">Liam</field>
141                    <field name="country">US</field>
142                </collection>
143                <collection name="person">
144                    <field name="name">Olivia</field>
145                    <field name="country">UK</field>
146                </collection>
147            </wd:update>
148        </wd:session>"#,
149                "",
150            )
151            .unwrap();
152
153        /*
154        client.exec(r#"
155            include-test:<wd:include src="hoge.xml" />
156            <wd:search name="p" collection="person">
157            </wd:search>
158            OK
159            <wd:result var="q" search="p">
160                <div>
161                    find <wd:print value:var="q.len" /> persons.
162                </div>
163                <ul>
164                    <wd:for var="r" key="i" in:var="q.rows"><li>
165                        <wd:print value:var="r.row" /> : <wd:print value:var="r.field.name" /> : <wd:print value:var="r.field.country" />
166                    </li></wd:for>
167                </ul>
168            </wd:result>
169        "#);
170
171
172        client.exec(r#"
173            <wd:search name="p" collection="person">
174                <field name="country" method="match" value="US" />
175            </wd:search>
176            <wd:result var="q" search="p">
177                <div>
178                    find <wd:print value:var="q.len" /> persons from the US.
179                </div>
180                <ul>
181                    <wd:for var="r" key="i" in:var="q.rows"><li>
182                        <wd:print value:var="r.row" /> : <wd:print value:var="r.field.name" /> : <wd:print value:var="r.field.country" />
183                    </li></wd:for>
184                </ul>
185            </wd:result>
186        "#);
187        client.exec(r#"
188            <?js
189                const ymd=function(){
190                    const now=new Date();
191                    return now.getFullYear()+"-"+(now.getMonth()+1)+"-"+now.getDate();
192                };
193                const uk="UK";
194            ?>
195            <wd:search name="p" collection="person">
196                <field name="country" method="match" value="uk" />
197            </wd:search>
198            <wd:result var="q" search="p">
199                <div>
200                    <wd:print value:js="ymd()" />
201                </div>
202                <div>
203                    find <wd:print value:var="q.len" /> persons from the <wd:print value="uk" />.
204                </div>
205                <ul>
206                    <wd:for var="r" key="i" in:var="q.rows"><li>
207                        <wd:print value:var="r.row" /> : <wd:print value:var="'.field.name" /> : <wd:print value:var="r.field.country" />
208                    </li></wd:for>
209                </ul>
210            </wd:result>
211        "#);
212        */
213        client
214            .exec(
215                r#"<wd:session name="hoge">
216            <wd:update commit="true">
217                <wd:search name="person" collection="person"></wd:search>
218                <wd:result var="q" search="person">
219                    <wd:for var="r" key="i" in:var="q.rows">
220                        <collection name="person" row:var="r.row">
221                            <field name="name">Renamed <wd:print value:var="r.field.name" /></field>
222                            <field name="country"><wd:print value:var="r.field.country" /></field>
223                        </collection>
224                    </wd:for>
225                </wd:result>
226            </wd:update>
227        </wd:session>"#,
228                "",
229            )
230            .unwrap();
231        let r=client.exec(r#"
232            <wd:search name="p" collection="person"></wd:search>
233            <wd:result var="q" search="p">
234                <div>
235                    find <wd:print value:var="q.len" /> persons.
236                </div>
237                <ul>
238                    <wd:for var="r" key="i" in:var="q.rows"><li>
239                        <wd:print value:var="r.row" /> : <wd:print value:var="'r.field.name" /> : <wd:print value:var="r.field.country" />
240                    </li></wd:for>
241                </ul>
242            </wd:result>
243        "#,"").unwrap();
244        println!("{}", std::str::from_utf8(&r.body()).unwrap());
245    }
246}