···5454 | Error e -> failwith ("Failed to re-encode json: " ^ Jsont.Error.to_string e)
5555 in
5656 from_json_string codec json_str
5757+5858+(** Convert a value to Jsont.json using a codec *)
5959+let to_json : 'a Jsont.t -> 'a -> (Jsont.json, string) result = fun codec value ->
6060+ let json_str = to_json_string codec value in
6161+ match Jsont_bytesrw.decode_string' Jsont.json json_str with
6262+ | Ok json -> Ok json
6363+ | Error e -> Error (Jsont.Error.to_string e)
+3
lib/zulip/encode.mli
···19192020(** Parse a Jsont.json value using a codec *)
2121val from_json : 'a Jsont.t -> Jsont.json -> ('a, string) result
2222+2323+(** Convert a value to Jsont.json using a codec *)
2424+val to_json : 'a Jsont.t -> 'a -> (Jsont.json, string) result
+175
lib/zulip/presence.ml
···11+type status = Active | Idle | Offline
22+33+type client_presence = {
44+ status : status;
55+ timestamp : float;
66+ client : string;
77+ pushable : bool;
88+}
99+1010+type user_presence = {
1111+ aggregated : client_presence option;
1212+ clients : (string * client_presence) list;
1313+}
1414+1515+let status_to_string = function
1616+ | Active -> "active"
1717+ | Idle -> "idle"
1818+ | Offline -> "offline"
1919+2020+let status_of_string = function
2121+ | "active" -> Some Active
2222+ | "idle" -> Some Idle
2323+ | "offline" -> Some Offline
2424+ | _ -> None
2525+2626+let pp_status fmt s = Format.fprintf fmt "%s" (status_to_string s)
2727+2828+let pp_user_presence fmt p =
2929+ let agg =
3030+ Option.fold ~none:"none"
3131+ ~some:(fun c -> Printf.sprintf "%s" (status_to_string c.status))
3232+ p.aggregated
3333+ in
3434+ Format.fprintf fmt "UserPresence{aggregated=%s, clients=%d}" agg
3535+ (List.length p.clients)
3636+3737+let status_jsont =
3838+ let of_string s =
3939+ match status_of_string s with
4040+ | Some s -> Ok s
4141+ | None -> Error (Printf.sprintf "Unknown status: %s" s)
4242+ in
4343+ Jsont.of_of_string ~kind:"Presence.status" of_string ~enc:status_to_string
4444+4545+let client_presence_jsont =
4646+ Jsont.Object.(
4747+ map ~kind:"ClientPresence"
4848+ (fun status timestamp client pushable ->
4949+ { status; timestamp; client; pushable })
5050+ |> mem "status" status_jsont ~enc:(fun p -> p.status)
5151+ |> mem "timestamp" Jsont.number ~enc:(fun p -> p.timestamp)
5252+ |> mem "client" Jsont.string ~dec_absent:"" ~enc:(fun p -> p.client)
5353+ |> mem "pushable" Jsont.bool ~dec_absent:false ~enc:(fun p -> p.pushable)
5454+ |> finish)
5555+5656+let parse_user_presence_from_json json =
5757+ match json with
5858+ | Jsont.Object (fields, _) ->
5959+ let assoc = List.map (fun ((k, _), v) -> (k, v)) fields in
6060+ let aggregated =
6161+ match List.assoc_opt "aggregated" assoc with
6262+ | Some agg_json -> Encode.from_json client_presence_jsont agg_json |> Result.to_option
6363+ | None -> None
6464+ in
6565+ let clients =
6666+ List.filter_map
6767+ (fun (name, json) ->
6868+ if name = "aggregated" then None
6969+ else
7070+ match Encode.from_json client_presence_jsont json with
7171+ | Ok cp -> Some (name, cp)
7272+ | Error _ -> None)
7373+ assoc
7474+ in
7575+ { aggregated; clients }
7676+ | _ -> { aggregated = None; clients = [] }
7777+7878+let user_presence_jsont =
7979+ Jsont.map ~kind:"UserPresence" Jsont.json
8080+ ~dec:parse_user_presence_from_json
8181+ ~enc:(fun p ->
8282+ let agg_field =
8383+ match p.aggregated with
8484+ | Some agg -> (
8585+ match Encode.to_json client_presence_jsont agg with
8686+ | Ok json -> [ (("aggregated", Jsont.Meta.none), json) ]
8787+ | Error _ -> [])
8888+ | None -> []
8989+ in
9090+ let client_fields =
9191+ List.filter_map
9292+ (fun (name, cp) ->
9393+ match Encode.to_json client_presence_jsont cp with
9494+ | Ok json -> Some ((name, Jsont.Meta.none), json)
9595+ | Error _ -> None)
9696+ p.clients
9797+ in
9898+ Jsont.Object (agg_field @ client_fields, Jsont.Meta.none))
9999+100100+let parse_presence_response json =
101101+ match json with
102102+ | Jsont.Object (fields, _) -> (
103103+ let assoc = List.map (fun ((k, _), v) -> (k, v)) fields in
104104+ match List.assoc_opt "presence" assoc with
105105+ | Some pres -> (
106106+ match Encode.from_json user_presence_jsont pres with
107107+ | Ok p -> p
108108+ | Error msg ->
109109+ Error.raise_with_context
110110+ (Error.make ~code:(Other "json_parse") ~message:msg ())
111111+ "parsing presence")
112112+ | None ->
113113+ Error.raise_with_context
114114+ (Error.make ~code:(Other "missing_field")
115115+ ~message:"Missing presence field" ())
116116+ "parsing presence response")
117117+ | _ ->
118118+ Error.raise_with_context
119119+ (Error.make ~code:(Other "json_parse")
120120+ ~message:"Expected JSON object for presence" ())
121121+ "parsing presence response"
122122+123123+let get_user client ~user_id =
124124+ let json =
125125+ Client.request client ~method_:`GET
126126+ ~path:("/api/v1/users/" ^ string_of_int user_id ^ "/presence")
127127+ ()
128128+ in
129129+ parse_presence_response json
130130+131131+let get_user_by_email client ~email =
132132+ let json =
133133+ Client.request client ~method_:`GET
134134+ ~path:("/api/v1/users/" ^ email ^ "/presence")
135135+ ()
136136+ in
137137+ parse_presence_response json
138138+139139+let get_all client =
140140+ let json =
141141+ Client.request client ~method_:`GET ~path:"/api/v1/realm/presence" ()
142142+ in
143143+ match json with
144144+ | Jsont.Object (fields, _) -> (
145145+ let assoc = List.map (fun ((k, _), v) -> (k, v)) fields in
146146+ match List.assoc_opt "presences" assoc with
147147+ | Some (Jsont.Object (pres_fields, _)) ->
148148+ List.filter_map
149149+ (fun ((user_id_str, _), pres_json) ->
150150+ match int_of_string_opt user_id_str with
151151+ | Some user_id -> (
152152+ match Encode.from_json user_presence_jsont pres_json with
153153+ | Ok p -> Some (user_id, p)
154154+ | Error _ -> None)
155155+ | None -> None)
156156+ pres_fields
157157+ | _ -> [])
158158+ | _ -> []
159159+160160+let update client ~status ?ping_only ?new_user_input () =
161161+ let params =
162162+ [ ("status", status_to_string status) ]
163163+ @ List.filter_map Fun.id
164164+ [
165165+ Option.map (fun v -> ("ping_only", string_of_bool v)) ping_only;
166166+ Option.map
167167+ (fun v -> ("new_user_input", string_of_bool v))
168168+ new_user_input;
169169+ ]
170170+ in
171171+ let _response =
172172+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/presence"
173173+ ~params ()
174174+ in
175175+ ()
+406
lib/zulip/server.ml
···11+type authentication_method = {
22+ password : bool;
33+ dev : bool;
44+ email : bool;
55+ ldap : bool;
66+ remoteuser : bool;
77+ github : bool;
88+ azuread : bool;
99+ gitlab : bool;
1010+ apple : bool;
1111+ google : bool;
1212+ saml : bool;
1313+ openid_connect : bool;
1414+}
1515+1616+type emoji = {
1717+ id : string;
1818+ name : string;
1919+ source_url : string;
2020+ deactivated : bool;
2121+ author_id : int option;
2222+}
2323+2424+(* Define types with conflicting fields after types they might conflict with *)
2525+type external_authentication_method = {
2626+ name : string;
2727+ display_name : string;
2828+ display_icon : string option;
2929+ login_url : string;
3030+ signup_url : string;
3131+}
3232+3333+type linkifier = { id : int; pattern : string; url_template : string }
3434+3535+type t = {
3636+ zulip_version : string;
3737+ zulip_feature_level : int;
3838+ zulip_merge_base : string option;
3939+ push_notifications_enabled : bool;
4040+ is_incompatible : bool;
4141+ email_auth_enabled : bool;
4242+ require_email_format_usernames : bool;
4343+ realm_uri : string;
4444+ realm_name : string;
4545+ realm_icon : string;
4646+ realm_description : string;
4747+ realm_web_public_access_enabled : bool;
4848+ authentication_methods : authentication_method;
4949+ external_authentication_methods : external_authentication_method list;
5050+}
5151+5252+(* Codecs for authentication methods *)
5353+let authentication_method_jsont =
5454+ Jsont.Object.(
5555+ map ~kind:"AuthenticationMethod"
5656+ (fun password dev email ldap remoteuser github azuread gitlab apple
5757+ google saml openid_connect ->
5858+ {
5959+ password;
6060+ dev;
6161+ email;
6262+ ldap;
6363+ remoteuser;
6464+ github;
6565+ azuread;
6666+ gitlab;
6767+ apple;
6868+ google;
6969+ saml;
7070+ openid_connect;
7171+ })
7272+ |> mem "Password" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.password)
7373+ |> mem "Dev" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.dev)
7474+ |> mem "Email" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.email)
7575+ |> mem "LDAP" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.ldap)
7676+ |> mem "RemoteUser" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.remoteuser)
7777+ |> mem "GitHub" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.github)
7878+ |> mem "AzureAD" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.azuread)
7979+ |> mem "GitLab" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.gitlab)
8080+ |> mem "Apple" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.apple)
8181+ |> mem "Google" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.google)
8282+ |> mem "SAML" Jsont.bool ~dec_absent:false ~enc:(fun a -> a.saml)
8383+ |> mem "OpenID Connect" Jsont.bool ~dec_absent:false ~enc:(fun a ->
8484+ a.openid_connect)
8585+ |> finish)
8686+8787+let external_authentication_method_jsont =
8888+ Jsont.Object.(
8989+ map ~kind:"ExternalAuthenticationMethod"
9090+ (fun name display_name display_icon login_url signup_url ->
9191+ { name; display_name; display_icon; login_url; signup_url })
9292+ |> mem "name" Jsont.string ~enc:(fun e -> e.name)
9393+ |> mem "display_name" Jsont.string ~enc:(fun e -> e.display_name)
9494+ |> opt_mem "display_icon" Jsont.string ~enc:(fun e -> e.display_icon)
9595+ |> mem "login_url" Jsont.string ~enc:(fun e -> e.login_url)
9696+ |> mem "signup_url" Jsont.string ~enc:(fun e -> e.signup_url)
9797+ |> finish)
9898+9999+let jsont =
100100+ Jsont.Object.(
101101+ map ~kind:"ServerSettings"
102102+ (fun zulip_version zulip_feature_level zulip_merge_base
103103+ push_notifications_enabled is_incompatible email_auth_enabled
104104+ require_email_format_usernames realm_uri realm_name realm_icon
105105+ realm_description realm_web_public_access_enabled
106106+ authentication_methods external_authentication_methods ->
107107+ {
108108+ zulip_version;
109109+ zulip_feature_level;
110110+ zulip_merge_base;
111111+ push_notifications_enabled;
112112+ is_incompatible;
113113+ email_auth_enabled;
114114+ require_email_format_usernames;
115115+ realm_uri;
116116+ realm_name;
117117+ realm_icon;
118118+ realm_description;
119119+ realm_web_public_access_enabled;
120120+ authentication_methods;
121121+ external_authentication_methods;
122122+ })
123123+ |> mem "zulip_version" Jsont.string ~enc:(fun s -> s.zulip_version)
124124+ |> mem "zulip_feature_level" Jsont.int ~enc:(fun s -> s.zulip_feature_level)
125125+ |> opt_mem "zulip_merge_base" Jsont.string ~enc:(fun s -> s.zulip_merge_base)
126126+ |> mem "push_notifications_enabled" Jsont.bool ~dec_absent:false
127127+ ~enc:(fun s -> s.push_notifications_enabled)
128128+ |> mem "is_incompatible" Jsont.bool ~dec_absent:false ~enc:(fun s ->
129129+ s.is_incompatible)
130130+ |> mem "email_auth_enabled" Jsont.bool ~dec_absent:true ~enc:(fun s ->
131131+ s.email_auth_enabled)
132132+ |> mem "require_email_format_usernames" Jsont.bool ~dec_absent:true
133133+ ~enc:(fun s -> s.require_email_format_usernames)
134134+ |> mem "realm_uri" Jsont.string ~enc:(fun s -> s.realm_uri)
135135+ |> mem "realm_name" Jsont.string ~dec_absent:"" ~enc:(fun s -> s.realm_name)
136136+ |> mem "realm_icon" Jsont.string ~dec_absent:"" ~enc:(fun s -> s.realm_icon)
137137+ |> mem "realm_description" Jsont.string ~dec_absent:"" ~enc:(fun s ->
138138+ s.realm_description)
139139+ |> mem "realm_web_public_access_enabled" Jsont.bool ~dec_absent:false
140140+ ~enc:(fun s -> s.realm_web_public_access_enabled)
141141+ |> mem "authentication_methods" authentication_method_jsont ~enc:(fun s ->
142142+ s.authentication_methods)
143143+ |> mem "external_authentication_methods"
144144+ (Jsont.list external_authentication_method_jsont)
145145+ ~dec_absent:[]
146146+ ~enc:(fun s -> s.external_authentication_methods)
147147+ |> finish)
148148+149149+let get_settings_json client =
150150+ Client.request client ~method_:`GET ~path:"/api/v1/server_settings" ()
151151+152152+let get_settings client =
153153+ let json = get_settings_json client in
154154+ Error.decode_or_raise jsont json "parsing server settings"
155155+156156+let feature_level client =
157157+ let settings = get_settings client in
158158+ settings.zulip_feature_level
159159+160160+let supports_feature client ~level = feature_level client >= level
161161+162162+(* Linkifier codec *)
163163+let linkifier_jsont =
164164+ Jsont.Object.(
165165+ map ~kind:"Linkifier" (fun id pattern url_template ->
166166+ { id; pattern; url_template })
167167+ |> mem "id" Jsont.int ~enc:(fun l -> l.id)
168168+ |> mem "pattern" Jsont.string ~enc:(fun l -> l.pattern)
169169+ |> mem "url_template" Jsont.string ~enc:(fun l -> l.url_template)
170170+ |> finish)
171171+172172+let get_linkifiers client =
173173+ let response_codec =
174174+ Jsont.Object.(
175175+ map ~kind:"LinkifiersResponse" Fun.id
176176+ |> mem "linkifiers" (Jsont.list linkifier_jsont) ~enc:Fun.id
177177+ |> finish)
178178+ in
179179+ let json =
180180+ Client.request client ~method_:`GET ~path:"/api/v1/realm/linkifiers" ()
181181+ in
182182+ Error.decode_or_raise response_codec json "getting linkifiers"
183183+184184+let add_linkifier client ~pattern ~url_template =
185185+ let params = [ ("pattern", pattern); ("url_template", url_template) ] in
186186+ let response_codec =
187187+ Jsont.Object.(
188188+ map ~kind:"AddLinkifierResponse" Fun.id
189189+ |> mem "id" Jsont.int ~enc:Fun.id
190190+ |> finish)
191191+ in
192192+ let json =
193193+ Client.request client ~method_:`POST ~path:"/api/v1/realm/filters" ~params
194194+ ()
195195+ in
196196+ Error.decode_or_raise response_codec json "adding linkifier"
197197+198198+let update_linkifier client ~filter_id ~pattern ~url_template =
199199+ let params = [ ("pattern", pattern); ("url_template", url_template) ] in
200200+ let _response =
201201+ Client.request client ~method_:`PATCH
202202+ ~path:("/api/v1/realm/filters/" ^ string_of_int filter_id)
203203+ ~params ()
204204+ in
205205+ ()
206206+207207+let delete_linkifier client ~filter_id =
208208+ let _response =
209209+ Client.request client ~method_:`DELETE
210210+ ~path:("/api/v1/realm/filters/" ^ string_of_int filter_id)
211211+ ()
212212+ in
213213+ ()
214214+215215+(* Emoji codec *)
216216+let emoji_jsont : emoji Jsont.t =
217217+ Jsont.Object.(
218218+ map ~kind:"Emoji" (fun id name source_url deactivated author_id : emoji ->
219219+ { id; name; source_url; deactivated; author_id })
220220+ |> mem "id" Jsont.string ~enc:(fun (e : emoji) -> e.id)
221221+ |> mem "name" Jsont.string ~enc:(fun (e : emoji) -> e.name)
222222+ |> mem "source_url" Jsont.string ~enc:(fun (e : emoji) -> e.source_url)
223223+ |> mem "deactivated" Jsont.bool ~dec_absent:false ~enc:(fun (e : emoji) ->
224224+ e.deactivated)
225225+ |> opt_mem "author_id" Jsont.int ~enc:(fun (e : emoji) -> e.author_id)
226226+ |> finish)
227227+228228+let get_emoji client =
229229+ let json =
230230+ Client.request client ~method_:`GET ~path:"/api/v1/realm/emoji" ()
231231+ in
232232+ match json with
233233+ | Jsont.Object (fields, _) -> (
234234+ let assoc = List.map (fun ((k, _), v) -> (k, v)) fields in
235235+ match List.assoc_opt "emoji" assoc with
236236+ | Some (Jsont.Object (emoji_fields, _)) ->
237237+ List.filter_map
238238+ (fun ((name, _), emoji_json) ->
239239+ (* Add name to the emoji JSON before parsing *)
240240+ let emoji_with_name =
241241+ match emoji_json with
242242+ | Jsont.Object (e_fields, meta) ->
243243+ let name_field = (("name", Jsont.Meta.none), Jsont.String (name, Jsont.Meta.none)) in
244244+ Jsont.Object (name_field :: e_fields, meta)
245245+ | _ -> emoji_json
246246+ in
247247+ match Encode.from_json emoji_jsont emoji_with_name with
248248+ | Ok e -> Some e
249249+ | Error _ -> None)
250250+ emoji_fields
251251+ | _ -> [])
252252+ | _ -> []
253253+254254+let upload_emoji _client ~name:_ ~filename:_ =
255255+ (* TODO: Implement file upload using multipart/form-data *)
256256+ Error.raise
257257+ (Error.make ~code:(Other "not_implemented")
258258+ ~message:"Emoji upload not yet implemented" ())
259259+260260+let deactivate_emoji client ~name =
261261+ let _response =
262262+ Client.request client ~method_:`DELETE
263263+ ~path:("/api/v1/realm/emoji/" ^ name)
264264+ ()
265265+ in
266266+ ()
267267+268268+type profile_field_type =
269269+ | Short_text
270270+ | Long_text
271271+ | Choice
272272+ | Date
273273+ | Link
274274+ | User
275275+ | External_account
276276+ | Pronouns
277277+278278+type profile_field = {
279279+ id : int;
280280+ field_type : profile_field_type;
281281+ order : int;
282282+ name : string;
283283+ hint : string;
284284+ field_data : Jsont.json;
285285+ display_in_profile_summary : bool option;
286286+}
287287+288288+(* Profile field type codec *)
289289+let profile_field_type_to_int = function
290290+ | Short_text -> 1
291291+ | Long_text -> 2
292292+ | Choice -> 3
293293+ | Date -> 4
294294+ | Link -> 5
295295+ | User -> 6
296296+ | External_account -> 7
297297+ | Pronouns -> 8
298298+299299+let profile_field_type_of_int = function
300300+ | 1 -> Some Short_text
301301+ | 2 -> Some Long_text
302302+ | 3 -> Some Choice
303303+ | 4 -> Some Date
304304+ | 5 -> Some Link
305305+ | 6 -> Some User
306306+ | 7 -> Some External_account
307307+ | 8 -> Some Pronouns
308308+ | _ -> None
309309+310310+let profile_field_type_jsont =
311311+ Jsont.map ~kind:"ProfileFieldType" Jsont.int
312312+ ~dec:(fun i ->
313313+ match profile_field_type_of_int i with
314314+ | Some t -> t
315315+ | None -> Short_text)
316316+ ~enc:profile_field_type_to_int
317317+318318+let profile_field_jsont =
319319+ Jsont.Object.(
320320+ map ~kind:"ProfileField"
321321+ (fun id field_type order name hint field_data display_in_profile_summary ->
322322+ { id; field_type; order; name; hint; field_data; display_in_profile_summary })
323323+ |> mem "id" Jsont.int ~enc:(fun p -> p.id)
324324+ |> mem "type" profile_field_type_jsont ~enc:(fun p -> p.field_type)
325325+ |> mem "order" Jsont.int ~enc:(fun p -> p.order)
326326+ |> mem "name" Jsont.string ~enc:(fun p -> p.name)
327327+ |> mem "hint" Jsont.string ~dec_absent:"" ~enc:(fun p -> p.hint)
328328+ |> mem "field_data" Jsont.json ~dec_absent:(Jsont.Null ((), Jsont.Meta.none)) ~enc:(fun p ->
329329+ p.field_data)
330330+ |> opt_mem "display_in_profile_summary" Jsont.bool ~enc:(fun p ->
331331+ p.display_in_profile_summary)
332332+ |> finish)
333333+334334+let get_profile_fields client =
335335+ let response_codec =
336336+ Jsont.Object.(
337337+ map ~kind:"ProfileFieldsResponse" Fun.id
338338+ |> mem "custom_profile_fields" (Jsont.list profile_field_jsont) ~enc:Fun.id
339339+ |> finish)
340340+ in
341341+ let json =
342342+ Client.request client ~method_:`GET ~path:"/api/v1/realm/profile_fields" ()
343343+ in
344344+ Error.decode_or_raise response_codec json "getting profile fields"
345345+346346+let create_profile_field client ~field_type ~name ?hint ?field_data () =
347347+ let params =
348348+ [
349349+ ("field_type", string_of_int (profile_field_type_to_int field_type));
350350+ ("name", name);
351351+ ]
352352+ @ List.filter_map Fun.id
353353+ [
354354+ Option.map (fun h -> ("hint", h)) hint;
355355+ Option.map
356356+ (fun d -> ("field_data", Encode.to_json_string Jsont.json d))
357357+ field_data;
358358+ ]
359359+ in
360360+ let response_codec =
361361+ Jsont.Object.(
362362+ map ~kind:"CreateProfileFieldResponse" Fun.id
363363+ |> mem "id" Jsont.int ~enc:Fun.id
364364+ |> finish)
365365+ in
366366+ let json =
367367+ Client.request client ~method_:`POST ~path:"/api/v1/realm/profile_fields"
368368+ ~params ()
369369+ in
370370+ Error.decode_or_raise response_codec json "creating profile field"
371371+372372+let update_profile_field client ~field_id ?name ?hint ?field_data () =
373373+ let params =
374374+ List.filter_map Fun.id
375375+ [
376376+ Option.map (fun n -> ("name", n)) name;
377377+ Option.map (fun h -> ("hint", h)) hint;
378378+ Option.map
379379+ (fun d -> ("field_data", Encode.to_json_string Jsont.json d))
380380+ field_data;
381381+ ]
382382+ in
383383+ let _response =
384384+ Client.request client ~method_:`PATCH
385385+ ~path:("/api/v1/realm/profile_fields/" ^ string_of_int field_id)
386386+ ~params ()
387387+ in
388388+ ()
389389+390390+let delete_profile_field client ~field_id =
391391+ let _response =
392392+ Client.request client ~method_:`DELETE
393393+ ~path:("/api/v1/realm/profile_fields/" ^ string_of_int field_id)
394394+ ()
395395+ in
396396+ ()
397397+398398+let reorder_profile_fields client ~order =
399399+ let params =
400400+ [ ("order", Encode.to_json_string (Jsont.list Jsont.int) order) ]
401401+ in
402402+ let _response =
403403+ Client.request client ~method_:`PATCH ~path:"/api/v1/realm/profile_fields"
404404+ ~params ()
405405+ in
406406+ ()