Skip to content

Add Indian Bonds module and Flutter preview app with mock NSE CBRICS transport#1

Open
SumitUni7 wants to merge 1 commit intomainfrom
codex/add-bond-module-for-indian-market
Open

Add Indian Bonds module and Flutter preview app with mock NSE CBRICS transport#1
SumitUni7 wants to merge 1 commit intomainfrom
codex/add-bond-module-for-indian-market

Conversation

@SumitUni7
Copy link
Copy Markdown
Owner

@SumitUni7 SumitUni7 commented Mar 31, 2026

Motivation

  • Provide a self-contained Bonds feature demonstrating Indian market (NSE CBRICS) order flow and UI without backend dependencies.
  • Enable quick local previews and manual QA of instrument listing and order entry via an in-memory mock transport.
  • Establish a baseline domain, data, repository, use-case, controller and UI structure for future production integration and tests.

Description

  • Added a runnable Flutter entrypoint (lib/main.dart) and app-level pubspec.yaml plus a top-level README.md explaining how to run the preview app with BondPreviewPage.
  • Implemented domain models and repository contract: Bond and BondOrder entities, and BondRepository interface under lib/features/bonds/domain.
  • Implemented data layer and mock transport: NseCbricsDataSource, MockNseCbricsTransport, BondModel, and BondOrderModel for JSON mapping and translation of NSE CBRICS-style payloads.
  • Implemented repository and use case: BondRepositoryImpl and PlaceBondOrder for basic validation and order submission wiring.
  • Implemented presentation layer: BondController, UI pages BondPage and BondPreviewPage, and BondOrderTicket widget for placing limit buy orders using the mock transport.
  • Added module-level documentation at lib/features/bonds/README.md describing endpoints, wiring examples, and next steps for production hardening.

Testing

  • No automated unit or widget tests were added in this change set.
  • Performed static checks by running flutter analyze and ran flutter test (no tests present), with no blocking analyzer errors reported.

Codex Task

Summary by CodeRabbit

Release Notes

  • New Features

    • Indian Bond Module added with NSE CBRICS market data integration.
    • Browse available bonds and view detailed information including maturity dates, coupon rates, and issuer details.
    • Place limit buy orders with customizable quantity and price.
    • Track and view open orders.
    • Pull-to-refresh support for real-time bond data updates.
  • Documentation

    • Added comprehensive guides for module setup and usage.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Introduces a complete Flutter Bond Trading Module for Indian markets (NSE CBRICS), implementing clean architecture with domain entities, repository pattern, use cases, a presentation layer with UI components, HTTP data source abstraction, mock transport for local preview, and comprehensive documentation.

Changes

