diff --git a/AUTHORS b/AUTHORS index 7d248e8..580b1f6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,4 +3,5 @@ # # Name/Organization -Adaptant Labs \ No newline at end of file +Adaptant Labs +Stemco \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 544bafd..d33e625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) @@ -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 diff --git a/example/vin_decoder_example.dart b/example/vin_decoder_example.dart index 96fe069..6b0de2a 100644 --- a/example/vin_decoder_example.dart +++ b/example/vin_decoder_example.dart @@ -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}'); @@ -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()); @@ -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}'); diff --git a/lib/nhtsa.dart b/lib/nhtsa.dart index 0bddffa..44fd877 100644 --- a/lib/nhtsa.dart +++ b/lib/nhtsa.dart @@ -1,5 +1,4 @@ /// Support for querying NHTSA database by VIN. -// @dart=2.1 library nhtsa; export 'src/nhtsa_model.dart'; diff --git a/lib/src/nhtsa_model.dart b/lib/src/nhtsa_model.dart index af28dfc..7244959 100644 --- a/lib/src/nhtsa_model.dart +++ b/lib/src/nhtsa_model.dart @@ -5,24 +5,25 @@ 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 decodeVin(String vin) async { + static Future 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); } return null; } /// Obtain a map of key/value pairs containing known values for a given [vin] - static Future> decodeVinValues(String vin) async { + static Future?> decodeVinValues(String vin) async { var path = _uriBase + '/DecodeVinValues/' + vin + '?format=json'; final response = await http.get(Uri.parse(path)); @@ -30,8 +31,8 @@ class NHTSA { // 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 data = jsonDecode(response.body); - Map map = data['Results'][0]; + final Map data = jsonDecode(response.body) as Map; + final Map map = data['Results'][0] as Map; // Discard empty and not applicable entries from map map.removeWhere((key, value) => value == null || value == _RESULT_NOT_APPLICABLE || value == ''); @@ -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 json) { - value = json['Value']; - valueId = json['ValueId']; - variable = json['Variable']; - variableId = json['VariableId']; + factory NHTSAResult.fromJson(Map json) { + return NHTSAResult( + value: json['Value'] as String?, + valueId: json['ValueId'] as String?, + variable: json['Variable'] as String?, + variableId: json['VariableId'] as int? + ); } @override @@ -80,13 +83,11 @@ class NHTSAVehicleInfo { List 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 json) { - count = json['Count']; - message = json['Message']; - searchCriteria = json['SearchCriteria']; + /// Create a new [NHTSAVehicleInfo] instance from a fixed JSON payload. + factory NHTSAVehicleInfo.fromJson(Map json) { + List results = []; if (json['Results'] != null) { json['Results'].forEach((v) { if (v['Value'] != null && @@ -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 diff --git a/lib/src/vin_decoder_base.dart b/lib/src/vin_decoder_base.dart index 21d84d1..5cd5cec 100644 --- a/lib/src/vin_decoder_base.dart +++ b/lib/src/vin_decoder_base.dart @@ -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; @@ -21,14 +19,26 @@ class VIN { final bool extended; Map _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; } @@ -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. @@ -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]; @@ -77,7 +89,7 @@ class VIN { if (manufacturers.containsKey(id)) { return manufacturers[id]; } else { - return "Unknown (WMI: ${this.wmi.toUpperCase()})"; + return null; } } } @@ -85,22 +97,25 @@ class VIN { /// 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 _fetchExtendedVehicleInfo() async { if (this._vehicleInfo.isEmpty && extended == true) { - this._vehicleInfo = await NHTSA.decodeVinValues(this.number); + this._vehicleInfo = await NHTSA.decodeVinValues(this.number) ?? {}; } } @@ -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 getMakeIdAsync() async { + await _fetchExtendedVehicleInfo(); + return this._vehicleInfo["MakeID"]; + } + + /// Get the Model of the vehicle from the NHTSA database if [extended] mode is enabled. Future getModelAsync() async { await _fetchExtendedVehicleInfo(); return this._vehicleInfo['Model']; } - /// Get the Vehicle Type from the NHTSA database if [extended] mode is - /// enabled. + Future getModelIdAsync() async { + await _fetchExtendedVehicleInfo(); + return this._vehicleInfo['ModelID']; + } + + /// Get the Vehicle Type from the NHTSA database if [extended] mode is enabled. Future getVehicleTypeAsync() async { await _fetchExtendedVehicleInfo(); return this._vehicleInfo['VehicleType']; diff --git a/lib/src/year_map.dart b/lib/src/year_map.dart index 075277a..3bbbebb 100644 --- a/lib/src/year_map.dart +++ b/lib/src/year_map.dart @@ -1,32 +1,32 @@ const yearMap = { - 'N': 1992, - 'P': 1993, - 'R': 1994, - 'S': 1995, - 'T': 1996, - 'V': 1997, - 'W': 1998, - 'X': 1999, - 'Y': 2000, - '1': 2001, - '2': 2002, - '3': 2003, - '4': 2004, - '5': 2005, - '6': 2006, - '7': 2007, - '8': 2008, - '9': 2009, - 'A': 2010, - 'B': 2011, - 'C': 2012, - 'D': 2013, - 'E': 2014, - 'F': 2015, - 'G': 2016, - 'H': 2017, - 'J': 2018, - 'K': 2019, - 'L': 2020, - 'M': 2021, + "S": 1995, + "T": 1996, + "V": 1997, + "W": 1998, + "X": 1999, + "Y": 2000, + "1": 2001, + "2": 2002, + "3": 2003, + "4": 2004, + "5": 2005, + "6": 2006, + "7": 2007, + "8": 2008, + "9": 2009, + "A": 2010, + "B": 2011, + "C": 2012, + "D": 2013, + "E": 2014, + "F": 2015, + "G": 2016, + "H": 2017, + "J": 2018, + "K": 2019, + "L": 2020, + "M": 2021, + "N": 2022, + "P": 2023, + "R": 2024, }; diff --git a/lib/vin_decoder.dart b/lib/vin_decoder.dart index a596681..84467c7 100644 --- a/lib/vin_decoder.dart +++ b/lib/vin_decoder.dart @@ -1,5 +1,4 @@ /// Support for VIN parsing, validation, and generation. -// @dart=2.1 library vin_decoder; diff --git a/pubspec.yaml b/pubspec.yaml index 858c2d8..dbdedb9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,20 @@ -name: vin_decoder +name: custom_vin_decoder description: Dart library for working with Vehicle Identification Numbers (VINs) based on ISO 3779:2009 and World Manufacturer Identifiers (WMIs) based on ISO 3780:2009, enriched by NHTSA data. -version: 0.1.4+1 +version: 0.2.1 repository: https://github.com/adaptant-labs/vin-decoder-dart issue_tracker: https://github.com/adaptant-labs/vin-decoder-dart/issues environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.12.0 < 3.0.0' dependencies: - meta: ^1.4.0 + meta: ^1.8.0 basic_utils: ^3.4.0 http: ^0.13.3 - random_string: ^2.1.0 + random_string: ^2.2.0-nullsafety dev_dependencies: - test: ^1.15.7 + test: ^1.17.5 diff --git a/test/vin_decoder_test.dart b/test/vin_decoder_test.dart index 1527ae2..e8a190e 100644 --- a/test/vin_decoder_test.dart +++ b/test/vin_decoder_test.dart @@ -1,9 +1,9 @@ -import 'package:vin_decoder/vin_decoder.dart'; +import 'package:custom_vin_decoder/vin_decoder.dart'; import 'package:test/test.dart'; void main() { group('EU VIN Test', () { - final vin = VIN(number: 'WP0ZZZ99ZTS392124'); + final vin = VIN(vin: 'WP0ZZZ99ZTS392124'); test('Validity Test', () { expect(vin.valid(), isTrue); @@ -19,7 +19,7 @@ void main() { }); group('AS VIN Test', () { - final vin = VIN(number: 'JS1VX51L7X2175460'); + final vin = VIN(vin: 'JS1VX51L7X2175460'); test('Validity Test', () { expect(vin.valid(), isTrue); @@ -31,9 +31,14 @@ void main() { }); group('2-character WMI Manufacturer Test', () { - // Here the first 2 characters refer to the manufacturer, with the 3rd - // representing the class of vehicle specific to that manufacturer. - final vin = VIN(number: '5TENL42N94Z436445'); + + late VIN vin; + + setUp(() { + // Here the first 2 characters refer to the manufacturer, with the 3rd + // representing the class of vehicle specific to that manufacturer. + vin = VIN(vin: '5TENL42N94Z436445'); + }); test('Validity Test', () { expect(vin.valid(), isTrue); diff --git a/test/vin_generator_test.dart b/test/vin_generator_test.dart index 9e4b067..420f3ac 100644 --- a/test/vin_generator_test.dart +++ b/test/vin_generator_test.dart @@ -1,14 +1,14 @@ -import 'package:vin_decoder/vin_decoder.dart'; +import 'package:custom_vin_decoder/vin_decoder.dart'; import 'package:test/test.dart'; void main() { group('VIN Generator Test', () { VINGenerator generator; - VIN vin; + late VIN vin; setUp(() { generator = VINGenerator(); - vin = VIN(number: generator.generate()); + vin = VIN(vin: generator.generate()); }); test('Validity Test', () {