Committing the complete mess. I managed to trial&error on converting the php fantasy dictionaries into structs. Though some of the field types are mere blind guesses, so fingers crossed.

Signed-off-by: Adam Rocska <adam.rocska@adams.solutions>
This commit is contained in:
Adam Rocska 2020-06-02 16:38:44 +02:00
parent 1bf6817aab
commit e1a5976007
18 changed files with 348 additions and 76 deletions

View File

@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="rocskaadam">
<words>
<w>geoname</w>
<w>hexadectet</w>
<w>peekable</w>
<w>uniquing</w>

View File

@ -1,8 +1,37 @@
import Foundation
import enum Decoder.Payload
public struct CityModel {
let city: CityRecord
let location: LocationRecord
let postal: PostalRecord
let mostSpecificSubdivision: SubdivisionRecord
let subdivisions: [SubdivisionRecord]
var mostSpecificSubdivision: SubdivisionRecord {
get {
return subdivisions.last ?? SubdivisionRecord(nil)
}
}
public init(
city: CityRecord,
location: LocationRecord,
postal: PostalRecord,
subdivisions: [SubdivisionRecord]
) {
self.city = city
self.location = location
self.postal = postal
self.subdivisions = subdivisions
}
init(_ dictionary: [String: Payload]?) {
self.init(
city: CityRecord(dictionary?["city"]?.unwrap()),
location: LocationRecord(dictionary?["location"]?.unwrap()),
postal: PostalRecord(dictionary?["postal"]?.unwrap()),
subdivisions: (dictionary?["subdivisions"]?.unwrap() ?? [] as [Payload]).compactMap(
{ SubdivisionRecord($0.unwrap()) }
)
)
}
}

View File

