Files
fuelboard/Shared/FuelPriceProvider.swift
T

113 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 Cambridge (52.2053, 0.1218) so the app and
/// widget render meaningful data 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: "s1", name: "Shell Cambridge Retail Park", brand: "Shell",
address: "12 Retail Park Way", postcode: "CB1 3EW",
lat: 52.1955, lng: 0.1380,
prices: [.e10: 142.9, .e5: 149.9, .diesel: 148.9],
priceUpdated: nil),
FuelStation(id: "s2", name: "Tesco Express Hills Road", brand: "Tesco",
address: "245 Hills Road", postcode: "CB2 8RP",
lat: 52.1809, lng: 0.1398,
prices: [.e10: 138.9, .e5: 146.9, .diesel: 145.9],
priceUpdated: nil),
FuelStation(id: "s3", name: "BP Milton Road", brand: "BP",
address: "161 Milton Road", postcode: "CB4 1XE",
lat: 52.2160, lng: 0.1410,
prices: [.e10: 140.9, .diesel: 147.9],
priceUpdated: nil),
FuelStation(id: "s4", name: "Morrisons Newmarket Road", brand: "Morrisons",
address: "Newmarket Road", postcode: "CB5 8AA",
lat: 52.2164, lng: 0.1599,
prices: [.e10: 137.9, .e5: 144.9, .diesel: 144.9],
priceUpdated: nil),
FuelStation(id: "s5", name: "Sainsbury's Coldhams Lane", brand: "Sainsbury's",
address: "Coldhams Lane", postcode: "CB1 3HY",
lat: 52.2025, lng: 0.1589,
prices: [.e10: 139.9, .e5: 147.9, .diesel: 146.9],
priceUpdated: nil),
FuelStation(id: "s6", name: "Esso Cherry Hinton", brand: "Esso",
address: "Cherry Hinton Road", postcode: "CB1 9AP",
lat: 52.1854, lng: 0.1657,
prices: [.e10: 141.9, .e5: 148.9, .diesel: 147.9],
priceUpdated: nil),
FuelStation(id: "s7", name: "Gulf Fen Road", brand: "Gulf",
address: "Fen Road", postcode: "CB4 1UN",
lat: 52.2201, lng: 0.1470,
prices: [.e10: 143.9, .e5: 151.9, .diesel: 149.9],
priceUpdated: nil),
FuelStation(id: "s8", name: "Asda Beehive Centre", brand: "Asda",
address: "Coldhams Lane", postcode: "CB1 3ER",
lat: 52.1995, lng: 0.1641,
prices: [.e10: 136.9, .e5: 143.9, .diesel: 143.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." }
}