├── .gitignore ├── LICENSE.md ├── espocrm_tests.zig ├── README.md └── espocrm.zig /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-cache/ 2 | /.zig-cache/ 3 | /zig-out/ 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ari Mizrahi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /espocrm_tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const print = std.debug.print; 3 | const assert = std.debug.assert; 4 | const espocrm = @import("espocrm.zig"); 5 | 6 | test "deserialize json string" { 7 | const allocator = std.testing.allocator; 8 | 9 | const Account = struct { accountNumber: []u8, name: []u8 }; 10 | const json_string = "{\"name\":\"Alice\",\"registration\":\"Alice\",\"batchID\":\"111\",\"accountNumber\":\"12345\"}"; 11 | 12 | const parsed = try espocrm.Deserialize(allocator, Account, json_string); 13 | defer parsed.deinit(); 14 | 15 | try std.testing.expect(std.mem.eql(u8, parsed.value.accountNumber, "12345")); 16 | 17 | try std.testing.expect(std.mem.eql(u8, parsed.value.name, "Alice")); 18 | } 19 | 20 | test "serialize struct" { 21 | const allocator = std.testing.allocator; 22 | 23 | const Acc = struct { name: []const u8, registration: []const u8, batchID: []const u8, accountNumber: []const u8 }; 24 | const list = Acc{ .name = "Alice", .registration = "Alice", .batchID = "111", .accountNumber = "12345" }; 25 | 26 | const stringified = try espocrm.Serialize(allocator, list); 27 | defer allocator.free(stringified); 28 | 29 | try std.testing.expect(std.mem.eql( 30 | u8, 31 | stringified, 32 | "{\"name\":\"Alice\",\"registration\":\"Alice\",\"batchID\":\"111\",\"accountNumber\":\"12345\"}", 33 | )); 34 | } 35 | 36 | test "where filter encoding" { 37 | const allocator = std.testing.allocator; 38 | 39 | const where = try espocrm.Where.string(allocator, &[_]espocrm.Where{ 40 | .{ .filter_type = espocrm.FilterOption.Equals, .filter_attribute = "a", .filter_value = "c" }, 41 | .{ .filter_type = espocrm.FilterOption.NotEquals, .filter_attribute = "b", .filter_value = "d" }, 42 | }); 43 | defer allocator.free(where); 44 | 45 | try std.testing.expect(std.mem.eql( 46 | u8, 47 | where, 48 | "&where[0][type]=equals&where[0][attribute]=a&where[0][value]=c&where[1][type]=notEquals&where[1][attribute]=b&where[1][value]=d", 49 | )); 50 | } 51 | 52 | test "parameter encoding" { 53 | const allocator = std.testing.allocator; 54 | 55 | var params = espocrm.Parameters.init(); 56 | 57 | _ = params.setMaxSize(10).setOrder(espocrm.Parameters.Order.Asc); 58 | const res = try params.encode(allocator); 59 | defer allocator.free(res); 60 | 61 | try std.testing.expect(std.mem.eql(u8, res, "?maxSize=10&offset=0&total=false&order=desc")); 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | > The current state of the SDK only works in zig 0.14 until I can find time to migrate the codebase to the latest. 4 | 5 | This Zig `espocrm` library provides an API client for EspoCRM. To get started you'll have to provide the URL where EspoCRM is located and your method of authentication. Read more from the [official documentation](https://docs.espocrm.com/development/api/#authentication). 6 | 7 | ## Installation 8 | 9 | Add this to your build.zig.zon 10 | 11 | ```zig 12 | .dependencies = .{ 13 | .espocrmz = .{ 14 | .url = "https://github.com/definitepotato/espocrmz/archive/refs/heads/master.tar.gz", 15 | .hash = "the correct hash will be suggested by zig at build time", 16 | } 17 | } 18 | ``` 19 | 20 | Add this to your build.zig 21 | 22 | ```zig 23 | const espocrmz = b.dependency("espocrmz", .{ 24 | .target = target, 25 | .optimize = optimize, 26 | }); 27 | exe.root_module.addImport("espocrmz", espocrmz.module("espocrmz")); 28 | ``` 29 | 30 | You can then import the library into your code like this 31 | 32 | ```zig 33 | const espocrm = @import("espocrmz").Client; 34 | ``` 35 | 36 | ## Basic Usage 37 | 38 | ### Using API Key Authentication: 39 | 40 | ```zig 41 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 42 | defer _ = gpa.deinit(); 43 | const allocator = gpa.allocator(); 44 | const client = Client.init("https://espocrm.example.com", .{ .api_key = "Your API Key here" }); 45 | ``` 46 | 47 | ### Making a Read request: 48 | 49 | ```zig 50 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 51 | defer _ = gpa.deinit(); 52 | const allocator = gpa.allocator(); 53 | 54 | const result = try client.readEntity("Contact", "78abc123def456", allocator); 55 | defer allocator.free(result); 56 | ``` 57 | 58 | ### Making a List request: 59 | 60 | ```zig 61 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 62 | defer _ = gpa.deinit(); 63 | const allocator = gpa.allocator(); 64 | 65 | var params = espocrm.Parameters.init(); 66 | _ = params.setMaxSize(10).setOrder(espocrm.Parameters.Order.Asc); 67 | const params_encoded = try params.encode(allocator); 68 | 69 | const result = try client.listEntities(allocator, "Contact", params, &[_]espocrm.Where{ 70 | .{ .filter_type = espocrm.FilterOption.Equals, .filter_attribute = "name", .filter_value = "Alice" }, 71 | .{ .filter_type = espocrm.FilterOption.GreaterThan, .filter_attribute = "age", .filter_value = "42" }, 72 | }); 73 | defer allocator.free(result); 74 | ``` 75 | 76 | ### Making a Create request: 77 | 78 | ```zig 79 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 80 | defer _ = gpa.deinit(); 81 | const allocator = gpa.allocator(); 82 | 83 | const new_contact = 84 | \\{ 85 | \\ "name": "Alice", 86 | \\ "age": 69 87 | \\} 88 | ; 89 | 90 | const result = try espocrm.createEntity(allocator, "Contact", new_contact); 91 | defer allocator.free(result); 92 | ``` 93 | 94 | ### Making an Update request: 95 | 96 | ```zig 97 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 98 | defer _ = gpa.deinit(); 99 | const allocator = gpa.allocator(); 100 | 101 | const update_info = 102 | \\{ 103 | \\ "name": "Bob" 104 | \\} 105 | ; 106 | 107 | const result = try client.updateEntity(allocator, "Contact", "67abe33f5883bd9e", update_info); 108 | defer allocator.free(result); 109 | ``` 110 | 111 | ### Making a Delete request: 112 | 113 | ```zig 114 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 115 | defer _ = gpa.deinit(); 116 | const allocator = gpa.allocator(); 117 | 118 | const result = try client.deleteEntity(allocator, "Contact", "67abe33f5883bd9e"); 119 | defer allocator.free(result); 120 | ``` 121 | -------------------------------------------------------------------------------- /espocrm.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const json = std.json; 3 | const http = std.http; 4 | const Allocator = std.mem.Allocator; 5 | 6 | const api_path = "/api/v1"; 7 | 8 | /// Parses the json object `json_str` and returns the result packaged in a `std.json.Parsed`. 9 | /// You must call `deinit()` of the returned object to clean up allocated resources. 10 | pub fn Deserialize(allocator: Allocator, comptime T: type, json_str: []const u8) !json.Parsed(T) { 11 | const parsed = try json.parseFromSlice(T, allocator, json_str, .{ .ignore_unknown_fields = true }); 12 | return parsed; 13 | } 14 | 15 | /// Takes any struct and returns a json object stringified. 16 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 17 | pub fn Serialize(allocator: Allocator, any: anytype) ![]u8 { 18 | const result = try json.stringifyAlloc(allocator, any, .{}); 19 | return result; 20 | } 21 | 22 | /// Used to configure the espocrm `Client`. 23 | const ClientConfig = struct { 24 | api_key: ?[]const u8 = undefined, 25 | }; 26 | 27 | pub const Client = struct { 28 | const Self = @This(); 29 | 30 | url: []const u8 = undefined, 31 | config: ClientConfig = undefined, 32 | 33 | pub fn init(url: []const u8, config: ClientConfig) Client { 34 | return Client{ 35 | .url = url, 36 | .config = config, 37 | }; 38 | } 39 | 40 | /// Fetches an `entity_type` record based on the `entity_id`. 41 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 42 | /// Documented at https://docs.espocrm.com/development/api/crud/#read 43 | pub fn readEntity( 44 | self: Self, 45 | allocator: Allocator, 46 | entity_type: []const u8, 47 | entity_id: []const u8, 48 | ) ![]u8 { 49 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id }); 50 | defer allocator.free(uri); 51 | const endpoint = try std.Uri.parse(uri); 52 | 53 | const body = try self.sendRequest(allocator, http.Method.GET, endpoint, null); 54 | return body; 55 | } 56 | 57 | /// Lists all entities of an `entity_type` using `parameters` and `filter_params` (where). 58 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 59 | /// Documented at https://docs.espocrm.com/development/api/crud/#list 60 | pub fn listEntities( 61 | self: Self, 62 | allocator: Allocator, 63 | entity_type: []const u8, 64 | parameters: Parameters, 65 | filter_params: []const Where, 66 | ) ![]u8 { 67 | const params = try parameters.encode(allocator); 68 | defer allocator.free(params); 69 | 70 | const where = try Where.string(allocator, filter_params); 71 | defer allocator.free(where); 72 | 73 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}{s}{s}", .{ self.url, api_path, entity_type, params, where }); 74 | defer allocator.free(uri); 75 | const endpoint = try std.Uri.parse(uri); 76 | 77 | const body = try self.sendRequest(allocator, http.Method.GET, endpoint, null); 78 | return body; 79 | } 80 | 81 | /// Creates a new record of an `entity_type` using a json `payload` in the request body. 82 | /// `Payload` should contain a json string formatted as the `entity_type`, i.e., 83 | /// - { "name": "Alice" } 84 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 85 | /// Documented at https://docs.espocrm.com/development/api/crud/#create 86 | pub fn createEntity( 87 | self: Self, 88 | allocator: Allocator, 89 | entity_type: []const u8, 90 | payload: []const u8, 91 | ) ![]u8 { 92 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}", .{ self.url, api_path, entity_type }); 93 | defer allocator.free(uri); 94 | const endpoint = try std.Uri.parse(uri); 95 | 96 | const body = try self.sendRequest(allocator, http.Method.POST, endpoint, payload); 97 | return body; 98 | } 99 | 100 | /// Deletes a record of an `entity_type` using the `entity_id`. 101 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 102 | /// Documented at https://docs.espocrm.com/development/api/crud/#delete 103 | pub fn deleteEntity( 104 | self: Self, 105 | allocator: Allocator, 106 | entity_type: []const u8, 107 | entity_id: []const u8, 108 | ) ![]u8 { 109 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id }); 110 | defer allocator.free(uri); 111 | const endpoint = try std.Uri.parse(uri); 112 | 113 | const body = try self.sendRequest(allocator, http.Method.DELETE, endpoint, null); 114 | return body; 115 | } 116 | 117 | /// Updates a record of an `entity_type` using the `entity_id` and a json `payload` in the body of the request. 118 | /// `Payload` should contain a json string formatted as the `entity_type`, i.e., 119 | /// - { "name" : "Alice" } 120 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 121 | /// Documented at https://docs.espocrm.com/development/api/crud/#update 122 | pub fn updateEntity( 123 | self: Self, 124 | allocator: Allocator, 125 | entity_type: []const u8, 126 | entity_id: []const u8, 127 | payload: []const u8, 128 | ) ![]u8 { 129 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id }); 130 | defer allocator.free(uri); 131 | const endpoint = try std.Uri.parse(uri); 132 | 133 | const body = try self.sendRequest(allocator, http.Method.PUT, endpoint, payload); 134 | return body; 135 | } 136 | 137 | /// Lists all related entities of type `entity_type` of a record with `entity_id` for all related `related_type. 138 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 139 | /// Documented at https://docs.espocrm.com/development/api/relationships/#list-related-records 140 | pub fn listRelatedEntities( 141 | self: Self, 142 | allocator: Allocator, 143 | entity_type: []const u8, 144 | entity_id: []const u8, 145 | related_type: []const u8, 146 | ) ![]u8 { 147 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id, related_type }); 148 | defer allocator.free(uri); 149 | const endpoint = try std.Uri.parse(uri); 150 | 151 | const body = try self.sendRequest(allocator, http.Method.GET, endpoint, null); 152 | return body; 153 | } 154 | 155 | /// Links the `entity_id` of type `entity_type` to a `related_entity_type` via the `payload`. 156 | /// `Payload` should contain the id/ids of the related entity, i.e., 157 | /// - { "id": "6a183bcf77198a" } 158 | /// - { "ids": ["836bc38165a3", "2735b38a3a723"] } 159 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 160 | /// Documented at https://docs.espocrm.com/development/api/relationships/#link 161 | pub fn linkEntity( 162 | self: Self, 163 | allocator: Allocator, 164 | entity_type: []const u8, 165 | entity_id: []const u8, 166 | related_entity_type: []const u8, 167 | payload: []const u8, 168 | ) ![]u8 { 169 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id, related_entity_type }); 170 | defer allocator.free(uri); 171 | const endpoint = try std.Uri.parse(uri); 172 | 173 | const body = try self.sendRequest(allocator, http.Method.POST, endpoint, payload); 174 | return body; 175 | } 176 | 177 | // BUG: Requires a payload but DELETE method usually doesn't contain a payload. 178 | // `unlinkEntity()` has a transfer_encoding issue due to unexpected message body 179 | // for the DELETE method. 180 | 181 | /// Unlinks the `entity_id` of type `entity_type` related to a `related_entity_type` via the `payload`. 182 | /// `Payload` should contain the id/ids of the related entity, i.e., 183 | /// - { "id": "6a183bcf77198a" } 184 | /// - { "ids": ["836bc38165a3", "2735b38a3a723"] } 185 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 186 | /// Documented at https://docs.espocrm.com/development/api/relationships/#unlink 187 | pub fn unlinkEntity( 188 | self: Self, 189 | allocator: Allocator, 190 | entity_type: []const u8, 191 | entity_id: []const u8, 192 | related_entity_type: []const u8, 193 | payload: []const u8, 194 | ) ![]u8 { 195 | const uri = try std.fmt.allocPrint(allocator, "{s}{s}/{s}/{s}/{s}", .{ self.url, api_path, entity_type, entity_id, related_entity_type }); 196 | defer allocator.free(uri); 197 | const endpoint = try std.Uri.parse(uri); 198 | 199 | const body = try self.sendRequest(allocator, http.Method.DELETE, endpoint, payload); 200 | return body; 201 | } 202 | 203 | fn sendRequest( 204 | self: Self, 205 | allocator: Allocator, 206 | method: http.Method, 207 | endpoint: std.Uri, 208 | payload: ?[]const u8, 209 | ) ![]u8 { 210 | const buf = try allocator.alloc(u8, 1024 * 1024 * 4); 211 | defer allocator.free(buf); 212 | 213 | var client = http.Client{ .allocator = allocator }; 214 | defer client.deinit(); 215 | 216 | var req = try client.open(method, endpoint, .{ 217 | .server_header_buffer = buf, 218 | .extra_headers = &.{ 219 | .{ 220 | .name = "content-type", 221 | .value = "application/json", 222 | }, 223 | .{ 224 | .name = "x-api-key", 225 | .value = self.config.api_key.?, 226 | }, 227 | }, 228 | }); 229 | defer req.deinit(); 230 | 231 | switch (method) { 232 | .POST, .PUT => { 233 | req.transfer_encoding = .{ .content_length = payload.?.len }; 234 | try req.send(); 235 | var wtr = req.writer(); 236 | try wtr.writeAll(payload.?); 237 | try req.finish(); 238 | try req.wait(); 239 | }, 240 | else => { 241 | try req.send(); 242 | try req.finish(); 243 | try req.wait(); 244 | }, 245 | } 246 | 247 | try std.testing.expectEqual(req.response.status, .ok); 248 | 249 | var rdr = req.reader(); 250 | const body = try rdr.readAllAlloc(allocator, 1024 * 1024 * 4); 251 | 252 | return body; 253 | } 254 | }; 255 | 256 | pub const FilterOption = enum { 257 | Equals, 258 | NotEquals, 259 | GreaterThan, 260 | LessThan, 261 | GreaterThanOrEquals, 262 | LessThanOrEquals, 263 | IsNull, 264 | IsNotNull, 265 | IsTrue, 266 | IsFalse, 267 | LinkedWith, 268 | NotLinkedWith, 269 | IsLinked, 270 | IsNotLinked, 271 | In, 272 | NotIn, 273 | Contains, 274 | NotContains, 275 | StartsWith, 276 | EndsWith, 277 | Like, 278 | NotLike, 279 | Or, 280 | AndToday, 281 | Past, 282 | Future, 283 | LastSevenDays, 284 | CurrentMonth, 285 | LastMonth, 286 | NextMonth, 287 | CurrentQuarter, 288 | LastQuarter, 289 | CurrentYear, 290 | LastYear, 291 | CurrentFiscalYear, 292 | LastFiscalYear, 293 | CurrentFiscalQuarter, 294 | LastFiscalQuarter, 295 | LastXDays, 296 | NextXDays, 297 | OlderThanXDays, 298 | AfterXDays, 299 | Between, 300 | ArrayAnyOf, 301 | ArrayNoneOf, 302 | ArrayAllOf, 303 | ArrayIsEmpty, 304 | ArrayIsNotEmpty, 305 | 306 | /// Returns the url compatible string version of a `FilterOption` variant. 307 | pub fn string(self: FilterOption) []const u8 { 308 | return switch (self) { 309 | .Equals => "equals", 310 | .NotEquals => "notEquals", 311 | .GreaterThan => "greaterThan", 312 | .LessThan => "lessThan", 313 | .GreaterThanOrEquals => "greaterThanOrEquals", 314 | .LessThanOrEquals => "lessThanOrEquals", 315 | .IsNull => "isNull", 316 | .IsNotNull => "isNotNull", 317 | .IsTrue => "isTrue", 318 | .IsFalse => "isFalse", 319 | .LinkedWith => "linkedWith", 320 | .NotLinkedWith => "notLinkedWith", 321 | .IsLinked => "isLinked", 322 | .IsNotLinked => "isNotLinked", 323 | .In => "in", 324 | .NotIn => "notIn", 325 | .Contains => "contains", 326 | .NotContains => "notContains", 327 | .StartsWith => "startsWith", 328 | .EndsWith => "endsWith", 329 | .Like => "like", 330 | .NotLike => "notLike", 331 | .Or => "or", 332 | .AndToday => "andToday", 333 | .Past => "past", 334 | .Future => "future", 335 | .LastSevenDays => "lastSevenDays", 336 | .CurrentMonth => "currentMonth", 337 | .LastMonth => "lastMonth", 338 | .NextMonth => "nextMonth", 339 | .CurrentQuarter => "currentQuarter", 340 | .LastQuarter => "lastQuarter", 341 | .CurrentYear => "currentYear", 342 | .LastYear => "lastYear", 343 | .CurrentFiscalYear => "currentFiscalYear", 344 | .LastFiscalYear => "lastFiscalYear", 345 | .CurrentFiscalQuarter => "currentFiscalQuarter", 346 | .LastFiscalQuarter => "lastFiscalQuarter", 347 | .LastXDays => "lastXDays", 348 | .NextXDays => "nextXDays", 349 | .OlderThanXDays => "olderThanXDays", 350 | .AfterXDays => "afterXDays", 351 | .Between => "between", 352 | .ArrayAnyOf => "arrayAnyOf", 353 | .ArrayNoneOf => "arrayNoneOf", 354 | .ArrayAllOf => "arrayAllOf", 355 | .ArrayIsEmpty => "arrayIsEmpty", 356 | .ArrayIsNotEmpty => "arrayIsNotEmpty", 357 | }; 358 | } 359 | }; 360 | 361 | pub const Where = struct { 362 | filter_type: FilterOption, 363 | filter_attribute: []const u8, 364 | filter_value: []const u8, 365 | 366 | /// Returns a string encoded `Where` to embed into a url. 367 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 368 | pub fn string( 369 | allocator: Allocator, 370 | filter_params: []const Where, 371 | ) ![]u8 { 372 | var idx: usize = 0; 373 | var list = std.ArrayList(u8).init(allocator); 374 | 375 | for (filter_params) |filter| { 376 | const params = try std.fmt.allocPrint( 377 | allocator, 378 | "&where[{d}][type]={s}&where[{d}][attribute]={s}&where[{d}][value]={s}", 379 | .{ idx, filter.filter_type.string(), idx, filter.filter_attribute, idx, filter.filter_value }, 380 | ); 381 | defer allocator.free(params); 382 | 383 | try list.appendSlice(params); 384 | idx += 1; 385 | } 386 | 387 | return try list.toOwnedSlice(); 388 | } 389 | }; 390 | 391 | pub const Parameters = struct { 392 | max_size: usize = 200, 393 | offset: usize = 0, 394 | order_asc_desc: []const u8 = undefined, 395 | order_by: []const u8 = Order.Desc.string(), 396 | total: bool = false, 397 | // TODO: select 398 | 399 | pub fn init() Parameters { 400 | return Parameters{}; 401 | } 402 | 403 | pub const Order = enum { 404 | Desc, 405 | Asc, 406 | 407 | pub fn string(self: Order) []const u8 { 408 | return switch (self) { 409 | .Desc => "desc", 410 | .Asc => "asc", 411 | }; 412 | } 413 | }; 414 | 415 | // TODO: orderBy() 416 | 417 | pub fn setMaxSize(self: *Parameters, value: usize) *Parameters { 418 | self.max_size = value; 419 | return self; 420 | } 421 | 422 | pub fn setOffset(self: *Parameters, value: usize) *Parameters { 423 | self.offset = value; 424 | return self; 425 | } 426 | 427 | pub fn setOrder(self: *Parameters, value: Order) *Parameters { 428 | self.order_asc_desc = value.string(); 429 | return self; 430 | } 431 | 432 | pub fn setTotal(self: *Parameters, value: bool) *Parameters { 433 | self.total = value; 434 | return self; 435 | } 436 | 437 | /// Returns string encoded `Parameters` to embed into a url. 438 | /// You must call `allocator.free()` of the returned object to clean up allocated resources. 439 | pub fn encode(self: Parameters, allocator: Allocator) ![]u8 { 440 | const params = try std.fmt.allocPrint( 441 | allocator, 442 | "?maxSize={d}&offset={d}&total={any}&order={s}", 443 | .{ self.max_size, self.offset, self.total, self.order_by }, 444 | ); 445 | 446 | return params; 447 | } 448 | }; 449 | --------------------------------------------------------------------------------