@ -9,12 +9,142 @@ public class CityFileBased {
public var metadata: DbMetadata { get { return dbReader.metadata } }
public init(dbReader: Reader) {
precondition(dbReader.metadata.databaseType == CityFileBased.databaseType.rawValue)
// php scripter style validation. Dumb as a rock, but that's what MaxMind implemented, and therefore expects.
precondition(dbReader.metadata.databaseType.contains(CityFileBased.databaseType.rawValue))
self.dbReader = dbReader
}
public func lookup(_ ip: IpAddress) -> CityModel? {
guard let dictionary = dbReader.get(ip) else { return nil }
// print(dictionary)
let model = CityModel(dictionary)
print(model)
// ["location": Decoder.Payload.map(
// ["time_zone": Decoder.Payload.utf8String("Europe/Budapest"),
// "longitude": Decoder.Payload.double(19.0404),
// "latitude": Decoder.Payload.double(47.4984),
// "accuracy_radius": Decoder.Payload.uInt16(20)]
// ),
// "continent": Decoder.Payload.map(
// ["geoname_id": Decoder.Payload.uInt32(6255148),
// "code": Decoder.Payload.utf8String("EU"),
// "names": Decoder.Payload.map(
// [
// "es": Decoder.Payload.utf8String("Europa"),
// "fr": Decoder.Payload.utf8String("Europe"),
// "ru": Decoder.Payload.utf8String("Европа"),
// "pt-BR": Decoder.Payload.utf8String("Europa"),
// "ja": Decoder.Payload.utf8String(""),
// "zh-CN": Decoder.Payload.utf8String(""),
// "de": Decoder.Payload.utf8String("Europa"),
// "en": Decoder.Payload.utf8String("Europe")
// ]
// )]
// ),
// "country": Decoder.Payload.map(
// ["names": Decoder.Payload.map(
// ["es": Decoder.Payload.utf8String("Hungría"),
// "ja": Decoder.Payload.utf8String(
// ""
// ),
// "en": Decoder.Payload.utf8String("Hungary"),
// "ru": Decoder.Payload.utf8String(
// "Венгрия"
// ),
// "pt-BR": Decoder.Payload.utf8String("Hungria"),
// "zh-CN": Decoder.Payload.utf8String(
// ""
// ),
// "de": Decoder.Payload.utf8String("Ungarn"),
// "fr": Decoder.Payload.utf8String("Hongrie")]
// ),
// "is_in_european_union": Decoder.Payload.boolean(true),
// "iso_code": Decoder.Payload.utf8String(
// "HU"
// ),
// "geoname_id": Decoder.Payload.uInt32(719819)]
// ),
// "subdivisions": Decoder.Payload.array(
// [Decoder.Payload.map(
// ["geoname_id": Decoder.Payload.uInt32(3054638),
// "iso_code": Decoder.Payload.utf8String(
// "BU"
// ),
// "names": Decoder.Payload.map(["en": Decoder.Payload.utf8String("Budapest")])]
// )]
// ),
// "city": Decoder.Payload.map(
// ["geoname_id": Decoder.Payload.uInt32(3054643),
// "names": Decoder.Payload.map(
// ["ja": Decoder.Payload.utf8String(""),
// "es": Decoder.Payload.utf8String(
// "Budapest"
// ),
// "ru": Decoder.Payload.utf8String("Будапешт"),
// "de": Decoder.Payload.utf8String(
// "Budapest"
// ),
// "en": Decoder.Payload.utf8String("Budapest"),
// "fr": Decoder.Payload.utf8String(
// "Budapest"
// ),
// "zh-CN": Decoder.Payload.utf8String(""),
// "pt-BR": Decoder.Payload.utf8String("Budapeste")]
// )]
// ),
// "registered_country": Decoder.Payload.map(
// ["geoname_id": Decoder.Payload.uInt32(719819),
// "is_in_european_union": Decoder.Payload.boolean(
// true
// ),
// "iso_code": Decoder.Payload.utf8String("HU"),
// "names": Decoder.Payload.map(
// ["de": Decoder.Payload.utf8String("Ungarn"),
// "zh-CN": Decoder.Payload.utf8String(
// ""
// ),
// "fr": Decoder.Payload.utf8String("Hongrie"),
// "ru": Decoder.Payload.utf8String(
// "Венгрия"
// ),
// "pt-BR": Decoder.Payload.utf8String("Hungria"),
// "en": Decoder.Payload.utf8String(
// "Hungary"
// ),
// "es": Decoder.Payload.utf8String("Hungría"),
// "ja": Decoder.Payload.utf8String("")]
// )]
// ),
// "postal": Decoder.Payload.map(["code": Decoder.Payload.utf8String("1096")])]
// CityModel(
// city: CityRecord(
// confidence: <#T##Int?##Swift.Int?#>,
// geonameId: <#T##Int?##Swift.Int?#>,
// name: <#T##String?##Swift.String?#>,
// names: <#T##[String: String]?##[Swift.String: Swift.String]?#>
// ),
// location: LocationRecord(
// averageIncome: <#T##Int?##Swift.Int?#>,
// accuracyRadius: <#T##Int?##Swift.Int?#>,
// latitude: <#T##Float?##Swift.Float?#>,
// longitude: <#T##Float?##Swift.Float?#>,
// populationDensity: <#T##Int?##Swift.Int?#>,
// metroCode: <#T##Int?##Swift.Int?#>,
// timeZone: <#T##String?##Swift.String?#>
// ),
// postal: PostalRecord(code: <#T##String?##Swift.String?#>,
// confidence: <#T##Int?##Swift.Int?#>),
// mostSpecificSubdivision: SubdivisionRecord(
// confidence: <#T##Int?##Swift.Int?#>,
// geonameId: <#T##Int?##Swift.Int?#>,
// isoCode: <#T##String?##Swift.String?#>,
// name: <#T##String?##Swift.String?#>,
// names: <#T##[String: String]?##[Swift.String: Swift.String]?#>
// )
// )
return nil
}
}

View File

@ -1,9 +1,24 @@
import Foundation
import enum Decoder.Payload
// WTF! SRSLY!
public struct CityRecord {
let confidence: Int?
let geonameId: Int?
let name: String?
// assumed
let confidence: UInt8?
let geonameId: UInt32?
let names: [String: String]?
var name: String? { get { return names?["en"] } }
public init(confidence: UInt8?, geonameId: UInt32?, names: [String: String]?) {
self.confidence = confidence
self.geonameId = geonameId
self.names = names
}
init(_ dictionary: [String: Payload]?) {
self.init(
confidence: dictionary?["confidence"]?.unwrap(),
geonameId: dictionary?["geoname_id"]?.unwrap(),
names: (dictionary?["names"]?.unwrap() ?? [:] as [String: Payload]).compactMapValues({ $0.unwrap() })
)
}
}

