feat: add JSON primitives + fix api.go placeholder
core.JSONMarshal(), JSONMarshalString(), JSONUnmarshal(), JSONUnmarshalString() wrap encoding/json so consumers don't import it directly. Same guardrail pattern as string.go wraps strings. api.go Call() now uses JSONMarshalString instead of placeholder optionsToJSON. 7 AX-7 tests. 490 tests total, 84.8% coverage. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
12adc97bbd
commit
8626710f9d
4 changed files with 128 additions and 20 deletions
21
api.go
21
api.go
|
|
@ -108,7 +108,7 @@ func (a *API) Call(endpoint string, action string, opts Options) Result {
|
|||
defer stream.Close()
|
||||
|
||||
// Encode the action call as JSON-RPC (MCP compatible)
|
||||
payload := Concat(`{"action":"`, action, `","options":`, optionsToJSON(opts), `}`)
|
||||
payload := Concat(`{"action":"`, action, `","options":`, JSONMarshalString(opts), `}`)
|
||||
|
||||
if err := stream.Send([]byte(payload)); err != nil {
|
||||
return Result{err, false}
|
||||
|
|
@ -139,25 +139,6 @@ func extractScheme(transport string) string {
|
|||
return transport
|
||||
}
|
||||
|
||||
// optionsToJSON is a minimal JSON serialiser for Options.
|
||||
// core/go stays stdlib-only — no encoding/json import.
|
||||
func optionsToJSON(opts Options) string {
|
||||
b := NewBuilder()
|
||||
b.WriteString("{")
|
||||
first := true
|
||||
for i := 0; ; i++ {
|
||||
r := opts.Get(Sprintf("_key_%d", i))
|
||||
if !r.OK {
|
||||
break
|
||||
}
|
||||
// This is a placeholder — real implementation needs proper iteration
|
||||
_ = first
|
||||
first = false
|
||||
}
|
||||
// Simple fallback: serialize known keys
|
||||
b.WriteString("}")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// RemoteAction resolves "host:action.name" syntax for transparent remote dispatch.
|
||||
// If the action name contains ":", the prefix is the endpoint and the suffix is the action.
|
||||
|
|
|
|||
|
|
@ -533,6 +533,12 @@ core.FilterArgs(args) // strip flags, keep positional
|
|||
core.ID() // "id-42-a3f2b1" — unique per process
|
||||
core.ValidateName("brain") // Result{OK: true} — rejects "", ".", "..", path seps
|
||||
core.SanitisePath("../../x") // "x" — extracts safe base, "invalid" for dangerous
|
||||
|
||||
// JSON (wraps encoding/json — consumers don't import it directly)
|
||||
core.JSONMarshal(myStruct) // Result{Value: []byte, OK: bool}
|
||||
core.JSONMarshalString(myStruct) // string (returns "{}" on error)
|
||||
core.JSONUnmarshal(data, &target) // Result{OK: bool}
|
||||
core.JSONUnmarshalString(s, &target)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
58
json.go
Normal file
58
json.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
// JSON helpers for the Core framework.
|
||||
// Wraps encoding/json so consumers don't import it directly.
|
||||
// Same guardrail pattern as string.go wraps strings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// data := core.JSONMarshal(myStruct)
|
||||
// if data.OK { json := data.Value.([]byte) }
|
||||
//
|
||||
// r := core.JSONUnmarshal(jsonBytes, &target)
|
||||
// if !r.OK { /* handle error */ }
|
||||
package core
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// JSONMarshal serialises a value to JSON bytes.
|
||||
//
|
||||
// r := core.JSONMarshal(myStruct)
|
||||
// if r.OK { data := r.Value.([]byte) }
|
||||
func JSONMarshal(v any) Result {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{data, true}
|
||||
}
|
||||
|
||||
// JSONMarshalString serialises a value to a JSON string.
|
||||
//
|
||||
// s := core.JSONMarshalString(myStruct)
|
||||
func JSONMarshalString(v any) string {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// JSONUnmarshal deserialises JSON bytes into a target.
|
||||
//
|
||||
// var cfg Config
|
||||
// r := core.JSONUnmarshal(data, &cfg)
|
||||
func JSONUnmarshal(data []byte, target any) Result {
|
||||
if err := json.Unmarshal(data, target); err != nil {
|
||||
return Result{err, false}
|
||||
}
|
||||
return Result{OK: true}
|
||||
}
|
||||
|
||||
// JSONUnmarshalString deserialises a JSON string into a target.
|
||||
//
|
||||
// var cfg Config
|
||||
// r := core.JSONUnmarshalString(`{"port":8080}`, &cfg)
|
||||
func JSONUnmarshalString(s string, target any) Result {
|
||||
return JSONUnmarshal([]byte(s), target)
|
||||
}
|
||||
63
json_test.go
Normal file
63
json_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testJSON struct {
|
||||
Name string `json:"name"`
|
||||
Port int `json:"port"`
|
||||
}
|
||||
|
||||
// --- JSONMarshal ---
|
||||
|
||||
func TestJson_JSONMarshal_Good(t *testing.T) {
|
||||
r := JSONMarshal(testJSON{Name: "brain", Port: 8080})
|
||||
assert.True(t, r.OK)
|
||||
assert.Contains(t, string(r.Value.([]byte)), `"name":"brain"`)
|
||||
}
|
||||
|
||||
func TestJson_JSONMarshal_Bad_Unmarshalable(t *testing.T) {
|
||||
r := JSONMarshal(make(chan int))
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
// --- JSONMarshalString ---
|
||||
|
||||
func TestJson_JSONMarshalString_Good(t *testing.T) {
|
||||
s := JSONMarshalString(testJSON{Name: "x", Port: 1})
|
||||
assert.Contains(t, s, `"name":"x"`)
|
||||
}
|
||||
|
||||
func TestJson_JSONMarshalString_Ugly_Fallback(t *testing.T) {
|
||||
s := JSONMarshalString(make(chan int))
|
||||
assert.Equal(t, "{}", s)
|
||||
}
|
||||
|
||||
// --- JSONUnmarshal ---
|
||||
|
||||
func TestJson_JSONUnmarshal_Good(t *testing.T) {
|
||||
var target testJSON
|
||||
r := JSONUnmarshal([]byte(`{"name":"brain","port":8080}`), &target)
|
||||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "brain", target.Name)
|
||||
assert.Equal(t, 8080, target.Port)
|
||||
}
|
||||
|
||||
func TestJson_JSONUnmarshal_Bad_Invalid(t *testing.T) {
|
||||
var target testJSON
|
||||
r := JSONUnmarshal([]byte(`not json`), &target)
|
||||
assert.False(t, r.OK)
|
||||
}
|
||||
|
||||
// --- JSONUnmarshalString ---
|
||||
|
||||
func TestJson_JSONUnmarshalString_Good(t *testing.T) {
|
||||
var target testJSON
|
||||
r := JSONUnmarshalString(`{"name":"x","port":1}`, &target)
|
||||
assert.True(t, r.OK)
|
||||
assert.Equal(t, "x", target.Name)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue