repogen/internal/spec/parser.go

149 lines
3.6 KiB
Go

package spec
import (
"errors"
"fmt"
"github.com/fatih/camelcase"
"github.com/sunboyy/repogen/internal/code"
)
// ParseRepositoryInterface returns repository spec from declared repository interface
func ParseRepositoryInterface(structModel code.Struct, intf code.Interface) (RepositorySpec, error) {
parser := repositoryInterfaceParser{
StructModel: structModel,
Interface: intf,
}
return parser.Parse()
}
type repositoryInterfaceParser struct {
StructModel code.Struct
Interface code.Interface
}
func (p repositoryInterfaceParser) Parse() (RepositorySpec, error) {
repositorySpec := RepositorySpec{
InterfaceName: p.Interface.Name,
}
for _, method := range p.Interface.Methods {
methodSpec, err := p.parseMethod(method)
if err != nil {
return RepositorySpec{}, err
}
repositorySpec.Methods = append(repositorySpec.Methods, methodSpec)
}
return repositorySpec, nil
}
func (p repositoryInterfaceParser) parseMethod(method code.Method) (MethodSpec, error) {
methodNameTokens := camelcase.Split(method.Name)
switch methodNameTokens[0] {
case "Find":
return p.parseFindMethod(method, methodNameTokens[1:])
}
return MethodSpec{}, errors.New("method name not supported")
}
func (p repositoryInterfaceParser) parseFindMethod(method code.Method, tokens []string) (MethodSpec, error) {
if len(tokens) == 0 {
return MethodSpec{}, errors.New("method name not supported")
}
mode, err := p.extractFindReturns(method.Returns)
if err != nil {
return MethodSpec{}, err
}
querySpec, err := p.parseQuery(tokens)
if err != nil {
return MethodSpec{}, err
}
if querySpec.NumberOfArguments()+1 != len(method.Params) {
return MethodSpec{}, errors.New("method parameter not supported")
}
return MethodSpec{
Name: method.Name,
Params: method.Params,
Returns: method.Returns,
Operation: FindOperation{
Mode: mode,
Query: querySpec,
},
}, nil
}
func (p repositoryInterfaceParser) extractFindReturns(returns []code.Type) (QueryMode, error) {
if len(returns) != 2 {
return "", errors.New("method return not supported")
}
if returns[1] != code.SimpleType("error") {
return "", errors.New("method return not supported")
}
pointerType, ok := returns[0].(code.PointerType)
if ok {
simpleType := pointerType.ContainedType
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeOne, nil
}
return "", fmt.Errorf("invalid return type %s", pointerType.Code())
}
arrayType, ok := returns[0].(code.ArrayType)
if ok {
pointerType, ok := arrayType.ContainedType.(code.PointerType)
if ok {
simpleType := pointerType.ContainedType
if simpleType == code.SimpleType(p.StructModel.Name) {
return QueryModeMany, nil
}
return "", fmt.Errorf("invalid return type %s", pointerType.Code())
}
}
return "", errors.New("method return not supported")
}
func (p repositoryInterfaceParser) parseQuery(tokens []string) (QuerySpec, error) {
if len(tokens) == 0 {
return QuerySpec{}, errors.New("method name not supported")
}
if len(tokens) == 1 && tokens[0] == "All" {
return QuerySpec{}, nil
}
if tokens[0] == "One" {
tokens = tokens[1:]
}
if tokens[0] == "By" {
tokens = tokens[1:]
}
if tokens[0] == "And" {
return QuerySpec{}, errors.New("method name not supported")
}
var queryFields []string
var aggregatedToken string
for _, token := range tokens {
if token != "And" {
aggregatedToken += token
} else {
queryFields = append(queryFields, aggregatedToken)
aggregatedToken = ""
}
}
if aggregatedToken == "" {
return QuerySpec{}, errors.New("method name not supported")
}
queryFields = append(queryFields, aggregatedToken)
return QuerySpec{Fields: queryFields}, nil
}