4
go.sum
4
go.sum
@@ -208,6 +208,10 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b h1:mSUCVIwDx4hfXJfWsOPfdzEHxzb2Xjl6BQ8YgPnazQA=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func writeResp(w http.ResponseWriter, r *http.Request, resp *spec.Response) erro
|
|||||||
res := metaResponse{Response: resp}
|
res := metaResponse{Response: resp}
|
||||||
params := r.Context().Value(CtxParams).(params.Params)
|
params := r.Context().Value(CtxParams).(params.Params)
|
||||||
ew := &errWriter{w: w}
|
ew := &errWriter{w: w}
|
||||||
switch params.Get("f") {
|
switch v, _ := params.Get("f"); v {
|
||||||
case "json":
|
case "json":
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
data, err := json.Marshal(res)
|
data, err := json.Marshal(res)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (c *Controller) ServeGetMusicDirectory(r *http.Request) *spec.Response {
|
|||||||
Find(&childTracks)
|
Find(&childTracks)
|
||||||
for _, c := range childTracks {
|
for _, c := range childTracks {
|
||||||
toAppend := spec.NewTCTrackByFolder(c, folder)
|
toAppend := spec.NewTCTrackByFolder(c, folder)
|
||||||
if params.Get("c") == "Jamstash" {
|
if v, _ := params.Get("c"); v == "Jamstash" {
|
||||||
// jamstash thinks it can't play flacs
|
// jamstash thinks it can't play flacs
|
||||||
toAppend.ContentType = "audio/mpeg"
|
toAppend.ContentType = "audio/mpeg"
|
||||||
toAppend.Suffix = "mp3"
|
toAppend.Suffix = "mp3"
|
||||||
|
|||||||
@@ -1,79 +1,262 @@
|
|||||||
package params
|
package params
|
||||||
|
|
||||||
|
// {get, get first, get or} * {str, int, id} * {list, not list} = 18
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Params struct {
|
var (
|
||||||
values url.Values
|
ErrKeyNotFound = errors.New("key(s) not found")
|
||||||
|
ErrIDInvalid = errors.New("invalid id")
|
||||||
|
ErrIDNotAnInt = errors.New("not an int")
|
||||||
|
)
|
||||||
|
|
||||||
|
const IDSeparator = "-"
|
||||||
|
|
||||||
|
type IDType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// type values copied from subsonic
|
||||||
|
IDTypeArtist IDType = "ar"
|
||||||
|
IDTypeAlbum IDType = "al"
|
||||||
|
IDTypeTrack IDType = "tr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ID struct {
|
||||||
|
Type IDType
|
||||||
|
Value int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IDArtist(id int) string { return fmt.Sprintf("%d-%s", id, IDTypeArtist) }
|
||||||
|
func IDAlbum(id int) string { return fmt.Sprintf("%d-%s", id, IDTypeAlbum) }
|
||||||
|
func IDTrack(id int) string { return fmt.Sprintf("%d-%s", id, IDTypeTrack) }
|
||||||
|
|
||||||
|
// ** begin type parsing, support {[],}{string,int,ID} => 6 types
|
||||||
|
|
||||||
|
func parse(values []string, i interface{}) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return ErrKeyNotFound
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
switch v := i.(type) {
|
||||||
|
case *string:
|
||||||
|
*v, err = parseStr(values[0])
|
||||||
|
case *int:
|
||||||
|
*v, err = parseInt(values[0])
|
||||||
|
case *ID:
|
||||||
|
*v, err = parseID(values[0])
|
||||||
|
case *[]string:
|
||||||
|
for _, val := range values {
|
||||||
|
parsed, err := parseStr(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = append(*v, parsed)
|
||||||
|
}
|
||||||
|
case *[]int:
|
||||||
|
for _, val := range values {
|
||||||
|
parsed, err := parseInt(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = append(*v, parsed)
|
||||||
|
}
|
||||||
|
case *[]ID:
|
||||||
|
for _, val := range values {
|
||||||
|
parsed, err := parseID(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = append(*v, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin parse funcs
|
||||||
|
|
||||||
|
func parseStr(in string) (string, error) {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(in string) (int, error) {
|
||||||
|
if v, err := strconv.Atoi(in); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return 0, ErrIDNotAnInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseID(in string) (ID, error) {
|
||||||
|
parts := strings.Split(in, IDSeparator)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return ID{}, fmt.Errorf("bad separator: %w", ErrIDInvalid)
|
||||||
|
}
|
||||||
|
partType := parts[0]
|
||||||
|
partValue := parts[1]
|
||||||
|
val, err := parseInt(partValue)
|
||||||
|
if err != nil {
|
||||||
|
return ID{}, fmt.Errorf("%s: %w", partValue, err)
|
||||||
|
}
|
||||||
|
switch partType {
|
||||||
|
case string(IDTypeArtist):
|
||||||
|
return ID{Type: IDTypeArtist, Value: val}, nil
|
||||||
|
case string(IDTypeAlbum):
|
||||||
|
return ID{Type: IDTypeAlbum, Value: val}, nil
|
||||||
|
case string(IDTypeTrack):
|
||||||
|
return ID{Type: IDTypeTrack, Value: val}, nil
|
||||||
|
}
|
||||||
|
return ID{}, ErrIDInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params url.Values
|
||||||
|
|
||||||
func New(r *http.Request) Params {
|
func New(r *http.Request) Params {
|
||||||
// first load params from the url
|
// first load params from the url
|
||||||
params := r.URL.Query()
|
params := r.URL.Query()
|
||||||
// also if there's any in the post body, use those too
|
// also if there's any in the post body, use those too
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err == nil {
|
||||||
return Params{params}
|
for k, v := range r.Form {
|
||||||
|
params[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for k, v := range r.Form {
|
return Params(params)
|
||||||
params[k] = v
|
|
||||||
}
|
|
||||||
return Params{params}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Params) Get(key string) string {
|
func (p Params) get(key string) []string {
|
||||||
return p.values.Get(key)
|
return p[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Params) GetOr(key, or string) string {
|
func (p Params) getFirst(keys []string) []string {
|
||||||
val := p.Get(key)
|
for _, k := range keys {
|
||||||
if val == "" {
|
if v, ok := p[k]; ok {
|
||||||
return or
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Params) GetInt(key string) (int, error) {
|
|
||||||
strVal := p.values.Get(key)
|
|
||||||
if strVal == "" {
|
|
||||||
return 0, fmt.Errorf("no param with key `%s`", key)
|
|
||||||
}
|
|
||||||
val, err := strconv.Atoi(strVal)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("not an int `%s`", strVal)
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Params) GetIntOr(key string, or int) int {
|
|
||||||
val, err := p.GetInt(key)
|
|
||||||
if err != nil {
|
|
||||||
return or
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Params) GetFirstList(keys ...string) []string {
|
|
||||||
for _, key := range keys {
|
|
||||||
if v, ok := p.values[key]; ok && len(v) > 0 {
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Params) GetFirstListInt(keys ...string) []int {
|
// ** begin str {get, get first, get or}
|
||||||
v := p.GetFirstList(keys...)
|
|
||||||
if v == nil {
|
func (p Params) Get(key string) (string, error) {
|
||||||
return nil
|
var ret string
|
||||||
}
|
return ret, parse(p.get(key), &ret)
|
||||||
ret := make([]int, 0, len(v))
|
}
|
||||||
for _, p := range v {
|
|
||||||
i, _ := strconv.Atoi(p)
|
func (p Params) GetFirst(keys ...string) (string, error) {
|
||||||
ret = append(ret, i)
|
var ret string
|
||||||
}
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
return ret
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOr(key string, or string) string {
|
||||||
|
var ret string
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin []str {get, get first, get or}
|
||||||
|
|
||||||
|
func (p Params) GetList(key string) ([]string, error) {
|
||||||
|
var ret []string
|
||||||
|
return ret, parse(p.get(key), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetFirstList(keys ...string) ([]string, error) {
|
||||||
|
var ret []string
|
||||||
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOrList(key string, or []string) []string {
|
||||||
|
var ret []string
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin int {get, get first, get or}
|
||||||
|
|
||||||
|
func (p Params) GetInt(key string) (int, error) {
|
||||||
|
var ret int
|
||||||
|
return ret, parse(p.get(key), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetFirstInt(keys ...string) (int, error) {
|
||||||
|
var ret int
|
||||||
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOrInt(key string, or int) int {
|
||||||
|
var ret int
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin []int {get, get first, get or}
|
||||||
|
|
||||||
|
func (p Params) GetIntList(key string) ([]int, error) {
|
||||||
|
var ret []int
|
||||||
|
return ret, parse(p.get(key), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetFirstIntList(keys ...string) ([]int, error) {
|
||||||
|
var ret []int
|
||||||
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOrIntList(key string, or []int) []int {
|
||||||
|
var ret []int
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin ID {get, get first, get or}
|
||||||
|
|
||||||
|
func (p Params) GetID(key string) (ID, error) {
|
||||||
|
var ret ID
|
||||||
|
return ret, parse(p.get(key), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetFirstID(keys ...string) (ID, error) {
|
||||||
|
var ret ID
|
||||||
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOrID(key string, or ID) ID {
|
||||||
|
var ret ID
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** begin []ID {get, get first, get or}
|
||||||
|
|
||||||
|
func (p Params) GetIDList(key string) ([]ID, error) {
|
||||||
|
var ret []ID
|
||||||
|
return ret, parse(p.get(key), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetFirstIDList(keys ...string) ([]ID, error) {
|
||||||
|
var ret []ID
|
||||||
|
return ret, parse(p.getFirst(keys), &ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Params) GetOrIDList(key string, or []ID) []ID {
|
||||||
|
var ret []ID
|
||||||
|
if err := parse(p.get(key), &ret); err == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return or
|
||||||
}
|
}
|
||||||
|
|||||||
55
server/ctrlsubsonic/params/params_test.go
Normal file
55
server/ctrlsubsonic/params/params_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package params
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseID(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
param string
|
||||||
|
expType IDType
|
||||||
|
expValue int
|
||||||
|
expErr error
|
||||||
|
}{
|
||||||
|
{param: "al-45", expType: IDTypeAlbum, expValue: 45},
|
||||||
|
{param: "ar-2", expType: IDTypeArtist, expValue: 2},
|
||||||
|
{param: "tr-43", expType: IDTypeTrack, expValue: 43},
|
||||||
|
{param: "xx-1", expErr: ErrIDInvalid},
|
||||||
|
{param: "al-howdy", expErr: ErrIDNotAnInt},
|
||||||
|
}
|
||||||
|
for _, tcase := range tcases {
|
||||||
|
t.Run(tcase.param, func(t *testing.T) {
|
||||||
|
act, err := parseID(tcase.param)
|
||||||
|
if err != nil && !errors.Is(err, tcase.expErr) {
|
||||||
|
t.Fatalf("expected err %q, got %q", tcase.expErr, err)
|
||||||
|
}
|
||||||
|
if act.Value != tcase.expValue {
|
||||||
|
t.Errorf("expected value %d, got %d", tcase.expValue, act.Value)
|
||||||
|
}
|
||||||
|
if act.Type != tcase.expType {
|
||||||
|
t.Errorf("expected type %v, got %v", tcase.expType, act.Type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO?
|
||||||
|
func TestGet(t *testing.T) {}
|
||||||
|
func TestGetFirst(t *testing.T) {}
|
||||||
|
func TestGetOr(t *testing.T) {}
|
||||||
|
func TestGetList(t *testing.T) {}
|
||||||
|
func TestGetFirstList(t *testing.T) {}
|
||||||
|
func TestGetOrList(t *testing.T) {}
|
||||||
|
func TestGetInt(t *testing.T) {}
|
||||||
|
func TestGetFirstInt(t *testing.T) {}
|
||||||
|
func TestGetOrInt(t *testing.T) {}
|
||||||
|
func TestGetIntList(t *testing.T) {}
|
||||||
|
func TestGetFirstIntList(t *testing.T) {}
|
||||||
|
func TestGetOrIntList(t *testing.T) {}
|
||||||
|
func TestGetID(t *testing.T) {}
|
||||||
|
func TestGetFirstID(t *testing.T) {}
|
||||||
|
func TestGetOrID(t *testing.T) {}
|
||||||
|
func TestGetIDList(t *testing.T) {}
|
||||||
|
func TestGetFirstIDList(t *testing.T) {}
|
||||||
|
func TestGetOrIDList(t *testing.T) {}
|
||||||
Reference in New Issue
Block a user