multipart.scm 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. ;;; guile-openai --- An OpenAI API client for Guile
  2. ;;; Copyright © 2023 Andrew Whatson <whatson@tailcall.au>
  3. ;;;
  4. ;;; This file is part of guile-openai.
  5. ;;;
  6. ;;; guile-openai is free software: you can redistribute it and/or modify
  7. ;;; it under the terms of the GNU Affero General Public License as
  8. ;;; published by the Free Software Foundation, either version 3 of the
  9. ;;; License, or (at your option) any later version.
  10. ;;;
  11. ;;; guile-openai is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. ;;; Affero General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU Affero General Public
  17. ;;; License along with guile-openai. If not, see
  18. ;;; <https://www.gnu.org/licenses/>.
  19. (define-module (openai utils multipart)
  20. #:use-module (ice-9 binary-ports)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 textual-ports)
  23. #:use-module (rnrs bytevectors)
  24. #:use-module (web http)
  25. #:export (multipart-param
  26. multipart-file
  27. multipart-param->tree
  28. multipart-params->tree
  29. multipart-tree-fold
  30. multipart-tree-for-each
  31. multipart-tree-visit
  32. write-multipart-tree
  33. multipart-tree->bytevector))
  34. (define (multipart-piece type key val)
  35. (if (not (unspecified? val))
  36. `((,type ,key ,val))
  37. '()))
  38. (define (multipart-param key val)
  39. (multipart-piece 'param key val))
  40. (define (multipart-file key val)
  41. (multipart-piece 'file key val))
  42. (define (multipart-param->tree param boundary)
  43. "Transform PARAM, a tagged list describing a single form parameter, into
  44. a tree of strings and bytevectors ready for serialization as a
  45. multipart/form-data payload, using BOUNDARY as the part separator."
  46. (match param
  47. (('param name value)
  48. ;; render a simple form parameter
  49. (list
  50. "--" boundary "\r\n"
  51. (call-with-output-string
  52. (lambda (port)
  53. (write-headers `((content-disposition form-data
  54. (name . ,(symbol->string name))))
  55. port)))
  56. "\r\n"
  57. (call-with-output-string
  58. (lambda (port)
  59. (display value port)))
  60. "\r\n"))
  61. (('file name filename type)
  62. ;; render a file form parameter
  63. (list
  64. "--" boundary "\r\n"
  65. (call-with-output-string
  66. (lambda (port)
  67. (write-headers `((content-disposition form-data
  68. (name . ,(symbol->string name))
  69. (filename . ,(basename filename)))
  70. (content-type . ,type))
  71. port)))
  72. "\r\n"
  73. (call-with-input-file filename
  74. (lambda (port)
  75. (get-bytevector-all port)))
  76. "\r\n"))
  77. (('file name filename)
  78. ;; provide a default content-type for files
  79. ;; TODO try to identify the correct mimetype
  80. (let ((type '(application/octet-stream)))
  81. (multipart-param->tree (list 'file name filename type) boundary)))))
  82. (define (multipart-params->tree params boundary)
  83. "Transform PARAMS, a list of form parameters in tagged list format, into
  84. a tree of strings and bytevectors ready for serialization as a
  85. multipart/form-data payload, using BOUNDARY as the part separator."
  86. (list
  87. (map (lambda (param)
  88. (multipart-param->tree param boundary))
  89. params)
  90. "--" boundary "--"))
  91. (define (multipart-tree-fold proc init tree)
  92. "Fold over the leaves of TREE in depth-first order, calling (PROC LEAF
  93. INIT) on the first leaf, and (PROC LEAF RESULT) on subsequent leaves,
  94. where RESULT is the return value of the previous call to PROC. Returns
  95. the final RESULT."
  96. (let loop ((tree tree) (result init))
  97. (cond ((null? tree)
  98. result)
  99. ((not (pair? tree))
  100. (proc tree result))
  101. (else
  102. (loop (cdr tree)
  103. (loop (car tree)
  104. result))))))
  105. (define (multipart-tree-for-each proc tree)
  106. "Call (PROC LEAF) on each leaf of TREE in depth-first order."
  107. (multipart-tree-fold (lambda (elem _)
  108. (proc elem)
  109. _)
  110. *unspecified* tree))
  111. (define (multipart-tree-visit on-string on-bytevector tree)
  112. "Traverse TREE in depth-first order, calling (ON-STRING LEAF) on string
  113. leaves, and (ON-BYTEVECTOR LEAF) on bytevector leaves."
  114. (multipart-tree-for-each (match-lambda
  115. ((? string? str)
  116. (on-string str))
  117. ((? bytevector? bv)
  118. (on-bytevector bv)))
  119. tree))
  120. (define (write-multipart-tree tree port)
  121. "Serialize TREE into PORT."
  122. (multipart-tree-visit
  123. (lambda (str) (put-string port str))
  124. (lambda (bv) (put-bytevector port bv))
  125. tree))
  126. (define (multipart-tree->bytevector tree)
  127. "Serialize TREE into a bytevector."
  128. (call-with-output-bytevector
  129. (lambda (port)
  130. (write-multipart-tree tree port))))