1
+ /**
2
+ * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ * Modifictions copyright 2021 Datadog, Inc.
4
+ *
5
+ * This module defines the functions for loading the user's code as specified
6
+ * in a handler string.
7
+ */
8
+
9
+ "use strict" ;
10
+
11
+ import path from "path" ;
12
+ import fs from "fs" ;
13
+ import {
14
+ HandlerNotFound ,
15
+ MalformedHandlerName ,
16
+ ImportModuleError ,
17
+ UserCodeSyntaxError ,
18
+ ExtendedError ,
19
+ } from "./errors" ;
20
+
21
+ const FUNCTION_EXPR = / ^ ( [ ^ . ] * ) \. ( .* ) $ / ;
22
+ const RELATIVE_PATH_SUBSTRING = ".." ;
23
+
24
+ /**
25
+ * Break the full handler string into two pieces, the module root and the actual
26
+ * handler string.
27
+ * Given './somepath/something/module.nestedobj.handler' this returns
28
+ * ['./somepath/something', 'module.nestedobj.handler']
29
+ */
30
+ function _moduleRootAndHandler ( fullHandlerString : string ) : [ string , string ] {
31
+ const handlerString = path . basename ( fullHandlerString ) ;
32
+ const moduleRoot = fullHandlerString . substring (
33
+ 0 ,
34
+ fullHandlerString . indexOf ( handlerString )
35
+ ) ;
36
+ return [ moduleRoot , handlerString ] ;
37
+ }
38
+
39
+ /**
40
+ * Split the handler string into two pieces: the module name and the path to
41
+ * the handler function.
42
+ */
43
+ function _splitHandlerString ( handler : string ) : [ string , string ] {
44
+ const match = handler . match ( FUNCTION_EXPR ) ;
45
+ if ( ! match || match . length != 3 ) {
46
+ throw new MalformedHandlerName ( "Bad handler" ) ;
47
+ }
48
+ return [ match [ 1 ] , match [ 2 ] ] ; // [module, function-path]
49
+ }
50
+
51
+ /**
52
+ * Resolve the user's handler function from the module.
53
+ */
54
+ function _resolveHandler ( object : any , nestedProperty : string ) : any {
55
+ return nestedProperty . split ( "." ) . reduce ( ( nested , key ) => {
56
+ return nested && nested [ key ] ;
57
+ } , object ) ;
58
+ }
59
+
60
+ /**
61
+ * Verify that the provided path can be loaded as a file per:
62
+ * https://nodejs.org/dist/latest-v10.x/docs/api/modules.html#modules_all_together
63
+ * @param string - the fully resolved file path to the module
64
+ * @return bool
65
+ */
66
+ function _canLoadAsFile ( modulePath : string ) : boolean {
67
+ return fs . existsSync ( modulePath ) || fs . existsSync ( modulePath + ".js" ) ;
68
+ }
69
+
70
+ /**
71
+ * Attempt to load the user's module.
72
+ * Attempts to directly resolve the module relative to the application root,
73
+ * then falls back to the more general require().
74
+ */
75
+ function _tryRequire ( appRoot : string , moduleRoot : string , module : string ) : any {
76
+ const lambdaStylePath = path . resolve ( appRoot , moduleRoot , module ) ;
77
+ if ( _canLoadAsFile ( lambdaStylePath ) ) {
78
+ return require ( lambdaStylePath ) ;
79
+ } else {
80
+ // Why not just require(module)?
81
+ // Because require() is relative to __dirname, not process.cwd()
82
+ const nodeStylePath = require . resolve ( module , {
83
+ paths : [ appRoot , moduleRoot ] ,
84
+ } ) ;
85
+ return require ( nodeStylePath ) ;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Load the user's application or throw a descriptive error.
91
+ * @throws Runtime errors in two cases
92
+ * 1 - UserCodeSyntaxError if there's a syntax error while loading the module
93
+ * 2 - ImportModuleError if the module cannot be found
94
+ */
95
+ function _loadUserApp (
96
+ appRoot : string ,
97
+ moduleRoot : string ,
98
+ module : string
99
+ ) : any {
100
+ try {
101
+ return _tryRequire ( appRoot , moduleRoot , module ) ;
102
+ } catch ( e ) {
103
+ if ( e instanceof SyntaxError ) {
104
+ throw new UserCodeSyntaxError ( < any > e ) ;
105
+ // @ts -ignore
106
+ } else if ( e . code !== undefined && e . code === "MODULE_NOT_FOUND" ) {
107
+ // @ts -ignore
108
+ throw new ImportModuleError ( e ) ;
109
+ } else {
110
+ throw e ;
111
+ }
112
+ }
113
+ }
114
+
115
+ function _throwIfInvalidHandler ( fullHandlerString : string ) : void {
116
+ if ( fullHandlerString . includes ( RELATIVE_PATH_SUBSTRING ) ) {
117
+ throw new MalformedHandlerName (
118
+ `'${ fullHandlerString } ' is not a valid handler name. Use absolute paths when specifying root directories in handler names.`
119
+ ) ;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Load the user's function with the approot and the handler string.
125
+ * @param appRoot {string}
126
+ * The path to the application root.
127
+ * @param handlerString {string}
128
+ * The user-provided handler function in the form 'module.function'.
129
+ * @return userFuction {function}
130
+ * The user's handler function. This function will be passed the event body,
131
+ * the context object, and the callback function.
132
+ * @throws In five cases:-
133
+ * 1 - if the handler string is incorrectly formatted an error is thrown
134
+ * 2 - if the module referenced by the handler cannot be loaded
135
+ * 3 - if the function in the handler does not exist in the module
136
+ * 4 - if a property with the same name, but isn't a function, exists on the
137
+ * module
138
+ * 5 - the handler includes illegal character sequences (like relative paths
139
+ * for traversing up the filesystem '..')
140
+ * Errors for scenarios known by the runtime, will be wrapped by Runtime.* errors.
141
+ */
142
+ export const load = function (
143
+ appRoot : string ,
144
+ fullHandlerString : string
145
+ ) {
146
+ _throwIfInvalidHandler ( fullHandlerString ) ;
147
+
148
+ const [ moduleRoot , moduleAndHandler ] = _moduleRootAndHandler (
149
+ fullHandlerString
150
+ ) ;
151
+ const [ module , handlerPath ] = _splitHandlerString ( moduleAndHandler ) ;
152
+
153
+ const userApp = _loadUserApp ( appRoot , moduleRoot , module ) ;
154
+ const handlerFunc = _resolveHandler ( userApp , handlerPath ) ;
155
+
156
+ if ( ! handlerFunc ) {
157
+ throw new HandlerNotFound (
158
+ `${ fullHandlerString } is undefined or not exported`
159
+ ) ;
160
+ }
161
+
162
+ if ( typeof handlerFunc !== "function" ) {
163
+ throw new HandlerNotFound ( `${ fullHandlerString } is not a function` ) ;
164
+ }
165
+
166
+ return handlerFunc ;
167
+ } ;
0 commit comments