Thicket data repository for the EEG
1{
2 "id": "https://www.tunbury.org/2025/04/07/ocaml-claude-box",
3 "title": "Box API with OCaml and Claude",
4 "link": "https://www.tunbury.org/2025/04/07/ocaml-claude-box/",
5 "updated": "2025-04-07T00:00:00",
6 "published": "2025-04-07T00:00:00",
7 "summary": "Over the weekend, I decided to extend my Box tool to incorporate file upload. There is a straightforward POST API for this with a curl one-liner given in the Box documentation. Easy.",
8 "content": "<p>Over the weekend, I decided to extend my <a href=\"https://box.com\">Box</a> <a href=\"https://github.com/mtelvers/ocaml-box-diff\">tool</a> to incorporate file upload. There is a straightforward POST API for this with a <code>curl</code> one-liner given in the Box <a href=\"https://developer.box.com/reference/post-files-content/\">documentation</a>. Easy.</p>\n\n<p>The documentation for <a href=\"https://mirage.github.io/ocaml-cohttp/cohttp-eio/Cohttp_eio/Client/index.html\">Cohttp-eio.Client</a> only gives the function signature for <code>post</code>, but it looked pretty similar to <code>get</code>, which I had already been working with. The <a href=\"https://github.com/mirage/ocaml-cohttp\">README</a> for Cohttp gave me pause when I read this comment about multipart forms.</p>\n\n<blockquote>\n <p>Multipart form data is not supported out of the box but is provided by external libraries</p>\n</blockquote>\n\n<p>Of the three options given, the second option looked abandoned, while the third said it didn’t support streaming, so I went with the first one <a href=\"https://github.com/dinosaure/multipart_form\">dionsaure/multipart_form</a>.</p>\n\n<p>The landing page included an example encoder. A couple of external functions are mentioned, and I found example code for these in <a href=\"https://github.com/dinosaure/multipart_form/blob/main/test/test.ml\">test/test.ml</a>. This built, but didn’t work against Box. I ran <code>nc -l 127.0.0.1 6789</code> and set that as the API endpoint for both the <code>curl</code> and my application. This showed I was missing the <code>Content-Type</code> header in the part boundary. It should be <code>application/octet-stream</code>.</p>\n\n<p>There is a <code>~header</code> parameter to <code>part</code>, and I hoped for a <code>Header.add</code> like the <code>Cohttp</code>, but sadly not. See the <a href=\"https://ocaml.org/p/multipart_form/latest/doc/Multipart_form/Header/index.html\">documentation</a>. There is <code>Header.content_type</code>, but that returns the content type. How do you make it? <code>Header.of_list</code> requires a <code>Field.field list</code>.</p>\n\n<p>In a bit of frustration, I decided to ask Claude. I’ve not tried it before, but I’ve seen some impressive demonstrations. My first lesson here was to be specific. Claude is not a mind reader. After a few questions, I got to this:</p>\n\n<div><div><pre><code><span>Field</span><span>.(</span><span>make</span> <span>Content_type</span><span>.</span><span>name</span> <span>(</span><span>Content_type</span><span>.</span><span>v</span> <span>`Application</span> <span>`Octet_stream</span><span>));</span>\n</code></pre></div></div>\n\n<p>I can see why this was suggested as <code>Content_disposition.v</code> exists, but <code>Content_type.v</code> does not, nor does <code>Field.make</code>. Claude quickly obliged with a new version when I pointed this out but added the <code>Content_type</code> to the HTTP header rather than the boundary header. This went back and forth for a while, with Claude repeatedly suggesting functions which did not exist. I gave up.</p>\n\n<p>On OCaml.org, the <a href=\"https://ocaml.org/p/multipart_form/latest\">multipart-form</a> documentation includes a <em>Used by</em> section that listed <code>dream</code> as the only (external) application which used the library. From the source, I could see <code>Field.Field (field_name, Field.Content_type, v)</code>, which looked good.</p>\n\n<p>There is a function <code>Content_type.of_string</code>. I used <code>:MerlinLocate</code> to find the source, which turned out to be an Angstrom parser which returns a <code>Content_type.t</code>. This led me to <code>Content_type.make</code>, and ultimately, I was able to write these two lines:</p>\n\n<div><div><pre><code><span>let</span> <span>v</span> <span>=</span> <span>Content_type</span><span>.</span><span>make</span> <span>`Application</span> <span>(</span><span>`Iana_token</span> <span>\"octet-stream\"</span><span>)</span> <span>Content_type</span><span>.</span><span>Parameters</span><span>.</span><span>empty</span>\n<span>let</span> <span>p0</span> <span>=</span> <span>part</span> <span>~</span><span>header</span><span>:</span><span>(</span><span>Header</span><span>.</span><span>of_list</span> <span>[</span> <span>Field</span> <span>(</span><span>Field_name</span><span>.</span><span>content_type</span><span>,</span> <span>Content_type</span><span>,</span> <span>v</span><span>)</span> <span>])</span> <span>...</span>\n</code></pre></div></div>\n\n<p>As a relatively new adopter of OCaml as my language of choice, the most significant challenge I face is documentation, particularly when I find a library on opam which I want to use. I find this an interesting contrast to the others in the community, where it is often cited that tooling is the most significant barrier to adoption. In my opinion, the time taken to set up a build environment is dwarfed by the time spent in that environment iterating code.</p>\n\n<p>I would like to take this opportunity to thank all contributors to opam repository for their time and effort in making packages available. This post mentions specific packages but only to illustrate my point.</p>",
9 "content_type": "html",
10 "author": {
11 "name": "Mark Elvers",
12 "email": "mark.elvers@tunbury.org",
13 "uri": null
14 },
15 "categories": [
16 "OCaml,Box",
17 "tunbury.org"
18 ],
19 "source": "https://www.tunbury.org/atom.xml"
20}