diff --git a/v2/api/template_models.go b/v2/api/template_models.go index 526b8c4..70eefd7 100644 --- a/v2/api/template_models.go +++ b/v2/api/template_models.go @@ -81,7 +81,13 @@ type UpdateTemplateArg struct { AllowedRequesters *[]string `json:"AllowedRequesters,omitempty"` RFCEnforcement *bool `json:"RFCEnforcement,omitempty"` RequiresApproval *bool `json:"RequiresApproval,omitempty"` - KeyUsage *bool `json:"KeyUsage,omitempty"` + // KeyUsage is an int32 bitmask on Command's wire format (e.g. 160 = + // digitalSignature|keyEncipherment), matching GetTemplateResponse.KeyUsage and + // Command's TemplateUpdateRequest/TemplateRetrievalResponse swagger schema + // (both typed "integer"/"int32"). A *bool here previously produced a live + // HTTP 400 ("Unexpected character encountered while parsing value: t. Path + // 'KeyUsage'") since Command rejects a JSON boolean for an integer field. + KeyUsage *int `json:"KeyUsage,omitempty"` } type UpdateTemplateResponse struct{ GetTemplateResponse } diff --git a/v3/api/template_models.go b/v3/api/template_models.go index 127683b..39d905b 100644 --- a/v3/api/template_models.go +++ b/v3/api/template_models.go @@ -119,7 +119,13 @@ type UpdateTemplateArg struct { AllowedRequesters *[]string `json:"AllowedRequesters,omitempty"` RFCEnforcement *bool `json:"RFCEnforcement,omitempty"` RequiresApproval *bool `json:"RequiresApproval,omitempty"` - KeyUsage *bool `json:"KeyUsage,omitempty"` + // KeyUsage is an int32 bitmask on Command's wire format (e.g. 160 = + // digitalSignature|keyEncipherment), matching GetTemplateResponse.KeyUsage and + // Command's TemplateUpdateRequest/TemplateRetrievalResponse swagger schema + // (both typed "integer"/"int32"). A *bool here previously produced a live + // HTTP 400 ("Unexpected character encountered while parsing value: t. Path + // 'KeyUsage'") since Command rejects a JSON boolean for an integer field. + KeyUsage *int `json:"KeyUsage,omitempty"` // TemplatePolicy must be round-tripped from the corresponding GetTemplateResponse // on every update; see the field comment on GetTemplateResponse.TemplatePolicy. TemplatePolicy *TemplatePolicy `json:"TemplatePolicy,omitempty"` diff --git a/v3/api/template_test.go b/v3/api/template_test.go index c18c629..ee27197 100644 --- a/v3/api/template_test.go +++ b/v3/api/template_test.go @@ -244,3 +244,62 @@ func TestUpdateTemplateArg_TemplatePolicy_Roundtrip(t *testing.T) { t.Fatalf("TemplatePolicy.PrimaryKeyAlgorithms on the wire = %v, want 2 entries", policy["PrimaryKeyAlgorithms"]) } } + +// TestUpdateTemplateArg_KeyUsage_SerializesAsInt verifies that UpdateTemplateArg.KeyUsage +// serializes onto the wire as a JSON number, matching Command's TemplateUpdateRequest +// swagger schema ({"type":"integer","format":"int32"}) confirmed against a live v25.5 +// instance. Before the fix, KeyUsage was typed *bool, which serialized as a JSON boolean +// and produced a live HTTP 400 from Command ("Unexpected character encountered while +// parsing value: t. Path 'KeyUsage'"). This also verifies the value returned by +// GetTemplateResponse.KeyUsage (an int) can be assigned directly to +// UpdateTemplateArg.KeyUsage without a type conversion, since both now agree on int. +func TestUpdateTemplateArg_KeyUsage_SerializesAsInt(t *testing.T) { + var receivedBody []byte + + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + receivedBody, err = io.ReadAll(r.Body) + if err != nil { + t.Fatalf("failed to read request body: %v", err) + } + w.Header().Set("Content-Type", "application/json") + w.Write(receivedBody) + })) + defer srv.Close() + + c := newTestClient(srv) + + // Simulate a real read-modify-write: KeyUsage comes straight off a + // GetTemplateResponse (int) with no bool<->int conversion required. + fetched := GetTemplateResponse{Id: 4, KeyUsage: 160} // digitalSignature|keyEncipherment + keyUsage := fetched.KeyUsage + + arg := &UpdateTemplateArg{ + Id: 4, + KeyUsage: &keyUsage, + } + + if _, err := c.UpdateTemplate(arg); err != nil { + t.Fatalf("UpdateTemplate() error: %v", err) + } + + var onWire map[string]interface{} + if err := json.Unmarshal(receivedBody, &onWire); err != nil { + t.Fatalf("failed to decode request body sent to server: %v", err) + } + + rawKeyUsage, ok := onWire["KeyUsage"] + if !ok { + t.Fatalf("request body sent to server has no KeyUsage field; got keys: %v", onWire) + } + switch v := rawKeyUsage.(type) { + case float64: + if v != 160 { + t.Errorf("KeyUsage on the wire = %v, want 160", v) + } + case bool: + t.Fatalf("KeyUsage on the wire is a JSON boolean (%v); Command's API expects an int32 bitmask and returns HTTP 400 for a boolean payload", v) + default: + t.Fatalf("KeyUsage on the wire has unexpected type %T (value %v), want a JSON number", v, v) + } +}