package wtr

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file wtr_ppx.ml

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
(*-------------------------------------------------------------------------
 * Copyright (c) 2021 Bikal Gurung. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License,  v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 *-------------------------------------------------------------------------*)

open Ppxlib
module Ast_builder = Ast_builder.Default

let ( let* ) r f = Result.bind r f

let ( >>= ) = ( let* )

let rec decode_wtr_expression ~loc wtr =
  (let* uri = decode_uri wtr in
   let* query_components = decode_query_tokens uri in
   let* path_components = decode_path_tokens uri in
   validate_tokens (path_components @ query_components))
  |> function
  | Ok wtr_tokens -> wtr_expression ~loc wtr_tokens
  | Error msg -> Location.raise_errorf ~loc "wtr: %s" msg

and decode_uri wtr =
  let wtr = String.trim wtr in
  if String.length wtr > 0 then
    Ok (Uri.of_string wtr)
  else
    Error "Empty uri path specification"

and decode_query_tokens uri =
  let exception E of string in
  try
    Uri.query uri
    |> List.map (fun (k, v) ->
           if List.length v != 1 then
             raise
               (E (Printf.sprintf "Invalid query specification for key: %s" k))
           else
             [ k; List.hd v ])
    |> List.concat
    |> Result.ok
  with
  | E msg -> Error msg

and decode_path_tokens uri = Ok (Uri.path uri |> String.split_on_char '/')

and validate_tokens tokens =
  let validate_start tokens =
    match List.hd tokens with
    | "" -> Ok (List.tl tokens)
    | _
    | (exception _) ->
      Error "Uri path specification must start with '/'"
  in
  let validate_end_slash path =
    let _, l2 = split_on (fun x -> String.equal "" x) path in
    if List.length l2 > 0 then
      Error
        "Invalid uri path specification. No tokens allowed after trailing '/' \
         token"
    else
      Ok path
  in
  let validate_full_splat path =
    let _, l2 = split_on (fun x -> String.equal "**" x) path in
    if List.length l2 > 0 then
      Error
        "Invalid uri path specification. No tokens allowed after full splat \
         (**) token"
    else
      Ok path
  in
  validate_start tokens >>= validate_end_slash >>= validate_full_splat

and findi f l =
  let rec loop n = function
    | [] -> None
    | x :: t ->
      if f x then
        Some n
      else
        loop (n + 1) t
  in
  loop 0 l

and split_on f l =
  match findi f l with
  | Some n ->
    (List.filteri (fun i _ -> i < n) l, List.filteri (fun i _ -> i > n) l)
  | None -> (l, [])

and wtr_expression ~loc = function
  | [] -> [%expr Wtr.Private.nil]
  | [ "" ] -> [%expr Wtr.Private.trailing_slash]
  | [ "**" ] -> [%expr Wtr.Private.full_splat]
  | "*" :: components ->
    [%expr
      Wtr.Private.decoder Wtr.Private.string [%e wtr_expression ~loc components]]
  | comp :: components when Char.equal comp.[0] ':' -> (
    let comp = String.sub comp 1 (String.length comp - 1) in
    match comp with
    | "int" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.int [%e wtr_expression ~loc components]]
    | "int32" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.int32
          [%e wtr_expression ~loc components]]
    | "int64" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.int64
          [%e wtr_expression ~loc components]]
    | "float" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.float
          [%e wtr_expression ~loc components]]
    | "string" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.string
          [%e wtr_expression ~loc components]]
    | "bool" ->
      [%expr
        Wtr.Private.decoder Wtr.Private.bool [%e wtr_expression ~loc components]]
    | custom_arg when capitalized custom_arg ->
      let longident_loc = { txt = Longident.parse (custom_arg ^ ".t"); loc } in
      [%expr
        Wtr.Private.decoder
          [%e Ast_builder.pexp_ident ~loc longident_loc]
          [%e wtr_expression ~loc components]]
    | x ->
      Location.raise_errorf ~loc
        "wtr: Invalid custom argument name '%s'. Custom argument component \
         name must be a valid module name."
        x)
  | comp :: components ->
    [%expr
      Wtr.Private.lit
        [%e Ast_builder.estring ~loc comp]
        [%e wtr_expression ~loc components]]

and capitalized s = Char.(uppercase_ascii s.[0] |> equal s.[0])

let extend ~loc ~path:_ wtr = decode_wtr_expression ~loc wtr

let ppx_name = "wtr"

let ext =
  Extension.declare ppx_name Extension.Context.Expression
    Ast_pattern.(single_expr_payload (estring __))
    extend

let () = Driver.register_transformation ppx_name ~extensions:[ ext ]
OCaml

Innovation. Community. Security.