feat: Allow setting state->open to unknown via DELETE

The schema defines the absence of state->open to represent the unknown
state. Previously it was not possible to explicitly set this state via
the HTTP endpoints.

types.SpaceAPIState.Open is now an enum which is either unknown, closed,
or open. Custom IsZero() and MarshalJSON() methods on the type ensure
marshaling the value yields a boolean for open/closed and an empty byte
slice for unknown (resulting in the field being absent from the final
response).

The handlers.StateOpenDELETE() function has been added to expose this
behavior.

Switching to encoding/json/v2 required raising the minimum Go version in
go.mod to 1.27.

Closes: CCCHH/spaceapid!37
Fixes:  CCCHH/spaceapid#38
This commit is contained in:
Bendodroid 2026-10-08 23:58:10 +02:00
commit 11046cb501
7 changed files with 110 additions and 38 deletions

View file

@ -1,7 +1,8 @@
package persistence
import (
"encoding/json"
"encoding/json/jsontext"
"encoding/json/v2"
"errors"
"log"
"os"
@ -20,7 +21,7 @@ func MergeOldState(response *types.SpaceAPIResponse) {
var (
err error
oldState []byte
persistedState types.PersistentSpaceState
persistedState types.PersistentState
)
log.Println("Merging old state from", persistentStatePath, "...")
@ -87,27 +88,38 @@ func SaveCurrentState(response types.SpaceAPIResponse) {
defer file.Close()
// Create persistent state
persistentStateV14 := types.PersistentSpaceState{
State: struct {
Open bool `json:"open"`
LastChange int64 `json:"lastchange"`
Message string `json:"message,omitempty"`
}{Open: response.State.Open, LastChange: response.State.LastChange, Message: response.State.Message},
var (
isOpen bool
isUnknown bool
)
switch response.State.Open {
case types.SpaceAPIStateOpenIsUnknown:
isUnknown = true
case types.SpaceAPIStateOpenIsClosed:
break
case types.SpaceAPIStateOpenIsOpen:
isOpen = true
}
persistentState := types.PersistentState{
State: types.PersistentSpaceState{
Open: isOpen, IsUnknown: isUnknown,
LastChange: response.State.LastChange, Message: response.State.Message,
},
Sensors: make(map[string][]types.PersistentEnvironmentSensor),
}
// Save sensor state
persistentStateV14.Sensors = make(map[string][]types.PersistentEnvironmentSensor)
for key, environmentSensors := range response.Sensors {
persistentStateV14.Sensors[key] = make([]types.PersistentEnvironmentSensor, len(environmentSensors))
persistentState.Sensors[key] = make([]types.PersistentEnvironmentSensor, len(environmentSensors))
for i, sensor := range environmentSensors {
persistentStateV14.Sensors[key][i].Value = sensor.Value
persistentStateV14.Sensors[key][i].Location = sensor.Location
persistentStateV14.Sensors[key][i].Name = sensor.Name
persistentStateV14.Sensors[key][i].LastChange = sensor.LastChange
persistentState.Sensors[key][i].Value = sensor.Value
persistentState.Sensors[key][i].Location = sensor.Location
persistentState.Sensors[key][i].Name = sensor.Name
persistentState.Sensors[key][i].LastChange = sensor.LastChange
}
}
// Serialize persistent state
marshal, err := json.MarshalIndent(persistentStateV14, "", "\t")
marshal, err := json.Marshal(persistentState, json.JoinOptions(jsontext.Multiline(true), jsontext.SpaceAfterComma(true)))
if err != nil {
log.Fatalln("Failed serializing persistent state... error:", err)
}