1use bon::Builder;
10use serde::{Deserialize, Serialize};
11
12use crate::ocpi_lenient_enum;
13use crate::types::validate_fields;
14use crate::types::{DateTime, DisplayText, Extensions, OcpiString, Validate, Validator, ViolationCode};
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
20#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[builder(on(_, into))]
22pub struct Token {
23 pub uid: OcpiString<36>,
25 #[serde(rename = "type")]
27 pub token_type: TokenType,
28 pub auth_id: OcpiString<36>,
32 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub visual_number: Option<OcpiString<64>>,
35 pub issuer: OcpiString<64>,
37 pub valid: bool,
39 pub whitelist: WhitelistType,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub language: Option<OcpiString<2>>,
44 pub last_updated: DateTime,
46 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
48 #[builder(default)]
49 pub extensions: Extensions,
50}
51
52impl Token {
53 #[must_use]
58 pub fn authorization_decision(&self, online: bool) -> AuthorizationDecision {
59 match self.whitelist {
60 WhitelistType::Always => AuthorizationDecision::AllowFromCache,
61 WhitelistType::Allowed => {
62 if online {
63 AuthorizationDecision::AuthorizeRealtime
64 } else if self.valid {
65 AuthorizationDecision::AllowFromCache
66 } else {
67 AuthorizationDecision::Deny
68 }
69 }
70 WhitelistType::AllowedOffline => {
71 if online {
72 AuthorizationDecision::AuthorizeRealtime
73 } else {
74 AuthorizationDecision::AllowFromCache
75 }
76 }
77 WhitelistType::Never => {
78 if online {
79 AuthorizationDecision::AuthorizeRealtime
80 } else {
81 AuthorizationDecision::Deny
82 }
83 }
84 }
85 }
86}
87
88impl Validate for Token {
89 fn validate_in(&self, v: &mut Validator) {
90 validate_fields!(
91 self, v, uid, token_type as "type", auth_id, visual_number, issuer, whitelist,
92 language, last_updated,
93 );
94 }
95}
96
97#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Builder)]
103#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
104#[builder(on(_, into))]
105pub struct AuthorizationInfo {
106 pub allowed: AllowedType,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub location: Option<LocationReferences>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub info: Option<DisplayText>,
114 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
116 #[builder(default)]
117 pub extensions: Extensions,
118}
119
120impl Validate for AuthorizationInfo {
121 fn validate_in(&self, v: &mut Validator) {
122 validate_fields!(self, v, allowed, location, info);
123 if self.allowed != AllowedType::Allowed && self.location.is_some() {
124 v.report_at(
125 "location",
126 ViolationCode::Inconsistent,
127 "a location is only returned when the driver is allowed to charge there",
128 );
129 }
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
139#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
140pub struct LocationReferences {
141 pub location_id: OcpiString<39>,
143 #[serde(default, skip_serializing_if = "Vec::is_empty")]
145 pub evse_uids: Vec<OcpiString<39>>,
146 #[serde(default, skip_serializing_if = "Vec::is_empty")]
148 pub connector_ids: Vec<OcpiString<36>>,
149 #[serde(flatten, default, skip_serializing_if = "Extensions::is_empty")]
151 pub extensions: Extensions,
152}
153
154impl Validate for LocationReferences {
155 fn validate_in(&self, v: &mut Validator) {
156 validate_fields!(self, v, location_id, evse_uids, connector_ids);
157 if !self.connector_ids.is_empty() && self.evse_uids.is_empty() {
158 v.report_at(
159 "evse_uids",
160 ViolationCode::MissingConditional,
161 "connectors are identified within an EVSE, so naming connectors without naming \
162 the EVSE they belong to is ambiguous",
163 );
164 }
165 }
166}
167
168pub use crate::v2_3_0::tokens::{AllowedType, AuthorizationDecision, WhitelistType};
170
171ocpi_lenient_enum! {
172 pub enum TokenType {
178 Other = "OTHER",
180 Rfid = "RFID",
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn the_2_1_1_token_type_has_two_values() {
191 assert_eq!(TokenType::ALL_KNOWN.len(), 2);
192 let app_user: TokenType = "APP_USER".into();
193 assert!(!app_user.is_known(), "APP_USER arrived in OCPI 2.2");
194 assert_eq!(serde_json::to_string(&app_user).unwrap(), "\"APP_USER\"");
195 }
196
197 #[test]
198 fn a_2_1_1_token_round_trips() {
199 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"}"#;
200 let token: Token = serde_json::from_str(json).unwrap();
201 assert!(token.validate().is_ok());
202 assert_eq!(serde_json::to_string(&token).unwrap(), json);
203 }
204
205 #[test]
206 fn naming_connectors_without_their_evse_is_ambiguous() {
207 let refs = LocationReferences {
208 location_id: OcpiString::new("LOC1").unwrap(),
209 evse_uids: Vec::new(),
210 connector_ids: vec![OcpiString::new("1").unwrap()],
211 extensions: Extensions::new(),
212 };
213 assert_eq!(refs.validate().unwrap_err().as_slice()[0].pointer, "/evse_uids");
214 }
215}