View File

@ -1,8 +1,23 @@
import Foundation
import enum Decoder.Payload
public struct ContinentRecord {
let code: String?
let geonameId: Int?
let name: String?
let geonameId: UInt32?
let names: [String: String]?
var name: String? { get { return names?["en"] } }
public init(code: String?, geonameId: UInt32?, names: [String: String]?) {
self.code = code
self.geonameId = geonameId
self.names = names
}
init(_ dictionary: [String: Payload]?) {
self.init(
code: dictionary?["code"]?.unwrap(),
geonameId: dictionary?["geoname_id"]?.unwrap(),
names: (dictionary?["names"]?.unwrap() ?? [:] as [String: Payload]).compactMapValues({ $0.unwrap() })
)
}
}

View File

@ -1,12 +1,35 @@
import Foundation
import enum Decoder.Payload
public struct CountryRecord {
// CONFIDENCE BEING AN INT?! ARE YOU FUCKING KIDDING ME! AND IT'S OPTIONAL.
let confidence: Int?
let geonameId: Int?
// At least the bool isn't optional. Thank's php people. 👍 🖕
let confidence: UInt8?
let geonameId: UInt32?
let isInEuropeanUnion: Bool
let isoCode: String?
let name: String?
let names: [String: String]?
var name: String? { get { return names?["en"] } }
public init(
confidence: UInt8?,
geonameId: UInt32?,
isInEuropeanUnion: Bool,
isoCode: String?,
names: [String: String]?
) {
self.confidence = confidence
self.geonameId = geonameId
self.isInEuropeanUnion = isInEuropeanUnion
self.isoCode = isoCode
self.names = names
}
init(_ dictionary: [String: Payload]?) {
self.init(
confidence: dictionary?["confidence"]?.unwrap(),
geonameId: dictionary?["geoname_id"]?.unwrap(),
isInEuropeanUnion: dictionary?["is_in_european_union"]?.unwrap() ?? false,
isoCode: dictionary?["iso_code"]?.unwrap(),
names: (dictionary?["names"]?.unwrap() ?? [:] as [String: Payload]).compactMapValues({ $0.unwrap() })
)
}
}

View File

@ -1,11 +1,45 @@
import Foundation
import enum Decoder.Payload
public struct LocationRecord {
let averageIncome: Int?
let accuracyRadius: Int?
let latitude: Float?
let longitude: Float?
let populationDensity: Int?
let metroCode: Int?
// assumed
let averageIncome: UInt32?
let accuracyRadius: UInt16?
let latitude: Double?
let longitude: Double?
// assumed
let populationDensity: UInt16?
// assumed
let metroCode: UInt16?
let timeZone: String?
public init(
averageIncome: UInt32?,
accuracyRadius: UInt16?,
latitude: Double?,
longitude: Double?,
populationDensity: UInt16?,
metroCode: UInt16?,
timeZone: String?
) {
self.averageIncome = averageIncome
self.accuracyRadius = accuracyRadius
self.latitude = latitude
self.longitude = longitude
self.populationDensity = populationDensity
self.metroCode = metroCode
self.timeZone = timeZone
}
init(_ dictionary: [String: Payload]?) {
self.init(
averageIncome: dictionary?["average_income"]?.unwrap(),
accuracyRadius: dictionary?["accuracy_radius"]?.unwrap(),
latitude: dictionary?["latitude"]?.unwrap(),
longitude: dictionary?["longitude"]?.unwrap(),
populationDensity: dictionary?["population_density"]?.unwrap(),
metroCode: dictionary?["metro_code"]?.unwrap(),
timeZone: dictionary?["time_zone"]?.unwrap()
)
}
}

View File

@ -1,6 +1,6 @@
import Foundation
public struct MaxMindRecord {
// I don't even know any more what to say. Stupidest shit I saw in years.
let queriesRemaining: Int?
// assumed
let queriesRemaining: UInt16?
}

