Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
## 3.2.0

### New Features

- **`isError` getter**: Convenience check for any client or server error (4xx or 5xx). Equivalent to `isClientError || isServerError`.

```dart
print(404.isError); // true
print(500.isError); // true
print(200.isError); // false
```

- **Packaged AI assets** (`extension/mcp/`): The package now ships MCP-compatible resources and prompts for AI coding agents, following the [Dart/Flutter packaged AI assets proposal](https://flutter.dev/go/packaged-ai-assets).
Once the Dart MCP server implements the proposal, agents will automatically have access to:
- `@functional_status_codes/api_reference` β€” full API cheat-sheet
- `@functional_status_codes/patterns` β€” idiomatic usage patterns
- `/functional_status_codes/handle_response` β€” prompt to generate HTTP response handling code
- `/functional_status_codes/cache_strategy` β€” prompt to derive a caching strategy from a status code

### Fixes

- Fixed: `maybeMapStatusCode` now performs the `null` check before calling `toRegisteredStatusCode()`, which was previously invoked unnecessarily for `null` inputs.
- Fixed: `StatusCode.random()` no longer wraps the source iterable in an intermediate `List.unmodifiable(...)` just to call `elementAt`. The allocation was unnecessary since `Iterable.elementAt` works directly.
- Fixed: `StatusCode.pattern` (and `StatusCode.regExp`) now matches only the valid HTTP status code range (`[1-5]\d{2}`, i.e. 100-599) instead of any three-digit number (`\d{3}`). This avoids false matches on strings like `"timeout after 999ms"`. Note: `tryParse` behavior is unchanged for all registered codes β€” only the raw pattern/regex changes.
- Fixed: `values` doc comment count corrected from 95 to 93.

### Documentation

- `maybeMapStatusCode` and `maybeWhenStatusCode`: added explicit note that the `isStatusCode` handler takes priority over all category handlers (`isSuccess`, `isClientError`, etc.) when both are provided.

## 3.1.0

This release adds several new features for better HTTP status code handling, including cacheable/retryable checks, pre-sorted category collections, and a random status code generator. Also includes important bug fixes.
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,3 @@ For more information on using this package, check out the API documentation and

If you like this package, please give it a star or a like. For more information on using this package, check out the API documentation. **PRs or ideas are always welcome**.
If you have any issues or suggestions for the package, please file them in the GitHub repository.


## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ftsinis%2Ffunctional_status_codes.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Ftsinis%2Ffunctional_status_codes?ref=badge_large)
35 changes: 35 additions & 0 deletions extension/mcp/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
resources:
- title: "API Reference"
description: "Concise cheat-sheet for all StatusCode constants, getters, methods,
and functional helpers. Include in agent context when working with HTTP status codes."
path: extension/mcp/resources/api_reference.md

- title: "Functional Patterns Guide"
description: "Idiomatic patterns for when/map/maybeWhen/const variants,
isCacheable, isRetryable, and real-world HTTP client integration."
path: extension/mcp/resources/patterns.md

prompts:
- title: "Handle HTTP Response"
description: "Generate idiomatic functional_status_codes code to handle an HTTP
response. Provide http_client (http, dio, or native) and optionally status_code."
path: extension/mcp/prompts/handle_response.md
arguments:
- name: http_client
description: "HTTP client in use: 'http', 'dio', or 'native' (dart:io)"
required: false
- name: status_code
description: "Specific status code to handle (e.g. 404). Leave blank for general handling."
required: false

- title: "Cache Strategy"
description: "Derive a caching strategy for an HTTP response using isCacheable
and RFC 7231 semantics."
path: extension/mcp/prompts/cache_strategy.md
arguments:
- name: status_code
description: "HTTP status code to evaluate (e.g. 200)"
required: true
- name: cache_backend
description: "Cache storage backend being used, if any"
required: false
51 changes: 51 additions & 0 deletions extension/mcp/prompts/cache_strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Derive a caching strategy for HTTP status code **{{status_code}}** using the `functional_status_codes` package and RFC 7231 semantics.

{{#cache_backend}}
The cache backend in use is: **{{cache_backend}}**
{{/cache_backend}}
{{^cache_backend}}
Use a generic `cache.store(key, value, {Duration? ttl})` / `cache.get(key)` interface in the example. The caller can adapt it to their backend.
{{/cache_backend}}

Follow these steps:

1. **Evaluate cacheability** using `.isCacheable`:

The RFC 7231 cacheable set is: `200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501`.

```dart
if ({{status_code}}.isCacheable) {
// safe to cache the response body
}
```

2. **Assign a cache TTL** appropriate for code `{{status_code}}`:
- `200 OK` β€” moderate TTL (e.g. 5 minutes), respect `Cache-Control` headers when present
- `301 Moved Permanently` β€” long TTL (e.g. 1 day), permanent redirect is stable
- `404 Not Found` β€” short negative-cache TTL (e.g. 1-10 minutes) to avoid repeated misses
- `405 Method Not Allowed`, `410 Gone` β€” moderate TTL, these are stable server decisions
- `501 Not Implemented` β€” long TTL, unlikely to change soon
- `204`, `203`, `206`, `300`, `414` β€” short to moderate TTL depending on use case

Use `whenConstOrNull` to express this as a compile-time mapping (`whenConstOrNull` is on `StatusCode`, so convert from raw `int` first):

```dart
final ttl = rawStatusCode.toRegisteredStatusCode()?.whenConstOrNull(
okHttp200: Duration(minutes: 5),
noContentHttp204: Duration(minutes: 1),
notFoundHttp404: Duration(minutes: 10),
movedPermanentlyHttp301: Duration(days: 1),
goneHttp410: Duration(hours: 1),
notImplementedHttp501: Duration(hours: 6),
);
```

3. **Generate the store/skip logic**:
- If `isCacheable` is `true` for `{{status_code}}`: generate a `cache.store(...)` call{{#cache_backend}} adapted to **{{cache_backend}}**{{/cache_backend}}
- If `isCacheable` is `false`: explain why this code should not be cached and suggest adding a `Cache-Control: no-store` header to the response if you control the server

4. **Emit a complete, reusable function** that:
- Checks `isStatusCode` before doing anything (guards against invalid codes)
- Uses `isCacheable` to gate the write
- Uses `toRegisteredStatusCode()?.whenConstOrNull(...)` for TTL assignment β€” convert first since these methods are on `StatusCode`, not raw `int` (no runtime allocations)
- Falls back gracefully for non-cacheable codes (log and skip, do not throw)
45 changes: 45 additions & 0 deletions extension/mcp/prompts/handle_response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Generate idiomatic Dart code to handle an HTTP response using the `functional_status_codes` package.

{{#http_client}}
The HTTP client in use is: **{{http_client}}**
{{/http_client}}
{{^http_client}}
Detect the HTTP client from the existing code (look for `http.Response`, `dio.Response`, `HttpClientResponse`, or a plain `int` status code field).
{{/http_client}}

{{#status_code}}
Focus specifically on handling status code **{{status_code}}** (in addition to general handling).
{{/status_code}}

Follow these steps:

1. **Extract the status code** as `int` or `num` from the response object:
- `package:http` β†’ `response.statusCode` (already `int`)
- `package:dio` β†’ `response.statusCode` (already `int?`)
- `dart:io HttpClientResponse` β†’ `response.statusCode` (already `int`)
- Custom β†’ use whatever field holds the integer status

2. **Apply category-level branching** using `maybeWhenStatusCode` or `maybeMapStatusCode`:
- Use `maybeWhenStatusCode` when the callbacks do not need the code value passed in
- Use `maybeMapStatusCode` when you need `(code) =>` in the callback
- Provide `orElse` for codes outside 100-599 or unhandled categories
- Do **not** provide `isStatusCode` alongside category handlers unless you intentionally want it to catch all valid codes first

3. **Drill into specific codes** when needed by converting first:

```dart
final registered = rawCode.toRegisteredStatusCode();
final result = registered?.maybeMap(
orElse: (c) => defaultHandling(c),
notFoundHttp404: (_) => handleNotFound(),
);
```

4. **Emit null-safe, idiomatic Dart**:
- Prefer `?.` and `??` over null-checks
- Use `whenConstStatusCode` / `whenConstOrNull` when the return values are constants β€” these accept **direct values**, not closures (e.g. `isSuccess: 'ok'`, never `isSuccess: () => 'ok'`); they must be called on a `StatusCode` value after `toRegisteredStatusCode()` conversion
- Avoid `if (code >= 200 && code < 300)` β€” use `.isSuccess` / `.isError` / `.isCacheable` etc.

5. **Handle the specific code** {{#status_code}}({{status_code}}){{/status_code}} with a dedicated branch inside `maybeMap` or `maybeWhen` on the `StatusCode`, using the constant name `<camelName>Http<NNN>`.

Produce a complete, runnable function or method β€” not just a snippet β€” with appropriate return type and error handling.
Loading