Cohort / File(s) Summary
Domain Layer
lib/features/bonds/domain/entities/bond.dart, lib/features/bonds/domain/entities/bond_order.dart, lib/features/bonds/domain/repositories/bond_repository.dart
Introduced domain entities Bond and BondOrder with three enums (BondOrderSide, BondOrderType, BondOrderStatus) and abstract repository contract defining getAvailableBonds(), placeOrder(), and getOpenOrders() methods.
Data Models
lib/features/bonds/data/models/bond_model.dart, lib/features/bonds/data/models/bond_order_model.dart
Added model classes extending domain entities with fromJson factories for deserializing NSE CBRICS API responses, including enum conversion helpers for order sides, types, and statuses.
Data Sources & Transport
lib/features/bonds/data/datasources/nse_cbrics_datasource.dart, lib/features/bonds/data/datasources/mock_nse_cbrics_transport.dart
Implemented NseCbricsDataSource with HTTP abstraction HttpTransport for API interaction; added MockNseCbricsTransport providing in-memory mock responses for /bonds, /orders, and /orders/open endpoints to enable preview mode without backend dependencies.
Repository Implementation
lib/features/bonds/data/repositories/bond_repository_impl.dart
Created concrete BondRepositoryImpl delegating repository contract methods to NseCbricsDataSource.
Use Cases
lib/features/bonds/domain/usecases/place_bond_order.dart
Introduced PlaceBondOrder use case validating isin, quantity, and limit price before delegating to repository.
Presentation Controller
lib/features/bonds/presentation/controllers/bond_controller.dart
Added BondController orchestrating use cases and repository calls; exposes loadBonds(), loadOpenOrders(), and placeLimitBuyOrder() methods for UI integration.
Presentation Pages & Widgets
lib/features/bonds/presentation/pages/bond_page.dart, lib/features/bonds/presentation/pages/bond_preview_page.dart, lib/features/bonds/presentation/widgets/bond_order_ticket.dart
Created BondPage displaying bonds in expandable tiles with order placement UI, BondOrderTicket widget for buy order submission, and BondPreviewPage wiring mock transport for offline demo mode.
Configuration & Entry Point
lib/main.dart, pubspec.yaml
Added Flutter app entrypoint launching BondDemoApp with Material3 theme; configured project manifest with SDK constraints and dependencies.
Documentation
README.md, lib/features/bonds/README.md
Added root README with setup instructions and feature README documenting module architecture, API contracts, wiring examples, and future production enhancements.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant BondPage
    participant BondController
    participant PlaceBondOrder
    participant BondRepository
    participant NseCbricsDataSource
    participant HttpTransport

    User->>BondPage: Load page / Tap refresh
    BondPage->>BondController: loadBonds()
    BondController->>BondRepository: getAvailableBonds()
    BondRepository->>NseCbricsDataSource: fetchBonds()
    NseCbricsDataSource->>HttpTransport: get('/bonds')
    HttpTransport-->>NseCbricsDataSource: JSON response
    NseCbricsDataSource->>NseCbricsDataSource: Deserialize to BondModel list
    NseCbricsDataSource-->>BondRepository: List<Bond>
    BondRepository-->>BondController: List<Bond>
    BondController-->>BondPage: List<Bond>
    BondPage->>BondPage: Render bonds in ListView
    
    User->>BondPage: Expand bond tile
    BondPage->>BondOrderTicket: Show with isin
    
    User->>BondOrderTicket: Enter quantity, price, submit
    BondOrderTicket->>BondController: placeLimitBuyOrder(isin, qty, price)
    BondController->>PlaceBondOrder: call(isin, BUY, LIMIT, qty, price)
    PlaceBondOrder->>PlaceBondOrder: Validate inputs
    PlaceBondOrder->>BondRepository: placeOrder(...)
    BondRepository->>NseCbricsDataSource: submitOrder(...)
    NseCbricsDataSource->>HttpTransport: post('/orders', body)
    HttpTransport-->>NseCbricsDataSource: JSON response with orderId
    NseCbricsDataSource->>NseCbricsDataSource: Deserialize to BondOrderModel
    NseCbricsDataSource-->>BondRepository: BondOrder
    BondRepository-->>PlaceBondOrder: BondOrder
    PlaceBondOrder-->>BondController: BondOrder
    BondController-->>BondOrderTicket: BondOrder
    BondOrderTicket->>BondOrderTicket: Display orderId in message
