import 'dart:convert'; import 'package:http/http.dart' as http; import 'alpaca_env.dart'; import 'alpaca_models.dart'; /// REST client for Alpaca's Trading-API `/v2/assets` endpoint. /// /// The asset universe lives behind the trading host, NOT the data host — /// see [AlpacaEnv.tradingBaseUrl]. We treat it as read-only here; the /// tradable_assets sync (§2.2) is the only writer to the DB. class AlpacaAssetsClient { AlpacaAssetsClient({ required AlpacaEnv env, http.Client? httpClient, }) : _env = env, _client = httpClient ?? http.Client(); final AlpacaEnv _env; final http.Client _client; /// `GET ${tradingBaseUrl}/v2/assets?status=active&asset_class=us_equity`. /// /// Filters to `tradable=true` are applied **server-side** by the caller /// (the asset-universe sync) so the client stays a thin wrapper and the /// audit trail in [tradable_assets] still records inactive symbols when /// they later disappear. Future> listActiveTradable() async { _env.requireCredentials(); final Uri uri = Uri.parse('${_env.tradingBaseUrl}/v2/assets').replace( queryParameters: { 'status': 'active', 'asset_class': 'us_equity', }, ); final http.Response response; try { response = await _client.get(uri, headers: _env.authHeaders); } on http.ClientException catch (e) { throw AlpacaAssetsException( 'GET /v2/assets transport error: ${e.message}', ); } if (response.statusCode != 200) { throw AlpacaAssetsException( 'GET /v2/assets failed: ' '${response.statusCode} ${response.body}', ); } final dynamic decoded = jsonDecode(response.body); if (decoded is! List) { throw AlpacaAssetsException( 'GET /v2/assets returned non-list body: ${response.body}', ); } return decoded .whereType() .map((Map m) => AlpacaAsset.fromJson(Map.from(m))) .toList(growable: false); } void close() => _client.close(); } /// Thrown by [AlpacaAssetsClient] when an upstream HTTP call fails. /// /// The message always includes the HTTP status code (when applicable) and /// the response body so the caller's audit row can capture it verbatim. class AlpacaAssetsException implements Exception { AlpacaAssetsException(this.message); final String message; @override String toString() => message; }