mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 10:01:30 +00:00
Ordered json output & Disallow unknown fields
This commit is contained in:
parent
85a695caa1
commit
ef5cfd59d4
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
@ -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
171
common/linkedhashmap/map.go
Normal 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
3
go.mod
|
@ -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
6
go.sum
|
@ -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=
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package option
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/netip"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type ListenAddress netip.Addr
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package option
|
||||
|
||||
import "encoding/json"
|
||||
import "github.com/goccy/go-json"
|
||||
|
||||
type Listable[T comparable] []T
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue