1#[macro_use]
16extern crate log;
17extern crate chrono;
18extern crate reqwest;
19extern crate xml;
20extern crate xmltree;
21
22pub mod http;
23pub mod rpser;
24pub mod wsdl;
25
26mod page;
27mod space;
28mod transforms;
29
30pub use page::{Page, PageSummary, PageUpdateOptions, UpdatePage};
31pub use space::Space;
32pub use transforms::FromElement;
33
34use std::io::Error as IoError;
35use std::result;
36
37use self::http::HttpError;
38use self::rpser::xml::BuildElement;
39use self::rpser::{Method, RpcError};
40use xmltree::Element;
41
42const V2_API_RPC_PATH: &str = "/rpc/soap-axis/confluenceservice-v2?wsdl";
43
44pub struct Session {
46 wsdl: wsdl::Wsdl,
47 token: String,
48}
49
50impl Drop for Session {
51 fn drop(&mut self) {
52 self.logout().unwrap();
53 }
54}
55
56impl Session {
57 pub fn login(url: &str, user: &str, pass: &str) -> Result<Session> {
71 debug!("logging in at url {:?} with user {:?}", url, user);
72
73 let url = if url.ends_with('/') {
74 &url[..url.len() - 1]
75 } else {
76 url
77 };
78 let wsdl_url = [url, V2_API_RPC_PATH].concat();
79
80 debug!("getting wsdl from url {:?}", wsdl_url);
81
82 let wsdl = try!(wsdl::fetch(&wsdl_url));
83 let mut session = Session {
84 wsdl,
85 token: String::new(),
86 };
87
88 let response = try!(session.call(
89 Method::new("login")
90 .with(Element::node("username").with_text(user))
91 .with(Element::node("password").with_text(pass))
92 ));
93
94 let token = match try!(response.body.descend(&["loginReturn"])).text {
95 Some(token) => token,
96 _ => return Err(Error::ReceivedNoLoginToken),
97 };
98
99 session.token = token;
100
101 Ok(session)
102 }
103
104 pub fn logout(&self) -> Result<bool> {
108 let response = try!(self.call(
109 Method::new("logout").with(Element::node("token").with_text(self.token.clone()))
110 ));
111
112 Ok(match try!(response.body.descend(&["logoutReturn"])).text {
113 Some(ref v) if v == "true" => {
114 debug!("logged out successfully");
115 true
116 }
117 _ => {
118 debug!("log out failed (maybe expired token, maybe not loged in)");
119 false
120 }
121 })
122 }
123
124 pub fn get_space(&self, space_key: &str) -> Result<Space> {
143 let response = try!(self.call(
144 Method::new("getSpace")
145 .with(Element::node("token").with_text(self.token.clone()))
146 .with(Element::node("spaceKey").with_text(space_key))
147 ));
148
149 let element = try!(response.body.descend(&["getSpaceReturn"]));
150
151 Ok(try!(Space::from_element(element)))
152 }
153
154 pub fn get_page_by_title(&self, space_key: &str, page_title: &str) -> Result<Page> {
169 let response = try!(self.call(
170 Method::new("getPage")
171 .with(Element::node("token").with_text(self.token.clone()))
172 .with(Element::node("spaceKey").with_text(space_key))
173 .with(Element::node("pageTitle").with_text(page_title))
174 ));
175
176 let element = try!(response.body.descend(&["getPageReturn"]));
177
178 Ok(try!(Page::from_element(element)))
179 }
180
181 pub fn get_page_by_id(&self, page_id: i64) -> Result<Page> {
196 let response = try!(self.call(
197 Method::new("getPage")
198 .with(Element::node("token").with_text(self.token.clone()))
199 .with(Element::node("pageId").with_text(page_id.to_string()))
200 ));
201
202 let element = try!(response.body.descend(&["getPageReturn"]));
203
204 Ok(try!(Page::from_element(element)))
205 }
206
207 pub fn store_page(&self, page: UpdatePage) -> Result<Page> {
270 let mut element_items = vec![
271 Element::node("space").with_text(page.space),
272 Element::node("title").with_text(page.title),
273 Element::node("content").with_text(page.content),
274 ];
275
276 if let Some(id) = page.id {
277 element_items.push(Element::node("id").with_text(id.to_string()));
278 }
279
280 if let Some(version) = page.version {
281 element_items.push(Element::node("version").with_text(version.to_string()));
282 }
283
284 if let Some(parent_id) = page.parent_id {
285 element_items.push(Element::node("parentId").with_text(parent_id.to_string()));
286 }
287
288 let response = try!(self.call(
289 Method::new("storePage")
290 .with(Element::node("token").with_text(self.token.clone()))
291 .with(Element::node("page").with_children(element_items))
292 ));
293
294 let element = try!(response.body.descend(&["storePageReturn"]));
295
296 Ok(try!(Page::from_element(element)))
297 }
298
299 pub fn update_page(&self, page: UpdatePage, options: PageUpdateOptions) -> Result<Page> {
305 let mut element_items = vec![
306 Element::node("space").with_text(page.space),
307 Element::node("title").with_text(page.title),
308 Element::node("content").with_text(page.content),
309 ];
310
311 if let Some(id) = page.id {
312 element_items.push(Element::node("id").with_text(id.to_string()));
313 }
314
315 if let Some(version) = page.version {
316 element_items.push(Element::node("version").with_text(version.to_string()));
317 }
318
319 if let Some(parent_id) = page.parent_id {
320 element_items.push(Element::node("parentId").with_text(parent_id.to_string()));
321 }
322
323 let mut update_options = vec![];
324
325 if let Some(comment) = options.version_comment {
326 update_options.push(Element::node("versionComment").with_text(comment));
327 }
328
329 update_options.push(Element::node("minorEdit").with_text(if options.minor_edit {
330 "true"
331 } else {
332 "false"
333 }));
334
335 let response = try!(self.call(
336 Method::new("updatePage")
337 .with(Element::node("token").with_text(self.token.clone()))
338 .with(Element::node("page").with_children(element_items))
339 .with(Element::node("pageUpdateOptions").with_children(update_options))
340 ));
341
342 let element = try!(response.body.descend(&["updatePageReturn"]));
343
344 Ok(try!(Page::from_element(element)))
345 }
346
347 pub fn get_children(&self, page_id: i64) -> Result<Vec<PageSummary>> {
362 let response = try!(self.call(
363 Method::new("getChildren")
364 .with(Element::node("token").with_text(self.token.clone()))
365 .with(Element::node("pageId").with_text(page_id.to_string()))
366 ));
367
368 let element = try!(response.body.descend(&["getChildrenReturn"]));
369
370 let mut summaries = vec![];
371
372 for element in element.children {
373 summaries.push(try!(PageSummary::from_element(element)));
374 }
375
376 Ok(summaries)
377 }
378
379 pub fn call(&self, method: rpser::Method) -> Result<rpser::Response> {
398 let url = match self.wsdl.operations.get(&method.name) {
399 None => return Err(Error::MethodNotFoundInWsdl(method.name)),
400 Some(ref op) => &op.url,
401 };
402
403 if method.name == "login" {
405 debug!("[call] login ******");
406 } else {
407 debug!("[call] {}", method);
408 }
409
410 let envelope = method.as_xml(url);
411
412 if method.name != "login" {
414 trace!("[method xml] {}", envelope);
415 }
416
417 let http_response = try!(http::soap_action(url, &method.name, &envelope));
418
419 trace!("[response xml] {}", http_response.body);
420
421 Ok(try!(rpser::Response::from_xml(&http_response.body)))
422 }
423}
424
425#[derive(Debug)]
427pub enum Error {
428 MethodNotFoundInWsdl(String),
429 ReceivedNoLoginToken,
430 Io(IoError),
431 Http(HttpError),
432 Rpc(Box<RpcError>),
433}
434
435impl From<HttpError> for Error {
436 fn from(other: HttpError) -> Error {
437 Error::Http(other)
438 }
439}
440
441impl From<RpcError> for Error {
442 fn from(other: RpcError) -> Error {
443 Error::Rpc(Box::new(other))
444 }
445}
446
447impl From<rpser::xml::Error> for Error {
448 fn from(other: rpser::xml::Error) -> Error {
449 RpcError::from(other).into()
450 }
451}
452
453impl From<IoError> for Error {
454 fn from(other: IoError) -> Error {
455 Error::Io(other)
456 }
457}
458
459pub type Result<T> = result::Result<T, Error>;