View File

@ -1,6 +1,20 @@
import Foundation
import enum Decoder.Payload
public struct PostalRecord {
let code:String?
let confidence:Int?
let code: String?
// assumed
let confidence: UInt8?
public init(code: String?, confidence: UInt8?) {
self.code = code
self.confidence = confidence
}
init(_ dictionary: [String: Payload]?) {
self.init(
code: dictionary?["code"]?.unwrap(),
confidence: dictionary?["confidence"]?.unwrap()
)
}
}

View File

@ -1,5 +1,6 @@
import Foundation
// TODO: WTF
public struct RepresentedCountryRecord {
let type: String?
}

View File

@ -1,9 +1,28 @@
import Foundation
import enum Decoder.Payload
public struct SubdivisionRecord {
let confidence: Int?
let geonameId: Int?
// Assumed
let confidence: UInt8?
let geonameId: UInt32?
let isoCode: String?
let name: String?
let names: [String: String]?
var name: String? { get { return names?["en"] } }
public init(confidence: UInt8?, geonameId: UInt32?, isoCode: String?, names: [String: String]?) {
self.confidence = confidence
self.geonameId = geonameId
self.isoCode = isoCode
self.names = names
}
init(_ dictionary: [String: Payload]?) {
self.init(
confidence: dictionary?["confidence"]?.unwrap(),
geonameId: dictionary?["geoname_id"]?.unwrap(),
isoCode: dictionary?["iso_cod"]?.unwrap(),
names: (dictionary?["names"]?.unwrap() ?? [:] as [String: Payload]).compactMapValues({ $0.unwrap() })
)
}
}

View File

