Allow unknown fields in some api calls
This commit is contained in:
parent
7ea3048027
commit
b60b629b30
|
@ -10,10 +10,10 @@ const Token = std.json.Token;
|
||||||
const unescapeValidString = std.json.unescapeValidString;
|
const unescapeValidString = std.json.unescapeValidString;
|
||||||
const UnescapeValidStringError = std.json.UnescapeValidStringError;
|
const UnescapeValidStringError = std.json.UnescapeValidStringError;
|
||||||
|
|
||||||
pub fn parse(comptime T: type, body: []const u8, alloc: std.mem.Allocator) !T {
|
pub fn parse(comptime T: type, allow_unknown_fields: bool, body: []const u8, alloc: std.mem.Allocator) !T {
|
||||||
var tokens = TokenStream.init(body);
|
var tokens = TokenStream.init(body);
|
||||||
|
|
||||||
const options = ParseOptions{ .allocator = alloc };
|
const options = ParseOptions{ .allocator = alloc, .ignore_unknown_fields = !allow_unknown_fields };
|
||||||
|
|
||||||
const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
|
||||||
const r = try parseInternal(T, token, &tokens, options);
|
const r = try parseInternal(T, token, &tokens, options);
|
||||||
|
|
|
@ -672,7 +672,13 @@ const BaseContentType = enum {
|
||||||
other,
|
other,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn parseBodyFromRequest(comptime T: type, content_type: ?[]const u8, reader: anytype, alloc: std.mem.Allocator) !T {
|
fn parseBodyFromRequest(
|
||||||
|
comptime T: type,
|
||||||
|
comptime options: ParseBodyOptions,
|
||||||
|
content_type: ?[]const u8,
|
||||||
|
reader: anytype,
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
) !T {
|
||||||
// Use json by default for now for testing purposes
|
// Use json by default for now for testing purposes
|
||||||
const eff_type = content_type orelse "application/json";
|
const eff_type = content_type orelse "application/json";
|
||||||
const parser_type = matchContentType(eff_type);
|
const parser_type = matchContentType(eff_type);
|
||||||
|
@ -681,7 +687,7 @@ fn parseBodyFromRequest(comptime T: type, content_type: ?[]const u8, reader: any
|
||||||
.octet_stream, .json => {
|
.octet_stream, .json => {
|
||||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||||
defer alloc.free(buf);
|
defer alloc.free(buf);
|
||||||
const body = try json_utils.parse(T, buf, alloc);
|
const body = try json_utils.parse(T, options.allow_unknown_fields, buf, alloc);
|
||||||
defer json_utils.parseFree(body, alloc);
|
defer json_utils.parseFree(body, alloc);
|
||||||
|
|
||||||
return try util.deepClone(alloc, body);
|
return try util.deepClone(alloc, body);
|
||||||
|
@ -689,14 +695,14 @@ fn parseBodyFromRequest(comptime T: type, content_type: ?[]const u8, reader: any
|
||||||
.url_encoded => {
|
.url_encoded => {
|
||||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||||
defer alloc.free(buf);
|
defer alloc.free(buf);
|
||||||
return urlencode.parse(alloc, T, buf) catch |err| switch (err) {
|
return urlencode.parse(alloc, options.allow_unknown_fields, T, buf) catch |err| switch (err) {
|
||||||
//error.NoQuery => error.NoBody,
|
//error.NoQuery => error.NoBody,
|
||||||
else => err,
|
else => err,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.multipart_formdata => {
|
.multipart_formdata => {
|
||||||
const boundary = fields.getParam(eff_type, "boundary") orelse return error.MissingBoundary;
|
const boundary = fields.getParam(eff_type, "boundary") orelse return error.MissingBoundary;
|
||||||
return try @import("./multipart.zig").parseFormData(T, boundary, reader, alloc);
|
return try @import("./multipart.zig").parseFormData(T, options.allow_unknown_fields, boundary, reader, alloc);
|
||||||
},
|
},
|
||||||
else => return error.UnsupportedMediaType,
|
else => return error.UnsupportedMediaType,
|
||||||
}
|
}
|
||||||
|
@ -714,12 +720,16 @@ fn matchContentType(hdr: []const u8) BaseContentType {
|
||||||
return .other;
|
return .other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const ParseBodyOptions = struct {
|
||||||
|
allow_unknown_fields: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
/// Parses a set of body arguments from the request body based on the request's Content-Type
|
/// Parses a set of body arguments from the request body based on the request's Content-Type
|
||||||
/// header.
|
/// header.
|
||||||
///
|
///
|
||||||
/// The exact method for parsing depends partially on the Content-Type. json types are preferred
|
/// The exact method for parsing depends partially on the Content-Type. json types are preferred
|
||||||
/// TODO: Need tests for this, including various Content-Type values
|
/// TODO: Need tests for this, including various Content-Type values
|
||||||
pub fn ParseBody(comptime Body: type) type {
|
pub fn ParseBody(comptime Body: type, comptime options: ParseBodyOptions) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
||||||
const content_type = req.headers.get("Content-Type");
|
const content_type = req.headers.get("Content-Type");
|
||||||
|
@ -731,7 +741,7 @@ pub fn ParseBody(comptime Body: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream = req.body orelse return error.NoBody;
|
var stream = req.body orelse return error.NoBody;
|
||||||
const body = try parseBodyFromRequest(Body, content_type, stream.reader(), ctx.allocator);
|
const body = try parseBodyFromRequest(Body, options, content_type, stream.reader(), ctx.allocator);
|
||||||
defer util.deepFree(ctx.allocator, body);
|
defer util.deepFree(ctx.allocator, body);
|
||||||
|
|
||||||
return next.handle(
|
return next.handle(
|
||||||
|
@ -751,7 +761,7 @@ test "parseBodyFromRequest" {
|
||||||
const testCase = struct {
|
const testCase = struct {
|
||||||
fn case(content_type: []const u8, body: []const u8, expected: anytype) !void {
|
fn case(content_type: []const u8, body: []const u8, expected: anytype) !void {
|
||||||
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||||
const result = try parseBodyFromRequest(@TypeOf(expected), content_type, stream.reader(), std.testing.allocator);
|
const result = try parseBodyFromRequest(@TypeOf(expected), .{}, content_type, stream.reader(), std.testing.allocator);
|
||||||
defer util.deepFree(std.testing.allocator, result);
|
defer util.deepFree(std.testing.allocator, result);
|
||||||
|
|
||||||
try util.testing.expectDeepEqual(expected, result);
|
try util.testing.expectDeepEqual(expected, result);
|
||||||
|
@ -797,7 +807,7 @@ pub fn ParseQueryParams(comptime QueryParams: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
||||||
if (QueryParams == void) return next.handle(req, res, addField(ctx, "query_params", {}), {});
|
if (QueryParams == void) return next.handle(req, res, addField(ctx, "query_params", {}), {});
|
||||||
const query = try urlencode.parse(ctx.allocator, QueryParams, ctx.query_string);
|
const query = try urlencode.parse(ctx.allocator, true, QueryParams, ctx.query_string);
|
||||||
defer util.deepFree(ctx.allocator, query);
|
defer util.deepFree(ctx.allocator, query);
|
||||||
|
|
||||||
return next.handle(
|
return next.handle(
|
||||||
|
|
|
@ -182,7 +182,7 @@ fn Deserializer(comptime Result: type) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parseFormData(comptime T: type, boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !T {
|
pub fn parseFormData(comptime T: type, allow_unknown_fields: bool, boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !T {
|
||||||
var form = openForm(try openMultipart(boundary, reader));
|
var form = openForm(try openMultipart(boundary, reader));
|
||||||
|
|
||||||
var ds = Deserializer(T){};
|
var ds = Deserializer(T){};
|
||||||
|
@ -196,7 +196,13 @@ pub fn parseFormData(comptime T: type, boundary: []const u8, reader: anytype, al
|
||||||
var part = (try form.next(alloc)) orelse break;
|
var part = (try form.next(alloc)) orelse break;
|
||||||
errdefer util.deepFree(alloc, part);
|
errdefer util.deepFree(alloc, part);
|
||||||
|
|
||||||
try ds.setSerializedField(part.name, part);
|
ds.setSerializedField(part.name, part) catch |err| switch (err) {
|
||||||
|
error.UnknownField => if (allow_unknown_fields) {
|
||||||
|
util.deepFree(alloc, part);
|
||||||
|
continue;
|
||||||
|
} else return err,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return try ds.finish(alloc);
|
return try ds.finish(alloc);
|
||||||
|
|
|
@ -98,13 +98,17 @@ pub const Iter = struct {
|
||||||
/// Would be used to parse a query string like
|
/// Would be used to parse a query string like
|
||||||
/// `?foo.baz=12345`
|
/// `?foo.baz=12345`
|
||||||
///
|
///
|
||||||
pub fn parse(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
|
pub fn parse(alloc: std.mem.Allocator, allow_unknown_fields: bool, comptime T: type, query: []const u8) !T {
|
||||||
var iter = Iter.from(query);
|
var iter = Iter.from(query);
|
||||||
|
|
||||||
var deserializer = Deserializer(T){};
|
var deserializer = Deserializer(T){};
|
||||||
|
|
||||||
while (iter.next()) |pair| {
|
while (iter.next()) |pair| {
|
||||||
try deserializer.setSerializedField(pair.key, pair.value);
|
try deserializer.setSerializedField(pair.key, pair.value);
|
||||||
|
deserializer.setSerializedField(pair.key, pair.value) catch |err| switch (err) {
|
||||||
|
error.UnknownField => if (allow_unknown_fields) continue else return err,
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return try deserializer.finish(alloc);
|
return try deserializer.finish(alloc);
|
||||||
|
|
|
@ -73,6 +73,13 @@ pub fn EndpointRequest(comptime Endpoint: type) type {
|
||||||
const Body = if (@hasDecl(Endpoint, "Body")) Endpoint.Body else void;
|
const Body = if (@hasDecl(Endpoint, "Body")) Endpoint.Body else void;
|
||||||
const Query = if (@hasDecl(Endpoint, "Query")) Endpoint.Query else void;
|
const Query = if (@hasDecl(Endpoint, "Query")) Endpoint.Query else void;
|
||||||
|
|
||||||
|
const body_options = .{
|
||||||
|
.allow_unknown_fields = if (@hasDecl(Endpoint, "allow_unknown_fields_in_body"))
|
||||||
|
Endpoint.allow_unknown_fields_in_body
|
||||||
|
else
|
||||||
|
false,
|
||||||
|
};
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
method: http.Method,
|
method: http.Method,
|
||||||
|
@ -91,7 +98,7 @@ pub fn EndpointRequest(comptime Endpoint: type) type {
|
||||||
const body_middleware = //if (Body == void)
|
const body_middleware = //if (Body == void)
|
||||||
//mdw.injectContext(.{ .body = {} })
|
//mdw.injectContext(.{ .body = {} })
|
||||||
//else
|
//else
|
||||||
mdw.ParseBody(Body){};
|
mdw.ParseBody(Body, body_options){};
|
||||||
|
|
||||||
const query_middleware = //if (Query == void)
|
const query_middleware = //if (Query == void)
|
||||||
//mdw.injectContext(.{ .query_params = {} })
|
//mdw.injectContext(.{ .query_params = {} })
|
||||||
|
|
Loading…
Reference in New Issue