mirror of https://github.com/stashapp/stash.git
232 lines
6.8 KiB
Go
232 lines
6.8 KiB
Go
package clientgen
|
|
|
|
import (
|
|
"fmt"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"github.com/99designs/gqlgen/codegen/config"
|
|
"github.com/99designs/gqlgen/codegen/templates"
|
|
"github.com/vektah/gqlparser/v2/ast"
|
|
)
|
|
|
|
type Argument struct {
|
|
Variable string
|
|
Type types.Type
|
|
}
|
|
|
|
type ResponseField struct {
|
|
Name string
|
|
IsFragmentSpread bool
|
|
IsInlineFragment bool
|
|
Type types.Type
|
|
Tags []string
|
|
ResponseFields ResponseFieldList
|
|
}
|
|
|
|
type ResponseFieldList []*ResponseField
|
|
|
|
func (rs ResponseFieldList) StructType() *types.Struct {
|
|
vars := make([]*types.Var, 0)
|
|
structTags := make([]string, 0)
|
|
for _, filed := range rs {
|
|
// クエリーのフィールドの子階層がFragmentの場合、このフィールドにそのFragmentの型を追加する
|
|
if filed.IsFragmentSpread {
|
|
typ, ok := filed.ResponseFields.StructType().Underlying().(*types.Struct)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for j := 0; j < typ.NumFields(); j++ {
|
|
vars = append(vars, typ.Field(j))
|
|
structTags = append(structTags, typ.Tag(j))
|
|
}
|
|
} else {
|
|
vars = append(vars, types.NewVar(0, nil, templates.ToGo(filed.Name), filed.Type))
|
|
structTags = append(structTags, strings.Join(filed.Tags, " "))
|
|
}
|
|
}
|
|
|
|
return types.NewStruct(vars, structTags)
|
|
}
|
|
|
|
func (rs ResponseFieldList) IsFragment() bool {
|
|
if len(rs) != 1 {
|
|
return false
|
|
}
|
|
|
|
return rs[0].IsInlineFragment || rs[0].IsFragmentSpread
|
|
}
|
|
|
|
func (rs ResponseFieldList) IsBasicType() bool {
|
|
return len(rs) == 0
|
|
}
|
|
|
|
func (rs ResponseFieldList) IsStructType() bool {
|
|
return len(rs) > 0 && !rs.IsFragment()
|
|
}
|
|
|
|
type SourceGenerator struct {
|
|
cfg *config.Config
|
|
binder *config.Binder
|
|
client config.PackageConfig
|
|
}
|
|
|
|
func NewSourceGenerator(cfg *config.Config, client config.PackageConfig) *SourceGenerator {
|
|
return &SourceGenerator{
|
|
cfg: cfg,
|
|
binder: cfg.NewBinder(),
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
func (r *SourceGenerator) NewResponseFields(selectionSet ast.SelectionSet) ResponseFieldList {
|
|
responseFields := make(ResponseFieldList, 0, len(selectionSet))
|
|
for _, selection := range selectionSet {
|
|
responseFields = append(responseFields, r.NewResponseField(selection))
|
|
}
|
|
|
|
return responseFields
|
|
}
|
|
|
|
func (r *SourceGenerator) NewResponseFieldsByDefinition(definition *ast.Definition) (ResponseFieldList, error) {
|
|
fields := make(ResponseFieldList, 0, len(definition.Fields))
|
|
for _, field := range definition.Fields {
|
|
if field.Type.Name() == "__Schema" || field.Type.Name() == "__Type" {
|
|
continue
|
|
}
|
|
|
|
var typ types.Type
|
|
if field.Type.Name() == "Query" || field.Type.Name() == "Mutation" {
|
|
var baseType types.Type
|
|
baseType, err := r.binder.FindType(r.client.Pkg().Path(), field.Type.Name())
|
|
if err != nil {
|
|
if !strings.Contains(err.Error(), "unable to find type") {
|
|
return nil, fmt.Errorf("not found type: %w", err)
|
|
}
|
|
|
|
// create new type
|
|
baseType = types.NewPointer(types.NewNamed(
|
|
types.NewTypeName(0, r.client.Pkg(), templates.ToGo(field.Type.Name()), nil),
|
|
nil,
|
|
nil,
|
|
))
|
|
}
|
|
|
|
// for recursive struct field in go
|
|
typ = types.NewPointer(baseType)
|
|
} else {
|
|
baseType, err := r.binder.FindTypeFromName(r.cfg.Models[field.Type.Name()].Model[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("not found type: %w", err)
|
|
}
|
|
|
|
typ = r.binder.CopyModifiersFromAst(field.Type, baseType)
|
|
}
|
|
|
|
tags := []string{
|
|
fmt.Sprintf(`json:"%s"`, field.Name),
|
|
fmt.Sprintf(`graphql:"%s"`, field.Name),
|
|
}
|
|
|
|
fields = append(fields, &ResponseField{
|
|
Name: field.Name,
|
|
Type: typ,
|
|
Tags: tags,
|
|
})
|
|
}
|
|
|
|
return fields, nil
|
|
}
|
|
|
|
func (r *SourceGenerator) NewResponseField(selection ast.Selection) *ResponseField {
|
|
switch selection := selection.(type) {
|
|
case *ast.Field:
|
|
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
|
|
|
|
var baseType types.Type
|
|
switch {
|
|
case fieldsResponseFields.IsBasicType():
|
|
baseType = r.Type(selection.Definition.Type.Name())
|
|
case fieldsResponseFields.IsFragment():
|
|
// 子フィールドがFragmentの場合はこのFragmentがフィールドの型になる
|
|
// if a child field is fragment, this field type became fragment.
|
|
baseType = fieldsResponseFields[0].Type
|
|
case fieldsResponseFields.IsStructType():
|
|
baseType = fieldsResponseFields.StructType()
|
|
default:
|
|
// ここにきたらバグ
|
|
// here is bug
|
|
panic("not match type")
|
|
}
|
|
|
|
// GraphQLの定義がオプショナルのはtypeのポインタ型が返り、配列の定義場合はポインタのスライスの型になって返ってきます
|
|
// return pointer type then optional type or slice pointer then slice type of definition in GraphQL.
|
|
typ := r.binder.CopyModifiersFromAst(selection.Definition.Type, baseType)
|
|
|
|
tags := []string{
|
|
fmt.Sprintf(`json:"%s"`, selection.Alias),
|
|
fmt.Sprintf(`graphql:"%s"`, selection.Alias),
|
|
}
|
|
|
|
return &ResponseField{
|
|
Name: selection.Alias,
|
|
Type: typ,
|
|
Tags: tags,
|
|
ResponseFields: fieldsResponseFields,
|
|
}
|
|
|
|
case *ast.FragmentSpread:
|
|
// この構造体はテンプレート側で使われることはなく、ast.FieldでFragment判定するために使用する
|
|
fieldsResponseFields := r.NewResponseFields(selection.Definition.SelectionSet)
|
|
typ := types.NewNamed(
|
|
types.NewTypeName(0, r.client.Pkg(), templates.ToGo(selection.Name), nil),
|
|
fieldsResponseFields.StructType(),
|
|
nil,
|
|
)
|
|
|
|
return &ResponseField{
|
|
Name: selection.Name,
|
|
Type: typ,
|
|
IsFragmentSpread: true,
|
|
ResponseFields: fieldsResponseFields,
|
|
}
|
|
|
|
case *ast.InlineFragment:
|
|
// InlineFragmentは子要素をそのままstructとしてもつので、ここで、構造体の型を作成します
|
|
fieldsResponseFields := r.NewResponseFields(selection.SelectionSet)
|
|
|
|
return &ResponseField{
|
|
Name: selection.TypeCondition,
|
|
Type: fieldsResponseFields.StructType(),
|
|
IsInlineFragment: true,
|
|
Tags: []string{fmt.Sprintf(`graphql:"... on %s"`, selection.TypeCondition)},
|
|
ResponseFields: fieldsResponseFields,
|
|
}
|
|
}
|
|
|
|
panic("unexpected selection type")
|
|
}
|
|
|
|
func (r *SourceGenerator) OperationArguments(variableDefinitions ast.VariableDefinitionList) []*Argument {
|
|
argumentTypes := make([]*Argument, 0, len(variableDefinitions))
|
|
for _, v := range variableDefinitions {
|
|
argumentTypes = append(argumentTypes, &Argument{
|
|
Variable: v.Variable,
|
|
Type: r.binder.CopyModifiersFromAst(v.Type, r.Type(v.Type.Name())),
|
|
})
|
|
}
|
|
|
|
return argumentTypes
|
|
}
|
|
|
|
// Typeの引数に渡すtypeNameは解析した結果からselectionなどから求めた型の名前を渡さなければいけない
|
|
func (r *SourceGenerator) Type(typeName string) types.Type {
|
|
goType, err := r.binder.FindTypeFromName(r.cfg.Models[typeName].Model[0])
|
|
if err != nil {
|
|
// 実装として正しいtypeNameを渡していれば必ず見つかるはずなのでpanic
|
|
panic(fmt.Sprintf("%+v", err))
|
|
}
|
|
|
|
return goType
|
|
}
|