@@ -13,6 +13,157 @@ type Category struct {
13
13
Slug *string `json:"slug"`
14
14
}
15
15
16
+ <%
17
+ // {'columns':
18
+ // [
19
+ // {
20
+ // expr: { type: 'column_ref', table: null, column: 'name_ru' },
21
+ // as: 'nameRu'
22
+ // }
23
+ // ]
24
+ // } => [ 'nameRu' ]
25
+ function extractSelectParameters (queryAst ) {
26
+ return queryAst .columns
27
+ .map (column => column .as !== null ? column .as : column .expr .column );
28
+ }
29
+
30
+ // {'values':
31
+ // [
32
+ // {
33
+ // type: 'expr_list',
34
+ // value: [ { type: 'param', value: 'user_id' } ]
35
+ // }
36
+ // ]
37
+ // } => [ 'user_id' ]
38
+ function extractInsertValues (queryAst ) {
39
+ const values = queryAst .values .flatMap (elem => elem .value )
40
+ .map (elem => elem .type === ' param' ? elem .value : null )
41
+ .filter (elem => elem); // filter out nulls
42
+ return Array .from (new Set (values));
43
+ }
44
+
45
+ // {'set':
46
+ // [
47
+ // {
48
+ // column: 'updated_by',
49
+ // value: { type: 'param', value: 'user_id' },
50
+ // table: null
51
+ // }
52
+ // ]
53
+ // } => [ 'user_id' ]
54
+ function extractUpdateValues (queryAst ) {
55
+ // TODO: distinguish between b.param and q.param and extract only first
56
+ return queryAst .set .map (elem => elem .value .type === ' param' ? elem .value .value : null )
57
+ .filter (value => value) // filter out nulls
58
+ }
59
+
60
+ function extractProperties (queryAst ) {
61
+ if (queryAst .type === ' select' ) {
62
+ return extractSelectParameters (queryAst);
63
+ }
64
+
65
+ if (queryAst .type === ' insert' ) {
66
+ return extractInsertValues (queryAst);
67
+ }
68
+
69
+ if (queryAst .type === ' update' ) {
70
+ return extractUpdateValues (queryAst);
71
+ }
72
+
73
+ return [];
74
+ }
75
+
76
+ function addTypes (props ) {
77
+ return props .map (prop => {
78
+ return {
79
+ " name" : prop,
80
+ // TODO: resolve/autoguess types
81
+ " type" : " string"
82
+ }
83
+ });
84
+ }
85
+
86
+ function query2dto (parser , query ) {
87
+ const queryAst = parser .astify (query);
88
+ const props = extractProperties (queryAst).map (snake2camelCase);
89
+ if (props .length === 0 ) {
90
+ console .warn (' Could not create DTO for query:' , formatQuery (query));
91
+ console .debug (' Query AST:' );
92
+ console .debug (queryAst);
93
+ return null ;
94
+ }
95
+ const propsWithTypes = addTypes (props);
96
+ return {
97
+ // TODO: assign DTO name dynamically
98
+ " name" : " Dto" + ++ globalDtoCounter,
99
+ " props" : propsWithTypes,
100
+ // max length is needed for proper formatting
101
+ " maxFieldNameLength" : lengthOfLongestString (props),
102
+ // required for de-duplication
103
+ // [ {name:foo, type:int}, {name:bar, type:string} ] => "foo=int bar=string"
104
+ // TODO: sort before join
105
+ " signature" : propsWithTypes .map (field => ` ${ field .name } =${ field .type } ` ).join (' ' )
106
+ };
107
+ }
108
+
109
+ // "nameRu" => "NameRu"
110
+ function capitalize (str ) {
111
+ return str[0 ].toUpperCase () + str .slice (1 );
112
+ }
113
+
114
+ // "name_ru" => "nameRu"
115
+ function snake2camelCase (str ) {
116
+ return str .replace (/ _([a-z ] )/ g , (match , group1 ) => group1 .toUpperCase ());
117
+ }
118
+
119
+ // ["a", "bb", "ccc"] => 3
120
+ function lengthOfLongestString (arr ) {
121
+ return arr
122
+ .map (el => el .length )
123
+ .reduce (
124
+ (acc , val ) => val > acc ? val : acc,
125
+ 0 /* initial value */
126
+ );
127
+ }
128
+
129
+ function dto2struct (dto ) {
130
+ let result = ` type ${ dto .name } struct {\n ` ;
131
+ dto .props .forEach (prop => {
132
+ const fieldName = capitalize (snake2camelCase (prop .name )).padEnd (dto .maxFieldNameLength );
133
+ result += ` \t ${ fieldName} ${ prop .type } \` json:"${ prop .name } "\`\n `
134
+ });
135
+ result += ' }\n ' ;
136
+
137
+ return result;
138
+ }
139
+
140
+ let globalDtoCounter = 0 ;
141
+
142
+ const dtoCache = {};
143
+ function cacheDto (dto ) {
144
+ dtoCache[dto .signature ] = dto .name ;
145
+ return dto;
146
+ }
147
+ function dtoInCache (dto ) {
148
+ return dtoCache .hasOwnProperty (dto .signature );
149
+ }
150
+
151
+ const verbs_with_dto = [ ' get' , ' get_list' , ' post' , ' put' ]
152
+ endpoints .forEach (function (endpoint ) {
153
+ const dtos = Object .keys (endpoint)
154
+ .filter (propName => verbs_with_dto .includes (propName))
155
+ .map (propName => endpoint[propName])
156
+ .map (query => query2dto (sqlParser, removePlaceholders (query)))
157
+ .filter (elem => elem) // filter out nulls
158
+ .filter (dto => ! dtoInCache (dto))
159
+ .map (dto => dto2struct (cacheDto (dto)))
160
+ .forEach (struct => {
161
+ -% >
162
+ < %- struct % >
163
+ < %
164
+ });
165
+ });
166
+ - %>
16
167
func registerRoutes(r chi.Router) {
17
168
categories := make(map[int]Category)
18
169
cnt := 0
0 commit comments