Skip to content

Commit

Permalink
feat: extend unavailable status with a reason (#38)
Browse files Browse the repository at this point in the history
### Why?

Users will be interested in why a verification came back as unavailable.
For example, if the user has been rate-limited they would benefit from
having that information to adapt.

### What changed?

- `VerificationStatus::Unavailable` ->
`VerificationStatus::Unavailable(UnavailableReason)`
- New type `UnavailableReason` which currently can be one of:
   - `ServiceUnavailable`
   - `Timeout`
   - `Block`
   - `RateLimit`
 - All verifiers have been adapted to this
  • Loading branch information
Mollemoll authored May 16, 2024
1 parent e74eb38 commit 773a420
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 31 deletions.
16 changes: 8 additions & 8 deletions src/ch_vat/bfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use lazy_static::lazy_static;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE};
use roxmltree;
use serde_json::json;
use crate::verification::{Verifier, Verification, VerificationStatus, VerificationResponse};
use crate::verification::{Verifier, Verification, VerificationStatus::{*}, VerificationResponse, UnavailableReason::{*}};
use crate::errors::VerificationError;
use crate::TaxId;

Expand Down Expand Up @@ -97,16 +97,16 @@ impl Verifier for Bfs {
.and_then(|x| x.as_deref());

let status = match fault_string {
Some("Data_validation_failed") => VerificationStatus::Unverified,
Some("Request_limit_exceeded") => VerificationStatus::Unavailable,
Some("Data_validation_failed") => Unverified,
Some("Request_limit_exceeded") => Unavailable(RateLimit),
Some(_) => return Err(VerificationError::UnexpectedResponse(
format!("Unexpected faultstring: {}", fault_string.unwrap().to_string())
)),
None => {
let result = hash.get("ValidateVatNumberResult").and_then(|x| x.as_deref());
match result {
Some("true") => VerificationStatus::Verified,
Some("false") => VerificationStatus::Unverified,
Some("true") => Verified,
Some("false") => Unverified,
None | Some(_) => return Err(VerificationError::UnexpectedResponse(
"ValidateVatNumberResult should be 'true' or 'false'".to_string()
)),
Expand Down Expand Up @@ -157,7 +157,7 @@ mod tests {
let verifier = Bfs;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Verified);
assert_eq!(verification.status(), &Verified);
assert_eq!(verification.data(), &json!({
"ValidateVatNumberResult": "true"
}));
Expand All @@ -181,7 +181,7 @@ mod tests {
let verifier = Bfs;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Unverified);
assert_eq!(verification.status(), &Unverified);
assert_eq!(verification.data(), &json!({
"ValidateVatNumberResult": "false"
}));
Expand Down Expand Up @@ -213,7 +213,7 @@ mod tests {
let verifier = Bfs;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Unavailable);
assert_eq!(verification.status(), &Unavailable(RateLimit));
assert_eq!(verification.data(), &json!({
"error": "Request_limit_exceeded",
"errorDetail": "Maximum number of 20 requests per 1 minute(s) exceeded",
Expand Down
36 changes: 33 additions & 3 deletions src/eu_vat/vies.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use std::collections::HashMap;
use lazy_static::lazy_static;

use roxmltree;
use serde_json::json;
use crate::verification::{Verifier, Verification, VerificationStatus, VerificationResponse};

use crate::errors::VerificationError;
use crate::TaxId;
use crate::verification::{Verification, VerificationResponse, VerificationStatus, UnavailableReason, Verifier};
use crate::verification::UnavailableReason::{*};

// INFO(2024-05-08 mollemoll):
// Data from Vies
Expand All @@ -22,6 +26,23 @@ static ENVELOPE: &'static str = "
</soapenv:Envelope>
";

lazy_static! {
pub static ref FAULT_MAP: HashMap<&'static str, UnavailableReason> = {
let mut m = HashMap::new();
m.insert("SERVICE_UNAVAILABLE", ServiceUnavailable);
m.insert("MS_UNAVAILABLE", ServiceUnavailable);
// Not implemented: 'INVALID_REQUESTER_INFO'
m.insert("TIMEOUT", Timeout);
m.insert("VAT_BLOCKED", Block);
m.insert("IP_BLOCKED", Block);
m.insert("GLOBAL_MAX_CONCURRENT_REQ", RateLimit);
m.insert("GLOBAL_MAX_CONCURRENT_REQ_TIME", RateLimit);
m.insert("MS_MAX_CONCURRENT_REQ", RateLimit);
m.insert("MS_MAX_CONCURRENT_REQ_TIME", RateLimit);
m
};
}

#[derive(Debug)]
pub struct Vies;

Expand Down Expand Up @@ -78,7 +99,16 @@ impl Verifier for Vies {
.and_then(|x| x.as_deref());

let verification_status = match fault_string {
Some(_) => VerificationStatus::Unavailable,
Some(fault) => {
match FAULT_MAP.get(fault){
Some(reason) => VerificationStatus::Unavailable(*reason),
None => {
return Err(VerificationError::UnexpectedResponse(
format!("Unknown fault code: {}", fault)
));
}
}
}
None => {
let validity_value = hash.get("valid")
.and_then(|x| x.as_deref());
Expand Down Expand Up @@ -212,7 +242,7 @@ mod tests {
let verifier = Vies;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Unavailable);
assert_eq!(verification.status(), &VerificationStatus::Unavailable(UnavailableReason::RateLimit));
assert_eq!(verification.data(), &json!({
"faultcode": "env:Server",
"faultstring": "MS_MAX_CONCURRENT_REQ"
Expand Down
17 changes: 9 additions & 8 deletions src/gb_vat/hmrc.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use serde_json::json;
use crate::errors::VerificationError;
use crate::TaxId;
use crate::verification::{Verification, VerificationResponse, VerificationStatus, Verifier};
use crate::verification::{Verification, VerificationResponse, VerificationStatus::{*}, Verifier};
use crate::verification::UnavailableReason::ServiceUnavailable;

// INFO(2024-05-08 mollemoll):
// Data from HMRC
// https://www.tax.service.gov.uk/check-vat-number/enter-vat-details
// https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/vat-api/1.0
// https://developer.service.hmrc.gov.uk/api-documentation/docs/api/service/vat-registered-companies-api/1.0/oas/page

static BASE_URI: &'static str = "https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup";

Expand Down Expand Up @@ -39,19 +40,19 @@ impl Verifier for Hmrc {
let verification_result = match fault {
None => {
Verification::new(
VerificationStatus::Verified,
Verified,
json!(hash.get("target"))
)
},
Some(fault_code) if fault_code == "NOT_FOUND" => {
Verification::new(
VerificationStatus::Unverified,
Unverified,
json!(hash)
)
},
Some(_) => {
Verification::new(
VerificationStatus::Unavailable,
Unavailable(ServiceUnavailable),
json!(hash)
)
},
Expand Down Expand Up @@ -89,7 +90,7 @@ mod tests {
let verifier = Hmrc;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Verified);
assert_eq!(verification.status(), &Verified);
assert_eq!(verification.data(), &json!({
"name": "VIRGIN ATLANTIC AIRWAYS LTD",
"vatNumber": "425216184",
Expand Down Expand Up @@ -117,7 +118,7 @@ mod tests {
let verifier = Hmrc;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Unverified);
assert_eq!(verification.status(), &Unverified);
assert_eq!(verification.data(), &json!({
"code": "NOT_FOUND",
"reason": "targetVrn does not match a registered company"
Expand All @@ -136,7 +137,7 @@ mod tests {
let verifier = Hmrc;
let verification = verifier.parse_response(response).unwrap();

assert_eq!(verification.status(), &VerificationStatus::Unavailable);
assert_eq!(verification.status(), &Unavailable(ServiceUnavailable));
assert_eq!(verification.data().get("code").unwrap(), "SERVER_ERROR");
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use std::fmt;
use regex::Regex;
use syntax::SYNTAX;
use verification::{Verifier};
pub use verification::{Verification, VerificationStatus};
pub use verification::{Verification, VerificationStatus, UnavailableReason};
pub use errors::{ValidationError, VerificationError};


Expand Down
25 changes: 16 additions & 9 deletions src/no_vat/brreg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use lazy_static::lazy_static;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT};
use serde_json::{json, Value};
use crate::verification::{Verifier, Verification, VerificationStatus, VerificationResponse};
use crate::verification::VerificationStatus::{*};
use crate::errors::VerificationError;
use crate::no_vat::NoVat;
use crate::no_vat::translator::translate_keys;
use crate::TaxId;
use crate::verification::UnavailableReason::{ServiceUnavailable};

// INFO(2024-05-08 mollemoll):
// Data from Brønnøysund Register Centre
Expand Down Expand Up @@ -49,9 +51,9 @@ impl BrReg {
}

if valid {
VerificationStatus::Verified
Verified
} else {
VerificationStatus::Unverified
Unverified
}
}
}
Expand All @@ -77,7 +79,7 @@ impl Verifier for BrReg {
match response.status() {
404 | 410 => return Ok(
Verification::new(
VerificationStatus::Unverified, json!({})
Unverified, json!({})
)
),
200 | 500 => {
Expand All @@ -87,7 +89,12 @@ impl Verifier for BrReg {
let hash = v.as_object().unwrap();

if response.status() == 500 {
return Ok(Verification::new(VerificationStatus::Unavailable, json!(hash)));
return Ok(
Verification::new(
Unavailable(ServiceUnavailable),
json!(hash)
)
);
}

Ok(
Expand Down Expand Up @@ -133,7 +140,7 @@ mod tests {

let verifier = BrReg;
let verification = verifier.parse_response(response).unwrap();
assert_eq!(verification.status(), &VerificationStatus::Verified);
assert_eq!(verification.status(), &Verified);
assert_eq!(verification.data(), &json!({
"organizationNumber": "123456789",
"name": "Test Company AS",
Expand Down Expand Up @@ -164,7 +171,7 @@ mod tests {

let verifier = BrReg;
let verification = verifier.parse_response(response).unwrap();
assert_eq!(verification.status(), &VerificationStatus::Unverified);
assert_eq!(verification.status(), &Unverified);
assert_eq!(verification.data(), &json!({}));
}

Expand All @@ -184,7 +191,7 @@ mod tests {

let verifier = BrReg;
let verification = verifier.parse_response(response).unwrap();
assert_eq!(verification.status(), &VerificationStatus::Unverified);
assert_eq!(verification.status(), &Unverified);
assert_eq!(verification.data(), &json!({
"organizationNumber": "123456789",
"name": "Test Company AS",
Expand Down Expand Up @@ -212,7 +219,7 @@ mod tests {

let verifier = BrReg;
let verification = verifier.parse_response(response).unwrap();
assert_eq!(verification.status(), &VerificationStatus::Unverified);
assert_eq!(verification.status(), &Unverified);
assert_eq!(verification.data(), &json!({}));
}

Expand All @@ -234,7 +241,7 @@ mod tests {

let verifier = BrReg;
let verification = verifier.parse_response(response).unwrap();
assert_eq!(verification.status(), &VerificationStatus::Unavailable);
assert_eq!(verification.status(), &Unavailable(ServiceUnavailable));
assert_eq!(verification.data(), &json!({
"timestamp": "2024-01-05T07:36:21.523+0000",
"status": 500,
Expand Down
12 changes: 10 additions & 2 deletions src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@ impl VerificationResponse {
pub fn body(&self) -> &str { &self.body }
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum VerificationStatus {
Verified,
Unverified,
Unavailable,
Unavailable(UnavailableReason),
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum UnavailableReason {
ServiceUnavailable,
Timeout,
Block,
RateLimit,
}

#[derive(Debug, PartialEq)]
Expand Down

0 comments on commit 773a420

Please sign in to comment.