Files
fuelboard/Shared/FuelPriceProvider.swift
T

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." }
}