#[macro_use]
extern crate log;
extern crate chrono;
extern crate reqwest;
extern crate xml;
extern crate xmltree;
pub mod http;
pub mod rpser;
pub mod wsdl;
mod page;
mod space;
mod transforms;
pub use page::{Page, PageSummary, PageUpdateOptions, UpdatePage};
pub use space::Space;
pub use transforms::FromElement;
use std::io::Error as IoError;
use std::result;
use self::http::HttpError;
use self::rpser::xml::BuildElement;
use self::rpser::{Method, RpcError};
use xmltree::Element;
const V2_API_RPC_PATH: &str = "/rpc/soap-axis/confluenceservice-v2?wsdl";
pub struct Session {
wsdl: wsdl::Wsdl,
token: String,
}
impl Drop for Session {
fn drop(&mut self) {
self.logout().unwrap();
}
}
impl Session {
pub fn login(url: &str, user: &str, pass: &str) -> Result<Session> {
debug!("logging in at url {:?} with user {:?}", url, user);
let url = if url.ends_with('/') {
&url[..url.len() - 1]
} else {
url
};
let wsdl_url = [url, V2_API_RPC_PATH].concat();
debug!("getting wsdl from url {:?}", wsdl_url);
let wsdl = try!(wsdl::fetch(&wsdl_url));
let mut session = Session {
wsdl,
token: String::new(),
};
let response = try!(session.call(
Method::new("login")
.with(Element::node("username").with_text(user))
.with(Element::node("password").with_text(pass))
));
let token = match try!(response.body.descend(&["loginReturn"])).text {
Some(token) => token,
_ => return Err(Error::ReceivedNoLoginToken),
};
session.token = token;
Ok(session)
}
pub fn logout(&self) -> Result<bool> {
let response = try!(self.call(
Method::new("logout").with(Element::node("token").with_text(self.token.clone()))
));
Ok(match try!(response.body.descend(&["logoutReturn"])).text {
Some(ref v) if v == "true" => {
debug!("logged out successfully");
true
}
_ => {
debug!("log out failed (maybe expired token, maybe not loged in)");
false
}
})
}
pub fn get_space(&self, space_key: &str) -> Result<Space> {
let response = try!(self.call(
Method::new("getSpace")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("spaceKey").with_text(space_key))
));
let element = try!(response.body.descend(&["getSpaceReturn"]));
Ok(try!(Space::from_element(element)))
}
pub fn get_page_by_title(&self, space_key: &str, page_title: &str) -> Result<Page> {
let response = try!(self.call(
Method::new("getPage")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("spaceKey").with_text(space_key))
.with(Element::node("pageTitle").with_text(page_title))
));
let element = try!(response.body.descend(&["getPageReturn"]));
Ok(try!(Page::from_element(element)))
}
pub fn get_page_by_id(&self, page_id: i64) -> Result<Page> {
let response = try!(self.call(
Method::new("getPage")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("pageId").with_text(page_id.to_string()))
));
let element = try!(response.body.descend(&["getPageReturn"]));
Ok(try!(Page::from_element(element)))
}
pub fn store_page(&self, page: UpdatePage) -> Result<Page> {
let mut element_items = vec![
Element::node("space").with_text(page.space),
Element::node("title").with_text(page.title),
Element::node("content").with_text(page.content),
];
if let Some(id) = page.id {
element_items.push(Element::node("id").with_text(id.to_string()));
}
if let Some(version) = page.version {
element_items.push(Element::node("version").with_text(version.to_string()));
}
if let Some(parent_id) = page.parent_id {
element_items.push(Element::node("parentId").with_text(parent_id.to_string()));
}
let response = try!(self.call(
Method::new("storePage")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("page").with_children(element_items))
));
let element = try!(response.body.descend(&["storePageReturn"]));
Ok(try!(Page::from_element(element)))
}
pub fn update_page(&self, page: UpdatePage, options: PageUpdateOptions) -> Result<Page> {
let mut element_items = vec![
Element::node("space").with_text(page.space),
Element::node("title").with_text(page.title),
Element::node("content").with_text(page.content),
];
if let Some(id) = page.id {
element_items.push(Element::node("id").with_text(id.to_string()));
}
if let Some(version) = page.version {
element_items.push(Element::node("version").with_text(version.to_string()));
}
if let Some(parent_id) = page.parent_id {
element_items.push(Element::node("parentId").with_text(parent_id.to_string()));
}
let mut update_options = vec![];
if let Some(comment) = options.version_comment {
update_options.push(Element::node("versionComment").with_text(comment));
}
update_options.push(Element::node("minorEdit").with_text(if options.minor_edit {
"true"
} else {
"false"
}));
let response = try!(self.call(
Method::new("updatePage")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("page").with_children(element_items))
.with(Element::node("pageUpdateOptions").with_children(update_options))
));
let element = try!(response.body.descend(&["updatePageReturn"]));
Ok(try!(Page::from_element(element)))
}
pub fn get_children(&self, page_id: i64) -> Result<Vec<PageSummary>> {
let response = try!(self.call(
Method::new("getChildren")
.with(Element::node("token").with_text(self.token.clone()))
.with(Element::node("pageId").with_text(page_id.to_string()))
));
let element = try!(response.body.descend(&["getChildrenReturn"]));
let mut summaries = vec![];
for element in element.children {
summaries.push(try!(PageSummary::from_element(element)));
}
Ok(summaries)
}
pub fn call(&self, method: rpser::Method) -> Result<rpser::Response> {
let url = match self.wsdl.operations.get(&method.name) {
None => return Err(Error::MethodNotFoundInWsdl(method.name)),
Some(ref op) => &op.url,
};
if method.name == "login" {
debug!("[call] login ******");
} else {
debug!("[call] {}", method);
}
let envelope = method.as_xml(url);
if method.name != "login" {
trace!("[method xml] {}", envelope);
}
let http_response = try!(http::soap_action(url, &method.name, &envelope));
trace!("[response xml] {}", http_response.body);
Ok(try!(rpser::Response::from_xml(&http_response.body)))
}
}
#[derive(Debug)]
pub enum Error {
MethodNotFoundInWsdl(String),
ReceivedNoLoginToken,
Io(IoError),
Http(HttpError),
Rpc(Box<RpcError>),
}
impl From<HttpError> for Error {
fn from(other: HttpError) -> Error {
Error::Http(other)
}
}
impl From<RpcError> for Error {
fn from(other: RpcError) -> Error {
Error::Rpc(Box::new(other))
}
}
impl From<rpser::xml::Error> for Error {
fn from(other: rpser::xml::Error) -> Error {
RpcError::from(other).into()
}
}
impl From<IoError> for Error {
fn from(other: IoError) -> Error {
Error::Io(other)
}
}
pub type Result<T> = result::Result<T, Error>;