Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nullsafety update #11

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
#
# Name/Organization <email address>

Adaptant Labs <[email protected]>
Adaptant Labs <[email protected]>
Stemco
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.2.1
- Migrated completely to nullsafety

## 0.2.0

- Expose helpers for querying NHTSA DB and accessing extended vehicle information (requested by @ride4sun, issue #8)
Expand All @@ -10,6 +13,13 @@

- Support VINs with 2-character manufacturer IDs in their WMI (reported by @huangkaichang, issue #7)
- Fix Uri parsing for NHTSA DB REST API
## 0.2.1-nullsafety

- Support VINs with 2-character manufacturer IDs in their WMI (reported by @huangkaichang, issue #7)

## 0.2.0-nullsafety

- Migrate for null safety

## 0.1.3

Expand Down
12 changes: 6 additions & 6 deletions example/vin_decoder_example.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:vin_decoder/nhtsa.dart';
import 'package:vin_decoder/vin_decoder.dart';
import 'package:custom_vin_decoder/nhtsa.dart';
import 'package:custom_vin_decoder/vin_decoder.dart';

void main() async {
var vin = VIN(number: 'WP0ZZZ99ZTS392124', extended: true);
var vin = VIN(vin: 'WP0ZZZ99ZTS392124', extended: true);

print('WMI: ${vin.wmi}');
print('VDS: ${vin.vds}');
Expand All @@ -11,7 +11,7 @@ void main() async {
print("Model year is " + vin.modelYear());
print("Serial number is " + vin.serialNumber());
print("Assembly plant is " + vin.assemblyPlant());
print("Manufacturer is " + vin.getManufacturer());
print("Manufacturer is " + (vin.getManufacturer() ?? ""));
print("Year is " + vin.getYear().toString());
print("Region is " + vin.getRegion());
print("VIN string is " + vin.toString());
Expand All @@ -27,10 +27,10 @@ void main() async {
print("Type is ${type}");

var info = await NHTSA.decodeVin(vin.number);
print('Plant Country is ' + info.value('Plant Country'));
print('Plant Country is ' + (info?.value('Plant Country') ?? ""));

var values = await NHTSA.decodeVinValues(vin.number);
print('Manufacturer from NHTSA DB is ' + values['Manufacturer']);
print('Manufacturer from NHTSA DB is ' + values?['Manufacturer']);

var generated = VINGenerator().generate();
print('Randomly Generated VIN is ${generated}');
Expand Down
1 change: 0 additions & 1 deletion lib/nhtsa.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/// Support for querying NHTSA database by VIN.
// @dart=2.1
library nhtsa;

export 'src/nhtsa_model.dart';
66 changes: 38 additions & 28 deletions lib/src/nhtsa_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,34 @@ import 'dart:convert';
// NHTSA Results not relevant for a specific vehicle can be either null or N/A
const String _RESULT_NOT_APPLICABLE = 'Not Applicable';

// ignore: avoid_classes_with_only_static_members
/// A wrapper for the NHTSA REST API
class NHTSA {
static const String _uriBase = 'https://vpic.nhtsa.dot.gov/api/vehicles';

/// Obtain information about a given [vin] from the NHTSA DB.
static Future<NHTSAVehicleInfo> decodeVin(String vin) async {
static Future<NHTSAVehicleInfo?> decodeVin(String vin) async {
var path = _uriBase + '/DecodeVin/' + vin + '?format=json';
final response = await http.get(Uri.parse(path));

if (response.statusCode == 200) {
return NHTSAVehicleInfo.fromJson(jsonDecode(response.body));
return NHTSAVehicleInfo.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
}

return null;
}

/// Obtain a map of key/value pairs containing known values for a given [vin]
static Future<Map<String, dynamic>> decodeVinValues(String vin) async {
static Future<Map<String, dynamic>?> decodeVinValues(String vin) async {
var path = _uriBase + '/DecodeVinValues/' + vin + '?format=json';
final response = await http.get(Uri.parse(path));

// The DecodeVinValues endpoint returns a single Results object with all
// variables and values as an array of encapsulated key/value pairs.
// Manually unpack this in order to provide the caller a populated Dart map.
if (response.statusCode == 200) {
Map<String, dynamic> data = jsonDecode(response.body);
Map<String, dynamic> map = data['Results'][0];
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
final Map<String, dynamic> map = data['Results'][0] as Map<String, dynamic>;
// Discard empty and not applicable entries from map
map.removeWhere((key, value) =>
value == null || value == _RESULT_NOT_APPLICABLE || value == '');
Expand All @@ -45,25 +46,27 @@ class NHTSA {
/// The result of a single data point from the NHTSA DB for a specific variable.
class NHTSAResult {
/// The value associated with a given [variable] or [variableId]
String value;
String? value;

/// The ID number associated with a given [value]
String valueId;
String? valueId;

/// The variable name
String variable;
String? variable;

/// The ID number of a given [variable]
int variableId;
int? variableId;

NHTSAResult({this.value, this.valueId, this.variable, this.variableId});
NHTSAResult({required this.value, required this.valueId, required this.variable, required this.variableId});

/// Create a new [NHTSAResult] instance from a fixed JSON payload
NHTSAResult.fromJson(Map<String, dynamic> json) {
value = json['Value'];
valueId = json['ValueId'];
variable = json['Variable'];
variableId = json['VariableId'];
factory NHTSAResult.fromJson(Map<String, dynamic> json) {
return NHTSAResult(
value: json['Value'] as String?,
valueId: json['ValueId'] as String?,
variable: json['Variable'] as String?,
variableId: json['VariableId'] as int?
);
}

@override
Expand All @@ -80,13 +83,11 @@ class NHTSAVehicleInfo {
List<NHTSAResult> results = [];

NHTSAVehicleInfo(
{this.count, this.message, this.searchCriteria, this.results});
{required this.count, required this.message, required this.searchCriteria, required this.results});

/// Create a new [NHTSAVehicleInfo] instance from a fixed JSON payload
NHTSAVehicleInfo.fromJson(Map<String, dynamic> json) {
count = json['Count'];
message = json['Message'];
searchCriteria = json['SearchCriteria'];
/// Create a new [NHTSAVehicleInfo] instance from a fixed JSON payload.
factory NHTSAVehicleInfo.fromJson(Map<String, dynamic> json) {
List<NHTSAResult> results = [];
if (json['Results'] != null) {
json['Results'].forEach((v) {
if (v['Value'] != null &&
Expand All @@ -96,25 +97,34 @@ class NHTSAVehicleInfo {
}
});
}
return NHTSAVehicleInfo(
count: (json['Count'] as int?) ?? 0,
message: json['Message'] as String? ?? "",
searchCriteria: json['SearchCriteria'],
results: results
);
}

static String _normalizeStringValue(String s) {
static String? _normalizeStringValue(String? s) {
if (s == null){
return null;
}
return s.splitMapJoin(' ',
onNonMatch: (m) => StringUtils.capitalize(m.toLowerCase()));
}

/// Lookup the value of a variable by its [variableId] in the NHTSA DB results
String valueFromId(int variableId) {
String? valueFromId(int? variableId) {
var result = results.singleWhere((e) => e.variableId == variableId,
orElse: () => null);
return result != null ? _normalizeStringValue(result.value) : null;
orElse: () => NHTSAResult(value: null, valueId: null, variable: null, variableId: null));
return _normalizeStringValue(result.value);
}

/// Lookup the value of a named [variable] in the NHTSA DB results
String value(String variable) {
String? value(String variable) {
var result =
results.singleWhere((e) => e.variable == variable, orElse: () => null);
return result != null ? _normalizeStringValue(result.value) : null;
results.singleWhere((e) => e.variable == variable, orElse: () => NHTSAResult(value: null, valueId: null, variable: null, variableId: null));
return _normalizeStringValue(result.value);
}

@override
Expand Down
64 changes: 44 additions & 20 deletions lib/src/vin_decoder_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import 'manufacturers.dart';
import 'nhtsa_model.dart';
import 'year_map.dart';

import 'package:meta/meta.dart';

class VIN {
/// The VIN that the class was instantiated with.
final String number;
Expand All @@ -21,14 +19,26 @@ class VIN {
final bool extended;
Map<String, dynamic> _vehicleInfo = {};

VIN({@required this.number, this.extended = false})
: wmi = normalize(number).substring(0, 3),
vds = normalize(number).substring(3, 9),
vis = normalize(number).substring(9, 17);
/// Private named constructor. Creates a new VIN.
///
/// [wmi], [vds], and [vis] are populated based on [number].
VIN._({required this.number, required this.extended}) :
wmi = number.substring(0, 3),
vds = number.substring(3, 9),
vis = number.substring(9, 17);

/// Creates a new VIN.
///
/// This factory constructor makes sure the string is normallyed
factory VIN({required String vin, bool extended = false}){
return VIN._(number: normalize(vin), extended: extended);
}

/// Carry out VIN validation. A valid [number] must be 17 characters long
/// and contain only valid alphanumeric characters.
bool valid([String number]) {
///
/// If a number is provided, validates that number. Otherwise, it validates the number this object was initialized with.
bool valid([String? number]) {
String value = normalize(number != null ? number : this.number);
return RegExp(r"^[a-zA-Z0-9]+$").hasMatch(value) && value.length == 17;
}
Expand All @@ -39,7 +49,7 @@ class VIN {

/// Obtain the encoded manufacturing year in YYYY format.
int getYear() {
return yearMap[modelYear()];
return yearMap[modelYear()] ?? 2001;
}

/// Obtain the 2-character region code for the manufacturing region.
Expand All @@ -66,7 +76,9 @@ class VIN {
}

/// Get the full name of the vehicle manufacturer as defined by the [wmi].
String getManufacturer() {
///
/// If the full name cannot be found, returns null.
String? getManufacturer() {
// Check for the standard case - a 3 character WMI
if (manufacturers.containsKey(this.wmi)) {
return manufacturers[this.wmi];
Expand All @@ -77,30 +89,33 @@ class VIN {
if (manufacturers.containsKey(id)) {
return manufacturers[id];
} else {
return "Unknown (WMI: ${this.wmi.toUpperCase()})";
return null;
}
}
}

/// Returns the checksum for the VIN. Note that in the case of the EU region
/// checksums are not implemented, so this becomes a no-op. More information
/// is provided in ISO 3779:2009.
String getChecksum() {
return (getRegion() != "EU") ? normalize(this.number)[8] : null;
///
/// If the region is EU, returns null
String? getChecksum() {
return (getRegion() != "EU") ? this.number[8] : null;
}

/// Extract the single-character model year from the [number].
String modelYear() => normalize(this.number)[9];
String modelYear() => this.number[9];

/// Extract the single-character assembly plant designator from the [number].
String assemblyPlant() => normalize(this.number)[10];
String assemblyPlant() => this.number[10];

/// Extract the serial number from the [number].
String serialNumber() => normalize(this.number).substring(12, 17);
String serialNumber() => this.number.substring(12, 17);

/// Assigns the
Future<void> _fetchExtendedVehicleInfo() async {
if (this._vehicleInfo.isEmpty && extended == true) {
this._vehicleInfo = await NHTSA.decodeVinValues(this.number);
this._vehicleInfo = await NHTSA.decodeVinValues(this.number) ?? {};
}
}

Expand All @@ -111,15 +126,24 @@ class VIN {
return this._vehicleInfo['Make'];
}

/// Get the Model of the vehicle from the NHTSA database if [extended] mode
/// is enabled.
/// Get the Make ID of a vehicle from the NHTSA database if the [extended] mode is enabled
Future<int> getMakeIdAsync() async {
await _fetchExtendedVehicleInfo();
return this._vehicleInfo["MakeID"];
}

/// Get the Model of the vehicle from the NHTSA database if [extended] mode is enabled.
Future<String> getModelAsync() async {
await _fetchExtendedVehicleInfo();
return this._vehicleInfo['Model'];
}

/// Get the Vehicle Type from the NHTSA database if [extended] mode is
/// enabled.
Future<String> getModelIdAsync() async {
await _fetchExtendedVehicleInfo();
return this._vehicleInfo['ModelID'];
}

/// Get the Vehicle Type from the NHTSA database if [extended] mode is enabled.
Future<String> getVehicleTypeAsync() async {
await _fetchExtendedVehicleInfo();
return this._vehicleInfo['VehicleType'];
Expand Down
Loading