Commit 3a64693db3
Changed files (4)
lib
lib/std/http/method.zig
@@ -0,0 +1,65 @@
+//! HTTP Methods
+//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
+
+// Style guide is violated here so that @tagName can be used effectively
+/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton
+/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
+pub const Method = enum {
+ GET,
+ HEAD,
+ POST,
+ PUT,
+ DELETE,
+ CONNECT,
+ OPTIONS,
+ TRACE,
+ PATCH,
+
+ /// Returns true if a request of this method is allowed to have a body
+ /// Actual behavior from servers may vary and should still be checked
+ pub fn requestHasBody(self: Method) bool {
+ return switch (self) {
+ .POST, .PUT, .PATCH => true,
+ .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
+ };
+ }
+
+ /// Returns true if a response to this method is allowed to have a body
+ /// Actual behavior from clients may vary and should still be checked
+ pub fn responseHasBody(self: Method) bool {
+ return switch (self) {
+ .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
+ .HEAD, .PUT, .TRACE => false,
+ };
+ }
+
+ /// An HTTP method is safe if it doesn't alter the state of the server.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
+ pub fn safe(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD, .OPTIONS, .TRACE => true,
+ .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
+ };
+ }
+
+ /// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
+ pub fn idempotent(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
+ .CONNECT, .POST, .PATCH => false,
+ };
+ }
+
+ /// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
+ pub fn cacheable(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD => true,
+ .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
+ };
+ }
+};
lib/std/http/status.zig
@@ -0,0 +1,182 @@
+//! HTTP Status
+//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+
+const std = @import("../std.zig");
+
+pub const Status = enum(u10) {
+ @"continue" = 100, // RFC7231, Section 6.2.1
+ switching_protcols = 101, // RFC7231, Section 6.2.2
+ processing = 102, // RFC2518
+ early_hints = 103, // RFC8297
+
+ ok = 200, // RFC7231, Section 6.3.1
+ created = 201, // RFC7231, Section 6.3.2
+ accepted = 202, // RFC7231, Section 6.3.3
+ non_authoritative_info = 203, // RFC7231, Section 6.3.4
+ no_content = 204, // RFC7231, Section 6.3.5
+ reset_content = 205, // RFC7231, Section 6.3.6
+ partial_content = 206, // RFC7233, Section 4.1
+ multi_status = 207, // RFC4918
+ already_reported = 208, // RFC5842
+ im_used = 226, // RFC3229
+
+ multiple_choice = 300, // RFC7231, Section 6.4.1
+ moved_permanently = 301, // RFC7231, Section 6.4.2
+ found = 302, // RFC7231, Section 6.4.3
+ see_other = 303, // RFC7231, Section 6.4.4
+ not_modified = 304, // RFC7232, Section 4.1
+ use_proxy = 305, // RFC7231, Section 6.4.5
+ temporary_redirect = 307, // RFC7231, Section 6.4.7
+ permanent_redirect = 308, // RFC7538
+
+ bad_request = 400, // RFC7231, Section 6.5.1
+ unauthorized = 401, // RFC7235, Section 3.1
+ payment_required = 402, // RFC7231, Section 6.5.2
+ forbidden = 403, // RFC7231, Section 6.5.3
+ not_found = 404, // RFC7231, Section 6.5.4
+ method_not_allowed = 405, // RFC7231, Section 6.5.5
+ not_acceptable = 406, // RFC7231, Section 6.5.6
+ proxy_auth_required = 407, // RFC7235, Section 3.2
+ request_timeout = 408, // RFC7231, Section 6.5.7
+ conflict = 409, // RFC7231, Section 6.5.8
+ gone = 410, // RFC7231, Section 6.5.9
+ length_required = 411, // RFC7231, Section 6.5.10
+ precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
+ payload_too_large = 413, // RFC7231, Section 6.5.11
+ uri_too_long = 414, // RFC7231, Section 6.5.12
+ unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
+ range_not_satisfiable = 416, // RFC7233, Section 4.4
+ expectation_failed = 417, // RFC7231, Section 6.5.14
+ teapot = 418, // RFC 7168, 2.3.3
+ misdirected_request = 421, // RFC7540, Section 9.1.2
+ unprocessable_entity = 422, // RFC4918
+ locked = 423, // RFC4918
+ failed_dependency = 424, // RFC4918
+ too_early = 425, // RFC8470
+ upgrade_required = 426, // RFC7231, Section 6.5.15
+ precondition_required = 428, // RFC6585
+ too_many_requests = 429, // RFC6585
+ header_fields_too_large = 431, // RFC6585
+ unavailable_for_legal_reasons = 451, // RFC7725
+
+ internal_server_error = 500, // RFC7231, Section 6.6.1
+ not_implemented = 501, // RFC7231, Section 6.6.2
+ bad_gateway = 502, // RFC7231, Section 6.6.3
+ service_unavailable = 503, // RFC7231, Section 6.6.4
+ gateway_timeout = 504, // RFC7231, Section 6.6.5
+ http_version_not_supported = 505, // RFC7231, Section 6.6.6
+ variant_also_negotiates = 506, // RFC2295
+ insufficient_storage = 507, // RFC4918
+ loop_detected = 508, // RFC5842
+ not_extended = 510, // RFC2774
+ network_authentication_required = 511, // RFC6585
+
+ _,
+
+ pub fn phrase(self: Status) ?[]const u8 {
+ return switch (self) {
+ // 1xx statuses
+ .@"continue" => "Continue",
+ .switching_protcols => "Switching Protocols",
+ .processing => "Processing",
+ .early_hints => "Early Hints",
+
+ // 2xx statuses
+ .ok => "OK",
+ .created => "Created",
+ .accepted => "Accepted",
+ .non_authoritative_info => "Non-Authoritative Information",
+ .no_content => "No Content",
+ .reset_content => "Reset Content",
+ .partial_content => "Partial Content",
+ .multi_status => "Multi-Status",
+ .already_reported => "Already Reported",
+ .im_used => "IM Used",
+
+ // 3xx statuses
+ .multiple_choice => "Multiple Choice",
+ .moved_permanently => "Moved Permanently",
+ .found => "Found",
+ .see_other => "See Other",
+ .not_modified => "Not Modified",
+ .use_proxy => "Use Proxy",
+ .temporary_redirect => "Temporary Redirect",
+ .permanent_redirect => "Permanent Redirect",
+
+ // 4xx statuses
+ .bad_request => "Bad Request",
+ .unauthorized => "Unauthorized",
+ .payment_required => "Payment Required",
+ .forbidden => "Forbidden",
+ .not_found => "Not Found",
+ .method_not_allowed => "Method Not Allowed",
+ .not_acceptable => "Not Acceptable",
+ .proxy_auth_required => "Proxy Authentication Required",
+ .request_timeout => "Request Timeout",
+ .conflict => "Conflict",
+ .gone => "Gone",
+ .length_required => "Length Required",
+ .precondition_failed => "Precondition Failed",
+ .payload_too_large => "Payload Too Large",
+ .uri_too_long => "URI Too Long",
+ .unsupported_media_type => "Unsupported Media Type",
+ .range_not_satisfiable => "Range Not Satisfiable",
+ .expectation_failed => "Expectation Failed",
+ .teapot => "I'm a teapot",
+ .misdirected_request => "Misdirected Request",
+ .unprocessable_entity => "Unprocessable Entity",
+ .locked => "Locked",
+ .failed_dependency => "Failed Dependency",
+ .too_early => "Too Early",
+ .upgrade_required => "Upgrade Required",
+ .precondition_required => "Precondition Required",
+ .too_many_requests => "Too Many Requests",
+ .header_fields_too_large => "Request Header Fields Too Large",
+ .unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
+
+ // 5xx statuses
+ .internal_server_error => "Internal Server Error",
+ .not_implemented => "Not Implemented",
+ .bad_gateway => "Bad Gateway",
+ .service_unavailable => "Service Unavailable",
+ .gateway_timeout => "Gateway Timeout",
+ .http_version_not_supported => "HTTP Version Not Supported",
+ .variant_also_negotiates => "Variant Also Negotiates",
+ .insufficient_storage => "Insufficient Storage",
+ .loop_detected => "Loop Detected",
+ .not_extended => "Not Extended",
+ .network_authentication_required => "Network Authentication Required",
+
+ else => return null,
+ };
+ }
+
+ pub const Class = enum {
+ informational,
+ success,
+ redirect,
+ client_error,
+ server_error,
+ };
+
+ pub fn class(self: Status) ?Class {
+ return switch (@enumToInt(self)) {
+ 100...199 => .informational,
+ 200...299 => .success,
+ 300...399 => .redirect,
+ 400...499 => .client_error,
+ 500...599 => .server_error,
+ else => null,
+ };
+ }
+};
+
+test {
+ try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
+ try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
+}
+
+test {
+ try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
+ try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
+}
lib/std/http.zig
@@ -0,0 +1,8 @@
+const std = @import("std.zig");
+
+pub const Method = @import("http/method.zig").Method;
+pub const Status = @import("http/status.zig").Status;
+
+test {
+ std.testing.refAllDecls(@This());
+}
lib/std/std.zig
@@ -65,6 +65,7 @@ pub const fs = @import("fs.zig");
pub const hash = @import("hash.zig");
pub const hash_map = @import("hash_map.zig");
pub const heap = @import("heap.zig");
+pub const http = @import("http.zig");
pub const io = @import("io.zig");
pub const json = @import("json.zig");
pub const leb = @import("leb128.zig");