wild_doc_client_lib/
lib.rs1use 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 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}