@ -5,7 +5,7 @@ import protocol DataSection.DataSection
import protocol IndexReader.Index
import struct MetadataReader.Metadata
public class InMemoryReader<SearchIndex>: Reader where SearchIndex: IndexReader.Index {
class Mediator<SearchIndex>: Reader where SearchIndex: IndexReader.Index {
private let index: SearchIndex
public let metadata: Metadata

View File

@ -3,12 +3,15 @@ import class DataSection.InMemoryDataSection
import class IndexReader.InMemoryIndex
import class MetadataReader.Reader
class ReaderFactory {
func makeInMemoryReader(_ inputStream: @escaping () -> InputStream) throws -> Reader {
public class ReaderFactory {
public init() {}
public func makeInMemoryReader(_ inputStream: @escaping () -> InputStream) throws -> Reader {
let reader = MetadataReader.Reader(windowSize: 2048)
guard let metadata = reader.read(inputStream()) else { throw ReaderError.cantExtractMetadata }
let inMemoryIndex = InMemoryIndex<UInt>(metadata: metadata, stream: inputStream())
let inMemoryDataSection = InMemoryDataSection(metadata: metadata, stream: inputStream())
return InMemoryReader(index: inMemoryIndex, dataSection: inMemoryDataSection, metadata: metadata)
return Mediator(index: inMemoryIndex, dataSection: inMemoryDataSection, metadata: metadata)
}
}

View File

@ -1,24 +0,0 @@
import Foundation
extension Data {
func chunked(into size: Int) -> [Data] {
precondition(size > 0, "Can't chunk data into 0 sized chunks.")
precondition(size <= count, "Can't chunk data into chunks of greater size.")
precondition(count % size == 0, "Data is not divisible into \(size)sized chunks.")
var bounds = (
lower: startIndex,
upper: index(startIndex, offsetBy: size, limitedBy: endIndex) ?? endIndex
)
var result: [Data] = []
while bounds.lower != endIndex {
result.append(subdata(in: Range(uncheckedBounds: bounds)))
bounds = (
lower: bounds.upper,
upper: index(bounds.upper, offsetBy: size, limitedBy: endIndex) ?? endIndex
)
}
return result
}
}

View File

@ -7,6 +7,8 @@ import enum IndexReader.IpAddress
import enum Decoder.Payload
@testable import struct MetadataReader.Metadata
import class DBReader.ReaderFactory
class CityFileBasedTest: XCTestCase {
let stubMetadata = Metadata(
@ -43,6 +45,20 @@ class CityFileBasedTest: XCTestCase {
XCTAssertTrue(getWasCalled)
}
func testLookup_wut() throws {
let factory = ReaderFactory()
let streamFactory: () -> InputStream = {
InputStream(
fileAtPath: "/Users/rocskaadam/src/adam-rocska/src/GeoIP2-swift/Tests/ApiTests/Resources/GeoLite2-City_20200526/GeoLite2-City.mmdb"
// fileAtPath: "/Users/rocskaadam/src/adam-rocska/src/GeoIP2-swift/Tests/ApiTests/Resources/GeoLite2-ASN_20200526/GeoLite2-ASN.mmdb"
// fileAtPath: "/Users/rocskaadam/src/adam-rocska/src/GeoIP2-swift/Tests/DBReaderTests/Resources/GeoLite2-Country_20200421/GeoLite2-Country.mmdb"
)!
}
let reader = try factory.makeInMemoryReader(streamFactory)
let cityFileBased = CityFileBased(dbReader: reader)
cityFileBased.lookup(.v4("80.99.18.166"))
}
}
class MockReader: DBReader.Reader {

View File

@ -7,7 +7,7 @@ import protocol DataSection.DataSection
import protocol IndexReader.Index
@testable import struct MetadataReader.Metadata
class InMemoryReaderTest: XCTestCase {
class MediatorTest: XCTestCase {
private let stubMetadata = Metadata(
nodeCount: 123,
@ -31,7 +31,7 @@ class InMemoryReaderTest: XCTestCase {
mockSearchIndexLookupCalled = true
return nil
}
let reader = InMemoryReader(index: mockSearchIndex, dataSection: MockDataSection(), metadata: stubMetadata)
let reader = Mediator(index: mockSearchIndex, dataSection: MockDataSection(), metadata: stubMetadata)
XCTAssertNil(reader.get(expectedIp))
XCTAssertTrue(mockSearchIndexLookupCalled)
}
@ -52,7 +52,7 @@ class InMemoryReaderTest: XCTestCase {
mockDataSectionCalled = true
return nil
}
let reader = InMemoryReader(index: mockSearchIndex, dataSection: mockDataSection, metadata: stubMetadata)
let reader = Mediator(index: mockSearchIndex, dataSection: mockDataSection, metadata: stubMetadata)
XCTAssertNil(reader.get(expectedIp))
XCTAssertTrue(mockSearchIndexLookupCalled)
XCTAssertTrue(mockDataSectionCalled)
@ -75,7 +75,7 @@ class InMemoryReaderTest: XCTestCase {
mockDataSectionCalled = true
return expectedLookupResult
}
let reader = InMemoryReader(index: mockSearchIndex, dataSection: mockDataSection, metadata: stubMetadata)
let reader = Mediator(index: mockSearchIndex, dataSection: mockDataSection, metadata: stubMetadata)
XCTAssertEqual(expectedLookupResult, reader.get(expectedIp))
XCTAssertTrue(mockSearchIndexLookupCalled)
XCTAssertTrue(mockDataSectionCalled)

View File

@ -1,18 +0,0 @@
import Foundation
import XCTest
@testable import IndexReader
class DataTest: XCTestCase {
func testChunked() {
let data = Data([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
let chunked = data.chunked(into: 2)
XCTAssertEqual(Data([1, 2]), chunked[0])
XCTAssertEqual(Data([3, 4]), chunked[1])
XCTAssertEqual(Data([5, 6]), chunked[2])
XCTAssertEqual(Data([7, 8]), chunked[3])
XCTAssertEqual(Data([9, 10]), chunked[4])
XCTAssertEqual(5, chunked.count)
}
}

14
smart.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
/Library/Developer/Toolchains/swift-5.0.3-RELEASE.xctoolchain/usr/bin/swift test \
--enable-code-coverage \
--parallel \
--num-workers 4
reset
llvm-cov report \
.build/x86_64-apple-macosx/debug/GeoIP2PackageTests.xctest/Contents/MacOS/GeoIP2PackageTests \
-instr-profile=.build/x86_64-apple-macosx/debug/codecov/default.profdata \
-ignore-filename-regex=".build|Tests" \
-use-color