1+ using Microsoft . Extensions . Primitives ;
2+
3+ namespace NpgsqlRest . Defaults ;
4+
5+ internal static class DefaultCommentParser
6+ {
7+ private static readonly char [ ] newlineSeparator = [ '\r ' , '\n ' ] ;
8+ private static readonly char [ ] wordSeparator = [ ' ' ] ;
9+ private static readonly char [ ] headerSeparator = [ ':' ] ;
10+
11+ private const string HttpKey = "http" ;
12+ private static readonly string [ ] paramTypeKey = [
13+ "requestparamtype" ,
14+ "paramtype" ,
15+ "request_param_type" ,
16+ "param_type" ,
17+ "request-param-type" ,
18+ "param-type"
19+ ] ;
20+ private static readonly string [ ] queryKey = [
21+ "querystring" ,
22+ "query_string" ,
23+ "query-string" ,
24+ "query"
25+ ] ;
26+ private static readonly string [ ] jsonKey = [
27+ "bodyjson" ,
28+ "body_json" ,
29+ "body-json" ,
30+ "json" ,
31+ "body"
32+ ] ;
33+ private static readonly string [ ] authorizeKey = [
34+ "requiresauthorization" ,
35+ "authorize" ,
36+ "requires_authorization" ,
37+ "requires-authorization"
38+ ] ;
39+ private static readonly string [ ] timeoutKey = [
40+ "commandtimeout" ,
41+ "command_timeout" ,
42+ "command-timeout" ,
43+ "timeout"
44+ ] ;
45+ private const string ContentTypeKey = "content-type" ;
46+ private static readonly string [ ] requestHeadersModeKey = [
47+ "requestheadersmode" ,
48+ "request_headers_mode" ,
49+ "request-headers-mode" ,
50+ "requestheaders" ,
51+ "request_headers" ,
52+ "request-headers"
53+ ] ;
54+ private const string IgnoreKey = "ignore" ;
55+ private const string ContextKey = "context" ;
56+ private const string ParameterKey = "parameter" ;
57+ private static readonly string [ ] requestHeadersParameterNameKey = [
58+ "requestheadersparametername" ,
59+ "requestheadersparamname" ,
60+ "request_headers_parameter_name" ,
61+ "request_headers_param_name" ,
62+ "request-headers-parameter-name" ,
63+ "request-headers-param-name" ,
64+ ] ;
65+ private static readonly string [ ] bodyParameterNameKey = [
66+ "bodyparametername" ,
67+ "body-parameter-name" ,
68+ "body_parameter_name" ,
69+ "bodyparamname" ,
70+ "body-param-name" ,
71+ "body_param_name"
72+ ] ;
73+
74+ public static RoutineEndpoint ? Parse ( ref Routine routine , ref NpgsqlRestOptions options , ref ILogger ? logger , ref RoutineEndpoint routineEndpoint )
75+ {
76+ if ( options . CommentsMode == CommentsMode . Ignore )
77+ {
78+ return routineEndpoint ;
79+ }
80+
81+ var originalUrl = routineEndpoint . Url ;
82+ var originalMethod = routineEndpoint . Method ;
83+ var originalParamType = routineEndpoint . RequestParamType ;
84+
85+ var comment = routine . Comment ;
86+ if ( string . IsNullOrEmpty ( comment ) )
87+ {
88+ if ( options . CommentsMode == CommentsMode . OnlyWithHttpTag )
89+ {
90+ return null ;
91+ }
92+ }
93+ else
94+ {
95+ string [ ] lines = comment . Split ( newlineSeparator , StringSplitOptions . RemoveEmptyEntries ) ;
96+ bool hasHttpTag = false ;
97+ for ( var i = 0 ; i < lines . Length ; i ++ )
98+ {
99+ string line = lines [ i ] . Trim ( ) ;
100+ string [ ] words = line . Split ( wordSeparator , StringSplitOptions . RemoveEmptyEntries ) ;
101+ if ( words . Length > 0 )
102+ {
103+ if ( StringEquals ( ref words [ 0 ] , HttpKey ) )
104+ {
105+ hasHttpTag = true ;
106+ if ( words . Length == 2 || words . Length == 3 )
107+ {
108+ if ( Enum . TryParse < Method > ( words [ 1 ] , true , out var parsedMethod ) )
109+ {
110+ routineEndpoint . Method = parsedMethod ;
111+ routineEndpoint . RequestParamType = routineEndpoint . Method == Method . GET ? RequestParamType . QueryString : RequestParamType . BodyJson ;
112+ }
113+ else
114+ {
115+ logger ? . LogWarning ( $ "Invalid HTTP method '{ words [ 1 ] } ' in comment for routine '{ routine . Schema } .{ routine . Name } '. Using default '{ routineEndpoint . Method } '") ;
116+ }
117+ }
118+ if ( words . Length == 3 )
119+ {
120+ string urlPathSegment = words [ 2 ] ;
121+ if ( ! Uri . TryCreate ( urlPathSegment , UriKind . Relative , out Uri ? uri ) )
122+ {
123+ logger ? . LogWarning ( $ "Invalid URL path segment '{ urlPathSegment } ' in comment for routine '{ routine . Schema } .{ routine . Name } '. Using default '{ routineEndpoint . Url } '") ;
124+ }
125+ else
126+ {
127+ routineEndpoint . Url = Uri . EscapeDataString ( uri . ToString ( ) ) ;
128+ if ( ! routineEndpoint . Url . StartsWith ( '/' ) )
129+ {
130+ routineEndpoint . Url = string . Concat ( "/" , routineEndpoint . Url ) ;
131+ }
132+ }
133+ }
134+ if ( routineEndpoint . Method != originalMethod || ! string . Equals ( routineEndpoint . Url , originalUrl ) )
135+ {
136+ Info ( ref logger , ref options , ref routine , $ "has set HTTP by comment annotations to \" { routineEndpoint . Method } { routineEndpoint . Url } \" ") ;
137+ }
138+ }
139+
140+ else if ( /*hasHttpTag && */ words . Length >= 2 && StringEqualsToArray ( ref words [ 0 ] , paramTypeKey ) )
141+ {
142+ if ( StringEqualsToArray ( ref words [ 1 ] , queryKey ) )
143+ {
144+ routineEndpoint . RequestParamType = RequestParamType . QueryString ;
145+ }
146+ else if ( StringEqualsToArray ( ref words [ 1 ] , jsonKey ) )
147+ {
148+ routineEndpoint . RequestParamType = RequestParamType . BodyJson ;
149+ }
150+ else
151+ {
152+ logger ? . LogWarning ( $ "Invalid parameter type '{ words [ 1 ] } ' in comment for routine '{ routine . Schema } .{ routine . Name } ' Allowed values are QueryString or Query or BodyJson or Json. Using default '{ routineEndpoint . RequestParamType } '") ;
153+ }
154+
155+ if ( originalParamType != routineEndpoint . RequestParamType )
156+ {
157+ Info ( ref logger , ref options , ref routine , $ "has set REQUEST PARAMETER TYPE by comment annotations to \" { routineEndpoint . RequestParamType } \" ") ;
158+ }
159+ }
160+
161+ else if ( /*hasHttpTag && */ StringEqualsToArray ( ref words [ 0 ] , authorizeKey ) )
162+ {
163+ routineEndpoint . RequiresAuthorization = true ;
164+ Info ( ref logger , ref options , ref routine , $ "has set REQUIRED AUTHORIZATION by comment annotations.") ;
165+ }
166+
167+ else if ( /*hasHttpTag && */ words . Length >= 2 && StringEqualsToArray ( ref words [ 0 ] , timeoutKey ) )
168+ {
169+ if ( int . TryParse ( words [ 1 ] , out var parsedTimeout ) )
170+ {
171+ if ( routineEndpoint . CommandTimeout != parsedTimeout )
172+ {
173+ Info ( ref logger , ref options , ref routine , $ "has set COMMAND TIMEOUT by comment annotations to { parsedTimeout } seconds") ;
174+ }
175+ routineEndpoint . CommandTimeout = parsedTimeout ;
176+ }
177+ else
178+ {
179+ logger ? . LogWarning ( $ "Invalid command timeout '{ words [ 1 ] } ' in comment for routine '{ routine . Schema } .{ routine . Name } '. Using default command timeout '{ routineEndpoint . CommandTimeout } '") ;
180+ }
181+ }
182+
183+ else if ( /*hasHttpTag && */ StringEqualsToArray ( ref words [ 0 ] , requestHeadersModeKey ) )
184+ {
185+ if ( StringEquals ( ref words [ 1 ] , IgnoreKey ) )
186+ {
187+ routineEndpoint . RequestHeadersMode = RequestHeadersMode . Ignore ;
188+ }
189+ else if ( StringEquals ( ref words [ 1 ] , ContextKey ) )
190+ {
191+ routineEndpoint . RequestHeadersMode = RequestHeadersMode . Context ;
192+ }
193+ else if ( StringEquals ( ref words [ 1 ] , ParameterKey ) )
194+ {
195+ routineEndpoint . RequestHeadersMode = RequestHeadersMode . Parameter ;
196+ }
197+ else
198+ {
199+ logger ? . LogWarning ( $ "Invalid parameter type '{ words [ 1 ] } ' in comment for routine '{ routine . Schema } .{ routine . Name } ' Allowed values are Ignore or Context or Parameter. Using default '{ routineEndpoint . RequestHeadersMode } '") ;
200+ }
201+ if ( routineEndpoint . RequestHeadersMode != options . RequestHeadersMode )
202+ {
203+ Info ( ref logger , ref options , ref routine , $ "has set REQUEST HEADERS MODE by comment annotations to \" { routineEndpoint . RequestHeadersMode } \" ") ;
204+ }
205+ }
206+
207+ else if ( /*hasHttpTag && */ StringEqualsToArray ( ref words [ 0 ] , requestHeadersParameterNameKey ) )
208+ {
209+ if ( words . Length == 2 )
210+ {
211+ if ( ! string . Equals ( routineEndpoint . RequestHeadersParameterName , words [ 1 ] ) )
212+ {
213+ Info ( ref logger , ref options , ref routine , $ "has set REQUEST HEADERS PARAMETER NAME by comment annotations to \" { words [ 1 ] } \" ") ;
214+ }
215+ routineEndpoint . RequestHeadersParameterName = words [ 1 ] ;
216+ }
217+ }
218+
219+ else if ( /*hasHttpTag && */ StringEqualsToArray ( ref words [ 0 ] , bodyParameterNameKey ) )
220+ {
221+ if ( words . Length == 2 )
222+ {
223+ if ( ! string . Equals ( routineEndpoint . BodyParameterName , words [ 1 ] ) )
224+ {
225+ Info ( ref logger , ref options , ref routine , $ "has set BODY PARAMETER NAME by comment annotations to \" { words [ 1 ] } \" ") ;
226+ }
227+ routineEndpoint . BodyParameterName = words [ 1 ] ;
228+ }
229+ }
230+
231+ else if ( /*hasHttpTag && */ line . Contains ( ':' ) )
232+ {
233+ var parts = line . Split ( headerSeparator , 2 ) ;
234+ if ( parts . Length == 2 )
235+ {
236+ var headerName = parts [ 0 ] . Trim ( ) ;
237+ var headerValue = parts [ 1 ] . Trim ( ) ;
238+ if ( StringEquals ( ref headerName , ContentTypeKey ) )
239+ {
240+ if ( ! string . Equals ( routineEndpoint . ResponseContentType , headerValue ) )
241+ {
242+ Info ( ref logger , ref options , ref routine , $ "has set Content-Type HEADER by comment annotations to \" { headerValue } \" ") ;
243+ }
244+ routineEndpoint . ResponseContentType = headerValue ;
245+ }
246+ else
247+ {
248+ if ( routineEndpoint . ResponseHeaders is null )
249+ {
250+ routineEndpoint . ResponseHeaders = new ( )
251+ {
252+ [ headerName ] = new StringValues ( headerValue )
253+ } ;
254+ }
255+ else
256+ {
257+ if ( routineEndpoint . ResponseHeaders . TryGetValue ( headerName , out StringValues values ) )
258+ {
259+ routineEndpoint . ResponseHeaders [ headerName ] = StringValues . Concat ( values , headerValue ) ;
260+ }
261+ else
262+ {
263+ routineEndpoint . ResponseHeaders . Add ( headerName , new StringValues ( headerValue ) ) ;
264+ }
265+ }
266+ if ( ! string . Equals ( routineEndpoint . ResponseContentType , headerValue ) )
267+ {
268+ Info ( ref logger , ref options , ref routine , $ "has set { headerName } HEADER by comment annotations to \" { headerValue } \" ") ;
269+ }
270+ }
271+ }
272+ }
273+ }
274+ }
275+ if ( options . CommentsMode == CommentsMode . OnlyWithHttpTag && ! hasHttpTag )
276+ {
277+ return null ;
278+ }
279+ }
280+
281+ return routineEndpoint ;
282+ }
283+
284+ private static void Info ( ref ILogger ? logger , ref NpgsqlRestOptions options , ref Routine routine , string message )
285+ {
286+ if ( ! options . LogAnnotationSetInfo )
287+ {
288+ return ;
289+ }
290+ logger ? . LogInformation ( string . Concat ( $ "{ routine . Type } { routine . Schema } .{ routine . Name } ", message ) ) ;
291+ }
292+
293+ private static bool StringEquals ( ref string str1 , string str2 ) => str1 . Equals ( str2 , StringComparison . OrdinalIgnoreCase ) ;
294+
295+ private static bool StringEqualsToArray ( ref string str , string [ ] arr )
296+ {
297+ for ( var i = 0 ; i < arr . Length ; i ++ )
298+ {
299+ if ( str . Equals ( arr [ i ] , StringComparison . OrdinalIgnoreCase ) )
300+ {
301+ return true ;
302+ }
303+ }
304+ return false ;
305+ }
306+ }
0 commit comments