diff --git a/cmd/builder.go b/cmd/builder.go index 66dfc4aa..5fcfceb6 100644 --- a/cmd/builder.go +++ b/cmd/builder.go @@ -14,6 +14,7 @@ import ( "github.com/viant/datly/shared" "github.com/viant/datly/template/sanitize" "github.com/viant/datly/view" + "github.com/viant/parsly" "github.com/viant/sqlx/metadata/ast/query" "github.com/viant/toolbox" "github.com/viant/toolbox/format" @@ -283,7 +284,11 @@ func (s *Builder) readRouteSettings() error { s.routeBuilder.paramsIndex.AddConsts(s.routeBuilder.option.Const) } - return s.loadGoTypes(s.routeBuilder.option.TypeSrc) + if err := s.loadGoTypes(); err != nil { + return err + } + + return nil } func extractURIParams(URI string) map[string]bool { @@ -941,7 +946,7 @@ func (s *Builder) prepareRuleIfNeeded(SQL []byte) (string, error) { } } -func (s *Builder) loadGoTypes(typeSrc *option.TypeSrcConfig) error { +func (s *Builder) loadGoType(typeSrc *option.TypeSrcConfig) error { if typeSrc == nil { return nil } @@ -992,7 +997,7 @@ func (s *Builder) Type(typeName string) (string, error) { actualName = "*" + actualName } - return typeName, s.loadGoTypes(&option.TypeSrcConfig{ + return typeName, s.loadGoType(&option.TypeSrcConfig{ URL: sourcePath, Types: []string{actualName}, }) @@ -1010,3 +1015,69 @@ func (s *Builder) normalizeURL(typeSrc *option.TypeSrcConfig) { } } } + +func (s *Builder) loadGoTypes() error { + if err := s.loadGoType(s.routeBuilder.option.TypeSrc); err != nil { + return err + } + + cursor := parsly.NewCursor("", []byte(s.routeBuilder.sqlStmt), 0) + defer func() { + s.routeBuilder.sqlStmt = s.routeBuilder.sqlStmt[cursor.Pos:] + }() + + matched := cursor.MatchAfterOptional(whitespaceMatcher, importKeywordMatcher) + if matched.Code != importKeywordToken { + return nil + } + + matched = cursor.MatchAfterOptional(whitespaceMatcher, exprGroupMatcher, quotedMatcher) + switch matched.Code { + case quotedToken: + text := matched.Text(cursor) + typeSrc, err := s.parseTypeSrc(text[1 : len(text)-1]) + if err != nil { + return err + } + + return s.loadGoType(typeSrc) + case exprGroupToken: + exprContent := matched.Text(cursor) + exprGroupCursor := parsly.NewCursor("", []byte(exprContent[1:len(exprContent)-1]), 0) + + for { + + matched = exprGroupCursor.MatchAfterOptional(whitespaceMatcher, quotedMatcher) + switch matched.Code { + case quotedToken: + text := matched.Text(exprGroupCursor) + typeSrc, err := s.parseTypeSrc(text[1 : len(text)-1]) + if err != nil { + return err + } + + if err = s.loadGoType(typeSrc); err != nil { + return err + } + case parsly.EOF: + return nil + default: + return cursor.NewError(quotedMatcher) + } + } + } + + return nil +} + +func (s *Builder) parseTypeSrc(imported string) (*option.TypeSrcConfig, error) { + importSegments := strings.Split(imported, ".") + if len(importSegments) != 2 { + return nil, fmt.Errorf(`unsupported import format, supported: "[path].[type]"`) + } + + return &option.TypeSrcConfig{ + URL: importSegments[0], + Types: []string{importSegments[1]}, + }, nil +} diff --git a/cmd/insert.go b/cmd/insert.go index 0d5c1ddb..44ebf2cf 100644 --- a/cmd/insert.go +++ b/cmd/insert.go @@ -176,7 +176,6 @@ func (s *Builder) prepareStringBuilder(typeDef *inputMetadata, config *viewConfi } func (s *Builder) appendPostRouteOption(paramName string, routeOption *option.RouteConfig, typeName string, typeDef *inputMetadata, sb *strings.Builder) error { - requiredTypes := []string{"*" + typeDef.paramName} routeOption.RequestBody = &option.BodyConfig{ DataType: typeDef.bodyHolder, @@ -186,15 +185,7 @@ func (s *Builder) appendPostRouteOption(paramName string, routeOption *option.Ro From: paramName, } - if typeDef.bodyHolder != "" { - requiredTypes = append(requiredTypes, "*"+typeDef.bodyHolder) - } - routeOption.Declare = map[string]string{} - routeOption.TypeSrc = &option.TypeSrcConfig{ - URL: folderSQL, - Types: requiredTypes, - } routeOption.Declare[view.FirstNotEmpty(typeDef.bodyHolder, typeDef.paramName)] = typeName marshal, err := json.Marshal(routeOption) @@ -205,6 +196,20 @@ func (s *Builder) appendPostRouteOption(paramName string, routeOption *option.Ro if routeJSON := string(marshal); routeJSON != "{}" { sb.WriteString(fmt.Sprintf("/* %v */\n\n", routeJSON)) } + + requiredTypes := []string{typeDef.paramName} + if typeDef.bodyHolder != "" { + requiredTypes = append(requiredTypes, typeDef.bodyHolder) + } + + if len(requiredTypes) > 0 { + sb.WriteString("import (") + for _, requiredType := range requiredTypes { + sb.WriteString(fmt.Sprintf("\n \"%v.%v\"", folderSQL, requiredType)) + } + sb.WriteString("\n)\n\n") + } + return nil } diff --git a/cmd/lex.go b/cmd/lex.go index 3032fb02..b79307bc 100644 --- a/cmd/lex.go +++ b/cmd/lex.go @@ -9,8 +9,12 @@ const ( whitespaceToken int = iota condBlockToken exprGroupToken + importKeywordToken + quotedToken ) var whitespaceMatcher = parsly.NewToken(whitespaceToken, "Whitespace", matcher.NewWhiteSpace()) var condBlockMatcher = parsly.NewToken(condBlockToken, "#if .... #end", matcher.NewSeqBlock("#if", "#end")) var exprGroupMatcher = parsly.NewToken(exprGroupToken, "( .... )", matcher.NewBlock('(', ')', '\\')) +var importKeywordMatcher = parsly.NewToken(importKeywordToken, "import", matcher.NewFragmentsFold([]byte("import"))) +var quotedMatcher = parsly.NewToken(quotedToken, "quoted block", matcher.NewQuote('"', '\\')) diff --git a/cmd/prepare.go b/cmd/prepare.go index 414087b9..c729ee68 100644 --- a/cmd/prepare.go +++ b/cmd/prepare.go @@ -116,7 +116,7 @@ func (sb *stmtBuilder) paramHint(typeDef *inputMetadata) (string, error) { paramConfig, err := json.Marshal(&option.ParameterConfig{ Target: &target, - DataType: typeDef.paramName, + DataType: "*" + typeDef.paramName, Cardinality: typeDef.typeDef.Cardinality, Kind: sb.paramKind, }) diff --git a/router/marshal/json/marshal_test.go b/router/marshal/json/marshal_test.go index bf75a0ec..52df8c72 100644 --- a/router/marshal/json/marshal_test.go +++ b/router/marshal/json/marshal_test.go @@ -125,6 +125,15 @@ func TestJson_Marshal(t *testing.T) { CaseFormat: format.CaseLowerCamel, }, }, + //TODO: Handle that case + //{ + // description: "marshal non ptr", + // data: nonPtr, + // expect: `[{"id":10,"name":"foo","price":125.5}]`, + // defaultConfig: marshal.Default{ + // CaseFormat: format.CaseLowerCamel, + // }, + //}, } //for i, testcase := range testcases[:len(testcases)-1] { @@ -157,6 +166,35 @@ func TestJson_Marshal(t *testing.T) { } } +func nonPtr() interface{} { + type Response struct { + Message interface{} + Status string + } + + type Event struct { + ID int + Name string + Price float64 + } + + type Data struct { + Response + Events []*Event + } + + return Data{ + Response: Response{}, + Events: []*Event{ + { + ID: 1, + Name: "ABC", + Price: 125.5, + }, + }, + } +} + func idStruct() interface{} { type Foo struct { ID int diff --git a/view/column.go b/view/column.go index 2fe7831e..a72296a5 100644 --- a/view/column.go +++ b/view/column.go @@ -42,7 +42,7 @@ func (c *Column) SqlExpression() string { return c.sqlExpression } -func ParseType(dataType string) (reflect.Type, error) { +func ParseType(dataType string, types Types) (reflect.Type, error) { precisionIndex := strings.Index(dataType, "(") if precisionIndex != -1 { dataType = dataType[:precisionIndex] @@ -66,7 +66,10 @@ func ParseType(dataType string) (reflect.Type, error) { return t, nil } - return xreflect.Parse(dataType) + return xreflect.ParseWithLookup(dataType, true, func(packagePath, packageIdentifier, typeName string) (reflect.Type, bool) { + lookup, err := types.Lookup(typeName) + return lookup, err == nil + }) } //ColumnName returns Column Name @@ -99,7 +102,7 @@ func (c *Column) Init(resource *Resource, caser format.Case, allowNulls bool, co } if nonPtrType == nil || nonPtrType.Kind() == reflect.Interface { - rType, err := ParseType(c.DataType) + rType, err := ParseType(c.DataType, resource._types) if err != nil && c.rType == nil { return err } diff --git a/view/definition.go b/view/definition.go index ff597634..5b0c1387 100644 --- a/view/definition.go +++ b/view/definition.go @@ -51,7 +51,7 @@ func (d *Definition) Init(ctx context.Context, types Types) error { d.createSchemaIfNeeded() if d.Schema != nil { - parseType, err := GetOrParseType(map[string]reflect.Type{}, d.Schema.DataType) + parseType, err := GetOrParseType(types, d.Schema.DataType) if err != nil { return err } diff --git a/view/parameters.go b/view/parameters.go index a728da92..3f4f99a4 100644 --- a/view/parameters.go +++ b/view/parameters.go @@ -602,7 +602,7 @@ func GetOrParseType(types Types, dataType string) (reflect.Type, error) { return lookup, nil } - parseType, parseErr := ParseType(dataType) + parseType, parseErr := ParseType(dataType, types) if parseErr == nil { return parseType, nil }