use super::error::{AuthenticationError, AuthenticationResult};
use super::OdooRequest;
use crate::jsonrpc::{JsonRpcId, JsonRpcParams, OdooId, OdooWebMethod};
use crate::service::web::{SessionAuthenticate, SessionAuthenticateResponse};
use serde::Serialize;
use serde_json::{from_str, to_string};
use std::fmt::Debug;
pub trait AuthState {
fn get_session_id(&self) -> Option<&str>;
}
pub struct Authed {
pub(crate) database: String,
pub(crate) login: String,
pub(crate) uid: OdooId,
pub(crate) password: String,
pub(crate) session_id: Option<String>,
}
impl AuthState for Authed {
fn get_session_id(&self) -> Option<&str> {
self.session_id.as_deref()
}
}
pub struct NotAuthed {}
impl AuthState for NotAuthed {
fn get_session_id(&self) -> Option<&str> {
None
}
}
pub trait RequestImpl {
type Error: std::error::Error;
}
pub struct OdooClient<S, I>
where
S: AuthState,
I: RequestImpl,
{
pub(crate) url: String,
pub(crate) auth: S,
pub(crate) _impl: I,
pub(crate) id: JsonRpcId,
}
impl<S, I> OdooClient<S, I>
where
S: AuthState,
I: RequestImpl,
{
pub(crate) fn validate_url(url: &str) -> String {
let len = url.len();
if len > 0 && &url[len - 1..] == "/" {
url[0..len - 1].to_string()
} else {
url.to_string()
}
}
pub(crate) fn build_endpoint(&self, endpoint: &str) -> String {
format!("{}{}", self.url, endpoint)
}
pub(crate) fn build_request<'a, T>(&'a mut self, data: T, url: &str) -> OdooRequest<'a, T, I>
where
T: JsonRpcParams + Debug,
T::Container<T>: Debug + Serialize,
S: AuthState,
{
OdooRequest::new(
data.build(self.next_id()),
url.into(),
self.session_id(),
&self._impl,
)
}
pub(crate) fn next_id(&mut self) -> JsonRpcId {
let id = self.id;
self.id += 1;
id
}
pub(crate) fn get_auth_request(
&mut self,
db: &str,
login: &str,
password: &str,
) -> OdooRequest<SessionAuthenticate, I> {
let authenticate = crate::service::web::SessionAuthenticate {
db: db.into(),
login: login.into(),
password: password.into(),
};
let endpoint = self.build_endpoint(authenticate.endpoint());
self.build_request(authenticate, &endpoint)
}
pub(crate) fn parse_auth_response(
self,
db: &str,
login: &str,
password: &str,
response: SessionAuthenticateResponse,
session_id: Option<String>,
) -> AuthenticationResult<OdooClient<Authed, I>> {
let uid = response.data.get("uid").ok_or_else(|| {
AuthenticationError::UidParseError(
"Failed to parse UID from /web/session/authenticate call".into(),
)
})?;
let uid = from_str(&to_string(uid)?)?;
let auth = Authed {
database: db.into(),
uid,
login: login.into(),
password: password.into(),
session_id,
};
Ok(OdooClient {
url: self.url,
auth,
_impl: self._impl,
id: self.id,
})
}
pub fn session_id(&self) -> Option<&str> {
self.auth.get_session_id()
}
pub fn authenticate_manual(
self,
db: &str,
login: &str,
uid: OdooId,
password: &str,
session_id: Option<String>,
) -> OdooClient<Authed, I> {
let auth = Authed {
database: db.into(),
uid,
login: login.into(),
password: password.into(),
session_id,
};
OdooClient {
url: self.url,
auth,
_impl: self._impl,
id: self.id,
}
}
pub fn with_url(&mut self, url: &str) -> &mut Self {
self.url = Self::validate_url(url);
self
}
}
impl<I> OdooClient<NotAuthed, I>
where
I: RequestImpl,
{
pub(crate) fn new(url: &str, _impl: I) -> Self {
let url = Self::validate_url(url);
Self {
url,
auth: NotAuthed {},
_impl,
id: 1,
}
}
}