@voxgig/sdkgen 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ExternalTarget.js +1 -1
  3. package/dist/cmp/ExternalTarget.js.map +1 -1
  4. package/dist/helpers/naming.d.ts +4 -1
  5. package/dist/helpers/naming.js +119 -5
  6. package/dist/helpers/naming.js.map +1 -1
  7. package/dist/helpers/opShape.js +72 -0
  8. package/dist/helpers/opShape.js.map +1 -1
  9. package/dist/helpers/packageMeta.d.ts +2 -1
  10. package/dist/helpers/packageMeta.js +57 -20
  11. package/dist/helpers/packageMeta.js.map +1 -1
  12. package/dist/sdkgen.d.ts +3 -3
  13. package/dist/sdkgen.js +61 -18
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/testkit.d.ts +39 -0
  16. package/dist/testkit.js +663 -0
  17. package/dist/testkit.js.map +1 -0
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/package.json +32 -8
  20. package/project/.sdk/src/cmp/c/Test_c.ts +5 -7
  21. package/project/.sdk/src/cmp/clojure/Test_clojure.ts +3 -7
  22. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -7
  23. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  24. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -7
  25. package/project/.sdk/src/cmp/dart/Test_dart.ts +3 -4
  26. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +1 -1
  27. package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
  28. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +1 -1
  29. package/project/.sdk/src/cmp/go/Entity_go.ts +1 -1
  30. package/project/.sdk/src/cmp/go/Gitignore_go.ts +6 -0
  31. package/project/.sdk/src/cmp/go/Main_go.ts +2 -2
  32. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  33. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +59 -6
  34. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +1 -1
  35. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +1 -1
  36. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +1 -1
  37. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +1 -1
  38. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +1 -1
  39. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +1 -1
  40. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +1 -1
  41. package/project/.sdk/src/cmp/go/Test_go.ts +6 -8
  42. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +6 -2
  43. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +6 -2
  44. package/project/.sdk/src/cmp/java/Test_java.ts +4 -7
  45. package/project/.sdk/src/cmp/js/Package_js.ts +1 -1
  46. package/project/.sdk/src/cmp/js/Test_js.ts +4 -7
  47. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +4 -7
  48. package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
  49. package/project/.sdk/src/cmp/lua/Package_lua.ts +2 -2
  50. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -7
  51. package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +3 -7
  52. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  53. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
  54. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
  55. package/project/.sdk/src/cmp/php/Package_php.ts +2 -2
  56. package/project/.sdk/src/cmp/php/Test_php.ts +5 -7
  57. package/project/.sdk/src/cmp/py/Package_py.ts +2 -2
  58. package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
  59. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
  60. package/project/.sdk/src/cmp/rb/Package_rb.ts +3 -3
  61. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +1 -1
  62. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -7
  63. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -7
  64. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -2
  65. package/project/.sdk/src/cmp/seneca-provider/Gitignore_seneca-provider.ts +161 -0
  66. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +20 -11
  67. package/project/.sdk/src/cmp/swift/Test_swift.ts +5 -7
  68. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  69. package/project/.sdk/src/cmp/ts/Test_ts.ts +8 -7
  70. package/project/.sdk/src/cmp/zig/Test_zig.ts +3 -7
  71. package/project/.sdk/tm/c/utility/make_point.c +64 -3
  72. package/project/.sdk/tm/clojure/src/sdk/core.clj +49 -14
  73. package/project/.sdk/tm/cpp/utility/pipeline.hpp +55 -3
  74. package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
  75. package/project/.sdk/tm/csharp/utility/MakePoint.cs +65 -2
  76. package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +34 -4
  77. package/project/.sdk/tm/dart/lib/utility/MakePointUtility.dart +48 -2
  78. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +70 -10
  79. package/project/.sdk/tm/go/feature/test_feature.go +34 -2
  80. package/project/.sdk/tm/go/utility/make_point.go +66 -2
  81. package/project/.sdk/tm/java/feature/TestFeature.java +42 -2
  82. package/project/.sdk/tm/java/utility/MakePoint.java +61 -2
  83. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
  84. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +72 -2
  85. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +31 -2
  86. package/project/.sdk/tm/kotlin/utility/MakePoint.kt +58 -2
  87. package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -0
  88. package/project/.sdk/tm/lua/utility/make_point.lua +65 -2
  89. package/project/.sdk/tm/ocaml/sdk_runtime.ml +40 -2
  90. package/project/.sdk/tm/perl/utility/make_point.pm +55 -2
  91. package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
  92. package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
  93. package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
  94. package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
  95. package/project/.sdk/tm/rust/utility/make_point.rs +69 -2
  96. package/project/.sdk/tm/scala/feature/TestFeature.scala +36 -1
  97. package/project/.sdk/tm/scala/utility/Make.scala +57 -3
  98. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +29 -1
  99. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +51 -2
  100. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
  101. package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
  102. package/project/.sdk/tm/zig/core/utility.zig +66 -3
  103. package/project/sdkgen-package.json +1 -2
  104. package/src/cmp/ExternalTarget.ts +1 -1
  105. package/src/helpers/naming.ts +129 -5
  106. package/src/helpers/opShape.ts +85 -0
  107. package/src/helpers/packageMeta.ts +59 -20
  108. package/src/sdkgen.ts +68 -20
  109. package/src/testkit.ts +863 -0
  110. package/testkit.d.ts +3 -0
  111. package/testkit.js +12 -0
  112. package/project/.sdk/model/target/haskell.aontu +0 -72
  113. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
  114. package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
  115. package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
  116. package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
  117. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
  118. package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
  119. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
  120. package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
  121. package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
  122. package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
  123. package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
  124. package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
  125. package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
  126. package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
  127. package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
  128. package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
  129. package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
  130. package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
  131. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
  132. package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
  133. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
  134. package/project/.sdk/tm/haskell/LICENSE +0 -21
  135. package/project/.sdk/tm/haskell/Makefile +0 -22
  136. package/project/.sdk/tm/haskell/VERSION +0 -1
  137. package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
  138. package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
  139. package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
  140. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
  141. package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
  142. package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
  143. package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
  144. package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
  145. package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
  146. package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
  147. package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
  148. package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
  149. package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
  150. package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
  151. package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
  152. package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
  153. package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
  154. package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
  155. package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
  156. package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
  157. package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
  158. package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
  159. package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
  160. package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
  161. package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
  162. package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
  163. package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
  164. package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
  165. package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
  166. package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
  167. package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
  168. package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
  169. package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
  170. package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
  171. package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
