Skip to content

Automatically push context parameters into endpoints when safe #463

Open

Description

@frenchy64

Presents more opportunities to infer static contexts.

(context "/foo" []
  :path-params [id :- s/Str]
  (PATCH "/" []
    (ok id))
  (GET "/" []
    (ok id)))

=>

(context "/foo" []
  (PATCH "/" []
    :path-params [id :- s/Str]
    (ok id))
  (GET "/" []
    :path-params [id :- s/Str]
    (ok id)))

typed.clj.analyzer would be useful here as we both need to infer where locals occur and also do partial macroexpansion.

Activity

  1. ikitommi commented on Apr 23, 2024

    @ikitommi
    Member

    Great idea!

  2. frenchy64 commented on Apr 29, 2024

    @frenchy64
    CollaboratorAuthor

    If a context would be inferred as dynamic, we can push the context into each body form. Then we can proceed to handle individual cases, but the advantage is it automatically handles hybrid static/dynamic contexts. The inner contexts that are static will be inferred as static, ditto dynamic.

      (let [f (fn [] (GET "/bar" [] "body just for doc and coercion"))]
        (context "/foo" []
                 :body [body s/Str]
                 (GET "/bar" [] body)
                 (f)))
      =>
      (let [f (fn [] (GET "/bar" [] "body just for doc and coercion"))]
        (vector
          (context "/foo" []
                   :body [body s/Str]
                   (GET "/bar" [] body))
          (context "/foo" []
                   :body [body s/Str]
                   (f))))
      =>
      (let [f (fn [] (GET "/bar" [] "body just for doc and coercion"))]
        (vector
          (context "/foo" []
                   (GET "/bar" []
                        :body [body s/Str]
                        body))
          (context "/foo" []
                   (-> (f)
                       (update :handler (let [g (partial coercion/coerce-request! s/Str :body-params :body true false)]
                                          #(comp % g)))
                       (update-in [:info :public :parameters :body] #(or % s/Str))))))
  3. frenchy64 commented on Apr 29, 2024

    @frenchy64
    CollaboratorAuthor

    Another insight is that if all :lets and :letks bindings are never used in the body, we can treat it like a static context. This is because the bindings are just side effects, and as long as we update the handler with the side effect and any :info needed, it's equivalent.

    This is a useful observation if the context body calls a function. If the function doesn't get passed the body, then we can just update the route after it's been evaluated.

    (let [f (fn [] (GET "/bar" [] "body just for doc and coercion"))]
      (context "/foo" []
               :body [body s/Str]
               (f)))
    =>
    (let [f (fn [] (GET "/bar" [] "body just for doc and coercion"))]
      (context "/foo" []
               (-> (f)
                   ...for each route...
                   (update :handler (let [g (partial coercion/coerce-request! s/Str :body-params :body true false)]
                                      #(comp % g)))
                   (update-in [:info :public :parameters :body] #(or % s/Str)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions