Ordered json output & Disallow unknown fields

This commit is contained in:
世界 2022-07-03 11:28:15 +08:00
parent 85a695caa1
commit ef5cfd59d4
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
16 changed files with 243 additions and 26 deletions

View File

@ -4,7 +4,7 @@ import (
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/route/domain"
"github.com/sagernet/sing-box/common/domain"
"github.com/sagernet/sing/common"
)

View File

@ -2,11 +2,11 @@ package main
import (
"context"
"encoding/json"
"os"
"os/signal"
"syscall"
"github.com/goccy/go-json"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/option"
"github.com/sirupsen/logrus"
@ -51,7 +51,7 @@ func run(cmd *cobra.Command, args []string) {
var options option.Options
err = json.Unmarshal(configContent, &options)
if err != nil {
logrus.Fatal("parse config: ", err)
logrus.Fatal("decode config: ", err)
}
ctx, cancel := context.WithCancel(context.Background())

171
common/linkedhashmap/map.go Normal file
View File

@ -0,0 +1,171 @@
package linkedhashmap
import (
"bytes"
"github.com/goccy/go-json"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/x/list"
)
type Map[K comparable, V any] struct {
raw list.List[mapEntry[K, V]]
rawMap map[K]*list.Element[mapEntry[K, V]]
}
func (m *Map[K, V]) init() {
if m.rawMap == nil {
m.rawMap = make(map[K]*list.Element[mapEntry[K, V]])
}
}
func (m *Map[K, V]) MarshalJSON() ([]byte, error) {
buffer := new(bytes.Buffer)
buffer.WriteString("{")
for item := m.raw.Front(); item != nil; {
entry := item.Value
err := json.NewEncoder(buffer).Encode(entry.Key)
if err != nil {
return nil, err
}
buffer.WriteString(": ")
err = json.NewEncoder(buffer).Encode(entry.Value)
if err != nil {
return nil, err
}
item = item.Next()
if item != nil {
buffer.WriteString(", ")
}
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}
func (m *Map[K, V]) UnmarshalJSON(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
m.Clear()
m.init()
objectStart, err := decoder.Token()
if err != nil {
return err
} else if objectStart != json.Delim('{') {
return E.New("expected json object start, but starts with ", objectStart)
}
for decoder.More() {
var entryKey K
err = decoder.Decode(&entryKey)
if err != nil {
return err
}
var entryValue V
err = decoder.Decode(&entryValue)
if err != nil {
return err
}
m.rawMap[entryKey] = m.raw.PushBack(mapEntry[K, V]{Key: entryKey, Value: entryValue})
}
objectEnd, err := decoder.Token()
if err != nil {
return err
} else if objectEnd != json.Delim('}') {
return E.New("expected json object end, but ends with ", objectEnd)
}
return nil
}
type mapEntry[K comparable, V any] struct {
Key K
Value V
}
func (m *Map[K, V]) Size() int {
return m.raw.Size()
}
func (m *Map[K, V]) IsEmpty() bool {
return m.raw.IsEmpty()
}
func (m *Map[K, V]) ContainsKey(key K) bool {
m.init()
_, loaded := m.rawMap[key]
return loaded
}
func (m *Map[K, V]) Get(key K) (V, bool) {
m.init()
value, loaded := m.rawMap[key]
return value.Value.Value, loaded
}
func (m *Map[K, V]) Put(key K, value V) V {
m.init()
entry, loaded := m.rawMap[key]
if loaded {
oldValue := entry.Value.Value
entry.Value.Value = value
return oldValue
}
entry = m.raw.PushBack(mapEntry[K, V]{Key: key, Value: value})
m.rawMap[key] = entry
return common.DefaultValue[V]()
}
func (m *Map[K, V]) PutAll(other *Map[K, V]) {
for item := other.raw.Front(); item != nil; item = item.Next() {
m.Put(item.Value.Key, item.Value.Value)
}
}
func (m *Map[K, V]) Remove(key K) bool {
m.init()
entry, loaded := m.rawMap[key]
if !loaded {
return false
}
m.raw.Remove(entry)
delete(m.rawMap, key)
return true
}
func (m *Map[K, V]) RemoveAll(keys []K) {
m.init()
for _, key := range keys {
entry, loaded := m.rawMap[key]
if !loaded {
continue
}
m.raw.Remove(entry)
delete(m.rawMap, key)
}
}
func (m *Map[K, V]) AsMap() map[K]V {
result := make(map[K]V, m.raw.Len())
for item := m.raw.Front(); item != nil; item = item.Next() {
result[item.Value.Key] = item.Value.Value
}
return result
}
func (m *Map[K, V]) Keys() []K {
result := make([]K, 0, m.raw.Len())
for item := m.raw.Front(); item != nil; item = item.Next() {
result = append(result, item.Value.Key)
}
return result
}
func (m *Map[K, V]) Values() []V {
result := make([]V, 0, m.raw.Len())
for item := m.raw.Front(); item != nil; item = item.Next() {
result = append(result, item.Value.Value)
}
return result
}
func (m *Map[K, V]) Clear() {
*m = Map[K, V]{}
}

3
go.mod
View File

@ -4,9 +4,10 @@ go 1.18
require (
github.com/database64128/tfo-go v1.0.4
github.com/goccy/go-json v0.9.8
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/oschwald/geoip2-golang v1.7.0
github.com/sagernet/sing v0.0.0-20220702193452-6a6c180cf77e
github.com/sagernet/sing v0.0.0-20220703025722-d002d5ba3ba5
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0

6
go.sum
View File

@ -4,6 +4,8 @@ github.com/database64128/tfo-go v1.0.4/go.mod h1:q5W+W0+2IHrw/Lnl0yg4sz7Kz5IDsm9
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -18,8 +20,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/sing v0.0.0-20220702193452-6a6c180cf77e h1:GiH/gZcH8fupEfWJujaqGNBBaCkAouAJyVJy9Afxfvw=
github.com/sagernet/sing v0.0.0-20220702193452-6a6c180cf77e/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
github.com/sagernet/sing v0.0.0-20220703025722-d002d5ba3ba5 h1:0oNnpN43Z6TXDdea5tbKIXXJ62yJqTpOW4IyQSgN3KY=
github.com/sagernet/sing v0.0.0-20220703025722-d002d5ba3ba5/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=

View File

@ -1,8 +1,9 @@
package option
import (
"encoding/json"
"net/netip"
"github.com/goccy/go-json"
)
type ListenAddress netip.Addr

View File

@ -1,14 +1,27 @@
package option
import "github.com/sagernet/sing/common"
import (
"bytes"
type Options struct {
"github.com/goccy/go-json"
"github.com/sagernet/sing/common"
)
type _Options struct {
Log *LogOption `json:"log,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
}
type Options _Options
func (o *Options) UnmarshalJSON(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
decoder.DisallowUnknownFields()
return decoder.Decode((*_Options)(o))
}
func (o Options) Equals(other Options) bool {
return common.ComparablePtrEquals(o.Log, other.Log) &&
common.SliceEquals(o.Inbounds, other.Inbounds) &&

View File

@ -1,8 +1,7 @@
package option
import (
"encoding/json"
"github.com/goccy/go-json"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
@ -69,7 +68,11 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
default:
return nil
}
return json.Unmarshal(bytes, v)
err = UnmarshallExcluded(bytes, (*_Inbound)(h), v)
if err != nil {
return E.Cause(err, "inbound options")
}
return nil
}
type ListenOptions struct {

View File

@ -1,18 +1,19 @@
package option
import (
"encoding/json"
"bytes"
"github.com/sagernet/sing/common/x/linkedhashmap"
"github.com/goccy/go-json"
"github.com/sagernet/sing-box/common/linkedhashmap"
)
func ToMap(v any) (*linkedhashmap.Map[string, any], error) {
bytes, err := json.Marshal(v)
inputContent, err := json.Marshal(v)
if err != nil {
return nil, err
}
var content linkedhashmap.Map[string, any]
err = json.Unmarshal(bytes, &content)
err = content.UnmarshalJSON(inputContent)
if err != nil {
return nil, err
}
@ -36,5 +37,25 @@ func MarshallObjects(objects ...any) ([]byte, error) {
if err != nil {
return nil, err
}
return json.Marshal(content)
return content.MarshalJSON()
}
func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error {
parentContent, err := ToMap(parentObject)
if err != nil {
return err
}
var content linkedhashmap.Map[string, any]
err = content.UnmarshalJSON(inputContent)
if err != nil {
return err
}
content.RemoveAll(parentContent.Keys())
inputContent, err = content.MarshalJSON()
if err != nil {
return err
}
decoder := json.NewDecoder(bytes.NewReader(inputContent))
decoder.DisallowUnknownFields()
return decoder.Decode(object)
}

View File

@ -1,6 +1,6 @@
package option
import "encoding/json"
import "github.com/goccy/go-json"
type Listable[T comparable] []T

View File

@ -1,8 +1,7 @@
package option
import (
"encoding/json"
"github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
)

View File

@ -1,8 +1,7 @@
package option
import (
"encoding/json"
"github.com/goccy/go-json"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
@ -43,7 +42,11 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
default:
return nil
}
return json.Unmarshal(bytes, v)
err = UnmarshallExcluded(bytes, (*_Outbound)(h), v)
if err != nil {
return E.Cause(err, "outbound options")
}
return nil
}
type DialerOptions struct {

View File

@ -1,8 +1,7 @@
package option
import (
"encoding/json"
"github.com/goccy/go-json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
@ -68,7 +67,11 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error {
default:
return E.New("unknown rule type: " + r.Type)
}
return json.Unmarshal(bytes, v)
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
if err != nil {
return E.Cause(err, "route rule")
}
return nil
}
type DefaultRule struct {