add draft design

This commit is contained in:
Masaaki Goshima 2021-03-24 18:50:01 +09:00
parent 9903d614e3
commit e09e0ff5b3
4 changed files with 189 additions and 0 deletions

89
_example/draft-query.go Normal file
View File

@ -0,0 +1,89 @@
package main
import (
"fmt"
"github.com/goccy/go-json"
)
type User struct {
ID int64
Name string
Age int
Address UserAddressResolver
}
type UserAddress struct {
UserID int64
PostCode string
City string
Address1 string
Address2 string
}
type UserAddressResolver func() (*UserAddress, error)
func (resolver UserAddressResolver) MarshalJSON() ([]byte, error) {
address, err := resolver()
if err != nil {
return nil, err
}
return json.Marshal(address)
}
func (resolver UserAddressResolver) ResolveQueryJSON(q *json.Query) (interface{}, error) {
// validate or rewrite json.Query
//
address, err := resolver()
if err != nil {
return nil, err
}
return address, nil
}
type UserRepository struct{}
func (r UserRepository) FindByID(id int64) (*User, error) {
v := User{ID: id, Name: "Ken", Age: 20}
// resolve relation from User to UserAddress
uaRepo := new(UserAddressRepository)
v.Address = func() (*UserAddress, error) {
return uaRepo.FindByUserID(v.ID)
}
return v, nil
}
type UserAddressRepository struct{}
func (r UserAddressRepository) FindByUserID(id int64) (*UserAddress, error) {
return &UserAddress{UserID: id, City: "A", Address1: "hoge", Address2: "fuga"}, nil
}
func main() {
user, err := new(UserRepository).FindByID(1)
if err != nil {
panic(err)
}
{
b, err := json.Marshal(user)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
{
//json.QueryFromJSON(`["Name", "Age", { "Address": [ "City" ] }]`)
q := json.NewQuery().Fields(
"Name",
"Age",
json.NewQuery("Address").Fields(
"City",
),
)
b, err := json.MarshalWithQuery(user, q)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
}

View File

@ -148,6 +148,27 @@ func marshal(v interface{}, opt EncodeOption) ([]byte, error) {
return copied, nil return copied, nil
} }
func marshalQuery(v interface{}, q *Query) ([]byte, error) {
ctx := takeEncodeRuntimeContext()
buf, err := encode(ctx, v, EncodeOptionHTMLEscape)
if err != nil {
releaseEncodeRuntimeContext(ctx)
return nil, err
}
// this line exists to escape call of `runtime.makeslicecopy` .
// if use `make([]byte, len(buf)-1)` and `copy(copied, buf)`,
// dst buffer size and src buffer size are differrent.
// in this case, compiler uses `runtime.makeslicecopy`, but it is slow.
buf = buf[:len(buf)-1]
copied := make([]byte, len(buf))
copy(copied, buf)
releaseEncodeRuntimeContext(ctx)
return copied, nil
}
func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) { func marshalNoEscape(v interface{}, opt EncodeOption) ([]byte, error) {
ctx := takeEncodeRuntimeContext() ctx := takeEncodeRuntimeContext()

View File

@ -13,6 +13,10 @@ type Marshaler interface {
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
} }
type QueryResolver interface {
ResolveQueryJSON(*Query) (interface{}, error)
}
// Unmarshaler is the interface implemented by types // Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves. // that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of // The input can be assumed to be a valid encoding of
@ -172,6 +176,10 @@ func MarshalWithOption(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, err
return marshal(v, opt) return marshal(v, opt)
} }
func MarshalWithQuery(v interface{}, q *Query) ([]byte, error) {
return marshalQuery(v, q)
}
// MarshalIndent is like Marshal but applies Indent to format the output. // MarshalIndent is like Marshal but applies Indent to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix // Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting. // followed by one or more copies of indent according to the indentation nesting.

71
query.go Normal file
View File

@ -0,0 +1,71 @@
package json
import "fmt"
type Query struct {
query *subQuery
err error
}
func (q *Query) String() string {
if q.err != nil {
return ""
}
if q.query == nil {
return ""
}
b, err := Marshal(q.query.dump())
if err != nil {
return ""
}
return string(b)
}
func (q *Query) Error() error {
return q.err
}
func (q *Query) Fields(fieldNameOrQueryList ...interface{}) *Query {
for _, fieldNameOrQuery := range fieldNameOrQueryList {
switch v := fieldNameOrQuery.(type) {
case string:
q.fields = append(q.fields, &subQuery{name: v})
case *Query:
q.fields = append(q.fields, v.query)
q.err = v.err
default:
q.err = fmt.Errorf("children types must be string or *Query but found %T", fieldNameOrQuery)
}
if q.err != nil {
break
}
}
return q
}
func NewQuery(name ...string) *Query {
if len(name) > 1 {
return &Query{err: fmt.Errorf(
"NewQuery's argument allow empty or single name only, but passed %v", name,
)}
}
return &Query{query: &subQuery{name: name}}
}
type subQuery struct {
name string
fields []*subQuery
}
func (q *subQuery) dump() interface{} {
fields := []interface{}{}
for _, field := range q.fields {
fields = append(fields, field.dump())
}
if q.name != "" {
return map[string][]interface{}{
q.name: fields,
}
}
return interface{}(fields)
}