hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

query.ha (2685B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use io;
      5 use memio;
      6 use strings;
      7 
      8 export type query_decoder = struct {
      9 	tokenizer: strings::tokenizer,
     10 	bufs: (memio::stream, memio::stream),
     11 };
     12 
     13 // Initializes a decoder for a query string. Use [[query_next]] to walk it. The
     14 // caller must call [[query_finish]] once they're done using it.
     15 export fn decodequery(q: const str) query_decoder = query_decoder {
     16 	tokenizer = strings::tokenize(q, "&"),
     17 	bufs = (memio::dynamic(), memio::dynamic()),
     18 };
     19 
     20 // Frees resources associated with the [[query_decoder]].
     21 export fn query_finish(dec: *query_decoder) void = {
     22 	io::close(&dec.bufs.0)!;
     23 	io::close(&dec.bufs.1)!;
     24 };
     25 
     26 // Retrieves the next (key, value) pair from the query. The return value is
     27 // borrowed from the decoder and will be replaced on the next call, use
     28 // [[strings::dup]] to extend its lifetime.
     29 export fn query_next(dec: *query_decoder) ((str, str) | invalid | void) = {
     30 	const tok = match (strings::next_token(&dec.tokenizer)) {
     31 	case let s: str =>
     32 		yield s;
     33 	case => return;
     34 	};
     35 
     36 	const raw = strings::cut(tok, "=");
     37 	memio::reset(&dec.bufs.0);
     38 	percent_decode_static(&dec.bufs.0, raw.0)?;
     39 	memio::reset(&dec.bufs.1);
     40 	percent_decode_static(&dec.bufs.1, raw.1)?;
     41 	return (
     42 		memio::string(&dec.bufs.0)!,
     43 		memio::string(&dec.bufs.1)!,
     44 	);
     45 };
     46 
     47 // Encodes (key, value) pairs into a URI query string. The result must be
     48 // freed by the caller.
     49 export fn encodequery(pairs: [](str, str)) str = {
     50 	const buf = memio::dynamic();
     51 	for (let i = 0z; i < len(pairs); i += 1) {
     52 		const pair = pairs[i];
     53 		if (i > 0) memio::appendrune(&buf, '&')!;
     54 
     55 		assert(len(pair.0) > 0);
     56 		percent_encode(&buf, pair.0, unres_query_frag)!;
     57 		if (len(pair.1) > 0) {
     58 			memio::appendrune(&buf, '=')!;
     59 			percent_encode(&buf, pair.1, unres_query_frag)!;
     60 		};
     61 	};
     62 
     63 	return memio::string(&buf)!;
     64 };
     65 
     66 @test fn decodequery() void = {
     67 	const u = parse("https://sr.ht/projects?search=%23risc-v&sort=longest-active&quantity=100%25")!;
     68 	defer finish(&u);
     69 
     70 	const query = decodequery(u.query);
     71 	defer query_finish(&query);
     72 	const pair = query_next(&query)! as (str, str);
     73 	assert(pair.0 == "search");
     74 	assert(pair.1 == "#risc-v");
     75 
     76 	const pair = query_next(&query)! as (str, str);
     77 	assert(pair.0 == "sort");
     78 	assert(pair.1 == "longest-active");
     79 
     80 	const pair = query_next(&query)! as (str, str);
     81 	assert(pair.0 == "quantity");
     82 	assert(pair.1 == "100%");
     83 };
     84 
     85 @test fn encodequery() void = {
     86 	const pairs = [
     87 		("search", "#risc-v"),
     88 		("sort", "longest-active"),
     89 		("quantity", "100%")
     90 	];
     91 	const encoded = encodequery(pairs);
     92 	defer free(encoded);
     93 
     94 	assert(encoded == "search=%23risc-v&sort=longest-active&quantity=100%25");
     95 };