Start re-adding controllers
This commit is contained in:
parent
83ee7efba0
commit
228b9490ef
4 changed files with 691 additions and 40 deletions
|
@ -22,7 +22,7 @@ pub fn routeRequest(api_source: anytype, request: http.Request, response: http.R
|
|||
// todo 404
|
||||
}
|
||||
|
||||
const routes = .{sample_api};
|
||||
const routes = .{ sample_api, invites.create };
|
||||
|
||||
pub const sample_api = struct {
|
||||
const Self = @This();
|
||||
|
@ -54,7 +54,13 @@ pub fn Context(comptime Route: type) type {
|
|||
const Self = @This();
|
||||
|
||||
pub const Args = if (@hasDecl(Route, "Args")) Route.Args else void;
|
||||
|
||||
// TODO: if controller does not provide a body type, maybe we should
|
||||
// leave it as a simple reader instead of void
|
||||
pub const Body = if (@hasDecl(Route, "Body")) Route.Body else void;
|
||||
|
||||
// TODO: if controller does not provide a query type, maybe we should
|
||||
// leave it as a simple string instead of void
|
||||
pub const Query = if (@hasDecl(Route, "Query")) Route.Query else void;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
|
|
|
@ -1,41 +1,15 @@
|
|||
const root = @import("root");
|
||||
const std = @import("std");
|
||||
const http = @import("http");
|
||||
const Uuid = @import("util").Uuid;
|
||||
|
||||
const utils = @import("../controllers.zig").utils;
|
||||
|
||||
const RequestServer = root.RequestServer;
|
||||
const RouteArgs = http.RouteArgs;
|
||||
const api = @import("api");
|
||||
|
||||
pub const create = struct {
|
||||
pub const path = "/invites";
|
||||
pub const method = .POST;
|
||||
pub fn handler(srv: *RequestServer, ctx: *http.server.Context, _: RouteArgs) !void {
|
||||
// TODO: get rid of this temporary struct, find json library that lets me extend
|
||||
// it to parse UUIDs/etc in place
|
||||
const InviteRequest = struct {
|
||||
const Kind = @import("api").InviteRequest.Kind;
|
||||
pub const Body = api.InviteRequest;
|
||||
|
||||
name: ?[]const u8 = null,
|
||||
pub fn handler(req: anytype, res: anytype, srv: api.ApiSource.Conn) !void {
|
||||
// No need to free because it will be freed when the api conn
|
||||
// is closed
|
||||
const invite = srv.createInvite(req.body);
|
||||
|
||||
// admin only options
|
||||
kind: Kind = .user,
|
||||
to_community: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
const opt = try utils.parseRequestBody(InviteRequest, ctx);
|
||||
defer utils.freeRequestBody(opt, ctx.alloc);
|
||||
|
||||
var api = try utils.getApiConn(srv, ctx);
|
||||
defer api.close();
|
||||
|
||||
const invite = try api.createInvite(.{
|
||||
.name = opt.name,
|
||||
.kind = opt.kind,
|
||||
.to_community = if (opt.to_community) |id| try Uuid.parse(id) else null,
|
||||
});
|
||||
|
||||
try utils.respondJson(ctx, .created, invite);
|
||||
try res.writeJson(.created, invite);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
const std = @import("std");
|
||||
const util = @import("util");
|
||||
const sql = @import("sql");
|
||||
|
||||
const Uuid = util.Uuid;
|
||||
const DateTime = util.DateTime;
|
677
src/main/json.zig
Normal file
677
src/main/json.zig
Normal file
|
@ -0,0 +1,677 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
// This file is largely a copy of std.json
|
||||
|
||||
const StreamingParser = std.json.StreamingParser;
|
||||
const Token = std.json.Token;
|
||||
const unescapeValidString = std.json.unescapeValidString;
|
||||
const UnescapeValidStringError = std.json.UnescapeValidStringError;
|
||||
|
||||
pub fn parse(comptime T: type, body: []const u8, alloc: std.mem.Allocator) !T {
|
||||
var tokens = TokenStream.init(body);
|
||||
|
||||
const options = ParseOptions{ .allocator = alloc };
|
||||
|
||||
const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
const r = try parseInternal(T, token, &tokens, options);
|
||||
errdefer parseFreeInternal(T, r, options);
|
||||
if (!options.allow_trailing_data) {
|
||||
if ((try tokens.next()) != null) unreachable;
|
||||
assert(tokens.i >= tokens.slice.len);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
pub fn parseFree(value: anytype, alloc: std.mem.Allocator) void {
|
||||
parseFreeInternal(@TypeOf(value), value, .{ .allocator = alloc });
|
||||
}
|
||||
|
||||
// WARNING: the objects "parse" method must not contain a reference to the original value
|
||||
fn hasCustomParse(comptime T: type) bool {
|
||||
if (!std.meta.trait.hasFn("parse")(T)) return false;
|
||||
if (!@hasDecl(T, "JsonParseAs")) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///// The rest is (modified) from std.json
|
||||
|
||||
/// A small wrapper over a StreamingParser for full slices. Returns a stream of json Tokens.
|
||||
pub const TokenStream = struct {
|
||||
i: usize,
|
||||
slice: []const u8,
|
||||
parser: StreamingParser,
|
||||
token: ?Token,
|
||||
|
||||
pub const Error = StreamingParser.Error || error{UnexpectedEndOfJson};
|
||||
|
||||
pub fn init(slice: []const u8) TokenStream {
|
||||
return TokenStream{
|
||||
.i = 0,
|
||||
.slice = slice,
|
||||
.parser = StreamingParser.init(),
|
||||
.token = null,
|
||||
};
|
||||
}
|
||||
|
||||
fn stackUsed(self: *TokenStream) usize {
|
||||
return self.parser.stack.len + if (self.token != null) @as(usize, 1) else 0;
|
||||
}
|
||||
|
||||
pub fn next(self: *TokenStream) Error!?Token {
|
||||
if (self.token) |token| {
|
||||
self.token = null;
|
||||
return token;
|
||||
}
|
||||
|
||||
var t1: ?Token = undefined;
|
||||
var t2: ?Token = undefined;
|
||||
|
||||
while (self.i < self.slice.len) {
|
||||
try self.parser.feed(self.slice[self.i], &t1, &t2);
|
||||
self.i += 1;
|
||||
|
||||
if (t1) |token| {
|
||||
self.token = t2;
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
// Without this a bare number fails, the streaming parser doesn't know the input ended
|
||||
try self.parser.feed(' ', &t1, &t2);
|
||||
self.i += 1;
|
||||
|
||||
if (t1) |token| {
|
||||
return token;
|
||||
} else if (self.parser.complete) {
|
||||
return null;
|
||||
} else {
|
||||
return error.UnexpectedEndOfJson;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Checks to see if a string matches what it would be as a json-encoded string
|
||||
/// Assumes that `encoded` is a well-formed json string
|
||||
fn encodesTo(decoded: []const u8, encoded: []const u8) bool {
|
||||
var i: usize = 0;
|
||||
var j: usize = 0;
|
||||
while (i < decoded.len) {
|
||||
if (j >= encoded.len) return false;
|
||||
if (encoded[j] != '\\') {
|
||||
if (decoded[i] != encoded[j]) return false;
|
||||
j += 1;
|
||||
i += 1;
|
||||
} else {
|
||||
const escape_type = encoded[j + 1];
|
||||
if (escape_type != 'u') {
|
||||
const t: u8 = switch (escape_type) {
|
||||
'\\' => '\\',
|
||||
'/' => '/',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'f' => 12,
|
||||
'b' => 8,
|
||||
'"' => '"',
|
||||
else => unreachable,
|
||||
};
|
||||
if (decoded[i] != t) return false;
|
||||
j += 2;
|
||||
i += 1;
|
||||
} else {
|
||||
var codepoint = std.fmt.parseInt(u21, encoded[j + 2 .. j + 6], 16) catch unreachable;
|
||||
j += 6;
|
||||
if (codepoint >= 0xD800 and codepoint < 0xDC00) {
|
||||
// surrogate pair
|
||||
assert(encoded[j] == '\\');
|
||||
assert(encoded[j + 1] == 'u');
|
||||
const low_surrogate = std.fmt.parseInt(u21, encoded[j + 2 .. j + 6], 16) catch unreachable;
|
||||
codepoint = 0x10000 + (((codepoint & 0x03ff) << 10) | (low_surrogate & 0x03ff));
|
||||
j += 6;
|
||||
}
|
||||
var buf: [4]u8 = undefined;
|
||||
const len = std.unicode.utf8Encode(codepoint, &buf) catch unreachable;
|
||||
if (i + len > decoded.len) return false;
|
||||
if (!mem.eql(u8, decoded[i .. i + len], buf[0..len])) return false;
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(i == decoded.len);
|
||||
assert(j == encoded.len);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// parse tokens from a stream, returning `false` if they do not decode to `value`
|
||||
fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool {
|
||||
// TODO: should be able to write this function to not require an allocator
|
||||
const tmp = try parse(T, tokens, options);
|
||||
defer parseFree(T, tmp, options);
|
||||
|
||||
return parsedEqual(tmp, value);
|
||||
}
|
||||
|
||||
/// Returns if a value returned by `parse` is deep-equal to another value
|
||||
fn parsedEqual(a: anytype, b: @TypeOf(a)) bool {
|
||||
switch (@typeInfo(@TypeOf(a))) {
|
||||
.Optional => {
|
||||
if (a == null and b == null) return true;
|
||||
if (a == null or b == null) return false;
|
||||
return parsedEqual(a.?, b.?);
|
||||
},
|
||||
.Union => |info| {
|
||||
if (info.tag_type) |UnionTag| {
|
||||
const tag_a = std.meta.activeTag(a);
|
||||
const tag_b = std.meta.activeTag(b);
|
||||
if (tag_a != tag_b) return false;
|
||||
|
||||
inline for (info.fields) |field_info| {
|
||||
if (@field(UnionTag, field_info.name) == tag_a) {
|
||||
return parsedEqual(@field(a, field_info.name), @field(b, field_info.name));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
},
|
||||
.Array => {
|
||||
for (a) |e, i|
|
||||
if (!parsedEqual(e, b[i])) return false;
|
||||
return true;
|
||||
},
|
||||
.Struct => |info| {
|
||||
inline for (info.fields) |field_info| {
|
||||
if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.Pointer => |ptrInfo| switch (ptrInfo.size) {
|
||||
.One => return parsedEqual(a.*, b.*),
|
||||
.Slice => {
|
||||
if (a.len != b.len) return false;
|
||||
for (a) |e, i|
|
||||
if (!parsedEqual(e, b[i])) return false;
|
||||
return true;
|
||||
},
|
||||
.Many, .C => unreachable,
|
||||
},
|
||||
else => return a == b,
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
const ParseOptions = struct {
|
||||
allocator: ?Allocator = null,
|
||||
|
||||
/// Behaviour when a duplicate field is encountered.
|
||||
duplicate_field_behavior: enum {
|
||||
UseFirst,
|
||||
Error,
|
||||
UseLast,
|
||||
} = .Error,
|
||||
|
||||
/// If false, finding an unknown field returns an error.
|
||||
ignore_unknown_fields: bool = false,
|
||||
|
||||
allow_trailing_data: bool = false,
|
||||
};
|
||||
|
||||
const SkipValueError = error{UnexpectedJsonDepth} || TokenStream.Error;
|
||||
|
||||
fn skipValue(tokens: *TokenStream) SkipValueError!void {
|
||||
const original_depth = tokens.stackUsed();
|
||||
|
||||
// Return an error if no value is found
|
||||
_ = try tokens.next();
|
||||
if (tokens.stackUsed() < original_depth) return error.UnexpectedJsonDepth;
|
||||
if (tokens.stackUsed() == original_depth) return;
|
||||
|
||||
while (try tokens.next()) |_| {
|
||||
if (tokens.stackUsed() == original_depth) return;
|
||||
}
|
||||
}
|
||||
|
||||
fn ParseInternalError(comptime T: type) type {
|
||||
// `inferred_types` is used to avoid infinite recursion for recursive type definitions.
|
||||
const inferred_types = [_]type{};
|
||||
return ParseInternalErrorImpl(T, &inferred_types);
|
||||
}
|
||||
|
||||
fn ParseInternalErrorImpl(comptime T: type, comptime inferred_types: []const type) type {
|
||||
if (hasCustomParse(T)) {
|
||||
return ParseInternalError(T.JsonParseAs) || T.ParseError;
|
||||
}
|
||||
for (inferred_types) |ty| {
|
||||
if (T == ty) return error{};
|
||||
}
|
||||
|
||||
switch (@typeInfo(T)) {
|
||||
.Bool => return error{UnexpectedToken},
|
||||
.Float, .ComptimeFloat => return error{UnexpectedToken} || std.fmt.ParseFloatError,
|
||||
.Int, .ComptimeInt => {
|
||||
return error{ UnexpectedToken, InvalidNumber, Overflow } ||
|
||||
std.fmt.ParseIntError || std.fmt.ParseFloatError;
|
||||
},
|
||||
.Optional => |optionalInfo| {
|
||||
return ParseInternalErrorImpl(optionalInfo.child, inferred_types ++ [_]type{T});
|
||||
},
|
||||
.Enum => return error{ UnexpectedToken, InvalidEnumTag } || std.fmt.ParseIntError ||
|
||||
std.meta.IntToEnumError || std.meta.IntToEnumError,
|
||||
.Union => |unionInfo| {
|
||||
if (unionInfo.tag_type) |_| {
|
||||
var errors = error{NoUnionMembersMatched};
|
||||
for (unionInfo.fields) |u_field| {
|
||||
errors = errors || ParseInternalErrorImpl(u_field.field_type, inferred_types ++ [_]type{T});
|
||||
}
|
||||
return errors;
|
||||
} else {
|
||||
@compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
|
||||
}
|
||||
},
|
||||
.Struct => |structInfo| {
|
||||
var errors = error{
|
||||
DuplicateJSONField,
|
||||
UnexpectedEndOfJson,
|
||||
UnexpectedToken,
|
||||
UnexpectedValue,
|
||||
UnknownField,
|
||||
MissingField,
|
||||
} || SkipValueError || TokenStream.Error;
|
||||
for (structInfo.fields) |field| {
|
||||
errors = errors || ParseInternalErrorImpl(field.field_type, inferred_types ++ [_]type{T});
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
.Array => |arrayInfo| {
|
||||
return error{ UnexpectedEndOfJson, UnexpectedToken } || TokenStream.Error ||
|
||||
UnescapeValidStringError ||
|
||||
ParseInternalErrorImpl(arrayInfo.child, inferred_types ++ [_]type{T});
|
||||
},
|
||||
.Pointer => |ptrInfo| {
|
||||
var errors = error{AllocatorRequired} || std.mem.Allocator.Error;
|
||||
switch (ptrInfo.size) {
|
||||
.One => {
|
||||
return errors || ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T});
|
||||
},
|
||||
.Slice => {
|
||||
return errors || error{ UnexpectedEndOfJson, UnexpectedToken } ||
|
||||
ParseInternalErrorImpl(ptrInfo.child, inferred_types ++ [_]type{T}) ||
|
||||
UnescapeValidStringError || TokenStream.Error;
|
||||
},
|
||||
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||
}
|
||||
},
|
||||
else => return error{},
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn parseInternal(
|
||||
comptime T: type,
|
||||
token: Token,
|
||||
tokens: *TokenStream,
|
||||
options: ParseOptions,
|
||||
) ParseInternalError(T)!T {
|
||||
if (comptime hasCustomParse(T)) {
|
||||
const val = try parseInternal(T.JsonParseAs, token, tokens, options);
|
||||
defer parseFreeInternal(T.JsonParseAs, val, options);
|
||||
return try T.parse(val);
|
||||
}
|
||||
|
||||
switch (@typeInfo(T)) {
|
||||
.Bool => {
|
||||
return switch (token) {
|
||||
.True => true,
|
||||
.False => false,
|
||||
else => error.UnexpectedToken,
|
||||
};
|
||||
},
|
||||
.Float, .ComptimeFloat => {
|
||||
switch (token) {
|
||||
.Number => |numberToken| return try std.fmt.parseFloat(T, numberToken.slice(tokens.slice, tokens.i - 1)),
|
||||
.String => |stringToken| return try std.fmt.parseFloat(T, stringToken.slice(tokens.slice, tokens.i - 1)),
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
},
|
||||
.Int, .ComptimeInt => {
|
||||
switch (token) {
|
||||
.Number => |numberToken| {
|
||||
if (numberToken.is_integer)
|
||||
return try std.fmt.parseInt(T, numberToken.slice(tokens.slice, tokens.i - 1), 10);
|
||||
const float = try std.fmt.parseFloat(f128, numberToken.slice(tokens.slice, tokens.i - 1));
|
||||
if (@round(float) != float) return error.InvalidNumber;
|
||||
if (float > std.math.maxInt(T) or float < std.math.minInt(T)) return error.Overflow;
|
||||
return @floatToInt(T, float);
|
||||
},
|
||||
.String => |stringToken| {
|
||||
return std.fmt.parseInt(T, stringToken.slice(tokens.slice, tokens.i - 1), 10) catch |err| {
|
||||
switch (err) {
|
||||
error.Overflow => return err,
|
||||
error.InvalidCharacter => {
|
||||
const float = try std.fmt.parseFloat(f128, stringToken.slice(tokens.slice, tokens.i - 1));
|
||||
if (@round(float) != float) return error.InvalidNumber;
|
||||
if (float > std.math.maxInt(T) or float < std.math.minInt(T)) return error.Overflow;
|
||||
return @floatToInt(T, float);
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
},
|
||||
.Optional => |optionalInfo| {
|
||||
if (token == .Null) {
|
||||
return null;
|
||||
} else {
|
||||
return try parseInternal(optionalInfo.child, token, tokens, options);
|
||||
}
|
||||
},
|
||||
.Enum => |enumInfo| {
|
||||
switch (token) {
|
||||
.Number => |numberToken| {
|
||||
if (!numberToken.is_integer) return error.UnexpectedToken;
|
||||
const n = try std.fmt.parseInt(enumInfo.tag_type, numberToken.slice(tokens.slice, tokens.i - 1), 10);
|
||||
return try std.meta.intToEnum(T, n);
|
||||
},
|
||||
.String => |stringToken| {
|
||||
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
|
||||
switch (stringToken.escapes) {
|
||||
.None => return std.meta.stringToEnum(T, source_slice) orelse return error.InvalidEnumTag,
|
||||
.Some => {
|
||||
inline for (enumInfo.fields) |field| {
|
||||
if (field.name.len == stringToken.decodedLength() and encodesTo(field.name, source_slice)) {
|
||||
return @field(T, field.name);
|
||||
}
|
||||
}
|
||||
return error.InvalidEnumTag;
|
||||
},
|
||||
}
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
},
|
||||
.Union => |unionInfo| {
|
||||
if (unionInfo.tag_type) |_| {
|
||||
// try each of the union fields until we find one that matches
|
||||
inline for (unionInfo.fields) |u_field| {
|
||||
// take a copy of tokens so we can withhold mutations until success
|
||||
var tokens_copy = tokens.*;
|
||||
if (parseInternal(u_field.field_type, token, &tokens_copy, options)) |value| {
|
||||
tokens.* = tokens_copy;
|
||||
return @unionInit(T, u_field.name, value);
|
||||
} else |err| {
|
||||
// Bubble up error.OutOfMemory
|
||||
// Parsing some types won't have OutOfMemory in their
|
||||
// error-sets, for the condition to be valid, merge it in.
|
||||
if (@as(@TypeOf(err) || error{OutOfMemory}, err) == error.OutOfMemory) return err;
|
||||
// Bubble up AllocatorRequired, as it indicates missing option
|
||||
if (@as(@TypeOf(err) || error{AllocatorRequired}, err) == error.AllocatorRequired) return err;
|
||||
// otherwise continue through the `inline for`
|
||||
}
|
||||
}
|
||||
return error.NoUnionMembersMatched;
|
||||
} else {
|
||||
@compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
|
||||
}
|
||||
},
|
||||
.Struct => |structInfo| {
|
||||
switch (token) {
|
||||
.ObjectBegin => {},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
var r: T = undefined;
|
||||
var fields_seen = [_]bool{false} ** structInfo.fields.len;
|
||||
errdefer {
|
||||
inline for (structInfo.fields) |field, i| {
|
||||
if (fields_seen[i] and !field.is_comptime) {
|
||||
parseFreeInternal(field.field_type, @field(r, field.name), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
switch ((try tokens.next()) orelse return error.UnexpectedEndOfJson) {
|
||||
.ObjectEnd => break,
|
||||
.String => |stringToken| {
|
||||
const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
|
||||
var child_options = options;
|
||||
child_options.allow_trailing_data = true;
|
||||
var found = false;
|
||||
inline for (structInfo.fields) |field, i| {
|
||||
// TODO: using switches here segfault the compiler (#2727?)
|
||||
if ((stringToken.escapes == .None and mem.eql(u8, field.name, key_source_slice)) or (stringToken.escapes == .Some and (field.name.len == stringToken.decodedLength() and encodesTo(field.name, key_source_slice)))) {
|
||||
// if (switch (stringToken.escapes) {
|
||||
// .None => mem.eql(u8, field.name, key_source_slice),
|
||||
// .Some => (field.name.len == stringToken.decodedLength() and encodesTo(field.name, key_source_slice)),
|
||||
// }) {
|
||||
if (fields_seen[i]) {
|
||||
// switch (options.duplicate_field_behavior) {
|
||||
// .UseFirst => {},
|
||||
// .Error => {},
|
||||
// .UseLast => {},
|
||||
// }
|
||||
if (options.duplicate_field_behavior == .UseFirst) {
|
||||
// unconditonally ignore value. for comptime fields, this skips check against default_value
|
||||
const next_token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
parseFreeInternal(field.field_type, try parseInternal(field.field_type, next_token, tokens, child_options), child_options);
|
||||
found = true;
|
||||
break;
|
||||
} else if (options.duplicate_field_behavior == .Error) {
|
||||
return error.DuplicateJSONField;
|
||||
} else if (options.duplicate_field_behavior == .UseLast) {
|
||||
if (!field.is_comptime) {
|
||||
parseFreeInternal(field.field_type, @field(r, field.name), child_options);
|
||||
}
|
||||
fields_seen[i] = false;
|
||||
}
|
||||
}
|
||||
if (field.is_comptime) {
|
||||
if (!try parsesTo(field.field_type, @ptrCast(*const field.field_type, field.default_value.?).*, tokens, child_options)) {
|
||||
return error.UnexpectedValue;
|
||||
}
|
||||
} else {
|
||||
const next_token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
@field(r, field.name) = try parseInternal(field.field_type, next_token, tokens, child_options);
|
||||
}
|
||||
fields_seen[i] = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (options.ignore_unknown_fields) {
|
||||
try skipValue(tokens);
|
||||
continue;
|
||||
} else {
|
||||
return error.UnknownField;
|
||||
}
|
||||
}
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
}
|
||||
inline for (structInfo.fields) |field, i| {
|
||||
if (!fields_seen[i]) {
|
||||
if (field.default_value) |default_ptr| {
|
||||
if (!field.is_comptime) {
|
||||
const default = @ptrCast(*const field.field_type, default_ptr).*;
|
||||
@field(r, field.name) = default;
|
||||
}
|
||||
} else {
|
||||
return error.MissingField;
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
},
|
||||
.Array => |arrayInfo| {
|
||||
switch (token) {
|
||||
.ArrayBegin => {
|
||||
var r: T = undefined;
|
||||
var i: usize = 0;
|
||||
var child_options = options;
|
||||
child_options.allow_trailing_data = true;
|
||||
errdefer {
|
||||
// Without the r.len check `r[i]` is not allowed
|
||||
if (r.len > 0) while (true) : (i -= 1) {
|
||||
parseFreeInternal(arrayInfo.child, r[i], options);
|
||||
if (i == 0) break;
|
||||
};
|
||||
}
|
||||
while (i < r.len) : (i += 1) {
|
||||
const next_token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
r[i] = try parseInternal(arrayInfo.child, next_token, tokens, child_options);
|
||||
}
|
||||
const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
switch (tok) {
|
||||
.ArrayEnd => {},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
return r;
|
||||
},
|
||||
.String => |stringToken| {
|
||||
if (arrayInfo.child != u8) return error.UnexpectedToken;
|
||||
var r: T = undefined;
|
||||
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
|
||||
switch (stringToken.escapes) {
|
||||
.None => mem.copy(u8, &r, source_slice),
|
||||
.Some => try unescapeValidString(&r, source_slice),
|
||||
}
|
||||
return r;
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
},
|
||||
.Pointer => |ptrInfo| {
|
||||
const allocator = options.allocator orelse return error.AllocatorRequired;
|
||||
switch (ptrInfo.size) {
|
||||
.One => {
|
||||
const r: T = try allocator.create(ptrInfo.child);
|
||||
errdefer allocator.destroy(r);
|
||||
r.* = try parseInternal(ptrInfo.child, token, tokens, options);
|
||||
return r;
|
||||
},
|
||||
.Slice => {
|
||||
switch (token) {
|
||||
.ArrayBegin => {
|
||||
var arraylist = std.ArrayList(ptrInfo.child).init(allocator);
|
||||
errdefer {
|
||||
while (arraylist.popOrNull()) |v| {
|
||||
parseFreeInternal(ptrInfo.child, v, options);
|
||||
}
|
||||
arraylist.deinit();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||
switch (tok) {
|
||||
.ArrayEnd => break,
|
||||
else => {},
|
||||
}
|
||||
|
||||
try arraylist.ensureUnusedCapacity(1);
|
||||
const v = try parseInternal(ptrInfo.child, tok, tokens, options);
|
||||
arraylist.appendAssumeCapacity(v);
|
||||
}
|
||||
|
||||
if (ptrInfo.sentinel) |some| {
|
||||
const sentinel_value = @ptrCast(*const ptrInfo.child, some).*;
|
||||
try arraylist.append(sentinel_value);
|
||||
const output = arraylist.toOwnedSlice();
|
||||
return output[0 .. output.len - 1 :sentinel_value];
|
||||
}
|
||||
|
||||
return arraylist.toOwnedSlice();
|
||||
},
|
||||
.String => |stringToken| {
|
||||
if (ptrInfo.child != u8) return error.UnexpectedToken;
|
||||
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
|
||||
const len = stringToken.decodedLength();
|
||||
const output = try allocator.alloc(u8, len + @boolToInt(ptrInfo.sentinel != null));
|
||||
errdefer allocator.free(output);
|
||||
switch (stringToken.escapes) {
|
||||
.None => mem.copy(u8, output, source_slice),
|
||||
.Some => try unescapeValidString(output, source_slice),
|
||||
}
|
||||
|
||||
if (ptrInfo.sentinel) |some| {
|
||||
const char = @ptrCast(*const u8, some).*;
|
||||
output[len] = char;
|
||||
return output[0..len :char];
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
else => return error.UnexpectedToken,
|
||||
}
|
||||
},
|
||||
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||
}
|
||||
},
|
||||
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn ParseError(comptime T: type) type {
|
||||
return ParseInternalError(T) || error{UnexpectedEndOfJson} || TokenStream.Error;
|
||||
}
|
||||
|
||||
/// Releases resources created by `parse`.
|
||||
/// Should be called with the same type and `ParseOptions` that were passed to `parse`
|
||||
fn parseFreeInternal(comptime T: type, value: T, options: ParseOptions) void {
|
||||
switch (@typeInfo(T)) {
|
||||
.Bool, .Float, .ComptimeFloat, .Int, .ComptimeInt, .Enum => {},
|
||||
.Optional => {
|
||||
if (value) |v| {
|
||||
return parseFreeInternal(@TypeOf(v), v, options);
|
||||
}
|
||||
},
|
||||
.Union => |unionInfo| {
|
||||
if (unionInfo.tag_type) |UnionTagType| {
|
||||
inline for (unionInfo.fields) |u_field| {
|
||||
if (value == @field(UnionTagType, u_field.name)) {
|
||||
parseFreeInternal(u_field.field_type, @field(value, u_field.name), options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
},
|
||||
.Struct => |structInfo| {
|
||||
inline for (structInfo.fields) |field| {
|
||||
if (!field.is_comptime) {
|
||||
parseFreeInternal(field.field_type, @field(value, field.name), options);
|
||||
}
|
||||
}
|
||||
},
|
||||
.Array => |arrayInfo| {
|
||||
for (value) |v| {
|
||||
parseFreeInternal(arrayInfo.child, v, options);
|
||||
}
|
||||
},
|
||||
.Pointer => |ptrInfo| {
|
||||
const allocator = options.allocator orelse unreachable;
|
||||
switch (ptrInfo.size) {
|
||||
.One => {
|
||||
parseFreeInternal(ptrInfo.child, value.*, options);
|
||||
allocator.destroy(value);
|
||||
},
|
||||
.Slice => {
|
||||
for (value) |v| {
|
||||
parseFreeInternal(ptrInfo.child, v, options);
|
||||
}
|
||||
allocator.free(value);
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue