Last active
March 16, 2020 07:23
-
-
Save Sch3lp/2fa7afbb99f615fbfb868621cf5544cc to your computer and use it in GitHub Desktop.
VinValidator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Objects; | |
public class ValidationError { | |
private final String error; | |
private ValidationError(final String error) { | |
this.error = error; | |
} | |
public static ValidationError from(final String error) { | |
return new ValidationError(error); | |
} | |
public String getError() { | |
return error; | |
} | |
@Override | |
public boolean equals(final Object o) { | |
if (this == o) return true; | |
if (o == null || getClass() != o.getClass()) return false; | |
final ValidationError that = (ValidationError) o; | |
return Objects.equals(error, that.error); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hash(error); | |
} | |
@Override | |
public String toString() { | |
return "ValidationError{" + | |
"error='" + error + '\'' + | |
'}'; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Optional; | |
import static java.lang.Character.isDigit; | |
public class VinValidator { | |
private static final int[] VALUES = {1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 0, 7, 0, 9, 2, 3, 4, 5, 6, 7, 8, 9}; | |
private static final int[] WEIGHTS = {8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2}; | |
public static Optional<ValidationError> validate(String vinToValidate) { | |
if (vinToValidate == null) { | |
return Optional.of(ValidationError.from("VIN_MANDATORY")); | |
} | |
final var vin = cleanUpVin(vinToValidate); | |
if (vin.length() != 17) { | |
return Optional.of(ValidationError.from("VIN_MAX_LENGTH")); | |
} | |
int sum = 0; | |
for (int i = 0; i < 17; i++) { | |
char c = vin.charAt(i); | |
int value; | |
// Only accept the 26 letters of the alphabet | |
if (c >= 'A' && c <= 'Z') { | |
value = VALUES[c - 'A']; | |
if (value == 0) { | |
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER")); | |
} | |
} else if (isDigit(c)) { | |
value = c - '0'; | |
} else { // illegal character | |
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER")); | |
} | |
sum = sum + (WEIGHTS[i] * value); | |
} | |
// check digit | |
sum = sum % 11; | |
char check = vin.charAt(8); | |
if (sum == 10 && check == 'X') { | |
return Optional.empty(); | |
} | |
if (sum == transliterate(check)) { | |
return Optional.empty(); | |
} | |
return Optional.of(ValidationError.from("VIN_ILLEGAL")); | |
} | |
private static String cleanUpVin(String value) { | |
var result = value; | |
result = result.replaceAll("-", ""); | |
result = result.replaceAll(" ", ""); | |
result = result.toUpperCase(); | |
return result; | |
} | |
private static int transliterate(char check) { | |
if (check == 'A' || check == 'J') { | |
return 1; | |
} else if (check == 'B' || check == 'K' || check == 'S') { | |
return 2; | |
} else if (check == 'C' || check == 'L' || check == 'T') { | |
return 3; | |
} else if (check == 'D' || check == 'M' || check == 'U') { | |
return 4; | |
} else if (check == 'E' || check == 'N' || check == 'V') { | |
return 5; | |
} else if (check == 'F' || check == 'W') { | |
return 6; | |
} else if (check == 'G' || check == 'P' || check == 'X') { | |
return 7; | |
} else if (check == 'H' || check == 'Y') { | |
return 8; | |
} else if (check == 'R' || check == 'Z') { | |
return 9; | |
} else if (Integer.valueOf(Character.getNumericValue(check)) != null) { //hacky but works | |
return Character.getNumericValue(check); | |
} | |
return -1; | |
} | |
private VinValidator() { | |
// static utility class | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.* | |
object VinValidator { | |
private val VALUES = intArrayOf(1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 0, 7, 0, 9, 2, 3, 4, 5, 6, 7, 8, 9) | |
private val WEIGHTS = intArrayOf(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2) | |
fun validate(vinToValidate: String?): Optional<ValidationError> { | |
if (vinToValidate == null) { | |
return Optional.of(ValidationError.from("VIN_MANDATORY")) | |
} | |
val vin = cleanUpVin(vinToValidate) | |
if (vin.length != 17) { | |
return Optional.of(ValidationError.from("VIN_MAX_LENGTH")) | |
} | |
var sum = 0 | |
for (i in 0..16) { | |
val c = vin[i] | |
var value: Int | |
// Only accept the 26 letters of the alphabet | |
if (c >= 'A' && c <= 'Z') { | |
value = VALUES[c - 'A'] | |
if (value == 0) { | |
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER")) | |
} | |
} else if (Character.isDigit(c)) { | |
value = c - '0' | |
} else { // illegal character | |
return Optional.of(ValidationError.from("VIN_ILLEGAL_CHARACTER")) | |
} | |
sum = sum + WEIGHTS[i] * value | |
} | |
// check digit | |
sum = sum % 11 | |
val check = vin[8] | |
if (sum == 10 && check == 'X') { | |
return Optional.empty<ValidationError>() | |
} | |
return if (sum == transliterate(check)) { | |
Optional.empty<ValidationError>() | |
} else Optional.of(ValidationError.from("VIN_ILLEGAL")) | |
} | |
private fun cleanUpVin(value: String): String { | |
var result = value | |
result = result.replace("-".toRegex(), "") | |
result = result.replace(" ".toRegex(), "") | |
result = result.toUpperCase() | |
return result | |
} | |
private fun transliterate(check: Char): Int { | |
if (check == 'A' || check == 'J') { | |
return 1 | |
} else if (check == 'B' || check == 'K' || check == 'S') { | |
return 2 | |
} else if (check == 'C' || check == 'L' || check == 'T') { | |
return 3 | |
} else if (check == 'D' || check == 'M' || check == 'U') { | |
return 4 | |
} else if (check == 'E' || check == 'N' || check == 'V') { | |
return 5 | |
} else if (check == 'F' || check == 'W') { | |
return 6 | |
} else if (check == 'G' || check == 'P' || check == 'X') { | |
return 7 | |
} else if (check == 'H' || check == 'Y') { | |
return 8 | |
} else if (check == 'R' || check == 'Z') { | |
return 9 | |
} else if (Integer.valueOf(Character.getNumericValue(check)) != null) { //hacky but works | |
return Character.getNumericValue(check) | |
} | |
return -1 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Test; | |
import java.util.Arrays; | |
import java.util.List; | |
import static org.assertj.core.api.Assertions.assertThat; | |
class VinValidatorTest { | |
static final List<String> VALID_VINS = Arrays.asList( | |
"TRUWT28N011046197", | |
"2G1WD57C491198247", | |
"JH4DA3350GS005185", | |
"5TFUM5F18AX006026", | |
"1G1JC524417418958", | |
"JH4DB8580RS000024", | |
"1FTSX21P05EC23578", | |
"1M8GDM9AXKP042788" | |
); | |
static final List<String> INVALID_VINS_CHECKSUM_WRONG = Arrays.asList( | |
"TRUWT28N611046197", | |
"1M8GDM9AZKP042788" | |
); | |
static final List<String> INVALID_VINS_NOT17_CHAR = Arrays.asList( | |
"sagas", | |
"adsrvdsv", | |
"12345", | |
"1FTSX" | |
); | |
static final List<String> INVALID_VINS_EMPTY_VIN = Arrays.asList( | |
null, | |
"", | |
" ", | |
" " | |
); | |
static final List<String> INVALID_VINS_ILLEGAL_CHARS = Arrays.asList( | |
"IIIIIIIIIIIIIIIII", | |
"OOOOOOOOOOOOOOOOO", | |
"QQQQQQQQQQQQQQQQQ" | |
); | |
@Test | |
void givenAListOfValidVins_WhenTheCheckIsDone_ThenAnErrorShouldNOTBeThrown() { | |
VALID_VINS.forEach( | |
vin -> assertThat(VinValidator.validate(vin)).isEmpty() | |
); | |
} | |
@Test | |
void spacesAndDashesAreCleanedFromVins() { | |
String vinWithSpaces = "TRUW T28N01 104 6197"; | |
String vinWithDashes = "2G1-WD57C49-1198247"; | |
assertThat(VinValidator.validate(vinWithSpaces)).isEmpty(); | |
assertThat(VinValidator.validate(vinWithDashes)).isEmpty(); | |
} | |
@Test | |
void lowerCaseAreTransformedToUpperCase() { | |
String vinWithLowerCaseCharacters = "truwT28N011046197"; | |
assertThat(VinValidator.validate(vinWithLowerCaseCharacters)).isEmpty(); | |
} | |
@Test | |
void givenAListOfInValidVinsWithout17Char_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() { | |
INVALID_VINS_NOT17_CHAR.forEach( | |
vin -> assertThat(VinValidator.validate(vin)) | |
.hasValueSatisfying(s -> | |
assertThat(s.errorMessage).contains("VIN number must be 17 characters") | |
) | |
); | |
} | |
@Test | |
void givenAListOfInValidVinsWithWrongChecksum_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() { | |
INVALID_VINS_CHECKSUM_WRONG.forEach( | |
vin -> assertThat(VinValidator.validate(vin)) | |
.hasValueSatisfying(s -> | |
assertThat(s.errorMessage).contains("Illegal Vin, checksum does not match") | |
) | |
); | |
} | |
@Test | |
void givenAListOfInValidVinsThatAreEmpty_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() { | |
INVALID_VINS_EMPTY_VIN.forEach( | |
vin -> assertThat(VinValidator.validate(vin)) | |
.hasValueSatisfying(s -> | |
assertThat(s.errorMessage).contains("VIN is mandatory") | |
) | |
); | |
} | |
@Test | |
void givenAListOfInValidVinsWithoutIlligalChar_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() { | |
INVALID_VINS_ILLEGAL_CHARS.forEach( | |
vin -> assertThat(VinValidator.validate(vin)) | |
.hasValueSatisfying(s -> | |
assertThat(s.errorMessage).contains("Illegal character") | |
) | |
); | |
} | |
@Test | |
void givenASpecialValue_WhenTheCheckIsDOne_ThenAnErrorShouldBeThrown() { | |
assertThat(VinValidator.validate("ččččččččččččččččč")) | |
.hasValueSatisfying(s -> | |
assertThat(s.errorMessage).contains("Illegal character") | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment