feat: emit native types instead of Newtonsoft JObject/JArray in public APIs

Replace all Newtonsoft.Json.Linq types (JObject, JArray, JToken) exposed
in public service methods, model properties, and cmdlet OutputTypes with
native .NET types (Dictionary<string, object?>, List<Dictionary<string,
object?>>, PSObject).

New utilities:
- JsonHelper: ToNative, ToDictionary, ToListOfDictionaries for recursive
  JToken→native conversion
- NativeListConverter / NativeDictionaryConverter: JsonConverters for
  model properties that deserialize JArray/JObject to native types

Services updated (8 methods):
- NodeService: GetNodeConfig, GetNodeDns → Dictionary<string, object?>
- BackupService: GetNotBackedUp → List<Dictionary<string, object?>>
- ClusterConfigService: GetClusterConfig, GetTotem, GetQdevice → Dictionary
- HaService: GetManagerStatus → Dictionary<string, object?>
- VmService: GetGuestExecStatus → Dictionary<string, object?>

Models updated (2 properties):
- PvePool.Members: JArray → List<Dictionary<string, object?>>
- PveHaRule.Properties: JObject → Dictionary<string, object?>

Cmdlets updated (5):
- GetPveClusterConfigCmdlet: OutputType JToken → Dictionary<string, object>
- GetPveNodeConfigCmdlet: iteration updated for Dictionary
- GetPveNodeDnsCmdlet: iteration updated for Dictionary
- GetPveBackupInfoCmdlet: iteration updated for List<Dictionary>
- InvokePveVmGuestExecCmdlet: polling updated for Dictionary

Added D013 to DECISIONS.md: cmdlets must emit only native or module-defined types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-25 11:12:57 -05:00
parent 20ed5de69e
commit 3ce19a721c
17 changed files with 210 additions and 45 deletions
+49
View File
@@ -358,3 +358,52 @@ extracted to `const string` fields for maintainability.
Auth header names (`PVEAPIToken=`, `CSRFPreventionToken`) were inline string literals
used in multiple places. Extracting to `const string ApiTokenPrefix` and
`CsrfHeaderName` fields improves maintainability and reduces typo risk.
---
## D013 — Cmdlets must emit only native or module-defined types
**Status**: Active
**Finding refs**: F085
**Resolved in scan**: 2026-03-25
### Decision
All cmdlet output types and public model properties must be native .NET types (`string`,
`int`, `bool`, `Dictionary<string, object?>`, `List<T>`, `PSObject`, `void`) or types
defined within the module itself (`Pve*` classes). Never expose third-party types like
Newtonsoft's `JObject`, `JArray`, or `JToken` in public APIs.
### Rationale
PowerShell enumerates `JArray` unexpectedly and `JObject` properties are not discoverable
via `Get-Member` or tab completion. Users piping module output into `Format-Table`,
`Select-Object`, or `Where-Object` get confusing behavior when the underlying type is a
Newtonsoft container. Native dictionaries and lists work naturally in PowerShell pipelines.
### Anti-pattern (do not reintroduce)
```csharp
// Service returning Newtonsoft type
public JObject GetNodeConfig(...) { ... }
// Model exposing Newtonsoft type
[JsonProperty("members")]
public JArray? Members { get; set; }
// Cmdlet OutputType referencing Newtonsoft type
[OutputType(typeof(JObject))]
```
### Correct pattern
```csharp
// Service returns native dictionary
public Dictionary<string, object?> GetNodeConfig(...) { ... }
// Model uses native type with converter for deserialization
[JsonProperty("members")]
[JsonConverter(typeof(NativeListConverter))]
public List<Dictionary<string, object?>>? Members { get; set; }
// Cmdlet OutputType uses native or module type
[OutputType(typeof(Dictionary<string, object>))]
// or
[OutputType(typeof(PSObject))]
```