Files
go-unifi/fields/main.go
2020-03-26 16:12:52 -04:00

398 lines
8.8 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"github.com/iancoleman/strcase"
)
type replacement struct {
Old string
New string
}
var fieldReps = []replacement{
{"Dhcpdv6", "DHCPDV6"},
{"Dhcpd", "DHCPD"},
{"Idx", "IDX"},
{"Ipsec", "IPSec"},
{"Ipv6", "IPV6"},
{"Openvpn", "OpenVPN"},
{"Tftp", "TFTP"},
{"Wlangroup", "WLANGroup"},
{"Bc", "Broadcast"},
{"Dhcp", "DHCP"},
{"Dns", "DNS"},
{"Dpi", "DPI"},
{"Dtim", "DTIM"},
{"Firewallgroup", "FirewallGroup"},
{"Fixedip", "FixedIP"},
{"Icmp", "ICMP"},
{"Id", "ID"},
{"Igmp", "IGMP"},
{"Ip", "IP"},
{"Leasetime", "LeaseTime"},
{"Mac", "MAC"},
{"Mcastenhance", "MulticastEnhance"},
{"Minrssi", "MinRSSI"},
{"Monthdays", "MonthDays"},
{"Nat", "NAT"},
{"Networkconf", "Network"},
{"Networkgroup", "NetworkGroup"},
{"Pd", "PD"},
{"Pmf", "PMF"},
{"Qos", "QOS"},
{"Radius", "RADIUS"},
{"Ssid", "SSID"},
{"Startdate", "StartDate"},
{"Starttime", "StartTime"},
{"Stopdate", "StopDate"},
{"Stoptime", "StopTime"},
{"Tcp", "TCP"},
{"Udp", "UDP"},
{"Usergroup", "UserGroup"},
{"Utc", "UTC"},
{"Vlan", "VLAN"},
{"Vpn", "VPN"},
{"Wan", "WAN"},
{"Wep", "WEP"},
{"Wlan", "WLAN"},
{"Wpa", "WPA"},
}
var fileReps = []replacement{
{"WlanConf", "WLAN"},
{"Dhcp", "DHCP"},
{"Wlan", "WLAN"},
{"NetworkConf", "Network"},
}
func cleanName(name string, reps []replacement) string {
for _, rep := range reps {
name = strings.ReplaceAll(name, rep.Old, rep.New)
}
return name
}
func main() {
version := os.Args[1]
out := os.Args[2]
wd, err := os.Getwd()
if err != nil {
panic(err)
}
fieldsDir := filepath.Join(wd, version)
outDir := filepath.Join(wd, out)
fieldsFiles, err := ioutil.ReadDir(fieldsDir)
if err != nil {
panic(err)
}
for _, fieldsFile := range fieldsFiles {
name := fieldsFile.Name()
// if name != "WlanConf.json" {
// continue
// }
ext := filepath.Ext(name)
if filepath.Ext(name) != ".json" {
continue
}
if name == "Setting.json" {
continue
}
name = name[:len(name)-len(ext)]
urlPath := strings.ToLower(name)
structName := cleanName(name, fileReps)
goFile := strcase.ToSnake(structName) + ".generated.go"
code, err := generateCode(filepath.Join(fieldsDir, fieldsFile.Name()), structName, urlPath)
if err != nil {
fmt.Printf("skipping file %s: %s", fieldsFile.Name(), err)
continue
// panic(err)
}
_ = os.Remove(filepath.Join(outDir, goFile))
ioutil.WriteFile(filepath.Join(outDir, goFile), ([]byte)(code), 0644)
}
fmt.Printf("%s\n", outDir)
}
func generateCode(fieldsFile string, structName string, urlPath string) (string, error) {
b, err := ioutil.ReadFile(fieldsFile)
if err != nil {
return "", err
}
var fields map[string]interface{}
err = json.Unmarshal(b, &fields)
if err != nil {
return "", err
}
code := fmt.Sprintf(`// Code generated from ace.jar fields *.json files
// DO NOT EDIT.
package unifi
import (
"context"
"fmt"
)
// just to fix compile issues with the import
var (
_ fmt.Formatter
_ context.Context
)
type %s 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\"`"+`
`, structName)
fieldNames := []string{}
for name := range fields {
fieldNames = append(fieldNames, name)
}
// TODO: sort by normalized name, not this name
sort.Strings(fieldNames)
for _, name := range fieldNames {
switch {
case structName == "User" && name == "blocked":
code += "\tBlocked bool `json:\"blocked,omitempty\"`\n"
continue
case structName == "User" && name == "last_seen":
code += "\tLastSeen int `json:\"last_seen,omitempty\"`\n"
continue
case structName == "SettingUsg" && strings.HasSuffix(name, "_timeout"):
field := strcase.ToCamel(name)
field = cleanName(field, fieldReps)
code += fmt.Sprintf("\t%s int `json:\"%s,omitempty\"`\n", field, name)
continue
}
validation := fields[name]
fieldCode, err := generateField(name, validation)
if err != nil {
return "", err
}
code += fieldCode + "\n"
}
switch structName {
case "User":
code += "\t// non-generated fields\n\tIP string `json:\"ip,omitempty\"`\n"
}
code = code + "}\n"
if strings.HasPrefix(structName, "Setting") {
return code, nil
}
code = code + fmt.Sprintf(`
func (c *Client) list%[1]s(ctx context.Context, site string) ([]%[1]s, error) {
var respBody struct {
Meta meta `+"`"+`json:"meta"`+"`"+`
Data []%[1]s `+"`"+`json:"data"`+"`"+`
}
err := c.do(ctx, "GET", fmt.Sprintf("s/%%s/rest/%[2]s", site), nil, &respBody)
if err != nil {
return nil, err
}
return respBody.Data, nil
}
func (c *Client) get%[1]s(ctx context.Context, site, id string) (*%[1]s, error) {
var respBody struct {
Meta meta `+"`"+`json:"meta"`+"`"+`
Data []%[1]s `+"`"+`json:"data"`+"`"+`
}
err := c.do(ctx, "GET", fmt.Sprintf("s/%%s/rest/%[2]s/%%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) delete%[1]s(ctx context.Context, site, id string) error {
err := c.do(ctx, "DELETE", fmt.Sprintf("s/%%s/rest/%[2]s/%%s", site, id), struct{}{}, nil)
if err != nil {
return err
}
return nil
}
func (c *Client) create%[1]s(ctx context.Context, site string, d *%[1]s) (*%[1]s, error) {
var respBody struct {
Meta meta `+"`"+`json:"meta"`+"`"+`
Data []%[1]s `+"`"+`json:"data"`+"`"+`
}
err := c.do(ctx, "POST", fmt.Sprintf("s/%%s/rest/%[2]s", 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) update%[1]s(ctx context.Context, site string, d *%[1]s) (*%[1]s, error) {
var respBody struct {
Meta meta `+"`"+`json:"meta"`+"`"+`
Data []%[1]s `+"`"+`json:"data"`+"`"+`
}
err := c.do(ctx, "PUT", fmt.Sprintf("s/%%s/rest/%[2]s/%%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
}
`, structName, urlPath)
return code, nil
}
func normalizeValidation(re string) string {
re = strings.ReplaceAll(re, "\\d", "[0-9]")
re = strings.ReplaceAll(re, "[-+]?", "")
re = strings.ReplaceAll(re, "[+-]?", "")
re = strings.ReplaceAll(re, "[-]?", "")
re = strings.ReplaceAll(re, "\\.", ".")
re = strings.ReplaceAll(re, "[.]?", ".")
quants := regexp.MustCompile(`\{\d*,?\d*\}|\*|\+|\?`)
re = quants.ReplaceAllString(re, "")
control := regexp.MustCompile(`[\(\[\]\)\|\-\$\^]`)
re = control.ReplaceAllString(re, "")
re = strings.TrimPrefix(re, "^")
re = strings.TrimSuffix(re, "$")
return re
}
func typeFromValidation(validation interface{}) (string, string, bool, error) {
switch validation := validation.(type) {
case []interface{}:
if len(validation) == 0 {
return "[]string", "", false, nil
}
if len(validation) > 1 {
return "", "", false, fmt.Errorf("unknown validation %#v", validation)
}
elementType, elementComment, _, err := typeFromValidation(validation[0])
if err != nil {
return "", "", false, err
}
return fmt.Sprintf("[]%s", elementType), elementComment, true, nil
case string:
comment := validation
normalized := normalizeValidation(validation)
allowEmpty := strings.HasSuffix(validation, "|^$") || strings.HasPrefix(validation, "^$|")
switch {
case normalized == "falsetrue" || normalized == "truefalse":
return "bool", "", false, nil
default:
if _, err := strconv.ParseFloat(normalized, 64); err == nil {
if normalized == "09" || normalized == "09.09" {
comment = ""
}
if strings.Contains(normalized, ".") {
if strings.Contains(validation, "\\.){3}") {
break
}
return "float64", comment, true, nil
}
return "int", comment, true, nil
}
}
if validation != "" && normalized != "" {
fmt.Printf("normalize %q to %q\n", validation, normalized)
}
return "string", validation, !allowEmpty, nil
}
return "", "", false, fmt.Errorf("unable to determine type from validation %q", validation)
}
func generateField(name string, validation interface{}) (string, error) {
field := strcase.ToCamel(name)
field = cleanName(field, fieldReps)
fieldType, comment, omitempty, err := typeFromValidation(validation)
if err != nil {
return "", err
}
comment = strings.TrimSpace(fmt.Sprintf("// %s", comment))
if comment == "//" {
comment = ""
}
if fieldType == "string" && strings.HasSuffix(field, "ID") {
omitempty = false
}
omitemptyCode := ""
if omitempty {
omitemptyCode = ",omitempty"
}
return fmt.Sprintf("\t%s %s `json:\"%s%s\"` %s", field, fieldType, name, omitemptyCode, comment), nil
}