Skip to content

Commit 3a7aa05

Browse files
Merge branch 'master' of github.com:jaredhendrickson13/pfsense-api into next_minor
2 parents aa6bbf3 + 34f6acc commit 3a7aa05

File tree

9 files changed

+121
-16
lines changed

9 files changed

+121
-16
lines changed

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/ContentHandler.inc

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,46 @@ class ContentHandler {
269269
* @return array This ContentHandler as an OpenAPI 'content' schema for this MIME type in a given Response object.
270270
*/
271271
public function to_openapi_schema(Response $response, Endpoint $endpoint): array {
272-
# Format the data schema based on the endpoint for successful responses
273-
if ($response instanceof Success and $endpoint->many) {
272+
# Default to an empty schema in the event we cannot determine the schema
273+
$data_schema = [
274+
'oneOf' => [['type' => 'array', 'items' => ['type' => 'object']], ['type' => 'object']],
275+
];
276+
277+
# Ensure the 'id' field is included for success responses on many models without a parent
278+
if ($response instanceof Success and $endpoint->model->many and !$endpoint->model->parent_model_class) {
274279
$data_schema = [
275-
'type' => 'array',
276-
'items' => ['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"],
280+
'allOf' => [
281+
['type' => 'object', 'properties' => ['id' => ['type' => $endpoint->model->id_type]]],
282+
['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"],
283+
],
277284
];
278-
} elseif ($response instanceof Success and !$endpoint->many) {
285+
}
286+
# Ensure the 'parent_id' and 'id' fields are included for success responses on many models with a parent
287+
elseif ($response instanceof Success and $endpoint->model->many and $endpoint->model->parent_model_class) {
288+
$parent_type = $endpoint->model->get_parent_model()->id_type;
289+
$data_schema = [
290+
'allOf' => [
291+
[
292+
'type' => 'object',
293+
'properties' => [
294+
'parent_id' => ['type' => $parent_type],
295+
'id' => ['type' => $endpoint->model->id_type],
296+
],
297+
],
298+
['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"],
299+
],
300+
];
301+
}
302+
# Do not include any extra fields for non many models
303+
elseif ($response instanceof Success and !$endpoint->model->many) {
279304
$data_schema = ['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"];
280-
} else {
305+
}
306+
307+
# If this is a many endpoint, nest the data schema in an array
308+
if ($response instanceof Success and $endpoint->many) {
281309
$data_schema = [
282-
'oneOf' => [['type' => 'array', 'items' => ['type' => 'object']], ['type' => 'object']],
310+
'type' => 'array',
311+
'items' => $data_schema,
283312
];
284313
}
285314

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/FloatField.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ class FloatField extends RESTAPI\Core\Field {
144144

145145
# Otherwise, the internal value cannot be represented by this Field. Throw an error.
146146
throw new RESTAPI\Responses\ServerError(
147-
message: "Cannot parse FloatField '$this->name' from internal because its internal value is not
148-
a numeric value. Consider changing this field to a StringField.",
147+
message: "Cannot parse FloatField '$this->name' from internal because its internal value is not " .
148+
'a numeric value. Consider changing this field to a StringField.',
149149
response_id: 'FLOAT_FIELD_WITH_NON_FLOAT_INTERNAL_VALUE',
150150
);
151151
}

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/NetworkInterface.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class NetworkInterface extends Model {
165165
$this->gateway = new ForeignModelField(
166166
model_name: ['RoutingGateway', 'RoutingGatewayGroup'],
167167
model_field: 'name',
168-
model_query: ['ipprotocol' => 'inet', 'interface' => &$this->id],
168+
model_query: ['ipprotocol' => 'inet'],
169169
allow_null: true,
170170
conditions: ['typev4' => 'static'],
171171
help_text: 'Sets the upstream gateway this interface will use. This is only applicable for WAN-type interfaces.',
@@ -313,7 +313,7 @@ class NetworkInterface extends Model {
313313
$this->gatewayv6 = new ForeignModelField(
314314
model_name: ['RoutingGateway', 'RoutingGatewayGroup'],
315315
model_field: 'name',
316-
model_query: ['ipprotocol' => 'inet6', 'interface' => &$this->id],
316+
model_query: ['ipprotocol' => 'inet6'],
317317
allow_null: true,
318318
conditions: ['typev6' => 'staticv6'],
319319
help_text: 'Sets the upstream IPv6 gateway this interface will use. This is only applicable for WAN-type interfaces.',

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/RoutingGatewayGroupPriority.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class RoutingGatewayGroupPriority extends Model {
2222
$this->parent_model_class = 'RoutingGatewayGroup';
2323
$this->config_path = 'item';
2424
$this->subsystem = 'staticroutes';
25+
$this->update_strategy = 'replace';
2526
$this->many = true;
2627
$this->many_minimum = 1;
2728

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Models/SystemStatus.inc

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class SystemStatus extends Model {
116116
allow_null: true,
117117
read_only: true,
118118
many: true,
119-
delimiter: ', ',
119+
delimiter: ' ',
120120
help_text: 'The CPU load averages. The first value represents the load average for the last minute, the ' .
121121
'second value represents the load average for the last 5 minutes, and the third value represents the ' .
122122
'load average for the last 15 minutes.',
@@ -181,7 +181,7 @@ class SystemStatus extends Model {
181181
'mds_mitigation' => get_single_sysctl('hw.mds_disable_state'),
182182
'temp_c' => $this->get_system_temp(),
183183
'temp_f' => $this->get_system_temp(as_fahrenheit: true),
184-
'cpu_load_avg' => get_load_average(),
184+
'cpu_load_avg' => $this->get_cpu_load_average(),
185185
'cpu_count' => get_single_sysctl('hw.ncpu'),
186186
'cpu_usage' => $this->get_cpu_usage(),
187187
'mbuf_usage' => $this->get_mbuf_usage(),
@@ -223,6 +223,24 @@ class SystemStatus extends Model {
223223
return $as_fahrenheit ? $temp * 1.8 + 32 : $temp;
224224
}
225225

226+
/**
227+
* Obtains the CPU load average. This method is responsible for obtaining the raw load average string from pfSense
228+
* and ensuring it is formatted correctly before the cpu_load_average field splits it into an array.
229+
* @return string|null The CPU load average as a string or null if it could not be obtained.
230+
*/
231+
private function get_cpu_load_average(): ?string {
232+
# Obtain the load average using pfSense's get_load_average() function
233+
$load_avg = get_load_average();
234+
235+
# The system language may affect the load average's delimiter (missing a comma in some languages)
236+
# ensure values are only ever separated by a single space (no commas). #716
237+
if (str_contains($load_avg, ', ')) {
238+
$load_avg = str_replace(', ', ' ', $load_avg);
239+
}
240+
241+
return $load_avg ?? null;
242+
}
243+
226244
/**
227245
* Determines approximate CPU usage using the last minute load average.
228246
* @return float|null The CPU usage as a whole percentage.

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APICoreContentHandlerTestCase.inc

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,17 @@ class APICoreContentHandlerTestCase extends TestCase {
136136
'data' => [
137137
'type' => 'array',
138138
'items' => [
139-
'$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}",
139+
'allOf' => [
140+
[
141+
'type' => 'object',
142+
'properties' => [
143+
'id' => ['type' => $endpoint->model->id_type],
144+
],
145+
],
146+
[
147+
'$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}",
148+
],
149+
],
140150
],
141151
],
142152
],
@@ -165,7 +175,17 @@ class APICoreContentHandlerTestCase extends TestCase {
165175
[
166176
'type' => 'object',
167177
'properties' => [
168-
'data' => ['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"],
178+
'data' => [
179+
'allOf' => [
180+
[
181+
'type' => 'object',
182+
'properties' => [
183+
'id' => ['type' => $endpoint->model->id_type],
184+
],
185+
],
186+
['$ref' => "#/components/schemas/{$endpoint->model->get_class_shortname()}"],
187+
],
188+
],
169189
],
170190
],
171191
],

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsRoutingGatewayGroupPriorityTestCase.inc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ class APIModelsRoutingGatewayGroupPriorityTestCase extends TestCase {
5858
$gateway_prio->tier->value = 3;
5959
$gateway_prio->update();
6060

61+
# Ensure the priority was actually updated
62+
$this->assert_equals(
63+
RoutingGatewayGroupPriority::query(parent_id: $gateway_group->id, id: $gateway_prio->id)->first()->tier
64+
->value,
65+
3,
66+
);
67+
6168
# Delete the gateway group priority object and ensure it was deleted
6269
$gateway_prio->delete();
6370
$this->assert_equals(RoutingGatewayGroupPriority::read_all(parent_id: $gateway_group->id)->count(), 1);

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Tests/APIModelsSystemStatusTestCase.inc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace RESTAPI\Tests;
44

55
use RESTAPI\Core\Command;
6+
use RESTAPI\Core\Model;
67
use RESTAPI\Core\TestCase;
78
use RESTAPI\Models\SystemStatus;
89

@@ -44,4 +45,33 @@ class APIModelsSystemStatusTestCase extends TestCase {
4445
(new Command(command: '/bin/kenv -q smbios.bios.reldate 2>/dev/null', trim_whitespace: true))->output,
4546
);
4647
}
48+
49+
/**
50+
* Checks that the load average can can be read correctly despite the system's assign language. Normally,
51+
* the load average format can change based on the systems assigned language and cause issues parsing the
52+
* value into an array. This is a regression test for #716.
53+
*/
54+
public function test_language_does_not_change_cpu_load_avg(): void {
55+
# Change the language to German
56+
Model::set_config('system/language', 'de_DE');
57+
set_language();
58+
59+
# Ensure we can read the SystemStatus cpu_load_average without an error
60+
$this->assert_does_not_throw(
61+
callable: function () {
62+
$system_status = new SystemStatus();
63+
$this->assert_equals(count($system_status->cpu_load_avg->value), 3);
64+
},
65+
);
66+
67+
# Reset the language to English and check again
68+
Model::set_config('system/language', 'en_US');
69+
set_language();
70+
$this->assert_does_not_throw(
71+
callable: function () {
72+
$system_status = new SystemStatus();
73+
$this->assert_equals(count($system_status->cpu_load_avg->value), 3);
74+
},
75+
);
76+
}
4777
}

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
jinja2~=3.1.6
2-
pylint~=3.3.6
2+
pylint~=3.3.7
33
black~=25.1.0
44
mkdocs~=1.6.1

0 commit comments

Comments
 (0)