114 lines
5.3 KiB
Swift
114 lines
5.3 KiB
Swift
// FuelPriceProvider.swift — data-source seam for FuelBoard.
|
|
//
|
|
// The official UK Fuel Finder API (api.fuelfinder.service.gov.uk) is the live
|
|
// source for station-level prices, but it requires GOV.UK One Login + OAuth 2.0
|
|
// client credentials. For the scaffold we ship SampleFuelProvider (realistic
|
|
// stations around a location, works offline), plus the FuelFinderProvider
|
|
// skeleton with the exact integration steps documented inline so wiring the
|
|
// live API later is a drop-in: change `activeProvider` to `.fuelFinder`.
|
|
|
|
import Foundation
|
|
|
|
// MARK: - Provider protocol
|
|
|
|
protocol FuelPriceProviding {
|
|
/// Fetch stations with prices. `location` may be nil (sort by price only).
|
|
/// Throws on failure so callers can fall back to cached/sample data.
|
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType) async throws -> [FuelStation]
|
|
}
|
|
|
|
enum FuelPriceProvider {
|
|
static let active: FuelPriceProviding = SampleFuelProvider()
|
|
}
|
|
|
|
// MARK: - Sample provider (default for the scaffold)
|
|
|
|
/// Ships realistic stations around Halifax, West Yorkshire (53.7270, -1.8575)
|
|
/// so the app and widget render meaningful distances for the user with zero
|
|
/// setup. Prices in pence/litre.
|
|
struct SampleFuelProvider: FuelPriceProviding {
|
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType) async throws -> [FuelStation] {
|
|
try await Task.sleep(nanoseconds: 300_000_000) // simulate fetch
|
|
return Self.sampleStations
|
|
}
|
|
|
|
static let sampleStations: [FuelStation] = [
|
|
FuelStation(id: "h1", name: "Tesco Express Halifax", brand: "Tesco",
|
|
address: "12 Clare Road", postcode: "HX1 2HX",
|
|
lat: 53.7200, lng: -1.8630,
|
|
prices: [.e10: 138.9, .e5: 146.9, .diesel: 145.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h2", name: "Shell Skircoat Road", brand: "Shell",
|
|
address: "245 Skircoat Road", postcode: "HX3 0HP",
|
|
lat: 53.7120, lng: -1.8710,
|
|
prices: [.e10: 142.9, .e5: 149.9, .diesel: 148.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h3", name: "Morrisons Halifax", brand: "Morrisons",
|
|
address: "Haugh Shaw Road", postcode: "HX1 3TU",
|
|
lat: 53.7265, lng: -1.8580,
|
|
prices: [.e10: 137.9, .e5: 144.9, .diesel: 144.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h4", name: "BP Queensbury", brand: "BP",
|
|
address: "316 High Street, Queensbury", postcode: "BD13 2NB",
|
|
lat: 53.7550, lng: -1.8450,
|
|
prices: [.e10: 140.9, .diesel: 147.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h5", name: "Asda Halifax", brand: "Asda",
|
|
address: "Ovenden Way", postcode: "HX2 8DD",
|
|
lat: 53.7340, lng: -1.8890,
|
|
prices: [.e10: 136.9, .e5: 143.9, .diesel: 143.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h6", name: "Esso Sowerby Bridge", brand: "Esso",
|
|
address: "Wharf Street", postcode: "HX6 2AH",
|
|
lat: 53.7080, lng: -1.9080,
|
|
prices: [.e10: 141.9, .e5: 148.9, .diesel: 147.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h7", name: "Sainsbury's Elland", brand: "Sainsbury's",
|
|
address: "Southgate", postcode: "HX5 0PA",
|
|
lat: 53.6860, lng: -1.8380,
|
|
prices: [.e10: 139.9, .e5: 147.9, .diesel: 146.9],
|
|
priceUpdated: nil),
|
|
FuelStation(id: "h8", name: "Gulf Brighouse", brand: "Gulf",
|
|
address: "Bradford Road", postcode: "HD6 1RW",
|
|
lat: 53.7000, lng: -1.7850,
|
|
prices: [.e10: 143.9, .e5: 151.9, .diesel: 149.9],
|
|
priceUpdated: nil),
|
|
]
|
|
}
|
|
|
|
// MARK: - Fuel Finder API provider (integration point)
|
|
|
|
/// Live provider skeleton for the official GOV.UK Fuel Finder API.
|
|
///
|
|
/// To activate:
|
|
/// 1. Sign in at developer.fuel-finder.service.gov.uk with GOV.UK One Login,
|
|
/// register an app, and generate client credentials (client_id + secret).
|
|
/// 2. Implement token fetch: POST to the token endpoint with OAuth 2.0
|
|
/// client_credentials grant → access_token (expires in 1h).
|
|
/// 3. GET prices with `Authorization: Bearer <token>` and decode the
|
|
/// stations JSON into [FuelStation].
|
|
///
|
|
/// Endpoints (per gov.uk docs):
|
|
/// base: https://api.fuelfinder.service.gov.uk
|
|
/// auth: base + /v1/token
|
|
/// prices: base + /v1/prices (full snapshot)
|
|
/// price: base + /v1/prices/GB-12345 (single station)
|
|
///
|
|
/// Then switch `FuelPriceProvider.active` to `FuelFinderProvider(...)`.
|
|
struct FuelFinderProvider: FuelPriceProviding {
|
|
let baseURL = URL(string: "https://api.fuelfinder.service.gov.uk")!
|
|
let clientID: String
|
|
let clientSecret: String
|
|
|
|
func fetchStations(near lat: Double?, lng: Double?, fuel: FuelType) async throws -> [FuelStation] {
|
|
// TODO: OAuth token → GET /v1/prices → map to FuelStation.
|
|
// The live API requires authentication; see notes above.
|
|
throw FuelProviderError.notImplemented
|
|
}
|
|
}
|
|
|
|
enum FuelProviderError: LocalizedError {
|
|
case notImplemented
|
|
var errorDescription: String? { "Live Fuel Finder API not wired yet — activate with GOV.UK One Login credentials." }
|
|
}
|