summaryrefslogtreecommitdiff
path: root/src/tg/method-macros.lisp
blob: 9ab9e89ffca926a889c950d48efc8a8ab88eb645 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
;; SPDX-License-Identifier: EUPL-1.2
;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
(defpackage :ukkoclot/src/tg/method-macros
  (:documentation "Macros for easy defining TG methods.")
  (:use :c2cl :iterate)
  (:import-from :alexandria :make-keyword :with-gensyms)
  (:import-from :com.dieggsy.f-string :enable-f-strings)
  (:import-from :serapeum :-> :take)
  (:import-from :state)
  (:import-from :str)
  (:import-from :ukkoclot/src/transport :do-call :http-method)
  (:export :define-tg-method))
(in-package :ukkoclot/src/tg/method-macros)

(eval-when (:compile-toplevel :load-toplevel :execute)
  (enable-f-strings))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defstruct (param (:constructor make-param%)) name type default skip-if-default)

  (defparameter +unique+ (gensym))

  ;; TODO: Fix optional-and-key !
  (-> make-param (symbol t &optional t &key (:skip-if-default boolean)) param)
  (defun make-param (name type          ; lint:suppress avoid-optional-and-key
                     &optional (default +unique+)
                     &key (skip-if-default (not (eq default +unique+))))
    (let ((default (if (eq default +unique+)
                       `(error ,#f"No value given for {name}")
                       default)))
      (make-param% :name name
                   :type type
                   :default default
                   :skip-if-default skip-if-default)))

  ;; TODO: list-of-params, list-of-param-specs
  (-> parse-param-specs (list) list)
  (defun parse-param-specs (param-specs)
    (iter (for param-spec in param-specs)
          (collect (apply #'make-param param-spec))))

  (-> path-from-name (symbol) string)
  (defun path-from-name (name)
    (let ((str (str:camel-case name)))
      (if (str:ends-with-p "%" str :ignore-case nil)
          (take (- (length str) 1) str)
          str)))

  (-> emit-append-to-args (param symbol) list)
  (defun emit-append-to-args (param args)
    `(setf ,args (acons ',(param-name param) ,(param-name param) ,args)))

  (-> emit-arg-type (param) list)
  (defun emit-arg-type (param)
    `(,(make-keyword (param-name param))
      ,(param-type param)))

  (-> emit-defun-arg (param) list)
  (defun emit-defun-arg (param)
    `(,(param-name param) ,(param-default param)))

  ;; TODO: list-of-params
  (-> emit-defun (symbol t list http-method) list)
  (defun emit-defun (name return-type params method)
    (with-gensyms (args)
      `(defun ,name (&key ,@(iter (for param in params) ; lint:suppress lambda-list-invalid
                                  (collect (emit-defun-arg param))))
         (let (,args)
           ,@(iter (for param in (reverse params))
                   (collect (if (param-skip-if-default param)
                                `(unless (equal ,(param-name param)
                                                ,(param-default param))
                                   ,(emit-append-to-args param args))
                                (emit-append-to-args param args))))
           (do-call ,method ,(path-from-name name) ',return-type ,args)))))

  ;; TODO: list-of-params
  (-> emit-ftype (symbol t list) list)
  (defun emit-ftype (name return-type params)
    `(-> ,name
         (&key ,@(iter (for param in params)
                       (collect (emit-arg-type param))))
         ,return-type)))

(defmacro define-tg-method ((name type &optional (method :POST))
                            &body param-specs)
  (let ((params (parse-param-specs param-specs)))
    `(progn
       ,(emit-ftype name type params)
       ,(emit-defun name type params method))))