Format JSON examples with proper indentation and syntax highlighting

This commit is contained in:
Graham McIntire 2026-06-04 16:53:22 -05:00
parent 2dc42a6cb7
commit 0a4f921fd9
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 203 additions and 112 deletions

View file

@ -243,3 +243,54 @@
.historical-dot-marker {
z-index: 15 !important;
}
/* JSON syntax highlighting for API docs */
.json-key {
color: #7c3aed;
}
.json-string {
color: #059669;
}
.json-number {
color: #d97706;
}
.json-boolean {
color: #dc2626;
}
.json-null {
color: #6b7280;
font-style: italic;
}
.json-punc {
color: #6b7280;
}
[data-theme="dark"] .json-key {
color: #a78bfa;
}
[data-theme="dark"] .json-string {
color: #34d399;
}
[data-theme="dark"] .json-number {
color: #fbbf24;
}
[data-theme="dark"] .json-boolean {
color: #f87171;
}
[data-theme="dark"] .json-null {
color: #9ca3af;
font-style: italic;
}
[data-theme="dark"] .json-punc {
color: #9ca3af;
}

View file

@ -208,6 +208,151 @@ defmodule AprsmeWeb.ApiDocsLive do
defp sanitize_raw_packet(raw_packet), do: raw_packet
defp highlight_json(text) do
highlighted =
Regex.replace(
~r/(?<!\\)("(?:[^"\\]|\\.)*")(?=\s*:)|(?<!\\)("(?:[^"\\]|\\.)*")|(\btrue\b|\bfalse\b)|(\bnull\b)|(-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/,
text,
fn
_, key, "", "", "", "" ->
~s(<span class="json-key">#{key}</span>)
_, "", str, "", "", "" ->
~s(<span class="json-string">#{str}</span>)
_, "", "", bool, "", "" ->
~s(<span class="json-boolean">#{bool}</span>)
_, "", "", "", null, "" ->
~s(<span class="json-null">#{null}</span>)
_, "", "", "", "", num ->
~s(<span class="json-number">#{num}</span>)
end
)
Regex.replace(~r/[{}[\]\:,]/, highlighted, ~s(<span class="json-punc">\\0</span>))
end
defp callsign_success_json, do: ~s|{
"data": {
"id": "d7249877-d4a6-45c2-b314-2a8a355d2566",
"callsign": "N0CALL-9",
"base_callsign": "N0CALL",
"ssid": "9",
"sender": "N0CALL-9",
"destination": "APRS",
"path": "WIDE1-1,WIDE2-1",
"data_type": "position",
"information_field": "!4740.00N/12200.00W>Mobile Station",
"raw_packet": "N0CALL-9>APRS,WIDE1-1,WIDE2-1:!4740.00N/12200.00W>Mobile Station",
"received_at": "2024-01-15T10:30:45Z",
"region": "US-West",
"position": {
"latitude": 47.666667,
"longitude": -122.0,
"course": 90,
"speed": 35.5,
"altitude": 152.4
},
"symbol": {
"code": ">",
"table_id": "/"
},
"comment": "Mobile Station",
"timestamp": null,
"aprs_messaging": false,
"weather": null,
"equipment": {
"device_identifier": "APK004",
"manufacturer": "Kenwood",
"equipment_type": "TM-D710",
"device_model": "TM-D710G",
"device_vendor": "Kenwood",
"device_class": "rig"
},
"message": null,
"has_position": true,
"inserted_at": "2024-01-15T10:30:45Z",
"updated_at": "2024-01-15T10:30:45Z"
}
}|
defp callsign_not_found_json, do: ~s|{
"data": null,
"message": "No packets found for callsign N0CALL-9"
}|
defp callsign_error_json, do: ~s|{
"error": {
"message": "Invalid callsign format",
"code": "bad_request"
}
}|
defp weather_success_json, do: ~s|{
"data": [
{
"callsign": "N0CALL-13",
"base_callsign": "N0CALL",
"position": {
"lat": 37.7849,
"lon": -122.4094
},
"distance_miles": 0.87,
"weather": {
"temperature": 72.5,
"humidity": 65.0,
"pressure": 1013.2,
"wind_speed": 8.5,
"wind_direction": 270,
"wind_gust": 12.0,
"rain_1h": 0.0,
"rain_24h": 0.5,
"rain_since_midnight": 0.3
},
"symbol": {
"table_id": "/",
"code": "_"
},
"comment": "Davis Vantage Pro2",
"last_report": "2026-03-22T15:30:00Z"
}
],
"meta": {
"count": 1,
"params": {
"latitude": 37.7749,
"longitude": -122.4194,
"radius_miles": 25.0,
"hours": 6,
"limit": 50
}
}
}|
defp weather_empty_json, do: ~s|{
"data": [],
"meta": {
"count": 0,
"params": {
"latitude": 0.0,
"longitude": -180.0,
"radius_miles": 10.0,
"hours": 6,
"limit": 50
}
}
}|
defp weather_error_param_json, do: ~s|{
"error": "Missing required parameter: lat"
}|
defp weather_error_invalid_json, do: ~s|{
"error": "Invalid latitude: must be a number between -90 and 90"
}|
@impl true
def render(assigns) do
~H"""
@ -339,67 +484,17 @@ defmodule AprsmeWeb.ApiDocsLive do
<h4 class="text-md font-medium mb-2">Success Response (200 OK)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
<pre><code><%= raw ~s|{
"data": {
"id": "d7249877-d4a6-45c2-b314-2a8a355d2566",
"callsign": "N0CALL-9",
"base_callsign": "N0CALL",
"ssid": "9",
"sender": "N0CALL-9",
"destination": "APRS",
"path": "WIDE1-1,WIDE2-1",
"data_type": "position",
"information_field": "!4740.00N/12200.00W>Mobile Station",
"raw_packet": "N0CALL-9>APRS,WIDE1-1,WIDE2-1:!4740.00N/12200.00W>Mobile Station",
"received_at": "2024-01-15T10:30:45Z",
"region": "US-West",
"position": {
"latitude": 47.666667,
"longitude": -122.0,
"course": 90,
"speed": 35.5,
"altitude": 152.4
},
"symbol": {
"code": ">",
"table_id": "/"
},
"comment": "Mobile Station",
"timestamp": null,
"aprs_messaging": false,
"weather": null,
"equipment": {
"device_identifier": "APK004",
"manufacturer": "Kenwood",
"equipment_type": "TM-D710",
"device_model": "TM-D710G",
"device_vendor": "Kenwood",
"device_class": "rig"
},
"message": null,
"has_position": true,
"inserted_at": "2024-01-15T10:30:45Z",
"updated_at": "2024-01-15T10:30:45Z"
}
}| %></code></pre>
<pre><code><%= raw highlight_json(callsign_success_json()) %></code></pre>
</div>
<h4 class="text-md font-medium mb-2">Not Found Response (404)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
<pre><code><%= raw ~s|{
"data": null,
"message": "No packets found for callsign N0CALL-9"
}| %></code></pre>
<pre><code><%= raw highlight_json(callsign_not_found_json()) %></code></pre>
</div>
<h4 class="text-md font-medium mb-2">Error Response (400)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto">
<pre><code><%= raw ~s|{
"error": {
"message": "Invalid callsign format",
"code": "bad_request"
}
}| %></code></pre>
<pre><code><%= raw highlight_json(callsign_error_json()) %></code></pre>
</div>
</div>
</div>
@ -512,77 +607,22 @@ defmodule AprsmeWeb.ApiDocsLive do
<h4 class="text-md font-medium mb-2">Success Response (200 OK)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
<pre><code><%= raw ~s|{
"data": [
{
"callsign": "N0CALL-13",
"base_callsign": "N0CALL",
"position": {
"lat": 37.7849,
"lon": -122.4094
},
"distance_miles": 0.87,
"weather": {
"temperature": 72.5,
"humidity": 65.0,
"pressure": 1013.2,
"wind_speed": 8.5,
"wind_direction": 270,
"wind_gust": 12.0,
"rain_1h": 0.0,
"rain_24h": 0.5,
"rain_since_midnight": 0.3
},
"symbol": {
"table_id": "/",
"code": "_"
},
"comment": "Davis Vantage Pro2",
"last_report": "2026-03-22T15:30:00Z"
}
],
"meta": {
"count": 1,
"params": {
"latitude": 37.7749,
"longitude": -122.4194,
"radius_miles": 25.0,
"hours": 6,
"limit": 50
}
}
}| %></code></pre>
<pre><code><%= raw highlight_json(weather_success_json()) %></code></pre>
</div>
<h4 class="text-md font-medium mb-2">Empty Response (200 OK)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
<pre><code><%= raw ~s|{
"data": [],
"meta": {
"count": 0,
"params": {
"latitude": 0.0,
"longitude": -180.0,
"radius_miles": 10.0,
"hours": 6,
"limit": 50
}
}
}| %></code></pre>
<pre><code><%= raw highlight_json(weather_empty_json()) %></code></pre>
</div>
<h4 class="text-md font-medium mb-2">Error Response (400 Bad Request)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto mb-4">
<pre><code><%= raw ~s|{
"error": "Missing required parameter: lat"
}| %></code></pre>
<pre><code><%= raw highlight_json(weather_error_param_json()) %></code></pre>
</div>
<h4 class="text-md font-medium mb-2">Error Response (422 Unprocessable Entity)</h4>
<div class="bg-gray-900 text-gray-100 dark:bg-gray-950 p-4 rounded-lg overflow-x-auto">
<pre><code><%= raw ~s|{
"error": "Invalid latitude: must be a number between -90 and 90"
}| %></code></pre>
<pre><code><%= raw highlight_json(weather_error_invalid_json()) %></code></pre>
</div>
</div>
</div>