1 /*
2  * Copyright 2025 Matheus C. França
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 module ada.c.wrapper;
17 
18 /// Ada-URL low-level library.
19 private import ada.c.ada;
20 
21 @safe:
22 
23 /++
24  + A struct representing a parsed URL, providing methods to access and manipulate URL components.
25  + Wraps the functionality of the Ada URL parsing library.
26  +/
27 struct AdaUrl
28 {
29     /++
30      + Constructs an AdaUrl instance with the given parsing options.
31      + Params:
32      +   options = The parsing options containing the URL to parse.
33      +/
34     this(ParseOptions options) @nogc nothrow
35     {
36         _url = options.url;
37     }
38 
39     /++
40      + Destructor that frees the underlying URL resources.
41      +/
42     ~this() @nogc nothrow
43     {
44         ada_free(_url);
45     }
46 
47     /++
48      + Checks if the URL is valid.
49      + Returns: true if the URL is valid, false otherwise.
50      +/
51     bool isValid() @nogc nothrow
52     {
53         return ada_is_valid(_url);
54     }
55 
56     /++
57      + Retrieves the origin of the URL (e.g., protocol + host + port).
58      + Returns: A string representing the URL's origin.
59      +/
60     string getOrigin() @nogc nothrow
61     {
62         auto owned = ada_get_origin(_url);
63         scope (exit)
64             ada_free_owned_string(owned);
65         return str_convert(owned);
66     }
67 
68     /++
69      + Retrieves the full URL as a string (href).
70      + Returns: A string representing the complete URL.
71      +/
72     string getHref() @nogc nothrow
73     {
74         auto str = ada_get_href(_url);
75         return str_convert(str);
76     }
77 
78     /++
79      + Retrieves the username component of the URL.
80      + Returns: A string representing the username, or empty if none.
81      +/
82     string getUsername() @nogc nothrow
83     {
84         auto str = ada_get_username(_url);
85         return str_convert(str);
86     }
87 
88     /++
89      + Retrieves the password component of the URL.
90      + Returns: A string representing the password, or empty if none.
91      +/
92     string getPassword() @nogc nothrow
93     {
94         auto str = ada_get_password(_url);
95         return str_convert(str);
96     }
97 
98     /++
99      + Retrieves the port component of the URL.
100      + Returns: A string representing the port, or empty if none.
101      +/
102     string getPort() @nogc nothrow
103     {
104         auto str = ada_get_port(_url);
105         return str_convert(str);
106     }
107 
108     /++
109      + Retrieves the hash (fragment) component of the URL.
110      + Returns: A string representing the hash, or empty if none.
111      +/
112     string getHash() @nogc nothrow
113     {
114         auto str = ada_get_hash(_url);
115         return str_convert(str);
116     }
117 
118     /++
119      + Retrieves the host component of the URL (hostname + port).
120      + Returns: A string representing the host.
121      +/
122     string getHost() @nogc nothrow
123     {
124         auto str = ada_get_host(_url);
125         return str_convert(str);
126     }
127 
128     /++
129      + Retrieves the hostname component of the URL.
130      + Returns: A string representing the hostname.
131      +/
132     string getHostname() @nogc nothrow
133     {
134         auto str = ada_get_hostname(_url);
135         return str_convert(str);
136     }
137 
138     /++
139      + Retrieves the pathname component of the URL.
140      + Returns: A string representing the pathname.
141      +/
142     string getPathname() @nogc nothrow
143     {
144         auto str = ada_get_pathname(_url);
145         return str_convert(str);
146     }
147 
148     /++
149      + Retrieves the search (query) parameters of the URL.
150      + Returns: A SearchParams struct representing the query parameters.
151      +/
152     SearchParams getSearch() @nogc nothrow
153     {
154         return SearchParams(ada_get_search(_url));
155     }
156 
157     /++
158      + Converts the search parameters to a string.
159      + Params:
160      +   search = The SearchParams object to convert.
161      + Returns: A string representing the query parameters.
162      +/
163     string getSearchParams(SearchParams search) @nogc nothrow
164     {
165         auto str = search.toString();
166         return str_convert(str);
167     }
168 
169     /++
170      + Retrieves the protocol component of the URL (e.g., "http:").
171      + Returns: A string representing the protocol.
172      +/
173     string getProtocol() @nogc nothrow
174     {
175         auto str = ada_get_protocol(_url);
176         return str_convert(str);
177     }
178 
179     /++
180      + Retrieves the host type of the URL.
181      + Returns: A ubyte representing the host type (as defined by the Ada library).
182      +/
183     ubyte getHostType() @nogc nothrow
184     {
185         return ada_get_host_type(_url);
186     }
187 
188     /++
189      + Retrieves the scheme type of the URL.
190      + Returns: A ubyte representing the scheme type (as defined by the Ada library).
191      +/
192     ubyte getSchemeType() @nogc nothrow
193     {
194         return ada_get_scheme_type(_url);
195     }
196 
197     /++
198      + Sets the full URL (href).
199      + Params:
200      +   input = The new URL string to set.
201      + Returns: true if successful, false otherwise.
202      +/
203     bool setHref(string input) @nogc nothrow
204     {
205         return ada_set_href(_url, &input[0], input.length);
206     }
207 
208     /++
209      + Sets the host component of the URL.
210      + Params:
211      +   input = The new host string to set.
212      + Returns: true if successful, false otherwise.
213      +/
214     bool setHost(string input) @nogc nothrow
215     {
216         return ada_set_host(_url, &input[0], input.length);
217     }
218 
219     /++
220      + Sets the hostname component of the URL.
221      + Params:
222      +   input = The new hostname string to set.
223      + Returns: true if successful, false otherwise.
224      +/
225     bool setHostname(string input) @nogc nothrow
226     {
227         return ada_set_hostname(_url, &input[0], input.length);
228     }
229 
230     /++
231      + Sets the protocol component of the URL.
232      + Params:
233      +   input = The new protocol string to set (e.g., "http:").
234      + Returns: true if successful, false otherwise.
235      +/
236     bool setProtocol(string input) @nogc nothrow
237     {
238         return ada_set_protocol(_url, &input[0], input.length);
239     }
240 
241     /++
242      + Sets the username component of the URL.
243      + Params:
244      +   input = The new username string to set.
245      + Returns: true if successful, false otherwise.
246      +/
247     bool setUsername(string input) @nogc nothrow
248     {
249         return ada_set_username(_url, &input[0], input.length);
250     }
251 
252     /++
253      + Sets the password component of the URL.
254      + Params:
255      +   input = The new password string to set.
256      + Returns: true if successful, false otherwise.
257      +/
258     bool setPassword(string input) @nogc nothrow
259     {
260         return ada_set_password(_url, &input[0], input.length);
261     }
262 
263     /++
264      + Sets the port component of the URL.
265      + Params:
266      +   input = The new port string to set.
267      + Returns: true if successful, false otherwise.
268      +/
269     bool setPort(string input) @nogc nothrow
270     {
271         return ada_set_port(_url, &input[0], input.length);
272     }
273 
274     /++
275      + Sets the pathname component of the URL.
276      + Params:
277      +   input = The new pathname string to set.
278      + Returns: true if successful, false otherwise.
279      +/
280     bool setPathname(string input) @nogc nothrow
281     {
282         return ada_set_pathname(_url, &input[0], input.length);
283     }
284 
285     /++
286      + Sets the search (query) component of the URL.
287      + Params:
288      +   input = The new query string to set.
289      +/
290     void setSearch(string input) @nogc nothrow
291     {
292         ada_set_search(_url, &input[0], input.length);
293     }
294 
295     /++
296      + Sets the hash (fragment) component of the URL.
297      + Params:
298      +   input = The new hash string to set.
299      +/
300     void setHash(string input) @nogc nothrow
301     {
302         ada_set_hash(_url, &input[0], input.length);
303     }
304 
305     /++
306      + Clears the port component of the URL.
307      +/
308     void clearPort() @nogc nothrow
309     {
310         ada_clear_port(_url);
311     }
312 
313     /++
314      + Clears the hash (fragment) component of the URL.
315      +/
316     void clearHash() @nogc nothrow
317     {
318         ada_clear_hash(_url);
319     }
320 
321     /++
322      + Clears the search (query) component of the URL.
323      +/
324     void clearSearch() @nogc nothrow
325     {
326         ada_clear_search(_url);
327     }
328 
329     /++
330      + Creates a copy of the URL.
331      + Returns: A new ada_url handle representing the copied URL.
332      +/
333     ada_url copy() @nogc nothrow
334     {
335         return ada_copy(_url);
336     }
337 
338     /++
339      + Checks if the URL has credentials (username or password).
340      + Returns: true if credentials are present, false otherwise.
341      +/
342     bool hasCredentials() @nogc nothrow
343     {
344         return ada_has_credentials(_url);
345     }
346 
347     /++
348      + Checks if the URL has an empty hostname.
349      + Returns: true if the hostname is empty, false otherwise.
350      +/
351     bool hasEmptyHostname() @nogc nothrow
352     {
353         return ada_has_empty_hostname(_url);
354     }
355 
356     /++
357      + Checks if the URL has a hostname.
358      + Returns: true if a hostname is present, false otherwise.
359      +/
360     bool hasHostname() @nogc nothrow
361     {
362         return ada_has_hostname(_url);
363     }
364 
365     /++
366      + Checks if the URL has a non-empty username.
367      + Returns: true if the username is non-empty, false otherwise.
368      +/
369     bool hasNonEmptyUsername() @nogc nothrow
370     {
371         return ada_has_non_empty_username(_url);
372     }
373 
374     /++
375      + Checks if the URL has a non-empty password.
376      + Returns: true if the password is non-empty, false otherwise.
377      +/
378     bool hasNonEmptyPassword() @nogc nothrow
379     {
380         return ada_has_non_empty_password(_url);
381     }
382 
383     /++
384      + Checks if the URL has a port.
385      + Returns: true if a port is present, false otherwise.
386      +/
387     bool hasPort() @nogc nothrow
388     {
389         return ada_has_port(_url);
390     }
391 
392     /++
393      + Checks if the URL has a password.
394      + Returns: true if a password is present, false otherwise.
395      +/
396     bool hasPassword() @nogc nothrow
397     {
398         return ada_has_password(_url);
399     }
400 
401     /++
402      + Checks if the URL has a hash (fragment).
403      + Returns: true if a hash is present, false otherwise.
404      +/
405     bool hasHash() @nogc nothrow
406     {
407         return ada_has_hash(_url);
408     }
409 
410     /++
411      + Checks if the URL has a search (query) component.
412      + Returns: true if a query is present, false otherwise.
413      +/
414     bool hasSearch() @nogc nothrow
415     {
416         return ada_has_search(_url);
417     }
418 
419     /++
420      + Retrieves the URL components (e.g., offsets for protocol, host, etc.).
421      + Returns: A pointer to the ada_url_components struct.
422      +/
423     const(ada_url_components)* getComponents() @nogc nothrow
424     {
425         return ada_get_components(_url);
426     }
427 
428     private ada_url _url;
429 }
430 
431 /++
432  + A struct for parsing URLs with optional base URL support.
433  +/
434 struct ParseOptions
435 {
436     /++
437      + Constructs a ParseOptions instance by parsing a URL string.
438      + Params:
439      +   input = The URL string to parse.
440      +/
441     this(string input) @nogc nothrow
442     {
443         _url = ada_parse(&input[0], input.length);
444     }
445 
446     /++
447      + Constructs a ParseOptions instance by parsing a URL with a base URL.
448      + Params:
449      +   input = The URL string to parse.
450      +   base = The base URL string for relative URL resolution.
451      +/
452     this(string input, string base) @nogc nothrow
453     {
454         _url = ada_parse_with_base(&input[0], input.length, &base[0], base.length);
455     }
456 
457     /++
458      + Retrieves the parsed URL handle.
459      + Returns: The ada_url handle.
460      +/
461     @property ada_url url() @nogc nothrow
462     {
463         return _url;
464     }
465 
466     private ada_url _url;
467 }
468 
469 /++
470  + A struct for managing URL search (query) parameters.
471  +/
472 struct SearchParams
473 {
474     /++
475      + Constructs a SearchParams instance from a query string.
476      + Params:
477      +   input = The query string to parse.
478      +/
479     this(ref ada_string input) @nogc nothrow
480     {
481         _str = input;
482         _params = ada_parse_search_params(_str.data, _str.length);
483     }
484 
485     /++
486      + Destructor that frees the search parameters and associated iterators.
487      +/
488     ~this() @nogc nothrow
489     {
490         ada_free_search_params_keys_iter(keys);
491         ada_free_search_params_values_iter(values);
492         ada_free_search_params_entries_iter(entries);
493         ada_free_search_params(_params);
494     }
495 
496     /++
497      + Appends a key-value pair to the search parameters.
498      + Params:
499      +   key = The key to append.
500      +   value = The value to append.
501      +/
502     void append(ref string key, ref string value) @nogc nothrow
503     {
504         ada_search_params_append(_params, &key[0], key.length, &value[0], value.length);
505     }
506 
507     /++
508      + Converts the search parameters to a string.
509      + Returns: An ada_owned_string representing the query string.
510      +/
511     ada_owned_string toString() @nogc nothrow
512     {
513         return ada_search_params_to_string(_params);
514     }
515 
516     /++
517      + Retrieves the value for a given key.
518      + Params:
519      +   key = The key to look up.
520      + Returns: An ada_string containing the value, or empty if not found.
521      +/
522     ada_string get(ref string key) @nogc nothrow
523     {
524         return ada_search_params_get(_params, &key[0], key.length);
525     }
526 
527     /++
528      + Retrieves an iterator for the keys in the search parameters.
529      + Returns: An ada_url_search_params_keys_iter for the keys.
530      +/
531     @property ada_url_search_params_keys_iter keys() @nogc nothrow
532     {
533         return ada_search_params_get_keys(_params);
534     }
535 
536     /++
537      + Retrieves an iterator for the values in the search parameters.
538      + Returns: An ada_url_search_params_values_iter for the values.
539      +/
540     @property ada_url_search_params_values_iter values() @nogc nothrow
541     {
542         return ada_search_params_get_values(_params);
543     }
544 
545     /++
546      + Retrieves an iterator for the key-value pairs in the search parameters.
547      + Returns: An ada_url_search_params_entries_iter for the entries.
548      +/
549     @property ada_url_search_params_entries_iter entries() @nogc nothrow
550     {
551         return ada_search_params_get_entries(_params);
552     }
553 
554     /++
555      + Retrieves all values for a given key.
556      + Params:
557      +   key = The key to look up.
558      + Returns: A Strings struct containing all values for the key.
559      +/
560     Strings getAll(ref string key) @nogc nothrow
561     {
562         return Strings(ada_search_params_get_all(_params, &key[0], key.length));
563     }
564 
565     /++
566      + Checks if a key exists in the search parameters.
567      + Params:
568      +   key = The key to check.
569      + Returns: true if the key exists, false otherwise.
570      +/
571     bool has(ref string key) @nogc nothrow
572     {
573         return ada_search_params_has(_params, &key[0], key.length);
574     }
575 
576     /++
577      + Checks if a key-value pair exists in the search parameters.
578      + Params:
579      +   key = The key to check.
580      +   value = The value to check.
581      + Returns: true if the key-value pair exists, false otherwise.
582      +/
583     bool hasValue(ref string key, ref string value) @nogc nothrow
584     {
585         return ada_search_params_has_value(_params, &key[0], key.length, &value[0], value.length);
586     }
587 
588     /++
589      + Retrieves the number of key-value pairs in the search parameters.
590      + Returns: The number of pairs.
591      +/
592     size_t length() @nogc nothrow
593     {
594         return ada_search_params_size(_params);
595     }
596 
597     /++
598      + Resets the search parameters to the original query string.
599      +/
600     void reset() @nogc nothrow
601     {
602         ada_search_params_reset(_params, _str.data, _str.length);
603     }
604 
605     /++
606      + Removes all values for a given key.
607      + Params:
608      +   key = The key to remove.
609      +/
610     void remove(ref string key) @nogc nothrow
611     {
612         ada_search_params_remove(_params, &key[0], key.length);
613     }
614 
615     /++
616      + Removes a specific key-value pair.
617      + Params:
618      +   key = The key to remove.
619      +   value = The value to remove.
620      +/
621     void removeValue(ref string key, ref string value) @nogc nothrow
622     {
623         ada_search_params_remove_value(_params, &key[0], key.length, &value[0], value.length);
624     }
625 
626     /++
627      + Sets a key-value pair, replacing any existing values for the key.
628      + Params:
629      +   key = The key to set.
630      +   value = The value to set.
631      +/
632     void set(ref string key, ref string value) @nogc nothrow
633     {
634         ada_search_params_set(_params, &key[0], key.length, &value[0], value.length);
635     }
636 
637     /++
638      + Sorts the search parameters by key.
639      +/
640     void sort() @nogc nothrow
641     {
642         ada_search_params_sort(_params);
643     }
644 
645     /++
646      + Gets the next key-value pair from a search parameters iterator.
647      + Params:
648      +   it = The search parameters iterator to get the next pair from.
649      + Returns: The next key-value pair in the iterator, or an empty pair if iteration is complete.
650      +/
651     ada_string_pair itNext(scope ada_url_search_params_entries_iter it) @nogc nothrow
652     {
653         return ada_search_params_entries_iter_next(it);
654     }
655 
656     private
657     {
658         ada_url_search_params _params;
659         ada_string _str;
660     }
661 }
662 
663 /++
664  + A struct for managing a collection of strings, typically used for multiple values of a query parameter.
665  +/
666 struct Strings
667 {
668     /++
669      + Constructs a Strings instance from an ada_strings collection.
670      + Params:
671      +   input = The ada_strings collection to manage.
672      +/
673     this(ref ada_strings input) @nogc nothrow
674     {
675         _str = input;
676     }
677 
678     /++
679      + Destructor that frees the string collection.
680      +/
681     ~this() @nogc nothrow
682     {
683         ada_free_strings(_str);
684     }
685 
686     /++
687      + Retrieves a string at the specified index.
688      + Params:
689      +   index = The index of the string to retrieve.
690      + Returns: The string at the specified index.
691      +/
692     string getStr(size_t index) @nogc nothrow
693     {
694         return str_convert(ada_strings_get(_str, index));
695     }
696 
697     /++
698      + Retrieves the raw ada_string data at the specified index.
699      + Params:
700      +   index = The index of the string to retrieve.
701      + Returns: The ada_string at the specified index.
702      +/
703     ada_string getData(size_t index) @nogc nothrow
704     {
705         return ada_strings_get(_str, index);
706     }
707 
708     /++
709      + Retrieves the number of strings in the collection.
710      + Returns: The number of strings.
711      +/
712     size_t length() @nogc nothrow
713     {
714         return ada_strings_size(_str);
715     }
716 
717     private ada_strings _str;
718 }
719 
720 /++
721  + Converts an Ada string type (ada_string or ada_owned_string) to a D string.
722  + Params:
723  +   str = The Ada string to convert.
724  + Returns: A D string representing the input.
725  +/
726 string str_convert(T)(ref T str) @trusted @nogc nothrow
727 {
728     // pointer slicing not allowed in safe [checked] functions
729     static if (is(T == ada_string) || is(T == ada_owned_string))
730         return cast(string)(str.data[0 .. str.length]);
731     else
732         return cast(string)(str);
733 }