@@ -1,1219 +0,0 @@
1
- -- ProjectName SDK runtime: the operation pipeline.
2
- --
3
- -- The context builder, all the `*Util` pipeline utilities (the dynamic donors'
4
- -- utility/ layer), the utility bundle constructor, make_options and the
5
- -- innermost transport (fetcher). Utilities are top-level IO functions that call
6
- -- each other directly; the two members that vary per client — the transport
7
- -- (`uFetcher`, wrapped by features) and `uParam` — are IORef cells so features
8
- -- and tests can rebind them. Everything runs in IO because struct nodes are
9
- -- IORef-backed and reference-stable.
10
-
11
- module SdkRuntime where
12
-
13
- import Control.Exception (throwIO)
14
- import Control.Monad (forM_, when)
15
- import Data.Bits ((.&.))
16
- import Data.Char (isAlphaNum, toUpper)
17
- import Data.IORef
18
- import qualified Data.Map.Strict as Map
19
- import System.IO.Unsafe (unsafePerformIO)
20
- import Text.Printf (printf)
21
-
22
- import VoxgigStruct
23
- ( Value (..), InjArg (..), emptyList, emptyMap, mkList, mkMap
24
- , getprop, getpropAlt, setprop, delprop, getpath, getelem, keysof, listItems
25
- , items, clone, merge, validate, transform, select, size, isempty
26
- , isnode, ismap, islist, isfunc, isNoval, isNullish, vint
27
- , escurl, escre, stringify, walk, join )
28
- import SdkTypes
29
- import SdkHelpers
30
-
31
- -- ------------------------------------------------------------------
32
- -- random ids (no external dep; LCG)
33
- -- ------------------------------------------------------------------
34
-
35
- {-# NOINLINE randSeed #-}
36
- randSeed :: IORef Int
37
- randSeed = unsafePerformIO (newIORef 123456789)
38
-
39
- randInt :: Int -> IO Int
40
- randInt n
41
- | n <= 0 = pure 0
42
- | otherwise = do
43
- s <- readIORef randSeed
44
- let s' = (s * 1103515245 + 12345) .&. 0x7fffffff
45
- writeIORef randSeed s'
46
- pure (s' `mod` n)
47
-
48
- randHex4 :: IO String
49
- randHex4 = printf "%04x" <$> randInt 0x10000
50
-
51
- randId16 :: IO String
52
- randId16 = concat <$> sequence [randHex4, randHex4, randHex4, randHex4]
53
-
54
- nextCtxId :: IO String
55
- nextCtxId = do n <- randInt 90000000; pure ("C" ++ show (10000000 + n))
56
-
57
- escurlS :: String -> IO String
58
- escurlS s = do r <- escurl (VStr s); pure (case r of VStr x -> x; _ -> s)
59
-
60
- escreS :: String -> IO String
61
- escreS s = do r <- escre (VStr s); pure (case r of VStr x -> x; _ -> s)
62
-
63
- valEqScalar :: Value -> Value -> Bool
64
- valEqScalar a b = case (a, b) of
65
- (VNoval, VNoval) -> True
66
- (VNull, VNull) -> True
67
- (VNoval, VNull) -> True
68
- (VNull, VNoval) -> True
69
- (VStr x, VStr y) -> x == y
70
- (VNum x, VNum y) -> x == y
71
- (VBool x, VBool y) -> x == y
72
- _ -> False
73
-
74
- -- ------------------------------------------------------------------
75
- -- context builder
76
- -- ------------------------------------------------------------------
77
-
78
- clientOptionsMap :: Client -> IO Value
79
- clientOptionsMap cl = do
80
- o <- readIORef (clOptions cl)
81
- c <- clone o
82
- case c of VMap _ -> pure c; _ -> emptyMap
83
-
84
- resolveOp :: Context -> String -> IO Operation
85
- resolveOp ctx opname = do
86
- ment <- readIORef (cEntity ctx)
87
- let entname = maybe "_" eName ment
88
- cacheKey = opCacheKey entname opname
89
- m <- readIORef (cOpmap ctx)
90
- case Map.lookup cacheKey m of
91
- Just op -> pure op
92
- Nothing ->
93
- if opname == ""
94
- then newOperation =<< emptyMap
95
- else do
96
- cfg <- readIORef (cConfig ctx)
97
- opcfg <- getpathS cfg ("entity." ++ entname ++ ".op." ++ opname)
98
- let inpt = if opname == "update" || opname == "create" then "data" else "match"
99
- pts <- case opcfg of
100
- VMap _ -> do p <- getp opcfg "points"; case p of VList _ -> pure p; _ -> emptyList
101
- _ -> emptyList
102
- opm <- jo [("entity", VStr entname), ("name", VStr opname), ("input", VStr inpt), ("points", pts)]
103
- op <- newOperation opm
104
- modifyIORef' (cOpmap ctx) (Map.insert cacheKey op)
105
- pure op
106
-
107
- makeContextImpl :: CtxSpec -> Maybe Context -> IO Context
108
- makeContextImpl cs basectx = do
109
- cid <- nextCtxId
110
-
111
- client <- case csClient cs of
112
- Just c -> pure (Just c)
113
- Nothing -> maybe (pure Nothing) (readIORef . cClient) basectx
114
-
115
- utility <- case csUtility cs of
116
- Just u -> pure (Just u)
117
- Nothing -> maybe (pure Nothing) (readIORef . cUtility) basectx
118
-
119
- ctrl <- case csCtrl cs of
120
- Just cr@(VMap _) -> do
121
- c <- newControl
122
- thr <- getp cr "throw_err"
123
- case thr of
124
- VBool b -> setp c "throw" (VBool b)
125
- _ -> do t2 <- getp cr "throw"; case t2 of VBool b -> setp c "throw" (VBool b); _ -> pure ()
126
- ex <- getp cr "explain"; case ex of VMap _ -> setp c "explain" ex; _ -> pure ()
127
- ac <- getp cr "actor"; case ac of VNoval -> pure (); _ -> setp c "actor" ac
128
- pg <- getp cr "paging"; case pg of VMap _ -> setp c "paging" pg; _ -> pure ()
129
- pure c
130
- _ -> maybe newControl (readIORef . cCtrl) basectx
131
-
132
- meta <- case csMeta cs of
133
- Just m@(VMap _) -> pure m
134
- _ -> case basectx of
135
- Just b -> do mv <- readIORef (cMeta b); case mv of VMap _ -> pure mv; _ -> emptyMap
136
- Nothing -> emptyMap
137
-
138
- config <- inheritMap (csConfig cs) (cConfig <$> basectx) VNoval
139
- entopts <- inheritMap (csEntopts cs) (cEntopts <$> basectx) VNoval
140
- options <- inheritMap (csOptions cs) (cOptions <$> basectx) VNoval
141
-
142
- entity <- case csEntity cs of
143
- Just e -> pure (Just e)
144
- Nothing -> maybe (pure Nothing) (readIORef . cEntity) basectx
145
-
146
- shared <- case csShared cs of
147
- Just m@(VMap _) -> pure m
148
- _ -> maybe (pure VNoval) (readIORef . cShared) basectx
149
-
150
- opmapRef <- case csOpmap cs of
151
- Just r -> pure r
152
- Nothing -> case basectx of Just b -> pure (cOpmap b); Nothing -> newIORef Map.empty
153
-
154
- dat <- mapOf (csData cs)
155
- reqdata <- mapOf (csReqdata cs)
156
- mtch <- mapOf (csMatch cs)
157
- reqmatch <- mapOf (csReqmatch cs)
158
-
159
- point <- case csPoint cs of
160
- Just m@(VMap _) -> pure m
161
- _ -> maybe (pure VNoval) (readIORef . cPoint) basectx
162
-
163
- spec <- inheritObj (csSpec cs) (cSpec <$> basectx)
164
- result <- inheritObj (csResult cs) (cResult <$> basectx)
165
- response <- inheritObj (csResponse cs) (cResponse <$> basectx)
166
-
167
- op0 <- newOperation =<< emptyMap
168
- scratch <- emptyMap
169
- out <- emptyMap
170
-
171
- cOutR <- newIORef out
172
- cCtrlR <- newIORef ctrl
173
- cMetaR <- newIORef meta
174
- cClientR <- newIORef client
175
- cUtilR <- newIORef utility
176
- cOpR <- newIORef op0
177
- cPointR <- newIORef point
178
- cConfigR <- newIORef config
179
- cEntoptsR <- newIORef entopts
180
- cOptionsR <- newIORef options
181
- cResponseR <- newIORef response
182
- cResultR <- newIORef result
183
- cSpecR <- newIORef spec
184
- cDataR <- newIORef dat
185
- cReqdataR <- newIORef reqdata
186
- cMatchR <- newIORef mtch
187
- cReqmatchR <- newIORef reqmatch
188
- cEntityR <- newIORef entity
189
- cSharedR <- newIORef shared
190
- cScratchR <- newIORef scratch
191
-
192
- let ctx = Context
193
- { cId = cid, cOut = cOutR, cCtrl = cCtrlR, cMeta = cMetaR
194
- , cClient = cClientR, cUtility = cUtilR, cOp = cOpR, cPoint = cPointR
195
- , cConfig = cConfigR, cEntopts = cEntoptsR, cOptions = cOptionsR
196
- , cOpmap = opmapRef, cResponse = cResponseR, cResult = cResultR
197
- , cSpec = cSpecR, cData = cDataR, cReqdata = cReqdataR, cMatch = cMatchR
198
- , cReqmatch = cReqmatchR, cEntity = cEntityR, cShared = cSharedR
199
- , cScratch = cScratchR }
200
-
201
- let opname = maybe "" id (csOpname cs)
202
- op <- resolveOp ctx opname
203
- writeIORef cOpR op
204
- pure ctx
205
- where
206
- inheritMap mcs mbfield dflt = case mcs of
207
- Just m@(VMap _) -> pure m
208
- _ -> case mbfield of Just r -> readIORef r; Nothing -> pure dflt
209
- inheritObj mcs mbfield = case mcs of
210
- Just v -> pure v
211
- _ -> case mbfield of Just r -> readIORef r; Nothing -> pure VNoval
212
- mapOf mv = case mv of
213
- Just d -> case toMap d of VMap _ -> pure d; _ -> emptyMap
214
- Nothing -> emptyMap
215
-
216
- -- ------------------------------------------------------------------
217
- -- utilities
218
- -- ------------------------------------------------------------------
219
-
220
- cleanUtil :: Context -> Value -> IO Value
221
- cleanUtil _ v = pure v
222
-
223
- makeErrorUtil :: Context -> Maybe Value -> IO Value
224
- makeErrorUtil ctx merr = do
225
- op <- readIORef (cOp ctx)
226
- let opname0 = opName op
227
- opname = if opname0 == "" || opname0 == "_" then "unknown operation" else opname0
228
- resultV <- readIORef (cResult ctx)
229
- result <- case resultV of VMap _ -> pure resultV; _ -> newResult =<< emptyMap
230
- setp result "ok" (VBool False)
231
- err <- case merr of
232
- Just e -> pure e
233
- Nothing -> do
234
- re <- getp result "err"; isE <- isErr re
235
- if isE then pure re else mkErr "unknown" "unknown error"
236
- em <- errMsg err
237
- let msg = "ProjectNameSDK: " ++ opname ++ ": " ++ em
238
- setp result "err" VNoval
239
- ctrl <- readIORef (cCtrl ctx)
240
- explain <- getp ctrl "explain"
241
- case explain of
242
- VMap _ -> do e2 <- jo [("message", VStr msg)]; setp explain "err" e2
243
- _ -> pure ()
244
- ecode <- errCode err
245
- rv <- resultToValue result
246
- specV <- readIORef (cSpec ctx)
247
- sv <- case specV of VMap _ -> specToValue specV; _ -> pure VNoval
248
- sdkErr <- jo [ ("__sdkerr__", VBool True), ("code", VStr ecode), ("message", VStr msg)
249
- , ("result", rv), ("spec", sv) ]
250
- setp ctrl "err" sdkErr
251
- -- Fire PreUnexpected so observability features (metrics, telemetry, audit,
252
- -- debug) close/record error paths that never reach PreDone (e.g. a PrePoint
253
- -- rbac short-circuit). Fires after ctrl err is set so hooks can read the
254
- -- error; features guard against double-recording when PreDone already fired.
255
- featureHookUtil ctx "PreUnexpected"
256
- thr <- getp ctrl "throw"
257
- case thr of
258
- VBool False -> getp result "resdata"
259
- _ -> throwIO (SdkException sdkErr)
260
-
261
- doneUtil :: Context -> IO Value
262
- doneUtil ctx = do
263
- ctrl <- readIORef (cCtrl ctx)
264
- explain <- getp ctrl "explain"
265
- case explain of
266
- VMap _ -> do
267
- er <- getp explain "result"
268
- case er of VMap _ -> delp er "err"; _ -> pure ()
269
- _ -> pure ()
270
- resultV <- readIORef (cResult ctx)
271
- case resultV of
272
- VMap _ -> do
273
- ok <- getp resultV "ok"
274
- if isTrueV ok then getp resultV "resdata" else makeErrorUtil ctx Nothing
275
- _ -> makeErrorUtil ctx Nothing
276
-
277
- -- ----- feature utilities -----
278
-
279
- featureHookUtil :: Context -> String -> IO ()
280
- featureHookUtil ctx name = do
281
- mcl <- readIORef (cClient ctx)
282
- case mcl of
283
- Nothing -> pure ()
284
- Just cl -> do fs <- readIORef (clFeatures cl); mapM_ (\f -> fHook f name ctx) fs
285
-
286
- featureAddUtil :: Context -> Feature -> IO ()
287
- featureAddUtil ctx f = do
288
- cl <- cc ctx
289
- fopts <- readIORef (fOptions f)
290
- let posOpt k = do
291
- v <- case fopts of VMap _ -> getp fopts k; _ -> pure VNoval
292
- pure (case v of VStr s -> Just s; _ -> Nothing)
293
- before <- posOpt "__before__"
294
- after <- posOpt "__after__"
295
- replace <- posOpt "__replace__"
296
- feats <- readIORef (clFeatures cl)
297
- let hasPos = before /= Nothing || after /= Nothing || replace /= Nothing
298
- go _ [] = Nothing
299
- go acc (ef : rest)
300
- | before == Just (fName ef) = Just (reverse acc ++ (f : ef : rest))
301
- | after == Just (fName ef) = Just (reverse acc ++ (ef : f : rest))
302
- | replace == Just (fName ef) = Just (reverse acc ++ (f : rest))
303
- | otherwise = go (ef : acc) rest
304
- positioned = if hasPos then go [] feats else Nothing
305
- case positioned of
306
- Just l -> writeIORef (clFeatures cl) l
307
- Nothing -> writeIORef (clFeatures cl) (feats ++ [f])
308
-
309
- featureInitUtil :: Context -> Feature -> IO ()
310
- featureInitUtil ctx f = do
311
- let fname = fName f
312
- opts <- readIORef (cOptions ctx)
313
- fo <- getp opts "feature"
314
- fopts <- case fo of
315
- VMap _ -> do foo <- getp fo fname; case foo of VMap _ -> pure foo; _ -> emptyMap
316
- _ -> emptyMap
317
- active <- getp fopts "active"
318
- when (isTrueV active) (fInit f ctx fopts)
319
-
320
- -- ----- prepare / param -----
321
-
322
- -- The API definition is authoritative: a POST-only or PATCH-based API
323
- -- exposes `update` as POST or PATCH, not the PUT the op name implies. Only
324
- -- fall back to the op-name convention when the point has no method.
325
- prepareMethodUtil :: Context -> IO String
326
- prepareMethodUtil ctx = do
327
- point <- readIORef (cPoint ctx)
328
- pm <- getp point "method"
329
- case pm of
330
- VStr m | not (null m) -> pure (map toUpper m)
331
- _ -> do
332
- op <- readIORef (cOp ctx)
333
- pure $ case opName op of
334
- "create" -> "POST"; "update" -> "PUT"; "load" -> "GET"
335
- "list" -> "GET"; "remove" -> "DELETE"; "patch" -> "PATCH"; _ -> "GET"
336
-
337
- prepareHeadersUtil :: Context -> IO Value
338
- prepareHeadersUtil ctx = do
339
- cl <- cc ctx
340
- options <- clientOptionsMap cl
341
- h <- getp options "headers"
342
- case h of
343
- VNoval -> emptyMap
344
- _ -> do c <- clone h; case c of VMap _ -> pure c; _ -> emptyMap
345
-
346
- paramUtil :: Context -> Value -> IO Value
347
- paramUtil ctx paramdef = do
348
- point <- readIORef (cPoint ctx)
349
- specV <- readIORef (cSpec ctx)
350
- mtch <- readIORef (cMatch ctx)
351
- reqmatch <- readIORef (cReqmatch ctx)
352
- dat <- readIORef (cData ctx)
353
- reqdata <- readIORef (cReqdata ctx)
354
- key <- case paramdef of VStr s -> pure s; _ -> getStrD paramdef "name" ""
355
- aliasV <- getp point "alias"
356
- akey <- case aliasV of VMap _ -> getStrD aliasV key ""; _ -> pure ""
357
- let orElse v act = if isNoval v then act else pure v
358
- v1 <- getp reqmatch key
359
- v2 <- orElse v1 (getp mtch key)
360
- v3 <- if isNoval v2 && not (null akey)
361
- then do
362
- case specV of
363
- VMap _ -> do sa <- getp specV "alias"; setp sa akey (VStr key)
364
- _ -> pure ()
365
- getp reqmatch akey
366
- else pure v2
367
- v4 <- orElse v3 (getp reqdata key)
368
- v5 <- orElse v4 (getp dat key)
369
- if isNoval v5 && not (null akey)
370
- then do a <- getp reqdata akey; orElse a (getp dat akey)
371
- else pure v5
372
-
373
- prepareParamsUtil :: Context -> IO Value
374
- prepareParamsUtil ctx = do
375
- point <- readIORef (cPoint ctx)
376
- args <- getp point "args"
377
- params <- case args of
378
- VMap _ -> do p <- getp args "params"; case p of VList _ -> listItems p; _ -> pure []
379
- _ -> pure []
380
- out <- emptyMap
381
- u <- cu ctx
382
- pfn <- readIORef (uParam u)
383
- forM_ params $ \pd -> do
384
- v <- pfn ctx pd
385
- when (not (isNoval v)) $
386
- case pd of
387
- VMap _ -> do nm <- getStrD pd "name" ""; when (nm /= "") (setp out nm v)
388
- _ -> pure ()
389
- pure out
390
-
391
- preparePathUtil :: Context -> IO String
392
- preparePathUtil ctx = do
393
- point <- readIORef (cPoint ctx)
394
- p <- getp point "parts"
395
- parts <- case p of VList _ -> pure p; _ -> emptyList
396
- join_ parts
397
-
398
- prepareQueryUtil :: Context -> IO Value
399
- prepareQueryUtil ctx = do
400
- point <- readIORef (cPoint ctx)
401
- rmV <- readIORef (cReqmatch ctx)
402
- reqmatch <- case rmV of VMap _ -> pure rmV; _ -> emptyMap
403
- pl <- getp point "params"
404
- params <- case pl of VList _ -> listItems pl; _ -> pure []
405
- let containsParam s = any (\v -> case v of VStr x -> x == s; _ -> False) params
406
- out <- emptyMap
407
- ks <- keysof reqmatch
408
- forM_ ks $ \k -> do
409
- v <- getp reqmatch k
410
- when (not (isNoval v) && not (containsParam k)) (setp out k v)
411
- pure out
412
-
413
- prepareBodyUtil :: Context -> IO Value
414
- prepareBodyUtil ctx = do
415
- op <- readIORef (cOp ctx)
416
- if opInput op == "data" then transformRequestUtil ctx else pure VNoval
417
-
418
- -- ------------------------------------------------------------------
419
- -- graphql (transport)
420
- --
421
- -- GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
422
- -- produces uses this code unchanged. The API-specific part — which
423
- -- operations exist and what each one's document is — is model data, computed
424
- -- once by apidef and emitted into Config.
425
- --
426
- -- Two jobs:
427
- --
428
- -- graphqlBodyUtil — build { query, variables } for a point, binding the
429
- -- op's arguments to the document's declared variables.
430
- --
431
- -- graphqlErrorsUtil — lift a GraphQL failure into an SDK error. GraphQL
432
- -- reports failures as a top-level `errors` array under
433
- -- HTTP 200, so the status-driven path in resultBasic
434
- -- never sees them.
435
- -- ------------------------------------------------------------------
436
-
437
- -- Content type every GraphQL-over-HTTP request uses.
438
- graphqlContentType :: String
439
- graphqlContentType = "application/json"
440
-
441
- -- Map a GraphQL error to the same error codes the HTTP path produces, so a
442
- -- caller handles auth or rate limiting identically on both transports.
443
- -- Servers put the machine-readable code in `extensions.code`; Linear-style
444
- -- APIs use `extensions.type`.
445
- graphqlErrorCode :: Value -> IO String
446
- graphqlErrorCode gqlerr = do
447
- ext <- getp gqlerr "extensions"
448
- c0 <- getStrD ext "code" ""
449
- code <- if null c0 then getStrD ext "type" "" else pure c0
450
- let raw = map toUpper code
451
- pure $
452
- if substrContains raw "AUTH" || substrContains raw "FORBIDDEN"
453
- || substrContains raw "UNAUTHENTICATED"
454
- then "request_auth"
455
- else if substrContains raw "RATELIMIT" || substrContains raw "RATE_LIMIT"
456
- || substrContains raw "TOO_MANY"
457
- then "request_ratelimit"
458
- else if substrContains raw "BAD_USER_INPUT"
459
- || substrContains raw "VALIDATION"
460
- || substrContains raw "INVALID"
461
- then "request_invalid"
462
- else "request_graphql"
463
-
464
- -- Build the request body for a GraphQL point.
465
- --
466
- -- Variables come from the op's own arguments: a named variable binds to the
467
- -- like-named argument (`from`), and the input-object variable (empty `from`)
468
- -- takes the request data as a whole — which is what makes a generated
469
- -- create/update call look exactly like its REST equivalent.
470
- graphqlBodyUtil :: Context -> IO Value
471
- graphqlBodyUtil ctx = do
472
- point <- readIORef (cPoint ctx)
473
- gql <- getp point "graphql"
474
- case gql of
475
- VMap _ -> do
476
- -- reqmatch/reqdata hold the caller's arguments for THIS call;
477
- -- data/match hold the entity's current state. Which pair depends on
478
- -- whether the op takes match or data input. A named variable falls
479
- -- back to the current state, so updating a loaded entity with just
480
- -- {title} still binds the stored id the mutation requires.
481
- op <- readIORef (cOp ctx)
482
- let datainput = opInput op == "data"
483
- rsV <- readIORef (if datainput then cReqdata ctx else cReqmatch ctx)
484
- dsV <- readIORef (if datainput then cData ctx else cMatch ctx)
485
- reqsrc <- case rsV of VMap _ -> pure rsV; _ -> emptyMap
486
- datasrc <- case dsV of VMap _ -> pure dsV; _ -> emptyMap
487
- variables <- emptyMap
488
- vl <- getp gql "vars"
489
- varlist <- case vl of VList _ -> listItems vl; _ -> pure []
490
- forM_ varlist $ \spec -> case spec of
491
- VMap _ -> do
492
- name <- getStrD spec "name" ""
493
- from <- getStrD spec "from" ""
494
- when (not (null name)) $
495
- if null from
496
- then do
497
- -- The input object IS the request body. Strip the action
498
- -- selector, which is an SDK-side point discriminator, not an
499
- -- API field.
500
- body <- emptyMap
501
- ks <- keysof reqsrc
502
- forM_ ks $ \k -> when (k /= "$action") $ do
503
- v <- getp reqsrc k
504
- setp body k v
505
- setp variables name body
506
- else do
507
- -- Only send variables the caller actually supplied: sending
508
- -- an explicit null would clear a field on many APIs.
509
- v0 <- getp reqsrc from
510
- val <- if isNullish v0 then getp datasrc from else pure v0
511
- when (not (isNullish val)) (setp variables name val)
512
- _ -> pure ()
513
- doc <- getp gql "doc"
514
- jo [("query", doc), ("variables", variables)]
515
- _ -> pure VNoval
516
-
517
- -- Inspect a decoded GraphQL response body and record a failure when the
518
- -- server reported one. Returns True when an error was recorded.
519
- --
520
- -- Partial data (`data` alongside `errors`) is treated as failure: the REST
521
- -- surface has no partial-success concept, and silently returning half an
522
- -- object would be worse than failing.
523
- graphqlErrorsUtil :: Context -> IO Bool
524
- graphqlErrorsUtil ctx = do
525
- resultV <- readIORef (cResult ctx)
526
- point <- readIORef (cPoint ctx)
527
- kind <- getStrD point "kind" ""
528
- case resultV of
529
- VMap _ | kind == "graphql" -> do
530
- body <- getp resultV "body"
531
- ev <- getp body "errors"
532
- errors <- case ev of VList _ -> listItems ev; _ -> pure []
533
- if null errors
534
- then pure False
535
- else do
536
- let firsterr = head errors
537
- n = length errors
538
- m0 <- getStrD firsterr "message" ""
539
- let m1 = if null m0 then "graphql error" else m0
540
- msg = if 1 < n then m1 ++ " (+" ++ show (n - 1) ++ " more)" else m1
541
- code <- graphqlErrorCode firsterr
542
- e <- mkErr code ("graphql: " ++ msg)
543
- setp resultV "err" e
544
- setp resultV "ok" (VBool False)
545
- pure True
546
- _ -> pure False
547
-
548
- prepareAuthUtil :: Context -> UResult
549
- prepareAuthUtil ctx = do
550
- specV <- readIORef (cSpec ctx)
551
- case specV of
552
- VMap _ -> do
553
- headers <- getp specV "headers"
554
- cl <- cc ctx
555
- options <- clientOptionsMap cl
556
- authv <- getp options "auth"
557
- case authv of
558
- VNoval -> do delp headers "authorization"; pure (specV, Nothing)
559
- VNull -> do delp headers "authorization"; pure (specV, Nothing)
560
- _ -> do
561
- apikey <- getpropAlt (VStr "__NOTFOUND__") options (VStr "apikey")
562
- let isNotFound = case apikey of VStr "__NOTFOUND__" -> True; _ -> False
563
- apikeyStr = case apikey of VStr s -> s; _ -> ""
564
- if isNotFound || isNoval apikey || apikeyStr == ""
565
- then do delp headers "authorization"; pure (specV, Nothing)
566
- else do
567
- apV <- getpathS options "auth.prefix"
568
- let authPrefix = case apV of VStr s -> s; _ -> ""
569
- authval = if authPrefix /= "" then authPrefix ++ " " ++ apikeyStr else apikeyStr
570
- setp headers "authorization" (VStr authval)
571
- pure (specV, Nothing)
572
- _ -> do e <- mkErr "auth_no_spec" "Expected context spec property to be defined."; pure (VNoval, Just e)
573
-
574
- -- ----- transforms / result helpers -----
575
-
576
- transformRequestUtil :: Context -> IO Value
577
- transformRequestUtil ctx = do
578
- specV <- readIORef (cSpec ctx)
579
- case specV of VMap _ -> setp specV "step" (VStr "reqform"); _ -> pure ()
580
- point <- readIORef (cPoint ctx)
581
- reqdata <- readIORef (cReqdata ctx)
582
- tr <- toMap <$> getp point "transform"
583
- case tr of
584
- VMap _ -> do
585
- reqform <- getp tr "req"
586
- case reqform of
587
- VNoval -> pure reqdata
588
- _ -> do input <- jo [("reqdata", reqdata)]; transform INone input reqform
589
- _ -> pure reqdata
590
-
591
- transformResponseUtil :: Context -> IO Value
592
- transformResponseUtil ctx = do
593
- specV <- readIORef (cSpec ctx)
594
- case specV of VMap _ -> setp specV "step" (VStr "resform"); _ -> pure ()
595
- resultV <- readIORef (cResult ctx)
596
- case resultV of
597
- VMap _ -> do
598
- ok <- getp resultV "ok"
599
- if not (isTrueV ok) then pure VNoval
600
- else do
601
- point <- readIORef (cPoint ctx)
602
- tr <- toMap <$> getp point "transform"
603
- case tr of
604
- VMap _ -> do
605
- resform <- getp tr "res"
606
- case resform of
607
- VNoval -> pure VNoval
608
- _ -> do
609
- status <- getp resultV "status"
610
- st <- getp resultV "statusText"
611
- hdr <- getp resultV "headers"
612
- body <- getp resultV "body"
613
- errv <- getp resultV "err"
614
- isE <- isErr errv
615
- errOut <- if isE then do m <- getp errv "message"; jo [("message", m)] else pure VNoval
616
- resdata <- getp resultV "resdata"
617
- resmatch <- getp resultV "resmatch"
618
- input <- jo [ ("ok", ok), ("status", status), ("statusText", st)
619
- , ("headers", hdr), ("body", body), ("err", errOut)
620
- , ("resdata", resdata), ("resmatch", resmatch) ]
621
- rd <- transform INone input resform
622
- setp resultV "resdata" rd
623
- pure rd
624
- _ -> pure VNoval
625
- _ -> pure VNoval
626
-
627
- resultBasicUtil :: Context -> IO ()
628
- resultBasicUtil ctx = do
629
- responseV <- readIORef (cResponse ctx)
630
- resultV <- readIORef (cResult ctx)
631
- case (responseV, resultV) of
632
- (VMap _, VMap _) -> do
633
- st <- getp responseV "status"
634
- stt <- getp responseV "statusText"
635
- setp resultV "status" st
636
- setp resultV "statusText" stt
637
- let status = toInt st
638
- sttext = case stt of VStr s -> s; _ -> ""
639
- if status >= 400
640
- then do
641
- let msg = "request: " ++ show status ++ ": " ++ sttext
642
- prev <- getp resultV "err"
643
- isE <- isErr prev
644
- if isE
645
- then do pm <- errMsg prev; e <- mkErr "request_status" (pm ++ ": " ++ msg); setp resultV "err" e
646
- else do e <- mkErr "request_status" msg; setp resultV "err" e
647
- else do
648
- re <- getp responseV "err"
649
- isE <- isErr re
650
- when isE (setp resultV "err" re)
651
- _ -> pure ()
652
-
653
- resultBodyUtil :: Context -> IO ()
654
- resultBodyUtil ctx = do
655
- responseV <- readIORef (cResponse ctx)
656
- resultV <- readIORef (cResult ctx)
657
- case (responseV, resultV) of
658
- (VMap _, VMap _) -> do
659
- jsn <- getp responseV "json"
660
- body <- getp responseV "body"
661
- when (isCallable jsn && not (isNoval body)) $ do
662
- d <- callJson jsn
663
- setp resultV "body" d
664
- _ -> pure ()
665
-
666
- resultHeadersUtil :: Context -> IO ()
667
- resultHeadersUtil ctx = do
668
- resultV <- readIORef (cResult ctx)
669
- case resultV of
670
- VMap _ -> do
671
- responseV <- readIORef (cResponse ctx)
672
- case responseV of
673
- VMap _ -> do
674
- h <- getp responseV "headers"
675
- case h of VMap _ -> setp resultV "headers" h; _ -> do em <- emptyMap; setp resultV "headers" em
676
- _ -> do em <- emptyMap; setp resultV "headers" em
677
- _ -> pure ()
678
-
679
- -- ----- make_* pipeline stages -----
680
-
681
- makePointUtil :: Context -> UResult
682
- makePointUtil ctx = do
683
- out <- readIORef (cOut ctx)
684
- pre <- getp out "point"
685
- isE <- isErr pre
686
- if isE then pure (VNoval, Just pre)
687
- else case pre of
688
- VMap _ -> do writeIORef (cPoint ctx) pre; pure (pre, Nothing)
689
- _ -> do
690
- op <- readIORef (cOp ctx)
691
- options <- readIORef (cOptions ctx)
692
- av <- getpathS options "allow.op"
693
- let allowOp = case av of VStr s -> s; _ -> ""
694
- if not (substrContains allowOp (opName op))
695
- then do
696
- e <- mkErr "point_op_allow"
697
- ("Operation \"" ++ opName op ++ "\" not allowed by SDK option allow.op value: \"" ++ allowOp ++ "\"")
698
- pure (VNoval, Just e)
699
- else do
700
- pts <- listItems (opPoints op)
701
- case pts of
702
- [] -> do e <- mkErr "point_no_points" ("Operation \"" ++ opName op ++ "\" has no endpoint definitions."); pure (VNoval, Just e)
703
- [single] -> do writeIORef (cPoint ctx) single; pure (single, Nothing)
704
- _ -> do
705
- (reqsel, sel) <- if opInput op == "data"
706
- then (,) <$> readIORef (cReqdata ctx) <*> readIORef (cData ctx)
707
- else (,) <$> readIORef (cReqmatch ctx) <*> readIORef (cMatch ctx)
708
- let isFound pt = do
709
- selectDef <- toMap <$> getp pt "select"
710
- existOk <- case selectDef of
711
- VMap _ -> do
712
- exist <- getp selectDef "exist"
713
- case exist of
714
- VList _ -> do
715
- eks <- listItems exist
716
- let chk found ek = if not found then pure False else do
717
- let existkey = vstring ek
718
- rv <- getp reqsel existkey; sv <- getp sel existkey
719
- pure (not (isNoval rv && isNoval sv))
720
- foldMB True chk eks
721
- _ -> pure True
722
- _ -> pure True
723
- if not existOk then pure False
724
- else do
725
- reqAction <- getp reqsel "$action"
726
- selectAction <- getp selectDef "$action"
727
- pure (valEqScalar reqAction selectAction)
728
- choose [] = pure VNoval
729
- choose [pt] = pure pt
730
- choose (pt : rest) = do f <- isFound pt; if f then pure pt else choose rest
731
- chosen <- choose pts
732
- reqAction <- getp reqsel "$action"
733
- if not (isNoval reqAction) && not (isNoval chosen)
734
- then do
735
- pointSelect <- toMap <$> getp chosen "select"
736
- pointAction <- getp pointSelect "$action"
737
- if not (valEqScalar reqAction pointAction)
738
- then do e <- mkErr "point_action_invalid" ("Operation \"" ++ opName op ++ "\" action \"" ++ vstring reqAction ++ "\" is not valid."); pure (VNoval, Just e)
739
- else do writeIORef (cPoint ctx) chosen; pure (chosen, Nothing)
740
- else do writeIORef (cPoint ctx) chosen; pure (chosen, Nothing)
741
-
742
- foldMB :: Bool -> (Bool -> a -> IO Bool) -> [a] -> IO Bool
743
- foldMB acc _ [] = pure acc
744
- foldMB acc f (x : xs) = do acc' <- f acc x; foldMB acc' f xs
745
-
746
- makeSpecUtil :: Context -> UResult
747
- makeSpecUtil ctx = do
748
- out <- readIORef (cOut ctx)
749
- pre <- getp out "spec"
750
- isE <- isErr pre
751
- if isE then pure (VNoval, Just pre)
752
- else case pre of
753
- VMap _ -> do writeIORef (cSpec ctx) pre; pure (pre, Nothing)
754
- _ -> do
755
- options <- readIORef (cOptions ctx)
756
- base <- getStrD options "base" ""
757
- prefix <- getStrD options "prefix" ""
758
- suffix <- getStrD options "suffix" ""
759
- point <- readIORef (cPoint ctx)
760
- p <- getp point "parts"
761
- parts <- case p of VList _ -> pure p; _ -> emptyList
762
- specm <- jo [("base", VStr base), ("prefix", VStr prefix), ("parts", parts), ("suffix", VStr suffix), ("step", VStr "start")]
763
- sp <- newSpec specm
764
- writeIORef (cSpec ctx) sp
765
- method <- prepareMethodUtil ctx
766
- setp sp "method" (VStr method)
767
- amv <- getpathS options "allow.method"
768
- let allowMethod = case amv of VStr s -> s; _ -> ""
769
- if not (substrContains allowMethod method)
770
- then do e <- mkErr "spec_method_allow" ("Method \"" ++ method ++ "\" not allowed by SDK option allow.method value: \"" ++ allowMethod ++ "\""); pure (VNoval, Just e)
771
- else do
772
- params <- prepareParamsUtil ctx; setp sp "params" params
773
- query <- prepareQueryUtil ctx; setp sp "query" query
774
- headers <- prepareHeadersUtil ctx; setp sp "headers" headers
775
- kind <- getStrD point "kind" ""
776
- if kind == "graphql"
777
- -- GraphQL addresses one endpoint: no path parts, no query string,
778
- -- and the body carries the operation. prepareBody is skipped
779
- -- deliberately — it only emits a body for data-input ops, whereas
780
- -- every GraphQL op posts one, including load/list/remove.
781
- then do
782
- body <- graphqlBodyUtil ctx; setp sp "body" body
783
- setp sp "path" (VStr "")
784
- -- prepareQuery already copied the op's match arguments into the
785
- -- query string. Those same values are bound as operation
786
- -- variables, so leaving them would send /graphql?id=i1.
787
- emptyq <- emptyMap; setp sp "query" emptyq
788
- setp headers "content-type" (VStr graphqlContentType)
789
- else do
790
- body <- prepareBodyUtil ctx; setp sp "body" body
791
- path <- preparePathUtil ctx; setp sp "path" (VStr path)
792
- ctrl <- readIORef (cCtrl ctx)
793
- explain <- getp ctrl "explain"
794
- case explain of { VMap _ -> do { snap <- specToValue sp; setp explain "spec" snap }; _ -> pure () }
795
- (sp2, merr) <- prepareAuthUtil ctx
796
- case merr of
797
- Just e -> pure (VNoval, Just e)
798
- Nothing -> do writeIORef (cSpec ctx) sp2; pure (sp2, Nothing)
799
-
800
- makeUrlUtil :: Context -> UResult
801
- makeUrlUtil ctx = do
802
- specV <- readIORef (cSpec ctx)
803
- resultV <- readIORef (cResult ctx)
804
- case specV of
805
- VMap _ -> case resultV of
806
- VMap _ -> do
807
- base <- getStrD specV "base" ""
808
- prefix <- getStrD specV "prefix" ""
809
- path <- getStrD specV "path" ""
810
- suffix <- getStrD specV "suffix" ""
811
- arr <- ja [VStr base, VStr prefix, VStr path, VStr suffix]
812
- url0 <- join_ arr
813
- resmatch <- emptyMap
814
- params <- getp specV "params"
815
- pks <- keysof params
816
- url1 <- foldMS url0 pks $ \u k -> do
817
- v <- getp params k
818
- if isNoval v then pure u
819
- else do enc <- escurlS (vstring v); setp resmatch k v; pure (strReplaceAll u ("{" ++ k ++ "}") enc)
820
- query <- getp specV "query"
821
- qks <- keysof query
822
- (url2, _) <- foldMS2 (url1, "?") qks $ \(u, qsep) k -> do
823
- v <- getp query k
824
- if isNoval v then pure (u, qsep)
825
- else do ek <- escurlS k; ev <- escurlS (vstring v); setp resmatch k v; pure (u ++ qsep ++ ek ++ "=" ++ ev, "&")
826
- setp resultV "resmatch" resmatch
827
- pure (VStr url2, Nothing)
828
- _ -> do e <- mkErr "url_no_result" "Expected context result property to be defined."; pure (VStr "", Just e)
829
- _ -> do e <- mkErr "url_no_spec" "Expected context spec property to be defined."; pure (VStr "", Just e)
830
-
831
- foldMS :: b -> [a] -> (b -> a -> IO b) -> IO b
832
- foldMS z xs f = go z xs where go acc [] = pure acc; go acc (y : ys) = do acc' <- f acc y; go acc' ys
833
-
834
- foldMS2 :: (b, c) -> [a] -> ((b, c) -> a -> IO (b, c)) -> IO (b, c)
835
- foldMS2 z xs f = go z xs where go acc [] = pure acc; go acc (y : ys) = do acc' <- f acc y; go acc' ys
836
-
837
- makeFetchDefUtil :: Context -> UResult
838
- makeFetchDefUtil ctx = do
839
- specV <- readIORef (cSpec ctx)
840
- case specV of
841
- VMap _ -> do
842
- resultV <- readIORef (cResult ctx)
843
- case resultV of VMap _ -> pure (); _ -> do r <- newResult =<< emptyMap; writeIORef (cResult ctx) r
844
- setp specV "step" (VStr "prepare")
845
- (urlV, merr) <- makeUrlUtil ctx
846
- case merr of
847
- Just e -> pure (VNoval, Just e)
848
- Nothing -> do
849
- let url = vstring urlV
850
- setp specV "url" (VStr url)
851
- method <- getStrD specV "method" "GET"
852
- headers <- getp specV "headers"
853
- fetchdef <- jo [("url", VStr url), ("method", VStr method), ("headers", headers)]
854
- body <- getp specV "body"
855
- case body of
856
- VNoval -> pure ()
857
- VMap _ -> do bs <- jsonifyCompact body; setp fetchdef "body" (VStr bs)
858
- _ -> setp fetchdef "body" body
859
- pure (fetchdef, Nothing)
860
- _ -> do e <- mkErr "fetchdef_no_spec" "Expected context spec property to be defined."; pure (VNoval, Just e)
861
-
862
- makeRequestUtil :: Context -> UResult
863
- makeRequestUtil ctx = do
864
- out <- readIORef (cOut ctx)
865
- pre <- getp out "request"
866
- isE <- isErr pre
867
- if isE then pure (VNoval, Just pre)
868
- else case pre of
869
- VMap _ -> pure (pre, Nothing)
870
- _ -> do
871
- response0 <- newResponse =<< emptyMap
872
- result <- newResult =<< emptyMap
873
- writeIORef (cResult ctx) result
874
- specV <- readIORef (cSpec ctx)
875
- case specV of
876
- VMap _ -> do
877
- (fetchdef, merr) <- makeFetchDefUtil ctx
878
- case merr of
879
- Just e -> do
880
- setp response0 "err" e
881
- writeIORef (cResponse ctx) response0
882
- setp specV "step" (VStr "postrequest")
883
- pure (response0, Nothing)
884
- Nothing -> do
885
- ctrl <- readIORef (cCtrl ctx)
886
- explain <- getp ctrl "explain"
887
- case explain of VMap _ -> setp explain "fetchdef" fetchdef; _ -> pure ()
888
- setp specV "step" (VStr "prerequest")
889
- url <- getStrD fetchdef "url" ""
890
- u <- cu ctx
891
- fetcher <- readIORef (uFetcher u)
892
- (fetched, fetchErr) <- fetcher ctx url fetchdef
893
- response <- case fetchErr of
894
- Just fe -> do setp response0 "err" fe; pure response0
895
- Nothing ->
896
- if isNoval fetched || isNullV fetched
897
- then do r <- newResponse =<< emptyMap; e <- mkErr "request_no_response" "response: undefined"; setp r "err" e; pure r
898
- else case fetched of
899
- VMap _ -> newResponse fetched
900
- _ -> do setp response0 "err" =<< mkErr "request_invalid_response" "response: invalid type"; pure response0
901
- setp specV "step" (VStr "postrequest")
902
- writeIORef (cResponse ctx) response
903
- pure (response, Nothing)
904
- _ -> do e <- mkErr "request_no_spec" "Expected context spec property to be defined."; pure (VNoval, Just e)
905
-
906
- isNullV :: Value -> Bool
907
- isNullV VNull = True
908
- isNullV _ = False
909
-
910
- makeResponseUtil :: Context -> UResult
911
- makeResponseUtil ctx = do
912
- out <- readIORef (cOut ctx)
913
- pre <- getp out "response"
914
- isE <- isErr pre
915
- if isE then pure (VNoval, Just pre)
916
- else case pre of
917
- VMap _ -> pure (pre, Nothing)
918
- _ -> do
919
- specV <- readIORef (cSpec ctx)
920
- responseV <- readIORef (cResponse ctx)
921
- resultV <- readIORef (cResult ctx)
922
- case specV of
923
- VMap _ -> case responseV of
924
- VMap _ -> case resultV of
925
- VMap _ -> do
926
- setp specV "step" (VStr "response")
927
- resultBasicUtil ctx; resultHeadersUtil ctx; resultBodyUtil ctx
928
- -- GraphQL reports failures as a top-level `errors` array under
929
- -- HTTP 200, so resultBasic's status check never sees them. Lift
930
- -- them here, before the response transform tries to unwrap data
931
- -- that is not there.
932
- _ <- graphqlErrorsUtil ctx
933
- _ <- transformResponseUtil ctx
934
- errv <- getp resultV "err"
935
- isErrR <- isErr errv
936
- when (not isErrR) (setp resultV "ok" (VBool True))
937
- ctrl <- readIORef (cCtrl ctx)
938
- explain <- getp ctrl "explain"
939
- case explain of { VMap _ -> do { snap <- resultToValue resultV; setp explain "result" snap }; _ -> pure () }
940
- pure (responseV, Nothing)
941
- _ -> do e <- mkErr "response_no_result" "Expected context result property to be defined."; pure (VNoval, Just e)
942
- _ -> do e <- mkErr "response_no_response" "Expected context response property to be defined."; pure (VNoval, Just e)
943
- _ -> do e <- mkErr "response_no_spec" "Expected context spec property to be defined."; pure (VNoval, Just e)
944
-
945
- makeResultUtil :: Context -> UResult
946
- makeResultUtil ctx = do
947
- out <- readIORef (cOut ctx)
948
- pre <- getp out "result"
949
- isE <- isErr pre
950
- if isE then pure (VNoval, Just pre)
951
- else case pre of
952
- VMap _ -> pure (pre, Nothing)
953
- _ -> do
954
- op <- readIORef (cOp ctx)
955
- specV <- readIORef (cSpec ctx)
956
- resultV <- readIORef (cResult ctx)
957
- case specV of
958
- VMap _ -> case resultV of
959
- VMap _ -> do
960
- setp specV "step" (VStr "result")
961
- _ <- transformResponseUtil ctx
962
- when (opName op == "list") $ do
963
- resdata <- getp resultV "resdata"
964
- el <- emptyList
965
- setp resultV "resdata" el
966
- ment <- readIORef (cEntity ctx)
967
- case (resdata, ment) of
968
- (VList _, Just entity) -> do
969
- items0 <- listItems resdata
970
- entries <- mapM (\entry -> do
971
- ent <- eMake entity
972
- case entry of VMap _ -> eDataSet ent entry; _ -> pure ()
973
- pure entry) items0
974
- el2 <- mkList entries
975
- setp resultV "resdata" el2
976
- _ -> pure ()
977
- ctrl <- readIORef (cCtrl ctx)
978
- explain <- getp ctrl "explain"
979
- case explain of { VMap _ -> do { snap <- resultToValue resultV; setp explain "result" snap }; _ -> pure () }
980
- pure (resultV, Nothing)
981
- _ -> do e <- mkErr "result_no_result" "Expected context result property to be defined."; pure (VNoval, Just e)
982
- _ -> do e <- mkErr "result_no_spec" "Expected context spec property to be defined."; pure (VNoval, Just e)
983
-
984
- -- ----- fetcher (innermost transport) -----
985
-
986
- fetcherUtil :: Context -> String -> Value -> IO (Value, Maybe Value)
987
- fetcherUtil ctx fullurl fetchdef = do
988
- cl <- cc ctx
989
- mode <- readIORef (clMode cl)
990
- if mode /= "live"
991
- then do e <- mkErr "fetch_mode_block" ("Request blocked by mode: \"" ++ mode ++ "\" (URL was: \"" ++ fullurl ++ "\")"); pure (VNoval, Just e)
992
- else do
993
- options <- clientOptionsMap cl
994
- testActive <- getpathS options "feature.test.active"
995
- if isTrueV testActive
996
- then do e <- mkErr "fetch_test_block" ("Request blocked as test feature is active (URL was: \"" ++ fullurl ++ "\")"); pure (VNoval, Just e)
997
- else do
998
- sysFetch <- getpathS options "system.fetch"
999
- case sysFetch of
1000
- VFunc _ -> do
1001
- argsL <- ja [VStr fullurl, fetchdef]
1002
- out <- callVfn sysFetch argsL
1003
- errStr <- getStr out "__err__"
1004
- case errStr of
1005
- Just msg -> do e <- mkErr "fetch_system" msg; pure (VNoval, Just e)
1006
- Nothing -> pure (out, Nothing)
1007
- VNoval -> do e <- mkErr "fetch_no_transport" "No live HTTP transport in this build; provide options.system.fetch."; pure (VNoval, Just e)
1008
- VNull -> do e <- mkErr "fetch_no_transport" "No live HTTP transport in this build; provide options.system.fetch."; pure (VNoval, Just e)
1009
- _ -> do e <- mkErr "fetch_invalid" "system.fetch is not a valid function"; pure (VNoval, Just e)
1010
-
1011
- -- ------------------------------------------------------------------
1012
- -- make_options
1013
- -- ------------------------------------------------------------------
1014
-
1015
- optSpecValue :: IO Value
1016
- optSpecValue = do
1017
- auth <- jo [("prefix", VStr "")]
1018
- hdrs <- jo [("`$CHILD`", VStr "`$STRING`")]
1019
- -- OpenAPI server-variable defaults, carried by the generated config whenever
1020
- -- the spec's server URL is templated. Accepted here so validation does not
1021
- -- reject the SDK's own config; the {name} substitution into base is a
1022
- -- separate concern.
1023
- srv <- jo [("`$CHILD`", VStr "")]
1024
- allow <- jo [("method", VStr "GET,PUT,POST,PATCH,DELETE,OPTIONS"), ("op", VStr "create,update,load,list,remove,command,direct,graphql")]
1025
- entChild <- do a <- emptyMap; jo [("`$OPEN`", VBool True), ("active", VBool False), ("alias", a)]
1026
- ent <- jo [("`$CHILD`", entChild)]
1027
- featChild <- jo [("`$OPEN`", VBool True), ("active", VBool False)]
1028
- feat <- jo [("`$CHILD`", featChild)]
1029
- utilm <- emptyMap
1030
- sysm <- emptyMap
1031
- testEnt <- jo [("`$OPEN`", VBool True)]
1032
- test <- jo [("active", VBool False), ("entity", testEnt)]
1033
- clean <- jo [("keys", VStr "key,token,id")]
1034
- jo [ ("apikey", VStr ""), ("base", VStr "http://localhost:8000"), ("prefix", VStr ""), ("suffix", VStr "")
1035
- , ("auth", auth), ("headers", hdrs), ("server", srv), ("allow", allow), ("entity", ent), ("feature", feat)
1036
- , ("utility", utilm), ("system", sysm), ("test", test), ("clean", clean) ]
1037
-
1038
- makeOptionsUtil :: Context -> IO Value
1039
- makeOptionsUtil ctx = do
1040
- optionsV <- readIORef (cOptions ctx)
1041
- options <- case optionsV of VNoval -> emptyMap; v -> pure v
1042
- customUtils <- getp options "utility"
1043
- case customUtils of
1044
- VMap _ -> do
1045
- mu <- readIORef (cUtility ctx)
1046
- case mu of
1047
- Just u -> do ks <- keysof customUtils; forM_ ks $ \k -> do v <- getp customUtils k; c <- readIORef (uCustom u); setp c k v
1048
- Nothing -> pure ()
1049
- _ -> pure ()
1050
- optsC <- clone options
1051
- opts0 <- case optsC of VMap _ -> pure optsC; _ -> emptyMap
1052
- -- Feature add-order. options.feature may be given as an ordered LIST of
1053
- -- {name, active, ...opts} entries (list position = add order) or a
1054
- -- {name => {opts}} map. Normalize a list to a map (so merge/validate/init
1055
- -- are unchanged) and remember the explicit order; a map defaults to
1056
- -- test-first so the `test` mock transport is the base of the wrapper chain.
1057
- featureRaw <- getp opts0 "feature"
1058
- explicitOrder <- case featureRaw of
1059
- VList ref -> do
1060
- entries <- readIORef ref
1061
- fmap' <- emptyMap
1062
- order <- fmap concat $ mapM (\entry -> case entry of
1063
- VMap _ -> do
1064
- nm <- getp entry "name"
1065
- case nm of
1066
- VStr name -> do
1067
- fopts <- clone entry
1068
- _ <- delprop fopts (VStr "name")
1069
- setp fmap' name fopts
1070
- pure [name]
1071
- _ -> pure []
1072
- _ -> pure []) entries
1073
- setp opts0 "feature" fmap'
1074
- pure (Just order)
1075
- _ -> pure Nothing
1076
- configV <- readIORef (cConfig ctx)
1077
- config <- case configV of VMap _ -> pure configV; _ -> emptyMap
1078
- cfgoptsV <- toMap <$> getp config "options"
1079
- cfgopts <- case cfgoptsV of VMap _ -> pure cfgoptsV; _ -> emptyMap
1080
- optspec <- optSpecValue
1081
- sysFetch <- getpathS opts0 "system.fetch"
1082
- em <- emptyMap
1083
- mlist <- ja [em, cfgopts, opts0]
1084
- merged <- merge mlist
1085
- validated <- validate INone merged optspec
1086
- opts <- case validated of VMap _ -> pure validated; _ -> emptyMap
1087
- -- Resolve a templated base URL (e.g. https://{tenant_id}.hanko.io).
1088
- -- Every placeholder must resolve to a non-empty value: from options.server
1089
- -- (user), else the Config default. A placeholder that resolves to "" is a
1090
- -- construction ERROR in live mode - the URL cannot work - but in test mode
1091
- -- substitutes the deterministic value "test-<name>" so offline tests need no
1092
- -- configuration. The SDK constructor has no error return, so a missing
1093
- -- required variable THROWS: construction-time misconfiguration.
1094
- baseV <- getp opts "base"
1095
- case baseV of
1096
- VStr base | '{' `elem` base -> do
1097
- taV <- getpathS opts "test.active"
1098
- faV <- getpathS opts "feature.test.active"
1099
- -- Value has no Eq instance: match the constructor.
1100
- let isTrue v = case v of VBool b -> b; _ -> False
1101
- testmode = isTrue taV || isTrue faV
1102
- server <- getp opts "server"
1103
- mnV <- getpathS config "main.name"
1104
- let sdkname = case mnV of VStr s | not (null s) -> s; _ -> "SDK"
1105
-
1106
- -- Scanned by hand: a placeholder is `{` followed by [A-Za-z0-9_]+ and
1107
- -- `}`; anything else is literal text, so a stray brace is left alone.
1108
- let resolve [] = pure []
1109
- resolve ('{' : rest) =
1110
- let (name, tail') = span (\c -> isAlphaNum c || '_' == c) rest
1111
- in case tail' of
1112
- ('}' : more) | not (null name) -> do
1113
- valV <- case server of VMap _ -> getp server name; _ -> pure VNoval
1114
- let val = case valV of VStr s -> s; _ -> ""
1115
- sub <- if not (null val)
1116
- then pure val
1117
- else if testmode
1118
- then pure ("test-" ++ name)
1119
- else do
1120
- e <- jo [ ("code", VStr "server_var_required")
1121
- , ("msg", VStr (sdkname ++ ": the server variable '" ++
1122
- name ++ "' is required: the API base URL is '" ++
1123
- base ++ "' - pass server = { \"" ++ name ++
1124
- "\": \"...\" } in the SDK options"))
1125
- , ("sdk", VStr "ProjectName") ]
1126
- throwIO (SdkException e)
1127
- more' <- resolve more
1128
- pure (sub ++ more')
1129
- _ -> do r <- resolve rest; pure ('{' : r)
1130
- resolve (c : rest) = do r <- resolve rest; pure (c : r)
1131
- resolved <- resolve base
1132
- setp opts "base" (VStr resolved)
1133
- _ -> pure ()
1134
-
1135
- when (not (isNoval sysFetch)) $ do
1136
- sys <- getp opts "system"
1137
- case sys of
1138
- VMap _ -> setp sys "fetch" sysFetch
1139
- _ -> do s <- jo [("fetch", sysFetch)]; setp opts "system" s
1140
- ckv <- getpathS opts "clean.keys"
1141
- let cleanKeys = case ckv of VStr s -> s; _ -> "key,token,id"
1142
- parts <- fmap concat $ mapM (\p -> let t = strip p in if t == "" then pure [] else do e <- escreS t; pure [e]) (splitOnChar ',' cleanKeys)
1143
- let keyre = intercalate' "|" parts
1144
- cleanEmpty <- emptyMap
1145
- derived <- jo [("clean", cleanEmpty)]
1146
- when (keyre /= "") $ do cm <- jo [("keyre", VStr keyre)]; setp derived "clean" cm
1147
- -- Resolve the feature add-order: an explicit list order (above) wins;
1148
- -- otherwise order the map test-first, then the remaining names sorted
1149
- -- (keysof returns sorted keys), so the result is deterministic.
1150
- featureOrder <- case explicitOrder of
1151
- Just ord -> pure ord
1152
- Nothing -> do
1153
- fmapV <- getp opts "feature"
1154
- names <- case fmapV of VMap _ -> keysof fmapV; _ -> pure []
1155
- pure $ if "test" `elem` names then "test" : filter (/= "test") names else names
1156
- orderList <- ja (map VStr featureOrder)
1157
- setp derived "featureorder" orderList
1158
- setp opts "__derived__" derived
1159
- pure opts
1160
-
1161
- intercalate' :: String -> [String] -> String
1162
- intercalate' _ [] = ""
1163
- intercalate' _ [x] = x
1164
- intercalate' sep (x : xs) = x ++ sep ++ intercalate' sep xs
1165
-
1166
- -- ------------------------------------------------------------------
1167
- -- struct api exposure (utility.struct)
1168
- -- ------------------------------------------------------------------
1169
-
1170
- structApiInstance :: StructApi
1171
- structApiInstance = StructApi
1172
- { sGetprop = getprop
1173
- , sSetprop = setprop
1174
- , sGetpath = getpath INone
1175
- , sGetelem = getelem
1176
- , sClone = clone
1177
- , sMerge = \l -> mkList l >>= merge
1178
- , sItems = items
1179
- , sKeysof = keysof
1180
- , sSize = size
1181
- , sIsempty = isempty
1182
- , sStringify = stringify
1183
- , sJsonify = jsonifyCompact
1184
- , sEscurl = escurlS . vstring
1185
- , sEscre = escreS . vstring
1186
- , sTransform = transform INone
1187
- , sValidate = validate INone
1188
- , sSelect = select
1189
- , sWalk = \fn v -> walk (Just fn) Nothing VNoval v
1190
- }
1191
-
1192
- -- ------------------------------------------------------------------
1193
- -- utility construction
1194
- -- ------------------------------------------------------------------
1195
-
1196
- newUtility :: IO Utility
1197
- newUtility = do
1198
- custom <- newIORef =<< emptyMap
1199
- fetcher <- newIORef fetcherUtil
1200
- param <- newIORef paramUtil
1201
- pure Utility { uCustom = custom, uStruct = structApiInstance, uFetcher = fetcher, uParam = param }
1202
-
1203
- copyUtility :: Utility -> IO Utility
1204
- copyUtility src = do
1205
- f <- readIORef (uFetcher src)
1206
- p <- readIORef (uParam src)
1207
- fetcher <- newIORef f
1208
- param <- newIORef p
1209
- srcCustom <- readIORef (uCustom src)
1210
- custom <- emptyMap
1211
- case srcCustom of
1212
- VMap _ -> do ks <- keysof srcCustom; forM_ ks $ \k -> do v <- getp srcCustom k; setp custom k v
1213
- _ -> pure ()
1214
- customR <- newIORef custom
1215
- pure Utility { uCustom = customR, uStruct = uStruct src, uFetcher = fetcher, uParam = param }
1216
-
1217
- -- struct join helper: join parts with "/" as a URL path
1218
- join_ :: Value -> IO String
1219
- join_ arr = join arr (VStr "/") True