Loading
sequenceDiagram
    participant BondPreviewPage
    participant MockNseCbricsTransport
    participant NseCbricsDataSource
    participant BondRepositoryImpl
    participant BondController
    participant BondPage

    BondPreviewPage->>MockNseCbricsTransport: new()
    BondPreviewPage->>NseCbricsDataSource: new(baseUrl, apiKey, MockTransport)
    BondPreviewPage->>BondRepositoryImpl: new(dataSource)
    BondPreviewPage->>BondController: new(repository)
    BondPreviewPage->>BondPage: new(controller)
    BondPreviewPage-->>BondPreviewPage: Build with mock-backed BondPage
    
    Note over MockNseCbricsTransport: In-memory orders list
    Note over NseCbricsDataSource: Uses mock transport instead of real HTTP
    Note over BondRepositoryImpl: Delegates to mock data source
    Note over BondController: Operates on mock data
    Note over BondPage: Displays mock bonds & accepts mock orders
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A bond module hops into view,
Clean layers stacked, from old to new,
Mock transport for testing with cheer,
Indian markets drawn oh-so-near!
Orders placed with validation tight,
Our Flutter app shines oh-so-bright! 📱✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and accurately summarizes the main change: adding an Indian Bonds module with a Flutter preview app and mock NSE CBRICS transport, which aligns with the changeset's primary purpose.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-bond-module-for-indian-market

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8df8258bf3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +47 to +49
setState(() {
_message = 'Order placed: ${order.orderId}';
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard async result setState with mounted check

After awaiting placeLimitBuyOrder, _submit calls setState unconditionally in both success and error paths. If the user navigates away (or the widget is otherwise disposed) before the network call completes, this triggers setState() on a disposed State, which can raise a runtime exception in production. Add a mounted guard before updating _message after the await (and in the catch path as well).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/bonds/data/datasources/nse_cbrics_datasource.dart`:
- Around line 35-37: The code assumes jsonDecode(raw) yields a Map with a List
under key 'data' (variables payload and items); add defensive checks to validate
the envelope before casting: ensure jsonDecode(raw) is a Map<String, dynamic>,
that payload.containsKey('data'), and that payload['data'] is a List<dynamic>
(otherwise return a safe default, throw a descriptive exception, or handle the
API error envelope); apply the same guard/validation to the other parsing sites
referenced (the other payload/data casts and the submitOrder parsing) so all
casts are protected and errors include contextual messages indicating which
endpoint/operation failed.
- Around line 7-15: Update the HttpTransport contract to return a response
object containing both statusCode and body (instead of Future<String>) and
adjust callers to check status before parsing: modify the abstract class
HttpTransport methods get/post to return a type (e.g., HttpResponse { int
statusCode; String body; }) and then in NseCbricsDatasource.fetchBonds(),
submitOrder(), and fetchOpenOrders() inspect response.statusCode (accept
200/2xx) and only call jsonDecode(response.body) on success; for non-OK statuses
throw or return a meaningful error including response.statusCode and
response.body so error responses aren’t passed to jsonDecode.

In `@lib/features/bonds/data/models/bond_model.dart`:
- Around line 14-23: The BondModel.fromJson deserialization uses unsafe casts
and DateTime.parse that can throw opaque runtime errors; update
BondModel.fromJson to validate each field (isin, symbol, issuer, maturityDate,
couponRate, faceValue, category) using helper validators similar to the
BondOrderModel enum decoders: check types (is String / is num), parse numbers to
double safely, wrap DateTime.parse in try/catch and validate format, and throw
FormatException with a clear contextual message for each invalid field; also
ensure callers in nse_cbrics_datasource.dart propagate or handle these
FormatExceptions instead of letting raw TypeError/FormatException surface.

In `@lib/features/bonds/data/models/bond_order_model.dart`:
- Around line 23-24: The current JSON decode for the BondOrderModel fields
quantity and filledQuantity uses .toInt(), which silently truncates fractional
numbers; instead validate the incoming num values before converting and fail
loudly on malformed data: check (json['quantity'] as num) and
(json['filledQuantity'] as num?) to ensure they are whole numbers (e.g., num % 1
== 0) and throw a FormatException (or return an error) if not, then safely
convert to int; apply this validation in the BondOrderModel JSON
constructor/fromJson where quantity and filledQuantity are parsed to prevent
silent truncation.

In `@lib/features/bonds/domain/usecases/place_bond_order.dart`:
- Around line 16-34: The ISIN should be normalized and the limitPrice must be
checked for finiteness before calling the repository: create a trimmedIsin =
isin.trim() and use trimmedIsin in the call to _repository.placeOrder (replace
usages of raw isin), and when type == BondOrderType.limit validate that
limitPrice is not null, greater than 0 and isFinite (reject NaN/Infinity) before
proceeding; keep the existing behavior of passing null for market orders
(limitPrice: type == BondOrderType.market ? null : limitPrice) but use the
validated/normalized values.

In `@lib/features/bonds/presentation/pages/bond_page.dart`:
- Around line 25-30: The _refresh method should update _bondsFuture with
widget.controller.loadBonds() but catch and swallow any errors so they don't
propagate to the RefreshIndicator; change _refresh to assign _bondsFuture inside
setState then await it inside a try/catch (e.g., try { await _bondsFuture; }
catch (_) {}), leaving error rendering to the existing FutureBuilder that
watches _bondsFuture; reference _refresh, _bondsFuture, and
widget.controller.loadBonds when making the change.

In `@lib/features/bonds/presentation/pages/bond_preview_page.dart`:
- Around line 14-24: The build method currently constructs NseCbricsDataSource,
BondRepositoryImpl, and BondController every build, causing a new controller to
be created and stale UI state; convert BondPreviewPage from a StatelessWidget to
a StatefulWidget, move creation of NseCbricsDataSource (the instance with
baseUrl 'https://mock.nse-cbrics.local'), BondRepositoryImpl, and BondController
into the State object's initState(), store the controller as a field on the
State and pass that field to BondPage in build(), and implement dispose() on the
State to clean up the controller if it exposes a dispose/close method so the UI
uses a stable controller instance across rebuilds.

In `@lib/features/bonds/presentation/widgets/bond_order_ticket.dart`:
- Around line 38-57: The try/catch blocks update state without checking mounted,
which can throw after dispose; wrap the setState calls that assign _message in
both the success path (after awaiting widget.controller.placeLimitBuyOrder) and
the error path (inside catch) with an if (mounted) guard before calling setState
so the widget is still mounted; keep the existing mounted check in the finally
for _submitting and reference the same members (_qtyController,
_priceController, widget.controller.placeLimitBuyOrder, _message, _submitting)
when applying the guards.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1094ad10-bbd4-4ed7-b2ce-618460cead66

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc9c68 and 8df8258.

📒 Files selected for processing (17)
  • README.md
  • lib/features/bonds/README.md
  • lib/features/bonds/data/datasources/mock_nse_cbrics_transport.dart
  • lib/features/bonds/data/datasources/nse_cbrics_datasource.dart
  • lib/features/bonds/data/models/bond_model.dart
  • lib/features/bonds/data/models/bond_order_model.dart
  • lib/features/bonds/data/repositories/bond_repository_impl.dart
  • lib/features/bonds/domain/entities/bond.dart
  • lib/features/bonds/domain/entities/bond_order.dart
  • lib/features/bonds/domain/repositories/bond_repository.dart
  • lib/features/bonds/domain/usecases/place_bond_order.dart
  • lib/features/bonds/presentation/controllers/bond_controller.dart
  • lib/features/bonds/presentation/pages/bond_page.dart
  • lib/features/bonds/presentation/pages/bond_preview_page.dart
  • lib/features/bonds/presentation/widgets/bond_order_ticket.dart
  • lib/main.dart
  • pubspec.yaml

Comment on lines +7 to +15
abstract class HttpTransport {
Future<String> get(String path, {Map<String, String>? headers});

Future<String> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/features/bonds/data/datasources/nse_cbrics_datasource.dart

Repository: SumitUni7/Test-web

Length of output: 2697


🏁 Script executed:

# Search for HttpTransport interface and implementations
rg "class HttpTransport|abstract class HttpTransport" -A 10

Repository: SumitUni7/Test-web

Length of output: 984


🏁 Script executed:

# Search for usages of HttpTransport methods
rg "\.get\(|\.post\(" lib/features/bonds/data/datasources/ -B 2 -A 3

Repository: SumitUni7/Test-web

Length of output: 1824


🏁 Script executed:

# Check if there are any response handling or status checks in the datasource
rg "statusCode|status|200|400|500" lib/features/bonds/data/datasources/ -A 2 -B 2

Repository: SumitUni7/Test-web

Length of output: 548


🏁 Script executed:

# Search for HttpResponse or similar response wrapper classes
rg "class HttpResponse|statusCode.*body|response.*statusCode" --type dart

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Find all implementations of HttpTransport
rg "class.*HttpTransport|implements HttpTransport" --type dart -A 5

Repository: SumitUni7/Test-web

Length of output: 1246


🏁 Script executed:

# Search for any error handling or try-catch blocks around HTTP calls
rg "try|catch|Exception|Error" lib/features/bonds/data/datasources/ -B 2 -A 2

Repository: SumitUni7/Test-web

Length of output: 972


🏁 Script executed:

# Check if there are any repository implementations that handle errors
rg "class.*Repository|implements.*Repository" lib/features/bonds/data/ --type dart -A 10

Repository: SumitUni7/Test-web

Length of output: 1068


🏁 Script executed:

# Search for any file that might implement the real HTTP transport
fd ".*transport.*\.dart" lib/features/bonds/

Repository: SumitUni7/Test-web

Length of output: 128


🏁 Script executed:

# Check pubspec for HTTP dependencies
cat pubspec.yaml | grep -A 5 -B 5 "http\|dio"

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Search for http.Client or similar HTTP implementations
rg "http\.Client|Dio|HttpClient" --type dart

Repository: SumitUni7/Test-web

Length of output: 44


🏁 Script executed:

# Check the repository structure
find . -type f -name "*.dart" | grep -E "(test|spec|transport)" | head -20

Repository: SumitUni7/Test-web

Length of output: 130


🏁 Script executed:

# Search for any integration or real HTTP implementation
find . -type f -name "*.dart" | head -30

Repository: SumitUni7/Test-web

Length of output: 851


🏁 Script executed:

# Check if there's a pubspec.yaml at all and look for HTTP dependencies
find . -name "pubspec.yaml" -exec cat {} \;

Repository: SumitUni7/Test-web

Length of output: 308


HttpTransport must expose HTTP status codes, not only response body.

The datasource cannot currently distinguish successful HTTP responses from error responses (4xx/5xx), so failed requests will be passed to jsonDecode() as if they were valid. This affects all three methods: fetchBonds() (line 34), submitOrder() (line 50), and fetchOpenOrders() (line 68).

Change the interface to return a response object with both status code and body:

Suggested fix
 abstract class HttpTransport {
-  Future<String> get(String path, {Map<String, String>? headers});
+  Future<HttpResponse> get(String path, {Map<String, String>? headers});

-  Future<String> post(
+  Future<HttpResponse> post(
     String path, {
     Map<String, String>? headers,
     Object? body,
   });
 }
+
+class HttpResponse {
+  final int statusCode;
+  final String body;
+
+  const HttpResponse({required this.statusCode, required this.body});
+}

Then validate status before parsing:

-    final raw = await http.get('$baseUrl/bonds', headers: _headers);
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
+    final response = await http.get('$baseUrl/bonds', headers: _headers);
+    if (response.statusCode < 200 || response.statusCode >= 300) {
+      throw StateError('fetchBonds failed: ${response.statusCode}');
+    }
+    final payload = jsonDecode(response.body) as Map<String, dynamic>;

Apply the same pattern to submitOrder() and fetchOpenOrders().

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
abstract class HttpTransport {
Future<String> get(String path, {Map<String, String>? headers});
Future<String> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
abstract class HttpTransport {
Future<HttpResponse> get(String path, {Map<String, String>? headers});
Future<HttpResponse> post(
String path, {
Map<String, String>? headers,
Object? body,
});
}
class HttpResponse {
final int statusCode;
final String body;
const HttpResponse({required this.statusCode, required this.body});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/datasources/nse_cbrics_datasource.dart` around lines
7 - 15, Update the HttpTransport contract to return a response object containing
both statusCode and body (instead of Future<String>) and adjust callers to check
status before parsing: modify the abstract class HttpTransport methods get/post
to return a type (e.g., HttpResponse { int statusCode; String body; }) and then
in NseCbricsDatasource.fetchBonds(), submitOrder(), and fetchOpenOrders()
inspect response.statusCode (accept 200/2xx) and only call
jsonDecode(response.body) on success; for non-OK statuses throw or return a
meaningful error including response.statusCode and response.body so error
responses aren’t passed to jsonDecode.

Comment on lines +35 to +37
final payload = jsonDecode(raw) as Map<String, dynamic>;
final items = payload['data'] as List<dynamic>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard response envelope shape before casting data.

payload['data'] is assumed to always exist and match expected types. If API returns an error envelope or schema drift, this throws at runtime (or parses a non-order map in submitOrder).

Suggested defensive parsing
+  Map<String, dynamic> _decodeObject(String raw) {
+    final decoded = jsonDecode(raw);
+    if (decoded is! Map<String, dynamic>) {
+      throw const FormatException('Expected JSON object response');
+    }
+    return decoded;
+  }
+
+  List<Map<String, dynamic>> _readDataList(Map<String, dynamic> payload) {
+    final data = payload['data'];
+    if (data is! List) {
+      throw const FormatException('Expected `data` to be a list');
+    }
+    return data.map((e) {
+      if (e is! Map<String, dynamic>) {
+        throw const FormatException('Expected list items to be JSON objects');
+      }
+      return e;
+    }).toList(growable: false);
+  }
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final items = payload['data'] as List<dynamic>;
+    final payload = _decodeObject(raw);
+    final items = _readDataList(payload);
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final orderMap = (payload['data'] ?? payload) as Map<String, dynamic>;
+    final payload = _decodeObject(raw);
+    final data = payload['data'];
+    if (data is! Map<String, dynamic>) {
+      throw const FormatException('Expected `data` to be an order object');
+    }
+    final orderMap = data;
...
-    final payload = jsonDecode(raw) as Map<String, dynamic>;
-    final items = payload['data'] as List<dynamic>;
+    final payload = _decodeObject(raw);
+    final items = _readDataList(payload);

Also applies to: 62-64, 69-70

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/datasources/nse_cbrics_datasource.dart` around lines
35 - 37, The code assumes jsonDecode(raw) yields a Map with a List under key
'data' (variables payload and items); add defensive checks to validate the
envelope before casting: ensure jsonDecode(raw) is a Map<String, dynamic>, that
payload.containsKey('data'), and that payload['data'] is a List<dynamic>
(otherwise return a safe default, throw a descriptive exception, or handle the
API error envelope); apply the same guard/validation to the other parsing sites
referenced (the other payload/data casts and the submitOrder parsing) so all
casts are protected and errors include contextual messages indicating which
endpoint/operation failed.

Comment on lines +14 to +23
factory BondModel.fromJson(Map<String, dynamic> json) {
return BondModel(
isin: json['isin'] as String,
symbol: json['symbol'] as String,
issuer: json['issuer'] as String,
maturityDate: DateTime.parse(json['maturityDate'] as String),
couponRate: (json['couponRate'] as num).toDouble(),
faceValue: (json['faceValue'] as num).toDouble(),
category: json['category'] as String,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/features/bonds/data/models/bond_model.dart | head -50

Repository: SumitUni7/Test-web

Length of output: 974


🏁 Script executed:

# Search for other model files with fromJson patterns
find lib -name "*_model.dart" -type f | head -20

Repository: SumitUni7/Test-web

Length of output: 161


🏁 Script executed:

# Search for usages of BondModel.fromJson
rg "BondModel\.fromJson|\.fromJson.*bond" lib/

Repository: SumitUni7/Test-web

Length of output: 302


🏁 Script executed:

# Check the Bond entity
find lib -name "bond.dart" -path "*/domain/entities/*" | xargs cat

Repository: SumitUni7/Test-web

Length of output: 469


🏁 Script executed:

cat -n lib/features/bonds/data/models/bond_order_model.dart

Repository: SumitUni7/Test-web

Length of output: 2642


🏁 Script executed:

cat -n lib/features/bonds/data/datasources/nse_cbrics_datasource.dart | head -100

Repository: SumitUni7/Test-web

Length of output: 2697


Harden JSON parsing in BondModel.fromJson to match BondOrderModel patterns.

The current implementation uses unsafe casts that fail with opaque runtime errors on malformed API payloads. BondOrderModel in the same codebase demonstrates the pattern: validate fields and throw contextual errors (see enum decoders). BondModel should follow suit—add validation for strings, dates, and numeric fields so failures are diagnosable rather than cryptic TypeError or FormatException from runtime casts.

Specifically:

  • Direct as String and as num casts will crash on type mismatches
  • DateTime.parse() has no try-catch for malformed dates
  • nse_cbrics_datasource.dart has no error handling, so failures propagate uncontrolled

Add field-level validation helpers (following BondOrderModel's pattern) to throw FormatException with context before returning BondModel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/models/bond_model.dart` around lines 14 - 23, The
BondModel.fromJson deserialization uses unsafe casts and DateTime.parse that can
throw opaque runtime errors; update BondModel.fromJson to validate each field
(isin, symbol, issuer, maturityDate, couponRate, faceValue, category) using
helper validators similar to the BondOrderModel enum decoders: check types (is
String / is num), parse numbers to double safely, wrap DateTime.parse in
try/catch and validate format, and throw FormatException with a clear contextual
message for each invalid field; also ensure callers in
nse_cbrics_datasource.dart propagate or handle these FormatExceptions instead of
letting raw TypeError/FormatException surface.

Comment on lines +23 to +24
quantity: (json['quantity'] as num).toInt(),
filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent silent quantity truncation during JSON decode.

Line 23 and Line 24 use .toInt(), which will silently truncate fractional payloads (e.g., 10.9 -> 10). This can corrupt order state instead of rejecting malformed data.

Proposed fix
   factory BondOrderModel.fromJson(Map<String, dynamic> json) {
     return BondOrderModel(
       orderId: json['orderId'] as String,
       isin: json['isin'] as String,
       side: _decodeSide(json['side'] as String),
       type: _decodeType(json['type'] as String),
       status: _decodeStatus((json['status'] as String?) ?? 'PENDING'),
-      quantity: (json['quantity'] as num).toInt(),
-      filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
+      quantity: _asStrictInt(json['quantity'], 'quantity'),
+      filledQuantity: _asStrictInt(json['filledQuantity'] ?? 0, 'filledQuantity'),
       limitPrice: (json['limitPrice'] as num?)?.toDouble(),
       createdAt: DateTime.parse(json['createdAt'] as String),
     );
   }
+
+  static int _asStrictInt(Object? value, String field) {
+    if (value is int) return value;
+    if (value is num && value == value.roundToDouble()) return value.toInt();
+    throw ArgumentError.value(value, field, 'Expected an integer value');
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
quantity: (json['quantity'] as num).toInt(),
filledQuantity: ((json['filledQuantity'] as num?) ?? 0).toInt(),
factory BondOrderModel.fromJson(Map<String, dynamic> json) {
return BondOrderModel(
orderId: json['orderId'] as String,
isin: json['isin'] as String,
side: _decodeSide(json['side'] as String),
type: _decodeType(json['type'] as String),
status: _decodeStatus((json['status'] as String?) ?? 'PENDING'),
quantity: _asStrictInt(json['quantity'], 'quantity'),
filledQuantity: _asStrictInt(json['filledQuantity'] ?? 0, 'filledQuantity'),
limitPrice: (json['limitPrice'] as num?)?.toDouble(),
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
static int _asStrictInt(Object? value, String field) {
if (value is int) return value;
if (value is num && value == value.roundToDouble()) return value.toInt();
throw ArgumentError.value(value, field, 'Expected an integer value');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/data/models/bond_order_model.dart` around lines 23 - 24,
The current JSON decode for the BondOrderModel fields quantity and
filledQuantity uses .toInt(), which silently truncates fractional numbers;
instead validate the incoming num values before converting and fail loudly on
malformed data: check (json['quantity'] as num) and (json['filledQuantity'] as
num?) to ensure they are whole numbers (e.g., num % 1 == 0) and throw a
FormatException (or return an error) if not, then safely convert to int; apply
this validation in the BondOrderModel JSON constructor/fromJson where quantity
and filledQuantity are parsed to prevent silent truncation.

Comment on lines +16 to +34
if (isin.trim().isEmpty) {
throw ArgumentError.value(isin, 'isin', 'ISIN is required');
}

if (quantity <= 0) {
throw ArgumentError.value(quantity, 'quantity', 'Quantity must be positive');
}

if (type == BondOrderType.limit && (limitPrice == null || limitPrice <= 0)) {
throw ArgumentError.value(limitPrice, 'limitPrice', 'Limit price must be provided for limit orders');
}

return _repository.placeOrder(
isin: isin,
side: side,
type: type,
quantity: quantity,
limitPrice: type == BondOrderType.market ? null : limitPrice,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize ISIN and reject non-finite limit prices before repository call.

Line 16 validates trimmed ISIN, but Line 29 still forwards the raw value. Also, Line 24 allows NaN/Infinity because only <= 0 is checked.

Proposed fix
   Future<BondOrder> call({
     required String isin,
     required BondOrderSide side,
     required BondOrderType type,
     required int quantity,
     double? limitPrice,
   }) {
-    if (isin.trim().isEmpty) {
+    final normalizedIsin = isin.trim();
+    if (normalizedIsin.isEmpty) {
       throw ArgumentError.value(isin, 'isin', 'ISIN is required');
     }
@@
-    if (type == BondOrderType.limit && (limitPrice == null || limitPrice <= 0)) {
+    if (type == BondOrderType.limit &&
+        (limitPrice == null || !limitPrice.isFinite || limitPrice <= 0)) {
       throw ArgumentError.value(limitPrice, 'limitPrice', 'Limit price must be provided for limit orders');
     }
 
     return _repository.placeOrder(
-      isin: isin,
+      isin: normalizedIsin,
       side: side,
       type: type,
       quantity: quantity,
       limitPrice: type == BondOrderType.market ? null : limitPrice,
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/domain/usecases/place_bond_order.dart` around lines 16 -
34, The ISIN should be normalized and the limitPrice must be checked for
finiteness before calling the repository: create a trimmedIsin = isin.trim() and
use trimmedIsin in the call to _repository.placeOrder (replace usages of raw
isin), and when type == BondOrderType.limit validate that limitPrice is not
null, greater than 0 and isFinite (reject NaN/Infinity) before proceeding; keep
the existing behavior of passing null for market orders (limitPrice: type ==
BondOrderType.market ? null : limitPrice) but use the validated/normalized
values.

Comment on lines +25 to +30
Future<void> _refresh() async {
setState(() {
_bondsFuture = widget.controller.loadBonds();
});
await _bondsFuture;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n lib/features/bonds/presentation/pages/bond_page.dart

Repository: SumitUni7/Test-web

Length of output: 3591


Wrap refresh in try-catch to avoid redundant error handling.

The _refresh() method currently propagates load errors through the onRefresh callback. Since the FutureBuilder already handles and displays errors (lines 45-54), catching locally prevents duplicate error state handling from both RefreshIndicator and FutureBuilder.

✅ Suggested refresh handling
   Future<void> _refresh() async {
     setState(() {
       _bondsFuture = widget.controller.loadBonds();
     });
-    await _bondsFuture;
+    try {
+      await _bondsFuture;
+    } catch (_) {
+      // FutureBuilder renders the error state.
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<void> _refresh() async {
setState(() {
_bondsFuture = widget.controller.loadBonds();
});
await _bondsFuture;
}
Future<void> _refresh() async {
setState(() {
_bondsFuture = widget.controller.loadBonds();
});
try {
await _bondsFuture;
} catch (_) {
// FutureBuilder renders the error state.
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/presentation/pages/bond_page.dart` around lines 25 - 30,
The _refresh method should update _bondsFuture with
widget.controller.loadBonds() but catch and swallow any errors so they don't
propagate to the RefreshIndicator; change _refresh to assign _bondsFuture inside
setState then await it inside a try/catch (e.g., try { await _bondsFuture; }
catch (_) {}), leaving error rendering to the existing FutureBuilder that
watches _bondsFuture; reference _refresh, _bondsFuture, and
widget.controller.loadBonds when making the change.

Comment on lines +14 to +24
Widget build(BuildContext context) {
final dataSource = NseCbricsDataSource(
baseUrl: 'https://mock.nse-cbrics.local',
apiKey: 'preview-key',
http: MockNseCbricsTransport(),
);

final repository = BondRepositoryImpl(dataSource);
final controller = BondController(repository);

return BondPage(controller: controller);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "bond_preview_page.dart" | head -20

Repository: SumitUni7/Test-web

Length of output: 124


🏁 Script executed:

cat -n ./lib/features/bonds/presentation/pages/bond_preview_page.dart

Repository: SumitUni7/Test-web

Length of output: 1071


🏁 Script executed:

cat -n ./lib/features/bonds/presentation/pages/bond_page.dart | head -50

Repository: SumitUni7/Test-web

Length of output: 1854


🏁 Script executed:

find ./lib/features -type f -name "*page.dart" | head -20

Repository: SumitUni7/Test-web

Length of output: 179


Avoid constructing controller graph inside build() of StatelessWidget.

Lines 15–23 recreate the data source, repository, and controller on every build. If the parent widget rebuilds BondPreviewPage, a new controller instance is created and passed to BondPage, but BondPage's _bondsFuture (initialized from the old controller) is never updated. This causes the UI to display stale data from a detached controller instance.

Move initialization to initState() in a StatefulWidget:

Proposed fix
-class BondPreviewPage extends StatelessWidget {
+class BondPreviewPage extends StatefulWidget {
   const BondPreviewPage({super.key});
+
+  `@override`
+  State<BondPreviewPage> createState() => _BondPreviewPageState();
+}
+
+class _BondPreviewPageState extends State<BondPreviewPage> {
+  late final BondController controller;
+
+  `@override`
+  void initState() {
+    super.initState();
+    final dataSource = NseCbricsDataSource(
+      baseUrl: 'https://mock.nse-cbrics.local',
+      apiKey: 'preview-key',
+      http: MockNseCbricsTransport(),
+    );
+    final repository = BondRepositoryImpl(dataSource);
+    controller = BondController(repository);
+  }
 
   `@override`
   Widget build(BuildContext context) {
-    final dataSource = NseCbricsDataSource(
-      baseUrl: 'https://mock.nse-cbrics.local',
-      apiKey: 'preview-key',
-      http: MockNseCbricsTransport(),
-    );
-
-    final repository = BondRepositoryImpl(dataSource);
-    final controller = BondController(repository);
-
     return BondPage(controller: controller);
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/presentation/pages/bond_preview_page.dart` around lines 14
- 24, The build method currently constructs NseCbricsDataSource,
BondRepositoryImpl, and BondController every build, causing a new controller to
be created and stale UI state; convert BondPreviewPage from a StatelessWidget to
a StatefulWidget, move creation of NseCbricsDataSource (the instance with
baseUrl 'https://mock.nse-cbrics.local'), BondRepositoryImpl, and BondController
into the State object's initState(), store the controller as a field on the
State and pass that field to BondPage in build(), and implement dispose() on the
State to clean up the controller if it exposes a dispose/close method so the UI
uses a stable controller instance across rebuilds.

Comment on lines +38 to +57
try {
final qty = int.parse(_qtyController.text.trim());
final price = double.parse(_priceController.text.trim());
final order = await widget.controller.placeLimitBuyOrder(
isin: widget.isin,
quantity: qty,
price: price,
);

setState(() {
_message = 'Order placed: ${order.orderId}';
});
} catch (e) {
setState(() {
_message = 'Order failed: $e';
});
} finally {
if (mounted) {
setState(() => _submitting = false);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd bond_order_ticket.dart

Repository: SumitUni7/Test-web

Length of output: 124


🏁 Script executed:

cat -n lib/features/bonds/presentation/widgets/bond_order_ticket.dart | head -100

Repository: SumitUni7/Test-web

Length of output: 3096


Guard setState after async completion with mounted checks.

setState in success/error paths can run after dispose when the user navigates away mid-request, causing setState() called after dispose. The finally block correctly guards the state update, but the try/catch blocks do not.

🛠️ Proposed lifecycle-safe update
   Future<void> _submit() async {
     try {
       final qty = int.parse(_qtyController.text.trim());
       final price = double.parse(_priceController.text.trim());
       final order = await widget.controller.placeLimitBuyOrder(
         isin: widget.isin,
         quantity: qty,
         price: price,
       );
+      if (!mounted) return;
 
       setState(() {
         _message = 'Order placed: ${order.orderId}';
       });
     } catch (e) {
+      if (!mounted) return;
       setState(() {
         _message = 'Order failed: $e';
       });
     } finally {
       if (mounted) {
         setState(() => _submitting = false);
       }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/bonds/presentation/widgets/bond_order_ticket.dart` around lines
38 - 57, The try/catch blocks update state without checking mounted, which can
throw after dispose; wrap the setState calls that assign _message in both the
success path (after awaiting widget.controller.placeLimitBuyOrder) and the error
path (inside catch) with an if (mounted) guard before calling setState so the
widget is still mounted; keep the existing mounted check in the finally for
_submitting and reference the same members (_qtyController, _priceController,
widget.controller.placeLimitBuyOrder, _message, _submitting) when applying the
guards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant