From 82a3baaf00d43bc8a3e92a91d84faa7631cfadb5 Mon Sep 17 00:00:00 2001 From: Paul Tyng Date: Sun, 21 Mar 2021 21:13:58 -0400 Subject: [PATCH] Handle marshaling of numbers and strings for channels --- fields/api.go.tmpl | 6 +- unifi/ap_group.generated.go | 126 ++++++++++++++++++++++++++++++++ unifi/channel_plan.generated.go | 4 +- unifi/device.generated.go | 14 ++-- unifi/json.go | 31 +++++++- 5 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 unifi/ap_group.generated.go diff --git a/fields/api.go.tmpl b/fields/api.go.tmpl index 09ec6df..2561b79 100644 --- a/fields/api.go.tmpl +++ b/fields/api.go.tmpl @@ -5,6 +5,10 @@ {{ define "field-emptyStringInt" }} {{- if ne .FieldType "int" }}{{else}} {{ .FieldName }} {{ if .IsArray }}[]{{end}}emptyStringInt `json:"{{ .JSONName }}{{ if .OmitEmpty }}{{ end }}"`{{ end }} {{- end }} +{{ define "field-numberOrString" }} + {{- /* this is kind of a hack, probably a better way to do this when looking at the normalized validation */ -}} + {{- if and (eq .FieldType "string") (or (eq .JSONName "channel") (eq .JSONName "backup_channel")) }} + {{ .FieldName }} {{ if .IsArray }}[]{{end}}numberOrString `json:"{{ .JSONName }}{{ if .OmitEmpty }}{{ end }}"`{{ end }} {{- end }} {{ define "typecast" }} {{- if eq .FieldType "int" }}{{- if .IsArray }} dst.{{ .FieldName }}= make([]int, len(aux.{{ .FieldName }})) @@ -55,7 +59,7 @@ func (dst *{{ $k }}) UnmarshalJSON(b []byte) error { type Alias {{ $k }} aux := &struct { {{- range $fk, $fv := $v.Fields }}{{ if not $fv }} - {{- else }}{{- template "field-emptyStringInt" $fv }}{{ end }}{{- end }} + {{- else }}{{- template "field-emptyStringInt" $fv }}{{- template "field-numberOrString" $fv }}{{ end }}{{- end }} *Alias }{ diff --git a/unifi/ap_group.generated.go b/unifi/ap_group.generated.go new file mode 100644 index 0000000..703a9f2 --- /dev/null +++ b/unifi/ap_group.generated.go @@ -0,0 +1,126 @@ +// Code generated from ace.jar fields *.json files +// DO NOT EDIT. + +package unifi + +import ( + "context" + "encoding/json" + "fmt" +) + +// just to fix compile issues with the import +var ( + _ context.Context + _ fmt.Formatter + _ json.Marshaler +) + +type APGroup struct { + ID string `json:"_id,omitempty"` + SiteID string `json:"site_id,omitempty"` + + Hidden bool `json:"attr_hidden,omitempty"` + HiddenID string `json:"attr_hidden_id,omitempty"` + NoDelete bool `json:"attr_no_delete,omitempty"` + NoEdit bool `json:"attr_no_edit,omitempty"` + + Name string `json:"name,omitempty"` // .{1,128} +} + +func (dst *APGroup) UnmarshalJSON(b []byte) error { + type Alias APGroup + aux := &struct { + *Alias + }{ + Alias: (*Alias)(dst), + } + + err := json.Unmarshal(b, &aux) + if err != nil { + return fmt.Errorf("unable to unmarshal alias: %w", err) + } + + return nil +} + +func (c *Client) listAPGroup(ctx context.Context, site string) ([]APGroup, error) { + var respBody struct { + Meta meta `json:"meta"` + Data []APGroup `json:"data"` + } + + err := c.do(ctx, "GET", fmt.Sprintf("s/%s/apgroups", site), nil, &respBody) + if err != nil { + return nil, err + } + + return respBody.Data, nil +} + +func (c *Client) getAPGroup(ctx context.Context, site, id string) (*APGroup, error) { + var respBody struct { + Meta meta `json:"meta"` + Data []APGroup `json:"data"` + } + + err := c.do(ctx, "GET", fmt.Sprintf("s/%s/rest/apgroups/%s", site, id), nil, &respBody) + if err != nil { + return nil, err + } + + if len(respBody.Data) != 1 { + return nil, &NotFoundError{} + } + + d := respBody.Data[0] + return &d, nil +} + +func (c *Client) deleteAPGroup(ctx context.Context, site, id string) error { + err := c.do(ctx, "DELETE", fmt.Sprintf("s/%s/rest/apgroups/%s", site, id), struct{}{}, nil) + if err != nil { + return err + } + return nil +} + +func (c *Client) createAPGroup(ctx context.Context, site string, d *APGroup) (*APGroup, error) { + var respBody struct { + Meta meta `json:"meta"` + Data []APGroup `json:"data"` + } + + err := c.do(ctx, "POST", fmt.Sprintf("s/%s/rest/apgroups", site), d, &respBody) + if err != nil { + return nil, err + } + + if len(respBody.Data) != 1 { + return nil, &NotFoundError{} + } + + new := respBody.Data[0] + + return &new, nil +} + +func (c *Client) updateAPGroup(ctx context.Context, site string, d *APGroup) (*APGroup, error) { + var respBody struct { + Meta meta `json:"meta"` + Data []APGroup `json:"data"` + } + + err := c.do(ctx, "PUT", fmt.Sprintf("s/%s/rest/apgroups/%s", site, d.ID), d, &respBody) + if err != nil { + return nil, err + } + + if len(respBody.Data) != 1 { + return nil, &NotFoundError{} + } + + new := respBody.Data[0] + + return &new, nil +} diff --git a/unifi/channel_plan.generated.go b/unifi/channel_plan.generated.go index 4efac56..91f272b 100644 --- a/unifi/channel_plan.generated.go +++ b/unifi/channel_plan.generated.go @@ -119,7 +119,9 @@ type ChannelPlanRadioTable struct { func (dst *ChannelPlanRadioTable) UnmarshalJSON(b []byte) error { type Alias ChannelPlanRadioTable aux := &struct { - Width emptyStringInt `json:"width"` + BackupChannel numberOrString `json:"backup_channel"` + Channel numberOrString `json:"channel"` + Width emptyStringInt `json:"width"` *Alias }{ diff --git a/unifi/device.generated.go b/unifi/device.generated.go index 403380c..57ea3d6 100644 --- a/unifi/device.generated.go +++ b/unifi/device.generated.go @@ -281,7 +281,7 @@ type DeviceRadioTable struct { Ht string `json:"ht,omitempty"` // 20|40|80|160|1080|2160 LoadbalanceEnabled bool `json:"loadbalance_enabled,omitempty"` Maxsta int `json:"maxsta,omitempty"` // [1-9]|[1-9][0-9]|1[0-9]{2}|200|^$ - MinRssi int `json:"min_rssi,omitempty"` // ^-(6[7-9]|[7-8][0-9]|90)$ + MinRssi int `json:"min_rssi,omitempty"` // ^-([1-9]|[1-8][0-9]|9[0-4])$ MinRssiEnabled bool `json:"min_rssi_enabled,omitempty"` Name string `json:"name,omitempty"` Radio string `json:"radio,omitempty"` // ng|na|ad @@ -295,11 +295,13 @@ type DeviceRadioTable struct { func (dst *DeviceRadioTable) UnmarshalJSON(b []byte) error { type Alias DeviceRadioTable aux := &struct { - AntennaGain emptyStringInt `json:"antenna_gain"` - AntennaID emptyStringInt `json:"antenna_id"` - Maxsta emptyStringInt `json:"maxsta"` - MinRssi emptyStringInt `json:"min_rssi"` - SensLevel emptyStringInt `json:"sens_level"` + AntennaGain emptyStringInt `json:"antenna_gain"` + AntennaID emptyStringInt `json:"antenna_id"` + BackupChannel numberOrString `json:"backup_channel"` + Channel numberOrString `json:"channel"` + Maxsta emptyStringInt `json:"maxsta"` + MinRssi emptyStringInt `json:"min_rssi"` + SensLevel emptyStringInt `json:"sens_level"` *Alias }{ diff --git a/unifi/json.go b/unifi/json.go index 9f953b2..502642f 100644 --- a/unifi/json.go +++ b/unifi/json.go @@ -5,6 +5,32 @@ import ( "strings" ) +// numberOrString handles strings that can also accept JSON numbers. +// For example a field may contain a number or the string "auto". +type numberOrString string + +func (e *numberOrString) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + s := string(b) + if s == `""` { + *e = "" + return nil + } + var err error + if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) { + s, err = strconv.Unquote(s) + if err != nil { + return err + } + *e = numberOrString(s) + return nil + } + *e = numberOrString(string(b)) + return nil +} + // emptyStringInt was created due to the behavior change in // Go 1.14 with json.Number's handling of empty string. type emptyStringInt int @@ -13,11 +39,12 @@ func (e *emptyStringInt) UnmarshalJSON(b []byte) error { if len(b) == 0 { return nil } - if string(b) == `""` { + s := string(b) + if s == `""` { + *e = 0 return nil } var err error - s := string(b) if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) { s, err = strconv.Unquote(s) if err != nil {