use bon::Builder;
use serde::{Deserialize, Serialize};
use crate::ocpi_lenient_enum;
use crate::types::validate_fields;
use crate::types::{DateTime, DisplayText, Extensions, OcpiString, Validate, Validator, ViolationCode};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[builder(on(_, into))]
pub struct Token {
pub uid: OcpiString<36>,
#[serde(rename = "type")]
pub token_type: TokenType,
pub auth_id: OcpiString<36>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visual_number: Option<OcpiString<64>>,
pub issuer: OcpiString<64>,
pub valid: bool,
pub whitelist: WhitelistType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<OcpiString<2>>,
pub last_updated: DateTime,
#[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
#[builder(default)]
pub extensions: Extensions,
}
impl Token {
#[must_use]
pub fn authorization_decision(&self, online: bool) -> AuthorizationDecision {
match self.whitelist {
WhitelistType::Always => AuthorizationDecision::AllowFromCache,
WhitelistType::Allowed => {
if online {
AuthorizationDecision::AuthorizeRealtime
} else if self.valid {
AuthorizationDecision::AllowFromCache
} else {
AuthorizationDecision::Deny
}
}
WhitelistType::AllowedOffline => {
if online {
AuthorizationDecision::AuthorizeRealtime
} else {
AuthorizationDecision::AllowFromCache
}
}
WhitelistType::Never => {
if online {
AuthorizationDecision::AuthorizeRealtime
} else {
AuthorizationDecision::Deny
}
}
}
}
}
impl Validate for Token {
fn validate_in(&self, v: &mut Validator) {
validate_fields!(
self, v, uid, token_type as "type", auth_id, visual_number, issuer, whitelist,
language, last_updated,
);
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[builder(on(_, into))]
pub struct AuthorizationInfo {
pub allowed: AllowedType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub location: Option<LocationReferences>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub info: Option<DisplayText>,
#[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
#[builder(default)]
pub extensions: Extensions,
}
impl Validate for AuthorizationInfo {
fn validate_in(&self, v: &mut Validator) {
validate_fields!(self, v, allowed, location, info);
if self.allowed != AllowedType::Allowed && self.location.is_some() {
v.report_at(
"location",
ViolationCode::Inconsistent,
"a location is only returned when the driver is allowed to charge there",
);
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct LocationReferences {
pub location_id: OcpiString<39>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub evse_uids: Vec<OcpiString<39>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub connector_ids: Vec<OcpiString<36>>,
#[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
pub extensions: Extensions,
}
impl Validate for LocationReferences {
fn validate_in(&self, v: &mut Validator) {
validate_fields!(self, v, location_id, evse_uids, connector_ids);
if !self.connector_ids.is_empty() && self.evse_uids.is_empty() {
v.report_at(
"evse_uids",
ViolationCode::MissingConditional,
"connectors are identified within an EVSE, so naming connectors without naming \
the EVSE they belong to is ambiguous",
);
}
}
}
pub use crate::v2_3_0::tokens::{AllowedType, AuthorizationDecision, WhitelistType};
ocpi_lenient_enum! {
pub enum TokenType {
Other = "OTHER",
Rfid = "RFID",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_2_1_1_token_type_has_two_values() {
assert_eq!(TokenType::ALL_KNOWN.len(), 2);
let app_user: TokenType = "APP_USER".into();
assert!(!app_user.is_known(), "APP_USER arrived in OCPI 2.2");
assert_eq!(serde_json::to_string(&app_user).unwrap(), "\"APP_USER\"");
}
#[test]
fn a_2_1_1_token_round_trips() {
let json = r#"{"uid":"012345678","type":"RFID","auth_id":"DE8ACC12E46L89","visual_number":"DF000-2001-8999","issuer":"TheNewMotion","valid":true,"whitelist":"ALLOWED","last_updated":"2018-12-10T17:16:15Z"}"#;
let token: Token = serde_json::from_str(json).unwrap();
assert!(token.validate().is_ok());
assert_eq!(serde_json::to_string(&token).unwrap(), json);
}
#[test]
fn naming_connectors_without_their_evse_is_ambiguous() {
let refs = LocationReferences {
location_id: OcpiString::new("LOC1").unwrap(),
evse_uids: Vec::new(),
connector_ids: vec![OcpiString::new("1").unwrap()],
extensions: Extensions::new(),
};
assert_eq!(refs.validate().unwrap_err().as_slice()[0].pointer, "/evse_uids");
}
}