My aggregated monorepo of OCaml code, automaintained

Update clients to use regenerated Peer_tube module

- Rename Peertube module references to Peer_tube in client library
- Refactor bushel_peertube to use generated Peer_tube types
- Remove hand-coded JSON codecs in favor of generated ones
- Add helper functions to extract values from Jsont.json for untyped fields

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+59 -73
+46 -64
ocaml-bushel/lib_sync/bushel_peertube.ml
··· 10 10 11 11 (** {1 Types} *) 12 12 13 + (** Simplified video type used in bushel - projects relevant fields from PeerTube API *) 13 14 type video = { 14 15 id : int; 15 16 uuid : string; ··· 28 29 | Skipped of string 29 30 | Error of string 30 31 31 - (** {1 Date Parsing} *) 32 + (** {1 Conversion from Generated Types} *) 32 33 33 - let parse_date str = 34 - match Ptime.of_rfc3339 str with 35 - | Ok (date, _, _) -> date 36 - | Error _ -> 37 - Log.warn (fun m -> m "Could not parse date: %s" str); 38 - Ptime.epoch 34 + module PT = Peer_tube 39 35 40 - (** {1 Jsont Codecs} *) 36 + (** Extract int from Jsont.json *) 37 + let int_of_json (json : Jsont.json) : int = 38 + match json with 39 + | Jsont.Number (f, _) -> int_of_float f 40 + | _ -> 0 41 41 42 - let ptime_jsont = 43 - Jsont.string |> Jsont.map ~dec:parse_date ~enc:(fun t -> 44 - match Ptime.to_rfc3339 ~frac_s:0 t with 45 - | s -> s) 42 + (** Extract string from Jsont.json *) 43 + let string_of_json (json : Jsont.json) : string = 44 + match json with 45 + | Jsont.String (s, _) -> s 46 + | _ -> "" 46 47 47 - (** Nullable string - handles both absent and explicit null *) 48 - let nullable_string = 49 - let null = Jsont.null None in 50 - let some = Jsont.string |> Jsont.map ~dec:(fun s -> Some s) 51 - ~enc:(function Some s -> s | None -> "") in 52 - Jsont.any ~dec_null:null ~dec_string:some 53 - ~enc:(function None -> null | Some _ -> some) () 54 - 55 - (** Nullable ptime - handles both absent and explicit null *) 56 - let nullable_ptime = 57 - let null = Jsont.null None in 58 - let some = ptime_jsont |> Jsont.map ~dec:(fun t -> Some t) 59 - ~enc:(function Some t -> t | None -> Ptime.epoch) in 60 - Jsont.any ~dec_null:null ~dec_string:some 61 - ~enc:(function None -> null | Some _ -> some) () 62 - 63 - let make_video ~id ~uuid ~name ~description ~url ~embed_path 64 - ~published_at ~originally_published_at ~thumbnail_path ~tags = 65 - { id; uuid; name; description; url; embed_path; 66 - published_at; originally_published_at; thumbnail_path; tags } 67 - 68 - let video_jsont : video Jsont.t = 69 - let open Jsont in 70 - let open Object in 71 - map ~kind:"video" (fun id uuid name description url embed_path 72 - published_at originally_published_at thumbnail_path tags -> 73 - make_video ~id ~uuid ~name ~description ~url ~embed_path 74 - ~published_at ~originally_published_at ~thumbnail_path ~tags) 75 - |> mem "id" int ~enc:(fun v -> v.id) 76 - |> mem "uuid" string ~enc:(fun v -> v.uuid) 77 - |> mem "name" string ~enc:(fun v -> v.name) 78 - |> mem "description" nullable_string ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun v -> v.description) 79 - |> mem "url" string ~enc:(fun v -> v.url) 80 - |> mem "embedPath" string ~enc:(fun v -> v.embed_path) 81 - |> mem "publishedAt" ptime_jsont ~enc:(fun v -> v.published_at) 82 - |> mem "originallyPublishedAt" nullable_ptime ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun v -> v.originally_published_at) 83 - |> mem "thumbnailPath" nullable_string ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun v -> v.thumbnail_path) 84 - |> mem "tags" (list string) ~dec_absent:[] ~enc:(fun v -> v.tags) 85 - |> finish 48 + (** Convert from generated Peertube.Video.T.t to our simplified video type *) 49 + let video_of_peertube (pt : PT.Video.T.t) : video = 50 + let id = match PT.Video.T.id pt with 51 + | Some id_json -> int_of_json id_json 52 + | None -> 0 53 + in 54 + let uuid = match PT.Video.T.uuid pt with 55 + | Some uuid_json -> string_of_json uuid_json 56 + | None -> "" 57 + in 58 + { 59 + id; 60 + uuid; 61 + name = Option.value ~default:"" (PT.Video.T.name pt); 62 + description = PT.Video.T.truncated_description pt; 63 + url = ""; (* URL is constructed from endpoint + uuid *) 64 + embed_path = Option.value ~default:"" (PT.Video.T.embed_path pt); 65 + published_at = Option.value ~default:Ptime.epoch (PT.Video.T.published_at pt); 66 + originally_published_at = PT.Video.T.originally_published_at pt; 67 + thumbnail_path = PT.Video.T.thumbnail_path pt; 68 + tags = []; (* Tags not in base Video type, would need VideoDetails *) 69 + } 86 70 87 71 type channel_response = { 88 72 total : int; 89 73 data : video list; 90 74 } 91 75 92 - let channel_response_jsont : channel_response Jsont.t = 93 - let open Jsont in 94 - let open Object in 95 - map ~kind:"channel_response" (fun total data -> { total; data }) 96 - |> mem "total" int ~enc:(fun r -> r.total) 97 - |> mem "data" (list video_jsont) ~enc:(fun r -> r.data) 98 - |> finish 99 - 100 - (** {1 JSON decoding helpers} *) 76 + (** {1 JSON decoding using generated library} *) 101 77 102 78 let decode_video json_str = 103 - match Jsont_bytesrw.decode_string video_jsont json_str with 104 - | Ok v -> Result.Ok v 79 + match Jsont_bytesrw.decode_string PT.Video.T.jsont json_str with 80 + | Ok pt -> Result.Ok (video_of_peertube pt) 105 81 | Error e -> Result.Error e 106 82 107 83 let decode_channel_response json_str = 108 - match Jsont_bytesrw.decode_string channel_response_jsont json_str with 109 - | Ok r -> Result.Ok r 84 + match Jsont_bytesrw.decode_string PT.VideoList.Response.jsont json_str with 85 + | Ok r -> 86 + let total = Option.value ~default:0 (PT.VideoList.Response.total r) in 87 + let data = match PT.VideoList.Response.data r with 88 + | Some videos -> List.map video_of_peertube videos 89 + | None -> [] 90 + in 91 + Result.Ok { total; data } 110 92 | Error e -> Result.Error e 111 93 112 94 (** {1 URL Parsing} *)
+2 -1
ocaml-bushel/lib_sync/dune
··· 17 17 sortal.schema 18 18 sortal 19 19 srcsetter-cmd 20 - requests)) 20 + requests 21 + peertube))
+1
ocaml-immich/bin/cmd_albums.ml
··· 51 51 url; 52 52 status = Requests.Response.status_code response; 53 53 body = Requests.Response.text response; 54 + parsed_body = None; 54 55 }) 55 56 end 56 57 ) env
+2
ocaml-immich/bin/cmd_faces.ml
··· 59 59 url; 60 60 status = Requests.Response.status_code response; 61 61 body = Requests.Response.text response; 62 + parsed_body = None; 62 63 }) 63 64 end 64 65 ) env ··· 118 119 url; 119 120 status = Requests.Response.status_code response; 120 121 body = Requests.Response.text response; 122 + parsed_body = None; 121 123 }) 122 124 end 123 125 ) env
+7 -7
ocaml-peertube/lib/client.ml
··· 4 4 ---------------------------------------------------------------------------*) 5 5 6 6 type t = { 7 - client : Peertube.t; 7 + client : Peer_tube.t; 8 8 session : Session.t; 9 9 fs : Eio.Fs.dir_ty Eio.Path.t; 10 10 profile : string option; ··· 23 23 | Session.OAuth { access_token; _ } -> 24 24 Requests.set_auth requests_session (Requests.Auth.bearer ~token:access_token) 25 25 in 26 - let client = Peertube.create ~session:requests_session ~sw env ~base_url:server_url in 26 + let client = Peer_tube.create ~session:requests_session ~sw env ~base_url:server_url in 27 27 { client; session; fs; profile } 28 28 29 29 (* OAuth token response codec *) ··· 43 43 | Some config -> Requests.Cmd.create config env sw 44 44 | None -> Requests.create ~sw env 45 45 in 46 - let client = Peertube.create ~session:requests_session ~sw env ~base_url:server_url in 46 + let client = Peer_tube.create ~session:requests_session ~sw env ~base_url:server_url in 47 47 48 48 (* Step 1: Get OAuth client credentials *) 49 - let oauth_client = Peertube.OauthClient.get_oauth_client client () in 50 - let client_id = Option.get (Peertube.OauthClient.T.client_id oauth_client) in 51 - let client_secret = Option.get (Peertube.OauthClient.T.client_secret oauth_client) in 49 + let oauth_client = Peer_tube.OauthClient.get_oauth_client client () in 50 + let client_id = Option.get (Peer_tube.OauthClient.T.client_id oauth_client) in 51 + let client_secret = Option.get (Peer_tube.OauthClient.T.client_secret oauth_client) in 52 52 53 53 (* Step 2: Get OAuth token using password grant *) 54 54 let token_url = server_url ^ "/api/v1/users/token" in ··· 72 72 | None -> Requests.create ~sw env 73 73 in 74 74 let requests_session = Requests.set_auth requests_session (Requests.Auth.bearer ~token:access_token) in 75 - let client = Peertube.create ~session:requests_session ~sw env ~base_url:server_url in 75 + let client = Peer_tube.create ~session:requests_session ~sw env ~base_url:server_url in 76 76 77 77 (* Create and save session *) 78 78 let auth = Session.OAuth { access_token; refresh_token; client_id; client_secret } in
+1 -1
ocaml-peertube/lib/client.mli
··· 48 48 (** {1 Accessors} *) 49 49 50 50 (** [client t] returns the underlying PeerTube API client. *) 51 - val client : t -> Peertube.t 51 + val client : t -> Peer_tube.t 52 52 53 53 (** [session t] returns the session. *) 54 54 val session : t -> Session.t