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,5 +1,9 @@
package types
import (
"encoding/json/v2"
)
const CurrentMessageVersion = "15"
// SpaceAPIResponse represents the contents of the SpaceAPI message
@ -114,19 +118,50 @@ type SpaceAPILocationAreas struct {
SquareMeters float64 `json:"square_meters"` // REQUIRED
}
type SpaceAPIStateOpen int
const (
SpaceAPIStateOpenIsUnknown = iota
SpaceAPIStateOpenIsClosed
SpaceAPIStateOpenIsOpen
)
func (state SpaceAPIStateOpen) MarshalJSON() ([]byte, error) {
switch state {
case SpaceAPIStateOpenIsClosed:
return json.Marshal(false)
case SpaceAPIStateOpenIsOpen:
return json.Marshal(true)
default:
return []byte{}, nil
}
}
func (state SpaceAPIStateOpen) IsZero() bool {
return state == SpaceAPIStateOpenIsUnknown
}
type SpaceAPIState struct {
Open bool `json:"open,omitempty"`
LastChange int64 `json:"lastchange,omitempty"`
TriggerPerson string `json:"trigger_person,omitempty"`
Message string `json:"message,omitempty"`
Open SpaceAPIStateOpen `json:"open,omitzero"`
LastChange int64 `json:"lastchange,omitempty"`
TriggerPerson string `json:"trigger_person,omitempty"`
Message string `json:"message,omitempty"`
Icon struct {
Open string `json:"open,omitempty"` // REQUIRED
Closed string `json:"closed,omitempty"` // REQUIRED
} `json:"icon,omitzero"`
}
func (s *SpaceAPIState) FromPersistentSpaceState(ps PersistentSpaceState) {
s.Open = ps.State.Open
func (s *SpaceAPIState) FromPersistentSpaceState(ps PersistentState) {
if ps.State.IsUnknown {
s.Open = SpaceAPIStateOpenIsUnknown
} else {
if ps.State.Open {
s.Open = SpaceAPIStateOpenIsOpen
} else {
s.Open = SpaceAPIStateOpenIsClosed
}
}
s.LastChange = ps.State.LastChange
s.Message = ps.State.Message
}
@ -147,15 +182,18 @@ type EnvironmentSensor struct {
LastChange int64 `json:"lastchange,omitempty"`
}
type PersistentSpaceState struct {
State struct {
Open bool `json:"open"`
LastChange int64 `json:"lastchange"`
Message string `json:"message,omitempty"`
} `json:"state"`
type PersistentState struct {
State PersistentSpaceState `json:"state"`
Sensors map[string][]PersistentEnvironmentSensor `json:"sensors,omitempty"`
}
type PersistentSpaceState struct {
Open bool `json:"open"`
IsUnknown bool `json:"is_unknown"`
LastChange int64 `json:"lastchange"`
Message string `json:"message,omitempty"`
}
type PersistentEnvironmentSensor struct {
Value float64 `json:"value"`
Location string `json:"location,omitempty"`