mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Query String parser with JS integration
Former-commit-id: 8542778c30e9757fa87514f46ff5086d7c8f6bfa
This commit is contained in:
@@ -2,6 +2,7 @@ import ReactDOMServer from "react-dom/server.browser";
|
||||
|
||||
addEventListener("fetch", async (event: FetchEvent) => {
|
||||
var route = Wundle.match(event);
|
||||
console.log("Route", JSON.stringify(route.query, null, 2));
|
||||
const { default: PageComponent } = await import(route.filepath);
|
||||
// const router = Wundle.Router.match(event);
|
||||
// console.log("Route", router.name);
|
||||
|
||||
@@ -4,6 +4,7 @@ const Api = @import("../../../api/schema.zig").Api;
|
||||
const FilesystemRouter = @import("../../../router.zig");
|
||||
const http = @import("../../../http.zig");
|
||||
const JavaScript = @import("../javascript.zig");
|
||||
const QueryStringMap = @import("../../../query_string_map.zig").QueryStringMap;
|
||||
usingnamespace @import("../bindings/bindings.zig");
|
||||
usingnamespace @import("../webcore/response.zig");
|
||||
const Router = @This();
|
||||
@@ -11,8 +12,7 @@ const Router = @This();
|
||||
const VirtualMachine = JavaScript.VirtualMachine;
|
||||
|
||||
route: *const FilesystemRouter.Match,
|
||||
file_path_str: js.JSStringRef = null,
|
||||
pathname_str: js.JSStringRef = null,
|
||||
query_string_map: ?QueryStringMap = null,
|
||||
|
||||
pub fn importRoute(
|
||||
this: *Router,
|
||||
@@ -231,7 +231,9 @@ pub fn getFilePath(
|
||||
pub fn finalize(
|
||||
this: *Router,
|
||||
) void {
|
||||
// this.deinit();
|
||||
if (this.query_string_map) |*map| {
|
||||
map.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getPathname(
|
||||
@@ -283,6 +285,40 @@ pub fn getKind(
|
||||
return KindEnum.init(this.route.name).toValue(VirtualMachine.vm.global).asRef();
|
||||
}
|
||||
|
||||
threadlocal var query_string_values_buf: [256]string = undefined;
|
||||
threadlocal var query_string_value_refs_buf: [256]ZigString = undefined;
|
||||
pub fn createQueryObject(ctx: js.JSContextRef, map: *QueryStringMap, exception: js.ExceptionRef) callconv(.C) js.JSValueRef {
|
||||
const QueryObjectCreator = struct {
|
||||
query: *QueryStringMap,
|
||||
pub fn create(this: *@This(), obj: *JSObject, global: *JSGlobalObject) void {
|
||||
var iter = this.query.iter();
|
||||
var str: ZigString = undefined;
|
||||
while (iter.next(&query_string_values_buf)) |entry| {
|
||||
str = ZigString.init(entry.name);
|
||||
|
||||
std.debug.assert(entry.values.len > 0);
|
||||
if (entry.values.len > 1) {
|
||||
var values = query_string_value_refs_buf[0..entry.values.len];
|
||||
for (entry.values) |value, i| {
|
||||
values[i] = ZigString.init(value);
|
||||
}
|
||||
obj.putRecord(VirtualMachine.vm.global, &str, values.ptr, values.len);
|
||||
} else {
|
||||
query_string_value_refs_buf[0] = ZigString.init(entry.values[0]);
|
||||
|
||||
obj.putRecord(VirtualMachine.vm.global, &str, &query_string_value_refs_buf, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var creator = QueryObjectCreator{ .query = map };
|
||||
|
||||
var value = JSObject.createWithInitializer(QueryObjectCreator, &creator, VirtualMachine.vm.global, map.getNameCount());
|
||||
|
||||
return value.asRef();
|
||||
}
|
||||
|
||||
pub fn getQuery(
|
||||
this: *Router,
|
||||
ctx: js.JSContextRef,
|
||||
@@ -290,5 +326,16 @@ pub fn getQuery(
|
||||
prop: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
return ZigString.init(this.route.query_string).toValue(VirtualMachine.vm.global).asRef();
|
||||
if (this.query_string_map == null) {
|
||||
if (QueryStringMap.init(getAllocator(ctx), this.route.query_string)) |map| {
|
||||
this.query_string_map = map;
|
||||
} else |err| {}
|
||||
}
|
||||
|
||||
// If it's still null, the query string has no names.
|
||||
if (this.query_string_map) |*map| {
|
||||
return createQueryObject(ctx, map, exception);
|
||||
} else {
|
||||
return JSValue.createEmptyObject(VirtualMachine.vm.global, 0).asRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <JavaScriptCore/JSObject.h>
|
||||
#include <JavaScriptCore/JSSet.h>
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/ParserError.h>
|
||||
#include <JavaScriptCore/ScriptExecutable.h>
|
||||
#include <JavaScriptCore/StackFrame.h>
|
||||
@@ -31,9 +32,109 @@
|
||||
#include <wtf/text/StringCommon.h>
|
||||
#include <wtf/text/StringImpl.h>
|
||||
#include <wtf/text/StringView.h>
|
||||
|
||||
#include <wtf/text/WTFString.h>
|
||||
extern "C" {
|
||||
JSC__JSValue
|
||||
JSC__JSObject__create(JSC__JSGlobalObject *globalObject, size_t initialCapacity, void *arg2,
|
||||
void (*ArgFn3)(void *arg0, JSC__JSObject *arg1, JSC__JSGlobalObject *arg2)) {
|
||||
JSC::JSObject *object =
|
||||
JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), initialCapacity);
|
||||
|
||||
ArgFn3(arg2, object, globalObject);
|
||||
|
||||
return JSC::JSValue::encode(object);
|
||||
}
|
||||
|
||||
JSC__JSValue JSC__JSValue__createEmptyObject(JSC__JSGlobalObject *globalObject,
|
||||
size_t initialCapacity) {
|
||||
return JSC::JSValue::encode(
|
||||
JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), initialCapacity));
|
||||
}
|
||||
|
||||
void JSC__JSObject__putRecord(JSC__JSObject *object, JSC__JSGlobalObject *global, ZigString *key,
|
||||
ZigString *values, size_t valuesLen) {
|
||||
auto scope = DECLARE_THROW_SCOPE(global->vm());
|
||||
auto ident = Zig::toIdentifier(*key, global);
|
||||
JSC::PropertyDescriptor descriptor;
|
||||
|
||||
descriptor.setEnumerable(1);
|
||||
descriptor.setConfigurable(1);
|
||||
descriptor.setWritable(1);
|
||||
|
||||
if (valuesLen == 1) {
|
||||
descriptor.setValue(JSC::jsString(global->vm(), Zig::toString(values[0])));
|
||||
} else {
|
||||
|
||||
JSC::JSArray *array = nullptr;
|
||||
{
|
||||
JSC::ObjectInitializationScope initializationScope(global->vm());
|
||||
if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
|
||||
initializationScope, nullptr,
|
||||
global->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
|
||||
valuesLen))) {
|
||||
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
array->initializeIndexWithoutBarrier(
|
||||
initializationScope, i, JSC::jsString(global->vm(), Zig::toString(values[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!array) {
|
||||
JSC::throwOutOfMemoryError(global, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
descriptor.setValue(array);
|
||||
}
|
||||
|
||||
object->methodTable(global->vm())->defineOwnProperty(object, global, ident, descriptor, true);
|
||||
object->putDirect(global->vm(), ident, descriptor.value());
|
||||
scope.release();
|
||||
}
|
||||
void JSC__JSValue__putRecord(JSC__JSValue objectValue, JSC__JSGlobalObject *global, ZigString *key,
|
||||
ZigString *values, size_t valuesLen) {
|
||||
JSC::JSValue objValue = JSC::JSValue::decode(objectValue);
|
||||
JSC::JSObject *object = objValue.asCell()->getObject();
|
||||
auto scope = DECLARE_THROW_SCOPE(global->vm());
|
||||
auto ident = Zig::toIdentifier(*key, global);
|
||||
JSC::PropertyDescriptor descriptor;
|
||||
|
||||
descriptor.setEnumerable(1);
|
||||
descriptor.setConfigurable(1);
|
||||
descriptor.setWritable(1);
|
||||
|
||||
if (valuesLen == 1) {
|
||||
descriptor.setValue(JSC::jsString(global->vm(), Zig::toString(values[0])));
|
||||
} else {
|
||||
|
||||
JSC::JSArray *array = nullptr;
|
||||
{
|
||||
JSC::ObjectInitializationScope initializationScope(global->vm());
|
||||
if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
|
||||
initializationScope, nullptr,
|
||||
global->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
|
||||
valuesLen))) {
|
||||
|
||||
for (size_t i = 0; i < valuesLen; ++i) {
|
||||
array->initializeIndexWithoutBarrier(
|
||||
initializationScope, i, JSC::jsString(global->vm(), Zig::toString(values[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!array) {
|
||||
JSC::throwOutOfMemoryError(global, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
descriptor.setValue(array);
|
||||
}
|
||||
|
||||
object->methodTable(global->vm())->defineOwnProperty(object, global, ident, descriptor, true);
|
||||
object->putDirect(global->vm(), ident, descriptor.value());
|
||||
scope.release();
|
||||
}
|
||||
|
||||
// This is very naive!
|
||||
JSC__JSInternalPromise *JSC__VM__reloadModule(JSC__VM *vm, JSC__JSGlobalObject *arg1,
|
||||
|
||||
@@ -18,6 +18,29 @@ pub const JSObject = extern struct {
|
||||
});
|
||||
}
|
||||
|
||||
const InitializeCallback = fn (ctx: ?*c_void, obj: [*c]JSObject, global: [*c]JSGlobalObject) callconv(.C) void;
|
||||
pub fn create(global_object: *JSGlobalObject, length: usize, ctx: *c_void, initializer: InitializeCallback) JSValue {
|
||||
return cppFn("create", .{
|
||||
global_object,
|
||||
length,
|
||||
ctx,
|
||||
initializer,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn Initializer(comptime Ctx: type, comptime func: fn (*Ctx, obj: *JSObject, global: *JSGlobalObject) void) type {
|
||||
return struct {
|
||||
pub fn call(this: ?*c_void, obj: [*c]JSObject, global: [*c]JSGlobalObject) callconv(.C) void {
|
||||
@call(.{ .modifier = .always_inline }, func, .{ @ptrCast(*Ctx, @alignCast(@alignOf(*Ctx), this.?)), obj.?, global.? });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn createWithInitializer(comptime Ctx: type, creator: *Ctx, global: *JSGlobalObject, length: usize) JSValue {
|
||||
const Type = Initializer(Ctx, Ctx.create);
|
||||
return create(global, length, creator, Type.call);
|
||||
}
|
||||
|
||||
pub fn getIndex(this: *JSObject, globalThis: *JSGlobalObject, i: u32) JSValue {
|
||||
return cppFn("getIndex", .{
|
||||
this,
|
||||
@@ -26,6 +49,10 @@ pub const JSObject = extern struct {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn putRecord(this: *JSObject, global: *JSGlobalObject, key: *ZigString, values: [*]ZigString, values_len: usize) void {
|
||||
return cppFn("putRecord", .{ this, global, key, values, values_len });
|
||||
}
|
||||
|
||||
pub fn getDirect(this: *JSObject, globalThis: *JSGlobalObject, str: ZigString) JSValue {
|
||||
return cppFn("getDirect", .{
|
||||
this,
|
||||
@@ -44,6 +71,8 @@ pub const JSObject = extern struct {
|
||||
}
|
||||
|
||||
pub const Extern = [_][]const u8{
|
||||
"putRecord",
|
||||
"create",
|
||||
"getArrayLength",
|
||||
"getIndex",
|
||||
"putAtIndex",
|
||||
@@ -1126,6 +1155,14 @@ pub const JSValue = enum(i64) {
|
||||
return @intToEnum(JSValue, @intCast(i64, @ptrToInt(ptr)));
|
||||
}
|
||||
|
||||
pub fn createEmptyObject(global: *JSGlobalObject, len: usize) JSValue {
|
||||
return cppFn("createEmptyObject", .{ global, len });
|
||||
}
|
||||
|
||||
pub fn putRecord(value: JSValue, global: *JSGlobalObject, key: *ZigString, values: [*]ZigString, values_len: usize) void {
|
||||
return cppFn("putRecord", .{ value, global, key, values, values_len });
|
||||
}
|
||||
|
||||
pub fn getErrorsProperty(this: JSValue, globalObject: *JSGlobalObject) JSValue {
|
||||
return cppFn("getErrorsProperty", .{ this, globalObject });
|
||||
}
|
||||
@@ -1363,7 +1400,7 @@ pub const JSValue = enum(i64) {
|
||||
return @intToPtr(*c_void, @intCast(usize, @enumToInt(this)));
|
||||
}
|
||||
|
||||
pub const Extern = [_][]const u8{ "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
|
||||
pub const Extern = [_][]const u8{ "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "get", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" };
|
||||
};
|
||||
|
||||
pub const PropertyName = extern struct {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//-- AUTOGENERATED FILE -- 1628213116
|
||||
//-- AUTOGENERATED FILE -- 1628316335
|
||||
// clang-format off
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//-- AUTOGENERATED FILE -- 1628213116
|
||||
//-- AUTOGENERATED FILE -- 1628316335
|
||||
// clang-format: off
|
||||
#pragma once
|
||||
|
||||
@@ -231,10 +231,12 @@ typedef void* JSClassRef;
|
||||
|
||||
#pragma mark - JSC::JSObject
|
||||
|
||||
CPP_DECL JSC__JSValue JSC__JSObject__create(JSC__JSGlobalObject* arg0, size_t arg1, void* arg2, void (* ArgFn3)(void* arg0, JSC__JSObject* arg1, JSC__JSGlobalObject* arg2));
|
||||
CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0);
|
||||
CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString arg2);
|
||||
CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, uint32_t arg2);
|
||||
CPP_DECL void JSC__JSObject__putDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString arg2, JSC__JSValue JSValue3);
|
||||
CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4);
|
||||
CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1);
|
||||
CPP_DECL JSC__JSValue ZigString__toValue(ZigString arg0, JSC__JSGlobalObject* arg1);
|
||||
|
||||
@@ -409,6 +411,7 @@ CPP_DECL JSC__JSCell* JSC__JSValue__asCell(JSC__JSValue JSValue0);
|
||||
CPP_DECL double JSC__JSValue__asNumber(JSC__JSValue JSValue0);
|
||||
CPP_DECL bJSC__JSObject JSC__JSValue__asObject(JSC__JSValue JSValue0);
|
||||
CPP_DECL JSC__JSString* JSC__JSValue__asString(JSC__JSValue JSValue0);
|
||||
CPP_DECL JSC__JSValue JSC__JSValue__createEmptyObject(JSC__JSGlobalObject* arg0, size_t arg1);
|
||||
CPP_DECL bool JSC__JSValue__eqlCell(JSC__JSValue JSValue0, JSC__JSCell* arg1);
|
||||
CPP_DECL bool JSC__JSValue__eqlValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1);
|
||||
CPP_DECL void JSC__JSValue__forEach(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, void (* ArgFn2)(JSC__VM* arg0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2));
|
||||
@@ -452,6 +455,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromU16(uint16_t arg0);
|
||||
CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromUint64(uint64_t arg0);
|
||||
CPP_DECL JSC__JSValue JSC__JSValue__jsTDZValue();
|
||||
CPP_DECL JSC__JSValue JSC__JSValue__jsUndefined();
|
||||
CPP_DECL void JSC__JSValue__putRecord(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4);
|
||||
CPP_DECL bool JSC__JSValue__toBoolean(JSC__JSValue JSValue0);
|
||||
CPP_DECL int32_t JSC__JSValue__toInt32(JSC__JSValue JSValue0);
|
||||
CPP_DECL JSC__JSObject* JSC__JSValue__toObject(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1);
|
||||
|
||||
@@ -97,10 +97,12 @@ pub const JSC__ObjectPrototype = struct_JSC__ObjectPrototype;
|
||||
pub const JSC__CallFrame = bJSC__CallFrame;
|
||||
|
||||
pub const JSC__MapIteratorPrototype = struct_JSC__MapIteratorPrototype;
|
||||
pub extern fn JSC__JSObject__create(arg0: [*c]JSC__JSGlobalObject, arg1: usize, arg2: ?*c_void, ArgFn3: ?fn (?*c_void, [*c]JSC__JSObject, [*c]JSC__JSGlobalObject) callconv(.C) void) JSC__JSValue;
|
||||
pub extern fn JSC__JSObject__getArrayLength(arg0: [*c]JSC__JSObject) usize;
|
||||
pub extern fn JSC__JSObject__getDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: ZigString) JSC__JSValue;
|
||||
pub extern fn JSC__JSObject__getIndex(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: u32) JSC__JSValue;
|
||||
pub extern fn JSC__JSObject__putDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: ZigString, JSValue3: JSC__JSValue) void;
|
||||
pub extern fn JSC__JSObject__putRecord(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void;
|
||||
pub extern fn ZigString__toErrorInstance(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
|
||||
pub extern fn ZigString__toValue(arg0: ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
|
||||
pub extern fn JSC__JSCell__getObject(arg0: [*c]JSC__JSCell) [*c]JSC__JSObject;
|
||||
@@ -233,6 +235,7 @@ pub extern fn JSC__JSValue__asCell(JSValue0: JSC__JSValue) [*c]JSC__JSCell;
|
||||
pub extern fn JSC__JSValue__asNumber(JSValue0: JSC__JSValue) f64;
|
||||
pub extern fn JSC__JSValue__asObject(JSValue0: JSC__JSValue) bJSC__JSObject;
|
||||
pub extern fn JSC__JSValue__asString(JSValue0: JSC__JSValue) [*c]JSC__JSString;
|
||||
pub extern fn JSC__JSValue__createEmptyObject(arg0: [*c]JSC__JSGlobalObject, arg1: usize) JSC__JSValue;
|
||||
pub extern fn JSC__JSValue__eqlCell(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSCell) bool;
|
||||
pub extern fn JSC__JSValue__eqlValue(JSValue0: JSC__JSValue, JSValue1: JSC__JSValue) bool;
|
||||
pub extern fn JSC__JSValue__forEach(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, ArgFn2: ?fn ([*c]JSC__VM, [*c]JSC__JSGlobalObject, JSC__JSValue) callconv(.C) void) void;
|
||||
@@ -276,6 +279,7 @@ pub extern fn JSC__JSValue__jsNumberFromU16(arg0: u16) JSC__JSValue;
|
||||
pub extern fn JSC__JSValue__jsNumberFromUint64(arg0: u64) JSC__JSValue;
|
||||
pub extern fn JSC__JSValue__jsTDZValue(...) JSC__JSValue;
|
||||
pub extern fn JSC__JSValue__jsUndefined(...) JSC__JSValue;
|
||||
pub extern fn JSC__JSValue__putRecord(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void;
|
||||
pub extern fn JSC__JSValue__toBoolean(JSValue0: JSC__JSValue) bool;
|
||||
pub extern fn JSC__JSValue__toInt32(JSValue0: JSC__JSValue) i32;
|
||||
pub extern fn JSC__JSValue__toObject(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) [*c]JSC__JSObject;
|
||||
|
||||
652
src/query_string_map.zig
Normal file
652
src/query_string_map.zig
Normal file
@@ -0,0 +1,652 @@
|
||||
const std = @import("std");
|
||||
const Api = @import("./api/schema.zig").Api;
|
||||
usingnamespace @import("./global.zig");
|
||||
|
||||
/// QueryString hash table that does few allocations and preserves the original order
|
||||
pub const QueryStringMap = struct {
|
||||
allocator: *std.mem.Allocator,
|
||||
slice: string,
|
||||
buffer: []u8,
|
||||
list: Param.List,
|
||||
name_count: ?usize = null,
|
||||
|
||||
threadlocal var _name_count: [8]string = undefined;
|
||||
pub fn getNameCount(this: *QueryStringMap) usize {
|
||||
if (this.name_count == null) {
|
||||
var count: usize = 0;
|
||||
var iterate = this.iter();
|
||||
while (iterate.next(&_name_count) != null) {
|
||||
count += 1;
|
||||
}
|
||||
this.name_count = count;
|
||||
}
|
||||
return this.name_count.?;
|
||||
}
|
||||
|
||||
pub fn iter(this: *const QueryStringMap) Iterator {
|
||||
return Iterator.init(this);
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
// Assume no query string param will exceed 2048 keys
|
||||
const VisitedMap = std.bit_set.ArrayBitSet(usize, 2048);
|
||||
|
||||
i: usize = 0,
|
||||
map: *const QueryStringMap,
|
||||
visited: VisitedMap,
|
||||
|
||||
const Result = struct {
|
||||
name: string,
|
||||
values: []string,
|
||||
};
|
||||
|
||||
pub fn init(map: *const QueryStringMap) Iterator {
|
||||
return Iterator{ .i = 0, .map = map, .visited = VisitedMap.initEmpty() };
|
||||
}
|
||||
|
||||
pub fn next(this: *Iterator, target: []string) ?Result {
|
||||
while (this.visited.isSet(this.i)) : (this.i += 1) {}
|
||||
if (this.i >= this.map.list.len) return null;
|
||||
|
||||
var count: usize = 0;
|
||||
var slice = this.map.list.slice();
|
||||
const hash = slice.items(.name_hash)[this.i];
|
||||
var result = Result{ .name = this.map.str(slice.items(.name)[this.i]), .values = target[0..1] };
|
||||
target[0] = this.map.str(slice.items(.value)[this.i]);
|
||||
|
||||
this.visited.set(this.i);
|
||||
this.i += 1;
|
||||
|
||||
var remainder_hashes = slice.items(.name_hash)[this.i..];
|
||||
var remainder_values = slice.items(.value)[this.i..];
|
||||
|
||||
var target_i: usize = 1;
|
||||
var current_i: usize = 0;
|
||||
|
||||
while (std.mem.indexOfScalar(u64, remainder_hashes[current_i..], hash)) |next_index| {
|
||||
const real_i = current_i + next_index + this.i;
|
||||
if (comptime isDebug) {
|
||||
std.debug.assert(!this.visited.isSet(real_i));
|
||||
}
|
||||
|
||||
this.visited.set(real_i);
|
||||
target[target_i] = this.map.str(remainder_values[current_i + next_index]);
|
||||
target_i += 1;
|
||||
result.values = target[0..target_i];
|
||||
|
||||
current_i += next_index + 1;
|
||||
if (target_i >= target.len) return result;
|
||||
if (real_i + 1 >= this.map.list.len) return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn str(this: *const QueryStringMap, ptr: Api.StringPointer) string {
|
||||
return this.slice[ptr.offset .. ptr.offset + ptr.length];
|
||||
}
|
||||
|
||||
pub fn getIndex(this: *const QueryStringMap, input: string) ?usize {
|
||||
const hash = std.hash.Wyhash.hash(0, input);
|
||||
return std.mem.indexOfScalar(u64, this.list.items(.name_hash), hash);
|
||||
}
|
||||
|
||||
pub fn get(this: *const QueryStringMap, input: string) ?string {
|
||||
const hash = std.hash.Wyhash.hash(0, input);
|
||||
const _slice = this.list.slice();
|
||||
const i = std.mem.indexOfScalar(u64, _slice.items(.name_hash), hash) orelse return null;
|
||||
return this.str(_slice.items(.value)[i]);
|
||||
}
|
||||
|
||||
pub fn has(this: *const QueryStringMap, input: string) bool {
|
||||
return this.getIndex(input) != null;
|
||||
}
|
||||
|
||||
pub fn getAll(this: *const QueryStringMap, input: string, target: []string) usize {
|
||||
const hash = std.hash.Wyhash.hash(0, input);
|
||||
const _slice = this.list.slice();
|
||||
return @call(.{ .modifier = .always_inline }, getAllWithHashFromOffset, .{ this, target, hash, 0, _slice });
|
||||
}
|
||||
|
||||
pub fn getAllWithHashFromOffset(this: *const QueryStringMap, target: []string, hash: u64, offset: usize, _slice: Param.List.Slice) usize {
|
||||
var remainder_hashes = _slice.items(.name_hash)[offset..];
|
||||
var remainder_values = _slice.items(.value)[offset..];
|
||||
var target_i: usize = 0;
|
||||
while (remainder_hashes.len > 0 and target_i < target.len) {
|
||||
const i = std.mem.indexOfScalar(u64, remainder_hashes, hash) orelse break;
|
||||
target[target_i] = this.str(remainder_values[i]);
|
||||
remainder_values = remainder_values[i + 1 ..];
|
||||
remainder_hashes = remainder_hashes[i + 1 ..];
|
||||
target_i += 1;
|
||||
}
|
||||
return target_i;
|
||||
}
|
||||
|
||||
pub const Param = struct {
|
||||
name: Api.StringPointer,
|
||||
name_hash: u64,
|
||||
value: Api.StringPointer,
|
||||
|
||||
pub const List = std.MultiArrayList(Param);
|
||||
};
|
||||
|
||||
pub fn init(
|
||||
allocator: *std.mem.Allocator,
|
||||
query_string: string,
|
||||
) !?QueryStringMap {
|
||||
var list = Param.List{};
|
||||
|
||||
var scanner = Scanner.init(query_string);
|
||||
var count: usize = 0;
|
||||
var estimated_str_len: usize = 0;
|
||||
|
||||
var nothing_needs_decoding = true;
|
||||
while (scanner.next()) |result| {
|
||||
if (result.name_needs_decoding or result.value_needs_decoding) {
|
||||
nothing_needs_decoding = false;
|
||||
}
|
||||
estimated_str_len += result.name.length + result.value.length;
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (count == 0) return null;
|
||||
|
||||
scanner = Scanner.init(query_string);
|
||||
try list.ensureTotalCapacity(allocator, count);
|
||||
|
||||
if (nothing_needs_decoding) {
|
||||
scanner = Scanner.init(query_string);
|
||||
while (scanner.next()) |result| {
|
||||
std.debug.assert(!result.name_needs_decoding);
|
||||
std.debug.assert(!result.value_needs_decoding);
|
||||
|
||||
var name = result.name;
|
||||
var value = result.value;
|
||||
const name_hash: u64 = std.hash.Wyhash.hash(0, result.rawName(query_string));
|
||||
list.appendAssumeCapacity(Param{ .name = name, .value = value, .name_hash = name_hash });
|
||||
}
|
||||
|
||||
return QueryStringMap{
|
||||
.list = list,
|
||||
.buffer = &[_]u8{},
|
||||
.slice = query_string,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
var buf = try std.ArrayList(u8).initCapacity(allocator, estimated_str_len);
|
||||
var writer = buf.writer();
|
||||
var buf_writer_pos: u32 = 0;
|
||||
|
||||
var list_slice = list.slice();
|
||||
const Writer = @TypeOf(writer);
|
||||
while (scanner.next()) |result| {
|
||||
var name = result.name;
|
||||
var value = result.value;
|
||||
var name_hash: u64 = undefined;
|
||||
if (result.name_needs_decoding) {
|
||||
name.length = PercentEncoding.decode(Writer, writer, query_string[name.offset..][0..name.length]) catch continue;
|
||||
name.offset = buf_writer_pos;
|
||||
buf_writer_pos += name.length;
|
||||
} else {
|
||||
name_hash = std.hash.Wyhash.hash(0, result.rawName(query_string));
|
||||
if (std.mem.indexOfScalar(u64, list_slice.items(.name_hash), name_hash)) |index| {
|
||||
name = list_slice.items(.name)[index];
|
||||
} else {
|
||||
name.length = PercentEncoding.decode(Writer, writer, query_string[name.offset..][0..name.length]) catch continue;
|
||||
name.offset = buf_writer_pos;
|
||||
buf_writer_pos += name.length;
|
||||
}
|
||||
}
|
||||
|
||||
value.length = PercentEncoding.decode(Writer, writer, query_string[value.offset..][0..value.length]) catch continue;
|
||||
value.offset = buf_writer_pos;
|
||||
buf_writer_pos += value.length;
|
||||
|
||||
list.appendAssumeCapacity(Param{ .name = name, .value = value, .name_hash = name_hash });
|
||||
}
|
||||
|
||||
buf.expandToCapacity();
|
||||
return QueryStringMap{
|
||||
.list = list,
|
||||
.buffer = buf.items,
|
||||
.slice = buf.items[0..buf_writer_pos],
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *QueryStringMap) void {
|
||||
if (this.buffer.len > 0) {
|
||||
this.allocator.free(this.buffer);
|
||||
}
|
||||
|
||||
if (this.list.len > 0) {
|
||||
this.list.deinit(this.allocator);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const PercentEncoding = struct {
|
||||
pub fn decode(comptime Writer: type, writer: Writer, input: string) !u32 {
|
||||
var i: usize = 0;
|
||||
var written: u32 = 0;
|
||||
// unlike JavaScript's decodeURIComponent, we are not handling invalid surrogate pairs
|
||||
// we are assuming the input is valid ascii
|
||||
while (i < input.len) {
|
||||
switch (input[i]) {
|
||||
'%' => {
|
||||
if (!(i + 3 <= input.len and strings.isASCIIHexDigit(input[i + 1]) and strings.isASCIIHexDigit(input[i + 2]))) return error.DecodingError;
|
||||
try writer.writeByte((strings.toASCIIHexValue(input[i + 1]) << 4) | strings.toASCIIHexValue(input[i + 2]));
|
||||
i += 3;
|
||||
written += 1;
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
const start = i;
|
||||
i += 1;
|
||||
|
||||
// scan ahead assuming .writeAll is faster than .writeByte one at a time
|
||||
while (i < input.len and input[i] != '%') : (i += 1) {}
|
||||
try writer.writeAll(input[start..i]);
|
||||
written += @truncate(u32, i - start);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Scanner = struct {
|
||||
query_string: string,
|
||||
i: usize,
|
||||
|
||||
pub fn init(query_string: string) Scanner {
|
||||
if (query_string.len > 0 and query_string[0] == '?') {
|
||||
return Scanner{ .query_string = query_string, .i = 1 };
|
||||
}
|
||||
|
||||
return Scanner{ .query_string = query_string, .i = 0 };
|
||||
}
|
||||
|
||||
pub const Result = struct {
|
||||
name_needs_decoding: bool = false,
|
||||
value_needs_decoding: bool = false,
|
||||
name: Api.StringPointer,
|
||||
value: Api.StringPointer,
|
||||
|
||||
pub inline fn rawName(this: *const Result, query_string: string) string {
|
||||
return if (this.name.length > 0) query_string[this.name.offset..][0..this.name.length] else "";
|
||||
}
|
||||
|
||||
pub inline fn rawValue(this: *const Result, query_string: string) string {
|
||||
return if (this.value.length > 0) query_string[this.value.offset..][0..this.value.length] else "";
|
||||
}
|
||||
};
|
||||
|
||||
/// Get the next query string parameter without allocating memory.
|
||||
pub fn next(this: *Scanner) ?Result {
|
||||
var relative_i: usize = 0;
|
||||
defer this.i += relative_i;
|
||||
|
||||
// reuse stack space
|
||||
// otherwise we'd recursively call the function
|
||||
loop: while (true) {
|
||||
if (this.i >= this.query_string.len) return null;
|
||||
|
||||
var slice = this.query_string[this.i..];
|
||||
relative_i = 0;
|
||||
var name = Api.StringPointer{ .offset = @truncate(u32, this.i), .length = 0 };
|
||||
var value = Api.StringPointer{ .offset = 0, .length = 0 };
|
||||
var name_needs_decoding = false;
|
||||
|
||||
while (relative_i < slice.len) {
|
||||
const char = slice[relative_i];
|
||||
switch (char) {
|
||||
'=' => {
|
||||
name.length = @truncate(u32, relative_i);
|
||||
relative_i += 1;
|
||||
|
||||
value.offset = @truncate(u32, relative_i + this.i);
|
||||
|
||||
const offset = relative_i;
|
||||
var value_needs_decoding = false;
|
||||
while (relative_i < slice.len and slice[relative_i] != '&') : (relative_i += 1) {
|
||||
value_needs_decoding = value_needs_decoding or switch (slice[relative_i]) {
|
||||
'%', '+' => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
value.length = @truncate(u32, relative_i - offset);
|
||||
// If the name is empty and it's just a value, skip it.
|
||||
// This is kind of an opinion. But, it's hard to see where that might be intentional.
|
||||
if (name.length == 0) return null;
|
||||
return Result{ .name = name, .value = value, .name_needs_decoding = name_needs_decoding, .value_needs_decoding = value_needs_decoding };
|
||||
},
|
||||
'%', '+' => {
|
||||
name_needs_decoding = true;
|
||||
},
|
||||
'&' => {
|
||||
// key&
|
||||
if (relative_i > 0) {
|
||||
name.length = @truncate(u32, relative_i);
|
||||
return Result{ .name = name, .value = value, .name_needs_decoding = name_needs_decoding, .value_needs_decoding = false };
|
||||
}
|
||||
|
||||
// &&&&&&&&&&&&&key=value
|
||||
while (relative_i < slice.len and slice[relative_i] == '&') : (relative_i += 1) {}
|
||||
this.i += relative_i;
|
||||
|
||||
// reuse stack space
|
||||
continue :loop;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
relative_i += 1;
|
||||
}
|
||||
|
||||
if (relative_i == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name.length = @truncate(u32, relative_i);
|
||||
return Result{ .name = name, .value = value, .name_needs_decoding = name_needs_decoding };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const expect = std.testing.expect;
|
||||
const expectString = std.testing.expectEqualStrings;
|
||||
test "Scanner.init" {
|
||||
var scanner = Scanner.init("?hello=true");
|
||||
try expect(scanner.i == 1);
|
||||
scanner = Scanner.init("hello=true");
|
||||
try expect(scanner.i == 0);
|
||||
}
|
||||
|
||||
test "Scanner.next" {
|
||||
var scanner = Scanner.init("?hello=true&welcome=to&the=what&is=this&1=100&&&&bacon&&&&what=true&ok&=100");
|
||||
var result: Scanner.Result = undefined;
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "hello");
|
||||
try expectString(result.rawValue(scanner.query_string), "true");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "welcome");
|
||||
try expectString(result.rawValue(scanner.query_string), "to");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "the");
|
||||
try expectString(result.rawValue(scanner.query_string), "what");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "is");
|
||||
try expectString(result.rawValue(scanner.query_string), "this");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "1");
|
||||
try expectString(result.rawValue(scanner.query_string), "100");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "bacon");
|
||||
try expectString(result.rawValue(scanner.query_string), "");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "what");
|
||||
try expectString(result.rawValue(scanner.query_string), "true");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding == false);
|
||||
try expect(result.value_needs_decoding == false);
|
||||
try expectString(result.rawName(scanner.query_string), "ok");
|
||||
try expectString(result.rawValue(scanner.query_string), "");
|
||||
try expect(scanner.next() == null);
|
||||
}
|
||||
|
||||
test "Scanner.next - % encoded" {
|
||||
var scanner = Scanner.init("?foo%20=%201023%20&%20what%20the%20fuck%20=%20am%20i%20looking%20at");
|
||||
var result: Scanner.Result = undefined;
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding);
|
||||
try expect(result.value_needs_decoding);
|
||||
try expectString(result.rawName(scanner.query_string), "foo%20");
|
||||
try expectString(result.rawValue(scanner.query_string), "%201023%20");
|
||||
result = scanner.next() orelse return try std.testing.expect(false);
|
||||
try expect(result.name_needs_decoding);
|
||||
try expect(result.value_needs_decoding);
|
||||
try expectString(result.rawName(scanner.query_string), "%20what%20the%20fuck%20");
|
||||
try expectString(result.rawValue(scanner.query_string), "%20am%20i%20looking%20at");
|
||||
try expect(scanner.next() == null);
|
||||
}
|
||||
|
||||
test "PercentEncoding.decode" {
|
||||
var buffer: [4096]u8 = undefined;
|
||||
std.mem.set(u8, &buffer, 0);
|
||||
|
||||
var stream = std.io.fixedBufferStream(&buffer);
|
||||
var writer = stream.writer();
|
||||
const Writer = @TypeOf(writer);
|
||||
|
||||
{
|
||||
const written = try PercentEncoding.decode(Writer, writer, "hello%20world%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B%20%2B");
|
||||
const correct = "hello world + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +";
|
||||
try expect(written == correct.len);
|
||||
try expectString(buffer[0..written], correct);
|
||||
}
|
||||
|
||||
stream.reset();
|
||||
|
||||
{
|
||||
const correct = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const written = try PercentEncoding.decode(Writer, writer, correct);
|
||||
try expect(written == correct.len);
|
||||
try expectString(buffer[0..written], correct);
|
||||
}
|
||||
|
||||
stream.reset();
|
||||
|
||||
{
|
||||
const correct = "hello my name is ?????";
|
||||
const input = "hello%20my%20name%20is%20%3F%3F%3F%3F%3F";
|
||||
const written = try PercentEncoding.decode(Writer, writer, correct);
|
||||
try expect(written == correct.len);
|
||||
try expectString(buffer[0..written], correct);
|
||||
}
|
||||
}
|
||||
|
||||
test "QueryStringMap (full)" {
|
||||
|
||||
// This is copy pasted from a random twitter thread on Chrome
|
||||
const url = "?cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_quote_count=true&include_reply_count=1&tweet_mode=extended&dm_users=false&include_groups=true&include_inbox_timelines=true&include_ext_media_color=true&supports_reactions=true&muting_enabled=false&nsfw_filtering_enabled=false&cursor=GRwmkMCq6fLUnMAnFpDAquny1JzAJyUAAAA&filter_low_quality=true&include_quality=all&ext=mediaColor&ext=altText&ext=mediaStats&ext=highlightedLabel&ext=voiceInfo";
|
||||
// from chrome's devtools
|
||||
const fixture = .{
|
||||
.@"cards_platform" = "Web-12",
|
||||
.@"include_cards" = "1",
|
||||
.@"include_ext_alt_text" = "true",
|
||||
.@"include_quote_count" = "true",
|
||||
.@"include_reply_count" = "1",
|
||||
.@"tweet_mode" = "extended",
|
||||
.@"dm_users" = "false",
|
||||
.@"include_groups" = "true",
|
||||
.@"include_inbox_timelines" = "true",
|
||||
.@"include_ext_media_color" = "true",
|
||||
.@"supports_reactions" = "true",
|
||||
.@"muting_enabled" = "false",
|
||||
.@"nsfw_filtering_enabled" = "false",
|
||||
.@"cursor" = "GRwmkMCq6fLUnMAnFpDAquny1JzAJyUAAAA",
|
||||
.@"filter_low_quality" = "true",
|
||||
.@"include_quality" = "all",
|
||||
.@"ext" = &[_]string{ "mediaColor", "altText", "mediaStats", "highlightedLabel", "voiceInfo" },
|
||||
};
|
||||
|
||||
var map = (try QueryStringMap.init(std.testing.allocator, url)) orelse return try std.testing.expect(false);
|
||||
defer map.deinit();
|
||||
try expectString(fixture.cards_platform, map.get("cards_platform").?);
|
||||
try expectString(fixture.include_cards, map.get("include_cards").?);
|
||||
try expectString(fixture.include_ext_alt_text, map.get("include_ext_alt_text").?);
|
||||
try expectString(fixture.include_quote_count, map.get("include_quote_count").?);
|
||||
try expectString(fixture.include_reply_count, map.get("include_reply_count").?);
|
||||
try expectString(fixture.tweet_mode, map.get("tweet_mode").?);
|
||||
try expectString(fixture.dm_users, map.get("dm_users").?);
|
||||
try expectString(fixture.include_groups, map.get("include_groups").?);
|
||||
try expectString(fixture.include_inbox_timelines, map.get("include_inbox_timelines").?);
|
||||
try expectString(fixture.include_ext_media_color, map.get("include_ext_media_color").?);
|
||||
try expectString(fixture.supports_reactions, map.get("supports_reactions").?);
|
||||
try expectString(fixture.muting_enabled, map.get("muting_enabled").?);
|
||||
try expectString(fixture.nsfw_filtering_enabled, map.get("nsfw_filtering_enabled").?);
|
||||
try expectString(fixture.cursor, map.get("cursor").?);
|
||||
try expectString(fixture.filter_low_quality, map.get("filter_low_quality").?);
|
||||
try expectString(fixture.include_quality, map.get("include_quality").?);
|
||||
try expectString(fixture.ext[0], map.get("ext").?);
|
||||
|
||||
var target: [fixture.ext.len]string = undefined;
|
||||
try expect((map.getAll("ext", &target)) == fixture.ext.len);
|
||||
|
||||
for (target) |item, i| {
|
||||
try expectString(
|
||||
fixture.ext[i],
|
||||
item,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test "QueryStringMap not encoded" {
|
||||
const url = "?hey=1&wow=true";
|
||||
const fixture = .{
|
||||
.@"hey" = "1",
|
||||
.@"wow" = "true",
|
||||
};
|
||||
const url_slice = std.mem.span(url);
|
||||
var map = (try QueryStringMap.init(std.testing.allocator, url_slice)) orelse return try std.testing.expect(false);
|
||||
try expect(map.buffer.len == 0);
|
||||
try expect(url_slice.ptr == map.slice.ptr);
|
||||
defer map.deinit();
|
||||
try expectString(fixture.hey, map.get("hey").?);
|
||||
try expectString(fixture.wow, map.get("wow").?);
|
||||
}
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
test "QueryStringMap Iterator" {
|
||||
// This is copy pasted from a random twitter thread on Chrome
|
||||
// The only difference from the one above is "ext" is moved before the last one
|
||||
// This is to test order of iteration
|
||||
const url = "?cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_quote_count=true&include_reply_count=1&tweet_mode=extended&dm_users=false&include_groups=true&include_inbox_timelines=true&include_ext_media_color=true&supports_reactions=true&muting_enabled=false&nsfw_filtering_enabled=false&cursor=GRwmkMCq6fLUnMAnFpDAquny1JzAJyUAAAA&filter_low_quality=true&ext=voiceInfo&include_quality=all&ext=mediaColor&ext=altText&ext=mediaStats&ext=highlightedLabel";
|
||||
// from chrome's devtools
|
||||
const fixture = .{
|
||||
.@"cards_platform" = "Web-12",
|
||||
.@"include_cards" = "1",
|
||||
.@"include_ext_alt_text" = "true",
|
||||
.@"include_quote_count" = "true",
|
||||
.@"include_reply_count" = "1",
|
||||
.@"tweet_mode" = "extended",
|
||||
.@"dm_users" = "false",
|
||||
.@"include_groups" = "true",
|
||||
.@"include_inbox_timelines" = "true",
|
||||
.@"include_ext_media_color" = "true",
|
||||
.@"supports_reactions" = "true",
|
||||
.@"muting_enabled" = "false",
|
||||
.@"nsfw_filtering_enabled" = "false",
|
||||
.@"cursor" = "GRwmkMCq6fLUnMAnFpDAquny1JzAJyUAAAA",
|
||||
.@"filter_low_quality" = "true",
|
||||
.@"include_quality" = "all",
|
||||
.@"ext" = &[_]string{
|
||||
"voiceInfo",
|
||||
"mediaColor",
|
||||
"altText",
|
||||
"mediaStats",
|
||||
"highlightedLabel",
|
||||
},
|
||||
};
|
||||
|
||||
var map = (try QueryStringMap.init(std.testing.allocator, url)) orelse return try std.testing.expect(false);
|
||||
defer map.deinit();
|
||||
var buf_: [48]string = undefined;
|
||||
var buf = std.mem.span(&buf_);
|
||||
var iter = map.iter();
|
||||
|
||||
var result: QueryStringMap.Iterator.Result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("cards_platform", result.name);
|
||||
try expectString(fixture.cards_platform, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_cards", result.name);
|
||||
try expectString(fixture.include_cards, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_ext_alt_text", result.name);
|
||||
try expectString(fixture.include_ext_alt_text, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_quote_count", result.name);
|
||||
try expectString(fixture.include_quote_count, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_reply_count", result.name);
|
||||
try expectString(fixture.include_reply_count, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("tweet_mode", result.name);
|
||||
try expectString(fixture.tweet_mode, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("dm_users", result.name);
|
||||
try expectString(fixture.dm_users, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_groups", result.name);
|
||||
try expectString(fixture.include_groups, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_inbox_timelines", result.name);
|
||||
try expectString(fixture.include_inbox_timelines, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_ext_media_color", result.name);
|
||||
try expectString(fixture.include_ext_media_color, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("supports_reactions", result.name);
|
||||
try expectString(fixture.supports_reactions, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("muting_enabled", result.name);
|
||||
try expectString(fixture.muting_enabled, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("nsfw_filtering_enabled", result.name);
|
||||
try expectString(fixture.nsfw_filtering_enabled, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("cursor", result.name);
|
||||
try expectString(fixture.cursor, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("filter_low_quality", result.name);
|
||||
try expectString(fixture.filter_low_quality, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("ext", result.name);
|
||||
try expectEqual(result.values.len, fixture.ext.len);
|
||||
for (fixture.ext) |ext, i| {
|
||||
try expectString(ext, result.values[i]);
|
||||
}
|
||||
|
||||
result = iter.next(buf) orelse return try expect(false);
|
||||
try expectString("include_quality", result.name);
|
||||
try expectString(fixture.include_quality, result.values[0]);
|
||||
try expectEqual(result.values.len, 1);
|
||||
|
||||
try expect(iter.next(buf) == null);
|
||||
}
|
||||
@@ -444,6 +444,18 @@ pub fn sortDesc(in: []string) void {
|
||||
std.sort.sort([]const u8, in, {}, cmpStringsDesc);
|
||||
}
|
||||
|
||||
pub fn isASCIIHexDigit(c: u8) bool {
|
||||
return std.ascii.isDigit(c) or std.ascii.isXDigit(c);
|
||||
}
|
||||
|
||||
pub fn toASCIIHexValue(character: u8) u8 {
|
||||
std.debug.assert(isASCIIHexDigit(character));
|
||||
return switch (character) {
|
||||
0...('A' - 1) => character - '0',
|
||||
else => (character - 'A' + 10) & 0xF,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn utf8ByteSequenceLength(first_byte: u8) u3 {
|
||||
// The switch is optimized much better than a "smart" approach using @clz
|
||||
return switch (first_byte) {
|
||||
|
||||
Reference in New Issue
Block a user