├── .gitignore ├── Docker ├── Dockerfile └── base │ └── Dockerfile ├── Examples └── Classic │ ├── blob │ ├── .Dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Package.swift │ └── Sources │ │ └── blob │ │ ├── functions │ │ └── simpleblob.swift │ │ └── main.swift │ ├── http │ ├── .Dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Package.swift │ └── Sources │ │ └── http │ │ ├── functions │ │ └── simpleHttp.swift │ │ └── main.swift │ ├── queue │ ├── .Dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Package.swift │ └── Sources │ │ └── queue │ │ ├── functions │ │ ├── queueOutput.swift │ │ └── queueTrigger.swift │ │ └── main.swift │ ├── servicebus │ ├── .Dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Package.swift │ └── Sources │ │ └── servicebus │ │ ├── functions │ │ └── simple.swift │ │ └── main.swift │ └── timer │ ├── .Dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Package.swift │ └── Sources │ └── timer │ ├── functions │ └── timerfunc.swift │ └── main.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── AzureFunctions │ ├── AzureFunction.swift │ ├── AzureFunctionsWorker.swift │ ├── Bindings │ ├── Binding.swift │ ├── BindingFactory.swift │ ├── Blob.swift │ ├── Queue.swift │ ├── ServiceBusMessage.swift │ ├── Table.swift │ ├── Timer.swift │ ├── http │ │ ├── HttpRequest.swift │ │ └── HttpResponse.swift │ └── models │ │ ├── InvocationRequest.swift │ │ └── InvocationResponse.swift │ ├── Broker.swift │ ├── CodeGen.swift │ ├── Context.swift │ ├── Definitions.swift │ ├── Extentions.swift │ ├── FunctionInfo.swift │ ├── FunctionRegistry.swift │ ├── HandlerHTTPServer.swift │ ├── Logger.swift │ ├── RpcConverter.swift │ ├── Templates.swift │ ├── WorkerChannel.swift │ ├── WorkerChannelProtocol.swift │ └── protobuf │ ├── FunctionRpc.grpc.swift │ ├── FunctionRpc.pb.swift │ ├── identity │ └── ClaimsIdentityRpc.pb.swift │ └── shared │ └── NullableTypes.pb.swift ├── Tests ├── AzureFunctionsTests │ ├── SwiftFuncTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── protobuf ├── .gitignore ├── LICENSE ├── README.md └── src └── proto ├── FunctionRpc.proto ├── identity └── ClaimsIdentityRpc.proto └── shared └── NullableTypes.proto /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | /protobuf 7 | /.swiftpm 8 | Package.resolved 9 | -------------------------------------------------------------------------------- /Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM salehalbuga/azure-functions-swift-runtime:1.0 2 | 3 | LABEL MAINTAINER Saleh Albuga 4 | 5 | ENV languageWorkers__workersDirectory=/home/site/wwwroot/workers 6 | 7 | CMD ["/bin/true"] 8 | -------------------------------------------------------------------------------- /Docker/base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.2-bionic-slim 2 | LABEL MAINTAINER Saleh Albuga 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | ca-certificates \ 6 | # .NET Core dependencies 7 | #krb5-libs libgcc libintl libssl1.0 libstdc++ lttng-ust userspace-rcu zlib\ 8 | build-essential \ 9 | libkrb5-dev \ 10 | libc6-dev \ 11 | libssl1.0-dev \ 12 | libstdc++6 \ 13 | liblttng-ust0 \ 14 | tzdata \ 15 | liburcu-dev \ 16 | zlib1g-dev 17 | 18 | # Configure web servers to bind to port 80 when present 19 | ENV ASPNETCORE_URLS=http://+:80 \ 20 | # Enable detection of running in a container 21 | DOTNET_RUNNING_IN_CONTAINER=true \ 22 | # Set the invariant mode since icu_libs isn't included (see https://github.com/dotnet/announcements/issues/20) 23 | DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true \ 24 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true \ 25 | DOTNET_CLI_TELEMETRY_OPTOUT=true 26 | 27 | ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ 28 | HOME=/home \ 29 | FUNCTIONS_WORKER_RUNTIME=swift \ 30 | AzureFunctionsJobHost__Logging__Console__IsEnabled=true 31 | 32 | CMD ["/bin/true"] -------------------------------------------------------------------------------- /Examples/Classic/blob/.Dockerignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | .git -------------------------------------------------------------------------------- /Examples/Classic/blob/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ -------------------------------------------------------------------------------- /Examples/Classic/blob/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.0 AS build-image 2 | 3 | WORKDIR /src 4 | COPY . . 5 | RUN swift build -c release 6 | WORKDIR /home/site/wwwroot 7 | RUN [ "/src/.build/release/functions", "export", "--source", "/src", "--root", "/home/site/wwwroot" ] 8 | 9 | FROM mcr.microsoft.com/azure-functions/base:2.0 as functions-image 10 | 11 | FROM salehalbuga/azure-functions-swift-runtime:0.0.10 12 | 13 | COPY --from=functions-image [ "/azure-functions-host", "/azure-functions-host" ] 14 | 15 | COPY --from=build-image ["/home/site/wwwroot", "/home/site/wwwroot/"] 16 | 17 | CMD [ "/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost" ] 18 | -------------------------------------------------------------------------------- /Examples/Classic/blob/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "blob", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .executable(name: "functions", targets: ["blob"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | .package(url: "https://github.com/SalehAlbuga/azure-functions-swift", from: "0.6.2"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "blob", 24 | dependencies: [.product(name: "AzureFunctions", package: "azure-functions-swift")]) 25 | ] 26 | ) -------------------------------------------------------------------------------- /Examples/Classic/blob/Sources/blob/functions/simpleblob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // simpleblob.swift 3 | // blob 4 | // 5 | // Created on 26-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class simpleblob: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "simpleblob" 16 | self.trigger = Blob(name: "blobTrigger", path: "sample/{filename}", connection: "AzureWebJobsStorage") 17 | } 18 | 19 | override func exec(blob: Blob, context: inout Context, callback: @escaping callback) throws { 20 | if let data = blob.blob as? Data, let content = String(data: data, encoding: .utf8) { 21 | context.log("Got file with Content-Type \(blob.properties["ContentType"] ?? ""), content: \(content)") 22 | } 23 | 24 | return callback(true) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Classic/blob/Sources/blob/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Auto Generated by SwiftFunctionsSDK 6 | // 7 | // Only set env vars or register/remove Functions. Do Not modify/add other code 8 | // 9 | 10 | import AzureFunctions 11 | 12 | let registry = FunctionRegistry() 13 | 14 | // ****** optional: set debug AzureWebJobsStorage or other vars ****** 15 | //registry.AzureWebJobsStorage = "yourDebugConnection" //Remove before deploying. Do not commit or push any Storage Account keys 16 | //registry.EnvironmentVariables = ["fooConnection": "bar"] 17 | 18 | // ****** 19 | 20 | registry.register(simpleblob.self) 21 | 22 | 23 | AzureFunctionsWorker.shared.main(registry: registry) -------------------------------------------------------------------------------- /Examples/Classic/http/.Dockerignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | .git -------------------------------------------------------------------------------- /Examples/Classic/http/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ -------------------------------------------------------------------------------- /Examples/Classic/http/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.0 AS build-image 2 | 3 | WORKDIR /src 4 | COPY . . 5 | RUN swift build -c release 6 | WORKDIR /home/site/wwwroot 7 | RUN [ "/src/.build/release/functions", "export", "--source", "/src", "--root", "/home/site/wwwroot" ] 8 | 9 | FROM mcr.microsoft.com/azure-functions/base:2.0 as functions-image 10 | 11 | FROM salehalbuga/azure-functions-swift-runtime:0.0.10 12 | 13 | COPY --from=functions-image [ "/azure-functions-host", "/azure-functions-host" ] 14 | 15 | COPY --from=build-image ["/home/site/wwwroot", "/home/site/wwwroot/"] 16 | 17 | CMD [ "/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost" ] 18 | -------------------------------------------------------------------------------- /Examples/Classic/http/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "http", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .executable(name: "functions", targets: ["http"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | .package(url: "https://github.com/SalehAlbuga/azure-functions-swift", from: "0.6.2"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "http", 24 | dependencies: [.product(name: "AzureFunctions", package: "azure-functions-swift")]) 25 | ] 26 | ) -------------------------------------------------------------------------------- /Examples/Classic/http/Sources/http/functions/simpleHttp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // simpleHttp.swift 3 | // http 4 | // 5 | // Created on 08-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class simpleHttp: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "simpleHttp" 16 | self.trigger = HttpRequest(name: "req", methods: ["GET", "POST"]) 17 | } 18 | 19 | override func exec(request: HttpRequest, context: inout Context, callback: @escaping callback) throws { 20 | 21 | context.log("Function executing!") 22 | 23 | let res = HttpResponse() 24 | var name: String? 25 | 26 | if let data = request.body, let bodyObj: [String: Any] = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { 27 | name = bodyObj["name"] as? String 28 | } else { 29 | name = request.query["name"] 30 | } 31 | res.body = "Hello \(name ?? "buddy")!".data(using: .utf8) 32 | 33 | return callback(res); 34 | } 35 | } -------------------------------------------------------------------------------- /Examples/Classic/http/Sources/http/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Auto Generated by SwiftFunctionsSDK 6 | // 7 | // Only set env vars or register/remove Functions. Do Not modify/add other code 8 | // 9 | 10 | import AzureFunctions 11 | 12 | let registry = FunctionRegistry() 13 | 14 | // ****** optional: set debug AzureWebJobsStorage or other vars ****** 15 | //registry.AzureWebJobsStorage = "yourDebugConnection" //Remove before deploying. Do not commit or push any Storage Account keys 16 | //registry.EnvironmentVariables = ["fooConnection": "bar"] 17 | 18 | // ****** 19 | 20 | registry.register(simpleHttp.self) 21 | 22 | 23 | AzureFunctionsWorker.shared.main(registry: registry) -------------------------------------------------------------------------------- /Examples/Classic/queue/.Dockerignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | .git -------------------------------------------------------------------------------- /Examples/Classic/queue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ -------------------------------------------------------------------------------- /Examples/Classic/queue/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.0 AS build-image 2 | 3 | WORKDIR /src 4 | COPY . . 5 | RUN swift build -c release 6 | WORKDIR /home/site/wwwroot 7 | RUN [ "/src/.build/release/functions", "export", "--source", "/src", "--root", "/home/site/wwwroot" ] 8 | 9 | FROM mcr.microsoft.com/azure-functions/base:2.0 as functions-image 10 | 11 | FROM salehalbuga/azure-functions-swift-runtime:0.0.10 12 | 13 | COPY --from=functions-image [ "/azure-functions-host", "/azure-functions-host" ] 14 | 15 | COPY --from=build-image ["/home/site/wwwroot", "/home/site/wwwroot/"] 16 | 17 | CMD [ "/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost" ] 18 | -------------------------------------------------------------------------------- /Examples/Classic/queue/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "queue", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .executable(name: "functions", targets: ["queue"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | .package(url: "https://github.com/SalehAlbuga/azure-functions-swift", from: "0.6.2"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "queue", 24 | dependencies: [.product(name: "AzureFunctions", package: "azure-functions-swift")]) 25 | ] 26 | ) -------------------------------------------------------------------------------- /Examples/Classic/queue/Sources/queue/functions/queueOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // queueOutput.swift 3 | // queue 4 | // 5 | // Created on 08-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class queueOutput: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "queueOutput" 16 | self.trigger = Queue(name: "myQueueTrigger", queueName: "queueName", connection: "AzureWebJobsStorage") 17 | self.outputBindings = [Queue(name: "queueOutput", queueName: "outQueueName", connection: "AzureWebJobsStorage")] 18 | } 19 | 20 | override func exec(string: String, context: inout Context, callback: @escaping callback) throws { 21 | context.log("Got queue item: \(string), writing to output queue!") 22 | context.outputBindings["queueOutput"] = string 23 | callback(true) 24 | } 25 | } -------------------------------------------------------------------------------- /Examples/Classic/queue/Sources/queue/functions/queueTrigger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // queueTrigger.swift 3 | // queue 4 | // 5 | // Created on 08-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class queueTrigger: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "queueTrigger" 16 | self.trigger = Queue(name: "myQueueTrigger", queueName: "queueName", connection: "AzureWebJobsStorage") 17 | } 18 | 19 | override func exec(string: String, context: inout Context, callback: @escaping callback) throws { 20 | context.log("Got queue item: \(string)") 21 | callback(true) 22 | } 23 | } -------------------------------------------------------------------------------- /Examples/Classic/queue/Sources/queue/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Auto Generated by SwiftFunctionsSDK 6 | // 7 | // Only set env vars or register/remove Functions. Do Not modify/add other code 8 | // 9 | 10 | import AzureFunctions 11 | 12 | let registry = FunctionRegistry() 13 | 14 | // ****** optional: set debug AzureWebJobsStorage or other vars ****** 15 | //registry.AzureWebJobsStorage = "yourDebugConnection" //Remove before deploying. Do not commit or push any Storage Account keys 16 | //registry.EnvironmentVariables = ["fooConnection": "bar"] 17 | 18 | // ****** 19 | 20 | registry.register(queueOutput.self) 21 | registry.register(queueTrigger.self) 22 | 23 | 24 | AzureFunctionsWorker.shared.main(registry: registry) -------------------------------------------------------------------------------- /Examples/Classic/servicebus/.Dockerignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | .git -------------------------------------------------------------------------------- /Examples/Classic/servicebus/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ -------------------------------------------------------------------------------- /Examples/Classic/servicebus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.0 AS build-image 2 | 3 | WORKDIR /src 4 | COPY . . 5 | RUN swift build -c release 6 | WORKDIR /home/site/wwwroot 7 | RUN [ "/src/.build/release/functions", "export", "--source", "/src", "--root", "/home/site/wwwroot" ] 8 | 9 | FROM mcr.microsoft.com/azure-functions/base:2.0 as functions-image 10 | 11 | FROM salehalbuga/azure-functions-swift-runtime:0.0.10 12 | 13 | COPY --from=functions-image [ "/azure-functions-host", "/azure-functions-host" ] 14 | 15 | COPY --from=build-image ["/home/site/wwwroot", "/home/site/wwwroot/"] 16 | 17 | CMD [ "/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost" ] 18 | -------------------------------------------------------------------------------- /Examples/Classic/servicebus/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "servicebus", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .executable(name: "functions", targets: ["servicebus"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | .package(url: "https://github.com/SalehAlbuga/azure-functions-swift", from: "0.6.2"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "servicebus", 24 | dependencies: [.product(name: "AzureFunctions", package: "azure-functions-swift")]) 25 | ] 26 | ) -------------------------------------------------------------------------------- /Examples/Classic/servicebus/Sources/servicebus/functions/simple.swift: -------------------------------------------------------------------------------- 1 | // 2 | // simple.swift 3 | // servicebus 4 | // 5 | // Created on 08-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class simple: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "simple" 16 | self.trigger = ServiceBusMessage(name: "sbTrigger", topicName: "mytopic", subscriptionName: "mysubscription", connection: "ServiceBusConnection") 17 | } 18 | 19 | override func exec(sbMessage: ServiceBusMessage, context: inout Context, callback: @escaping callback) throws { 20 | if let msg: String = sbMessage.message as? String { 21 | context.log("Got topic message: \(msg)") 22 | } 23 | 24 | callback(true) 25 | } 26 | } -------------------------------------------------------------------------------- /Examples/Classic/servicebus/Sources/servicebus/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Auto Generated by SwiftFunctionsSDK 6 | // 7 | // Only set env vars or register/remove Functions. Do Not modify/add other code 8 | // 9 | 10 | import AzureFunctions 11 | 12 | let registry = FunctionRegistry() 13 | 14 | // ****** optional: set debug AzureWebJobsStorage or other vars ****** 15 | //registry.AzureWebJobsStorage = "yourDebugConnection" //Remove before deploying. Do not commit or push any Storage Account keys 16 | //registry.EnvironmentVariables = ["fooConnection": "bar"] 17 | 18 | // ****** 19 | 20 | registry.register(sbOutput.self) 21 | registry.register(simple.self) 22 | 23 | 24 | AzureFunctionsWorker.shared.main(registry: registry) -------------------------------------------------------------------------------- /Examples/Classic/timer/.Dockerignore: -------------------------------------------------------------------------------- 1 | .build 2 | .vscode 3 | .git -------------------------------------------------------------------------------- /Examples/Classic/timer/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | /*.Package.resolved 7 | -------------------------------------------------------------------------------- /Examples/Classic/timer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:5.0 AS build-image 2 | 3 | WORKDIR /src 4 | COPY . . 5 | RUN swift build -c release 6 | WORKDIR /home/site/wwwroot 7 | RUN [ "/src/.build/release/functions", "export", "--source", "/src", "--root", "/home/site/wwwroot" ] 8 | 9 | FROM mcr.microsoft.com/azure-functions/base:2.0 as functions-image 10 | 11 | FROM salehalbuga/azure-functions-swift-runtime:0.0.10 12 | 13 | COPY --from=functions-image [ "/azure-functions-host", "/azure-functions-host" ] 14 | 15 | COPY --from=build-image ["/home/site/wwwroot", "/home/site/wwwroot/"] 16 | 17 | CMD [ "/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost" ] 18 | -------------------------------------------------------------------------------- /Examples/Classic/timer/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "timer", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | .executable(name: "functions", targets: ["timer"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | .package(url: "https://github.com/SalehAlbuga/azure-functions-swift", from: "0.6.2"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "timer", 24 | dependencies: [.product(name: "AzureFunctions", package: "azure-functions-swift")]) 25 | ] 26 | ) -------------------------------------------------------------------------------- /Examples/Classic/timer/Sources/timer/functions/timerfunc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // timerfunc.swift 3 | // timer 4 | // 5 | // Created on 26-12-19. 6 | // 7 | 8 | import Foundation 9 | import AzureFunctions 10 | 11 | class timerfunc: Function { 12 | 13 | required init() { 14 | super.init() 15 | self.name = "timerfunc" 16 | self.trigger = TimerTrigger(name: "myTimer", schedule: "*/5 * * * * *") 17 | } 18 | 19 | override func exec(timer: TimerTrigger, context: inout Context, callback: @escaping callback) throws { 20 | context.log("It is time!") 21 | callback(true) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /Examples/Classic/timer/Sources/timer/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Auto Generated by SwiftFunctionsSDK 6 | // 7 | // Only set env vars or register/remove Functions. Do Not modify/add other code 8 | // 9 | 10 | import AzureFunctions 11 | 12 | let registry = FunctionRegistry() 13 | 14 | // ****** optional: set debug AzureWebJobsStorage or other vars ****** 15 | //registry.AzureWebJobsStorage = "yourDebugConnection" //Remove before deploying. Do not commit or push any Storage Account keys 16 | //registry.EnvironmentVariables = ["fooConnection": "bar"] 17 | 18 | // ****** 19 | 20 | registry.register(timerfunc.self) 21 | 22 | 23 | AzureFunctionsWorker.shared.main(registry: registry) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Saleh Albuga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "azure-functions-swift", 8 | platforms: [ 9 | .macOS(.v10_15) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "AzureFunctions", 15 | targets: ["AzureFunctions"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | .package(url: "https://github.com/vapor/vapor.git", from: "4.8.0"), 20 | .package(url: "https://github.com/Flight-School/AnyCodable", from: "0.2.3"), 21 | .package(url: "https://github.com/grpc/grpc-swift", .exact("1.0.0-alpha.12")), 22 | .package(url: "https://github.com/JohnSundell/Files", from: "4.1.1"), 23 | .package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.1"), 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 27 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 28 | .target( 29 | name: "AzureFunctions", 30 | dependencies: ["Files", "Stencil", "AnyCodable", .product(name: "Vapor", package: "vapor"), .product(name: "GRPC", package: "grpc-swift")]), 31 | .testTarget( 32 | name: "AzureFunctionsTests", 33 | dependencies: ["AzureFunctions"]), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions for Swift ⚡️ 2 | 3 | [![ver](https://img.shields.io/github/v/release/salehalbuga/azure-functions-swift?include_prereleases&label=version)](https://swiftfunc.developerhub.io) 4 | [![cliver](https://img.shields.io/github/v/release/salehalbuga/azure-functions-swift-tools?include_prereleases&label=CLI+version)](https://github.com/SalehAlbuga/azure-functions-swift-tools) 5 | [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) 6 | [![docs-status](https://img.shields.io/badge/read_the-docs-2196f3.svg)](https://docs.swiftfunc.dev/) 7 | [![Swift version](https://img.shields.io/badge/swift-5.2-brightgreen.svg)](https://swift.org) 8 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 9 | 10 | [![Chat](https://img.shields.io/discord/713477339463418016?label=Join%20Azure%20Functions%20Chat)](http://discord.gg/6rDzSuM) 11 | 12 | 13 | Write [Azure Functions](https://azure.microsoft.com/en-us/services/functions/) 14 | in [Swift](https://swift.org). 15 | 16 | #### This framework supports the new Azure Functions [Custom Handlers](https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers) (starting from 0.6.0) in addition to the traditional custom worker. 17 | 18 | > _Disclaimer: This is a community open source project, not an official Azure project_ 19 | 20 | #### [Documentation](https://docs.swiftfunc.dev/) 21 | 22 | Deploy a sample project to Azure! 23 | 24 | Classic worker sample: 25 | 26 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](http://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fgist.githubusercontent.com%2FSalehAlbuga%2Fb0d9eeaae04cc07faf95f11b01143e40%2Fraw%2Ff70a0896960d5d6d04ce3bcd5de06a3fed2d8c0b%2Fswiftfunc-classic-sample-arm.json) 27 | 28 | Custom Handler sample: 29 | 30 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fgist.githubusercontent.com%2FSalehAlbuga%2Fc937140075effe782996f12961b3f46d%2Fraw%2Fd94eb814fbb2250908e242aafea650576d620833%2Fswiftfunc-sample-arm.json) 31 | 32 | 33 | ## Examples 34 | 35 | A Timer Function (Custom Handler): 36 | 37 | ```swift 38 | import Foundation 39 | import AzureFunctions 40 | import Vapor 41 | 42 | class TimerFunction: Function { 43 | 44 | required init() { 45 | super.init() 46 | self.name = "TimerFunction" 47 | self.functionJsonBindings = 48 | [ 49 | [ 50 | "type" : "timerTrigger", 51 | "name" : "myTimer", 52 | "direction" : "in", 53 | "schedule" : "*/5 * * * * *" 54 | ] 55 | ] 56 | //or 57 | //self.trigger = TimerTrigger(name: "myTimer", schedule: "*/5 * * * * *") 58 | 59 | app.post([PathComponent(stringLiteral: name)], use: run(req:)) 60 | } 61 | 62 | func run(req: Request) -> InvocationResponse { 63 | var res = InvocationResponse() 64 | res.appendLog("Its is time!") 65 | return res 66 | } 67 | } 68 | ``` 69 | 70 | An HTTP Function (Classic Worker): 71 | ```swift 72 | import Foundation 73 | import AzureFunctions 74 | 75 | class HttpFunction: Function { 76 | 77 | required init() { 78 | super.init() 79 | self.name = "HttpFunction" 80 | self.trigger = HttpRequest(name: "req") 81 | } 82 | 83 | override func exec(request: HttpRequest, context: inout Context, callback: @escaping callback) throws { 84 | 85 | let res = HttpResponse() 86 | var name: String? 87 | 88 | if let data = request.body, let bodyObj: [String: Any] = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { 89 | name = bodyObj["name"] as? String 90 | } else { 91 | name = request.query["name"] 92 | } 93 | res.body = "Hello \(name ?? "buddy")!".data(using: .utf8) 94 | 95 | return callback(res); 96 | } 97 | } 98 | ``` 99 | 100 | ## Getting Started 101 | 102 | ### Installation and Requirements 103 | 104 | #### **Swift 5.2 or later or Xcode 11 or later on macOS** 105 | 106 | Swift installation: https://swift.org/getting-started/#installing-swift 107 | 108 | #### **Azure Functions Core Tools** 109 | 110 | Install the latest [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools). 111 | 112 | #### **Swift Functions Tools** 113 | 114 | Just like Core Tools, Swift Functions Tools make Swift functions development easier and much more convenient. 115 | 116 | On **macOS**, you can install it from [Homebrew](https://brew.sh) 🍺 117 | ```bash 118 | brew install salehalbuga/formulae/swift-func 119 | ``` 120 | 121 | on **Linux**, 122 | 123 | Clone the repo the tools repo 124 | ```bash 125 | git clone https://github.com/SalehAlbuga/azure-functions-swift-tools 126 | ``` 127 | 128 | Install 129 | ```bash 130 | make install 131 | ``` 132 | 133 | It installs a CLI tool called `swiftfunc` that can be used to create projects, functions and run them locally. 134 | 135 | ### Creating a new Project/Azure Functions app 136 | 137 | Run the init command to create a new Azure Functions application: 138 | 139 | ``` bash 140 | swiftfunc init myApp [-hw] 141 | ``` 142 | 143 | It will create a new app in a new folder, and a folder named `functions` inside the Sources target where Functions should be (*/myApp/Sources/myApp/functions*). 144 | The project created is a Swift package project with the Azure Functions framework dependency. 145 | 146 | Pass `-hw` or `--http-worker` option to create the project with the [Custom Handler](https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers) template. 147 | 148 | ### Creating a simple HTTP function 149 | 150 | Inside the new directory of your project, run the following to create a new HTTP Function named `hello`: 151 | 152 | ``` bash 153 | swiftfunc new http -n hello [-hw] 154 | ``` 155 | 156 | The new function file will be created in the following path `Sources/myApp/functions/hello.swift`. 157 | 158 | Similar to the `init` command, pass `-hw` or `--http-worker` option to create the new function with the Custom Handler template. 159 | 160 | 161 | ### Running the new Functions App 162 | Run `swiftfunc run` in the project directory to run your Swift Functions project locally. It will compile the code and start the host for you *(as if you were running `func host start`)*. The host output should show you the URL of `hello` function created above. Click on it to run the function and see output! 163 | 164 | ## **Deploying to Azure ☁️** 165 | 166 | There are 2 methods to deploy Swift Functions to Azure 167 | 168 | ### Container Functions 169 | 170 | To deploy the Function App in a Container, you can either use the Functions Core Tool `func deploy` command, where it will build the image, push it to a registry and set it in the destination Function App or you can do that manually as shown below. 171 | 172 | Build the image (Dockerfile is provided when the project is created) 173 | ```bash 174 | docker build -t . 175 | ``` 176 | If you're using DockerHub then the tag would be `username/imageName:version`. 177 | If you're using ACR ([Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/)) or any other private registry the tag would be `registryURL/imageName:version` 178 | 179 | Then push it 180 | ```bash 181 | docker push 182 | ``` 183 | 184 | In [Azure portal](https://portal.azure.com), create a new Function App with **Docker Container** as the Publish option. Under Hosting options make sure Linux is selected as OS. 185 | 186 | Once the app is created or in any existing Container Function App, under **Platform Features**, select **Container settings** and set the registry and select image you pushed. 187 | 188 | You can use the buttons below to deploy prebuilt sample project to your Azure subscription 189 | 190 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](http://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fgist.githubusercontent.com%2FSalehAlbuga%2Fb0d9eeaae04cc07faf95f11b01143e40%2Fraw%2Ff70a0896960d5d6d04ce3bcd5de06a3fed2d8c0b%2Fswiftfunc-classic-sample-arm.json) 191 | 192 | Custom Handler sample: 193 | 194 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fgist.githubusercontent.com%2FSalehAlbuga%2Fc937140075effe782996f12961b3f46d%2Fraw%2Fd94eb814fbb2250908e242aafea650576d620833%2Fswiftfunc-sample-arm.json) 195 | 196 | ### Hosting on a Linux Consumption Plan 197 | 198 | First, you need to set the following App Setting in the Function App on Azure. 199 | `LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/site/wwwroot/workers/swift/lib/` 200 | 201 | Then depending if you're developing on a Linux machine or a Mac: 202 | 203 | #### Linux 204 | 205 | Login to your Azure account from Azure CLI 206 | ```bash 207 | az login 208 | ``` 209 | 210 | When Azure CLI finishes loading your subscription(s) info, run: 211 | ```bash 212 | swiftfunc publish myswiftfunctions 213 | ``` 214 | 215 | Swift Function Tools publish command is going to compile, export and publish your Swift Functions project. 216 | 217 | #### macOS 218 | 219 | Publishing to a Function App in a Linux Consumption Plan from macOS requires the app to be build in a Linux container first, to do that you can use VSCode Dev Containers. 220 | The project needs to be created with the `-dc` or `--dev-container` option to have the Swift Function Dev Container added (or you can create a new one and copy the .devcontainer folder to your project). 221 | `swiftfunc init myFunctionApp -hw -dc` 222 | 223 | Reopen the folder in dev container (Command-Shift-P, search for and select _Remote-Containers: Reopen in Container_) 224 | 225 | Once the dev container is ready, follow the same Linux steps above to publish the app! 226 | 227 | ## **Bindings** 228 | Azure Functions offer a variety of [Bindings and Triggers](https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings) 229 | 230 | The trigger, input bindings and output bindings of a Function are set in its initializer. Azure Functions in Swift must subclass the **Function** class from the framework. 231 | 232 | ### Custom Handler (HTTP Worker) 233 | 234 | When using the Custom Handler mode you can use all Azure Functions bindings and triggers by setting the `functionJsonBindings` property to the JSON config of the bindings/triggers in Azure Functions [docs](https://docs.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings#supported-bindings). You can also use the framework supported Trigger/Binding types listed below. 235 | 236 | ### Traditional Worker (Classic) 237 | 238 | Currently the following are supported by this mode. More bindings will be implemented and many improvements will be made in the future. 239 | 240 | | Swift Type | Azure Functions Binding | Direction | 241 | |----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------|----------------| 242 | | HttpRequest | HTTP Trigger | in | 243 | | HttpResponse| Output HTTP Response | out | 244 | | TimerTrigger | Timer Trigger | in | 245 | | Message datatype **String** (binding defined by Table in constructor) | Input and Ouput Table | in, out | 246 | | Message datatype **String** (binding defined by Queue in constructor) | Output Queue Message | out | 247 | | Message datatype **String** (binding defined by Queue in constructor) | Queue Trigger | in | 248 | | Blob (the blob data prob is either String or Data) | Input Blob | in | 249 | | String or Data | Output Blob | out | 250 | | Blob | Blob Trigger | in | 251 | | ServiceBusMessage | Service Bus Output Message | out | 252 | | ServiceBusMessage | Service Bus Trigger | in | 253 | 254 | ### Custom Handler (HTTP Worker) 255 | 256 | ```swift 257 | import AzureFunctions 258 | import Vapor 259 | 260 | class QueueFunction: Function { 261 | 262 | required init() { 263 | super.init() 264 | self.name = "QueueFunction" 265 | self.functionJsonBindings = [ 266 | [ 267 | "connection" : "AzureWebJobsStorage", 268 | "type" : "queueTrigger", 269 | "name" : "myQueueTrigger", 270 | "queueName" : "myqueue", 271 | "direction" : "in" 272 | ] 273 | ] 274 | // or 275 | //self.trigger = Queue(name: "myQueueTrigger", queueName: "myqueue", connection: "AzureWebJobsStorage") 276 | 277 | app.post([PathComponent(stringLiteral: name)], use: run(req:)) 278 | } 279 | 280 | func run(req: Request) -> InvocationResponse { 281 | ... 282 | ``` 283 | 284 | ### Traditional Worker (Classic) 285 | 286 | ```swift 287 | import AzureFunctions 288 | 289 | class HttpFunction: Function { 290 | 291 | required init() { 292 | super.init() 293 | self.name = "HttpFunction" 294 | self.trigger = HttpRequest(name: "req") 295 | self.inputBindings = [Blob(name: "fileInput", path: "container/myBlob.json", connection: "AzureWebJobsStorage")] 296 | self.outputBindings = [Queue(name: "queueOutput", queueName: "myQueue", connection: "AzureWebJobsStorage")] 297 | } 298 | 299 | override func exec(request: HttpRequest, context: inout Context, callback: @escaping callback) throws { 300 | ... 301 | ``` 302 | 303 | ## Writing Swift Functions 304 | 305 | ### Traditional Worker (Classic) 306 | 307 | Based on your Function's trigger type the worker will call the appropriate `exec` overload. For instance, if the Function is timer-triggered, then the worker will call 308 | ```swift 309 | exec(timer:context:callback:) 310 | ``` 311 | If it was an HTTP-triggered one: 312 | ```swift 313 | exec(request:context:callback:) 314 | ``` 315 | You can see the list of available overloads in Xcode. 316 | 317 | Input and Output bindings are available in the context as Dictionaries, where you can access/set the values using the binding names specified in the constructor. 318 | For example: 319 | ```swift 320 | let tableVal = context.inputBindings["myTableInput"] 321 | ``` 322 | 323 | ```swift 324 | context.outputBindings["myQueueOutput"] = "new item!" 325 | ``` 326 | 327 | ### Custom Handler (HTTP Worker) 328 | 329 | The framework uses Vapor 4.0 HTTP server. The `Function` class has the `app` property, thats the Vapor app instance you can use to register your functions's HTTP route. 330 | 331 | ```swift 332 | class myFunction: Function { 333 | 334 | required init() { 335 | super.init() 336 | self.name = "myFunction" 337 | self.functionJsonBindings = [ 338 | [ 339 | "connection" : "AzureWebJobsStorage", 340 | "type" : "queueTrigger", 341 | "name" : "myQueueTrigger", 342 | "queueName" : "myqueue", 343 | "direction" : "in" 344 | ] 345 | ] 346 | 347 | app.post([PathComponent(stringLiteral: name)], use: run(req:)) 348 | } 349 | 350 | func run(req: Request) -> InvocationResponse { 351 | var res = InvocationResponse() 352 | if let payload = try? req.content.decode(InvocationRequest.self) { 353 | res.appendLog("Got \\(payload.Data?["myQueueTrigger"] ?? "")") 354 | } 355 | return res 356 | } 357 | } 358 | ``` 359 | 360 | The framework also provides the function invocation Request and Response models needed for Azure Function host, which conform to Content protocol from Vapor, along with helper methods. 361 | 362 | **Invocation Request:** 363 | 364 | ```swift 365 | /// Trigger/Bindings data (values). 366 | var data: [String:AnyCodable]? 367 | /// Trigger/Bindings metadata. 368 | var metadata: [String:AnyCodable]? 369 | ``` 370 | 371 | **Invocation Request:** 372 | ```swift 373 | /// Output bindings values dictionary 374 | var outputs: [String:AnyCodable]? 375 | /// Functions logs array. These will be logged when the Function is executed 376 | var logs: [String] = [] 377 | /// The $return binding value 378 | var returnValue: AnyCodable? 379 | ``` 380 | 381 | #### Framework Updates 382 | As the framework is being actively updated, update the framework and the tools if you're having any issues or want to have the latest features and improvements. 383 | 384 | To update the framework: 385 | 386 | ```bash 387 | swift package update 388 | ``` 389 | 390 | To update the tools on **macOS** 391 | ```bash 392 | brew upgrade salehalbuga/formulae/swift-func 393 | ``` 394 | 395 | on **Linux** 396 | ```bash 397 | git clone https://github.com/SalehAlbuga/azure-functions-swift-tools 398 | make install 399 | ``` 400 | 401 | ### Storage Connections and other settings 402 | In the generated `main.swift` you can define your debug `AzureWebJobsStorage` and optionally any other connections/environment vars. 403 | Additionally, you can change the default Extension Bundle id and version. 404 | 405 | ```swift 406 | // 407 | // main.swift 408 | // 409 | // 410 | // Auto Generated by SwiftFunctionsSDK 411 | // 412 | // Only set env vars or register/remove Functions. Do Not modify/add other code 413 | // 414 | 415 | import AzureFunctions 416 | 417 | let registry = FunctionRegistry() 418 | 419 | registry.AzureWebJobsStorage = "yourConnection" //Remove before deploying. Do not commit or push any Storage Account keys 420 | registry.EnvironmentVariables = ["queueStorageConnection": "otherConnection"] 421 | 422 | // Optionally you can change the default ExtensionBundleId and version 423 | registry.ExtensionBundleId = "Microsoft.Azure.Functions.ExtensionBundle" 424 | registry.ExtensionBundleVersion = "[1.*, 2.0.0)" 425 | 426 | registry.register(hello.self) 427 | ... 428 | ``` 429 | 430 | Be sure not to commit any debugging Storage Account keys to a repo 431 | 432 | ### Logging 433 | 434 | **Traditional Worker (Classic)** 435 | 436 | You can log using the log method in `context` object 437 | ```swift 438 | context.log(_) 439 | ``` 440 | 441 | **Custom Handler (HTTP Worker)** 442 | 443 | Logs are returned in the InvocationResponse obj. You can append logs: 444 | ```swift 445 | res.appendLog(_) 446 | ``` 447 | 448 | ### Code Execution Note 449 | **Traditional Worker (Classic)** 450 | 451 | When your Function is done executing the logic you should call the provided callback passing the [`$return`](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-return-value?tabs=csharp) output binding value or with `true` if none. 452 | 453 | ```swift 454 | callback(res) 455 | ``` 456 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/AzureFunction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Function.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/28/19. 6 | // 7 | 8 | import Foundation 9 | import AnyCodable 10 | import Vapor 11 | 12 | enum FunctionError: Error { 13 | case FunctionTypeNotImplementedException(String) 14 | case internalInconsistancyException(String) 15 | case JSONSerializationException(String) 16 | } 17 | 18 | public typealias callback = (Any) -> Void 19 | 20 | open class Function { 21 | 22 | /// Function name, has to match class and file names. 23 | public var name: String! 24 | 25 | public var id: String! 26 | 27 | internal var inputBindingsDic: [String: Binding] = [:] 28 | internal var outputBindingsDic: [String: Binding] = [:] 29 | 30 | /// Function input bindings (Classic and HTTP modes) 31 | public var inputBindings: [Binding] = [] 32 | /// Function output bindings (Classic and HTTP modes) 33 | public var outputBindings: [Binding] = [] 34 | public var isDisabled: Bool = false 35 | 36 | /// Function trigger (Classic and HTTP modes) 37 | public var trigger: Binding! 38 | 39 | /// function.json bindings dictionary (HTTP Worker mode only) 40 | public var functionJsonBindings: [[String: Any]] = [] 41 | /// Vapor app, to add the function route (HTTP Worker mode only) 42 | public var app: Application = HandlerHTTPServer.shared.app 43 | 44 | 45 | public required init() { 46 | 47 | } 48 | 49 | /// Function handler for HTTP trigger (Classic mode only) 50 | open func exec(request: HttpRequest, context: inout Context, callback: @escaping callback) throws { 51 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 52 | } 53 | 54 | /// Function handler for Timer trigger (Classic mode only) 55 | open func exec(timer: TimerTrigger, context: inout Context, callback: @escaping callback) throws { 56 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 57 | } 58 | 59 | /// Function handler for various triggers that has values of type Data (Classic mode only) 60 | open func exec(data: Data, context: inout Context, callback: @escaping callback) throws { 61 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 62 | } 63 | 64 | /// Function handler for various triggers that has values of type String (Classic mode only) 65 | open func exec(string: String, context: inout Context, callback: @escaping callback) throws { 66 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 67 | } 68 | 69 | /// Function handler for various triggers that has values of type Dictionary (Classic mode only) 70 | open func exec(dictionary: [String: Any], context: inout Context, callback: @escaping callback) throws { 71 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 72 | } 73 | 74 | /// Function handler for Blob trigger (Classic mode only) 75 | open func exec(blob: Blob, context: inout Context, callback: @escaping callback) throws { 76 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 77 | } 78 | 79 | /// Function handler for ServiceBus trigger (Classic mode only) 80 | open func exec(sbMessage: ServiceBusMessage, context: inout Context, callback: @escaping callback) throws { 81 | throw FunctionError.FunctionTypeNotImplementedException("Please override the right exec function for your trigger") 82 | } 83 | 84 | 85 | } 86 | 87 | internal extension Function { 88 | 89 | func convertInputsToDictionary() { 90 | for binding in self.inputBindings { 91 | self.inputBindingsDic[binding.name] = binding 92 | } 93 | } 94 | 95 | func validateBindings() { 96 | 97 | if functionJsonBindings.count > 0 && inputBindings.count == 0 && outputBindings.count == 0 && trigger == nil { 98 | return 99 | } else if functionJsonBindings.count == 0 && inputBindings.count == 0 && outputBindings.count == 0 && trigger == nil { 100 | fatalError("No bindings or trigger defined. Please set functionJsonBindings or trigger and other binding properties") 101 | } else { 102 | 103 | for binding in inputBindings { 104 | if (binding as! BindingCapability).isInput == false { 105 | fatalError("\(binding.self) of Function \(self.name!) is not an input binding") 106 | } 107 | } 108 | 109 | for binding in outputBindings { 110 | if (binding as! BindingCapability).isOutput == false { 111 | fatalError("\(binding.self) of Function \(self.name!) is not an output binding") 112 | } 113 | 114 | // precondition((binding as! BindingCapability).isOutput == true, "\(Logger.LogPrefix) \(binding.self) is not an output binding") 115 | } 116 | 117 | // precondition((trigger as! BindingCapability).isTrigger == true, "\(Logger.LogPrefix) \(String(describing: trigger.self)) is not a trigger") 118 | if trigger != nil { 119 | if (trigger as! BindingCapability).isTrigger == false { fatalError("\(String(describing: trigger.self)) of Function \(self.name!) is not a trigger") } 120 | } 121 | } 122 | } 123 | 124 | } 125 | 126 | 127 | // Codegen 128 | internal extension Function { 129 | func getFunctionJsonBindings() throws -> String { 130 | 131 | var bindings: [[String:Any]] = [] 132 | 133 | if functionJsonBindings.count > 0 { 134 | for binding in functionJsonBindings { 135 | bindings.append(binding) 136 | } 137 | } 138 | 139 | if trigger != nil { 140 | bindings.append(try (trigger as! BindingCapability).jsonDescription(direction: .trigger)) 141 | } 142 | 143 | for binding in inputBindings { 144 | bindings.append(try (binding as! BindingCapability).jsonDescription(direction: .input)) 145 | } 146 | 147 | for binding in outputBindings { 148 | bindings.append(try (binding as! BindingCapability).jsonDescription(direction: .output)) 149 | } 150 | 151 | if trigger is HttpRequest && (outputBindings.count == 0 || !outputBindings.contains { (binding) -> Bool in 152 | return binding is HttpResponse 153 | }) { 154 | bindings.append(try HttpResponse(bindingName: "$return").jsonDescription(direction: .output)) 155 | } 156 | 157 | let dic: [String : Any] = ["generatedBy": "azure-functions-swift", 158 | "disabled": isDisabled, 159 | "bindings": bindings 160 | ] 161 | 162 | return try dic.toJsonString() 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/AzureFunctionsWorker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LanguageWorker.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/1/19. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | public final class AzureFunctionsWorker { 12 | 13 | internal let version = "0.6.3" 14 | 15 | public static let shared = AzureFunctionsWorker() 16 | 17 | private init () { } 18 | 19 | internal var worker: WorkerChannel! 20 | 21 | /// Starts the worker 22 | /// given element. 23 | /// 24 | /// - Parameter registery: The functions registery 25 | /// - Parameter mode: Worker mode, can be set to .HTTP (for Azure Functions Custom Handler, HTTP worker mode) or .Classic. Defaults to .Classic 26 | /// 27 | public func main(registry: FunctionRegistry, mode: WorkerMode = .Classic) { 28 | 29 | #if !os(Linux) 30 | NSSetUncaughtExceptionHandler { (exception) in 31 | Logger.log("EXCEPTION") 32 | Logger.log(exception.description) 33 | Logger.log(exception.callStackSymbols.description) 34 | exit(1) 35 | } 36 | #endif 37 | 38 | 39 | registry.validateBindings() 40 | 41 | let args = CommandLine.arguments 42 | 43 | if args.contains("export"), let rootPathIdx = args.firstIndex(of: "--root"), let sourcePathIdx = args.firstIndex(of: "--source") { 44 | 45 | let sourcePath = args[sourcePathIdx+1] 46 | let rootPath = args[rootPathIdx+1] 47 | 48 | var isDebug: Bool = false 49 | if let _ = args.firstIndex(of: "--debug") { 50 | isDebug = true 51 | } 52 | 53 | var azWorkerPath: Bool = false 54 | if let _ = args.firstIndex(of: "--azure-worker-path") { 55 | azWorkerPath = true 56 | } 57 | 58 | do { 59 | try CodeGen.exportScriptRoot(registry: registry, sourceDir: sourcePath, rootDir: rootPath, debug: isDebug, azureWorkerPath: azWorkerPath, mode: mode) 60 | } catch { 61 | print("error exporting project: \(error.localizedDescription)") 62 | exit(1) 63 | } 64 | exit(0) 65 | 66 | } else if args.contains("functions-metadata"), let nameIdx = args.firstIndex(of: "--name") { 67 | let funcName = args[nameIdx+1] 68 | 69 | do { 70 | if let function = registry.byName(name: funcName) { 71 | let bindings = try function.getFunctionJsonBindings() 72 | print(bindings) 73 | exit(0) 74 | } else { 75 | print("function not found") 76 | exit(1) 77 | } 78 | } catch { 79 | print("error generating bindings \(error.localizedDescription)") 80 | exit(1) 81 | } 82 | 83 | } else { 84 | if mode == .Classic { 85 | guard args.contains("run"), let hostIndex = args.firstIndex(of: "--host"), let portIndex = args.firstIndex(of: "--port"), let reqIdIndex = args.firstIndex(of: "--requestId"), let wrkrIdIndex = args.firstIndex(of: "--workerId"), let msgLenIndex = args.firstIndex(of: "--grpcMaxMessageLength") else { 86 | return showInfo() 87 | } 88 | 89 | let host = args[hostIndex+1] 90 | let port = args[portIndex+1] 91 | let reqId = args[reqIdIndex+1] 92 | let workerId = args[wrkrIdIndex+1] 93 | let messageLengthStr = args[msgLenIndex+1] 94 | if let msgLen = Int32.init(messageLengthStr) { 95 | startGRPCWorker(host: host, port: port, reqId: reqId, workerId: workerId, msgLen: msgLen, registry: registry) 96 | } else { 97 | print("Azure Functions for Swift worker \(version)") 98 | print("MessageLength required") 99 | Logger.log("MessageLength required") 100 | exit(1) 101 | } 102 | } else { 103 | guard args.contains("run") else { 104 | return showInfo(GRPCInfo: false) 105 | } 106 | 107 | var workerId: String? = "" 108 | if let wrkrIdIndex = args.firstIndex(of: "--workerId") { 109 | workerId = args[wrkrIdIndex+1] 110 | } 111 | 112 | let port: Int 113 | if let portIndex = args.firstIndex(of: "--port"), let portNum = Int(args[portIndex+1]) { 114 | port = portNum 115 | } else if let envPort = Int(ProcessInfo.processInfo.environment["FUNCTIONS_HTTPWORKER_PORT"] ?? "") { 116 | port = envPort 117 | } else { 118 | return showInfo(GRPCInfo: false) 119 | } 120 | 121 | do { 122 | try HandlerHTTPServer.shared.startHttpWorker(port: Int(port), registry: registry, workerId: workerId) 123 | } catch { 124 | fatalError(error.localizedDescription) 125 | } 126 | } 127 | } 128 | 129 | } 130 | 131 | 132 | func showInfo(GRPCInfo: Bool = true) { 133 | print("Azure Functions for Swift worker \(version)") 134 | if GRPCInfo { 135 | print("Please provide command, host and port and host args") 136 | Logger.log("Please provide command, host and port and host args") 137 | } else { 138 | print("Please provide a command and a port") 139 | Logger.log("Please provide a command and a port") 140 | } 141 | exit(1) 142 | } 143 | 144 | func startGRPCWorker(host: String, port :String, reqId: String, workerId: String, msgLen: Int32, registry: FunctionRegistry) { 145 | worker = WorkerChannel(workerId: workerId, requestId: reqId, messageLength: msgLen, registry: registry) 146 | worker.runClient(host: host, port: Int(port)!) 147 | } 148 | } 149 | 150 | public enum WorkerMode { 151 | case Classic 152 | case HTTP 153 | } 154 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/Binding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Binding.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/25/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol Binding { 11 | var name: String { get set } 12 | } 13 | 14 | internal enum BindingDirection { 15 | case trigger 16 | case input 17 | case output 18 | } 19 | 20 | internal protocol BindingCapability { 21 | var isInput: Bool { get } 22 | var isOutput: Bool { get } 23 | var isTrigger: Bool { get } 24 | 25 | static var triggerTypeKey: String { get } 26 | static var typeKey: String { get } 27 | 28 | func jsonDescription(direction: BindingDirection) throws -> [String:Any] 29 | func stringJsonDescription(direction: BindingDirection) throws -> String 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/BindingFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BindingFactory.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/17/19. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | internal final class BindingFactory { 12 | 13 | static func buildBinding(bindingDefinition: Binding, rpcBinding: AzureFunctionsRpcMessages_BindingInfo, binding: AzureFunctionsRpcMessages_ParameterBinding, metadata: Dictionary?) throws -> Binding? { 14 | switch bindingDefinition { 15 | case let http as HttpRequest: 16 | if rpcBinding.type == "http" || rpcBinding.type == "httpTrigger" { 17 | let httpReq = try RpcConverter.fromTypedData(data: binding.data) as! HttpRequest 18 | httpReq.name = http.name 19 | httpReq.route = http.route 20 | return httpReq 21 | } else { 22 | throw FunctionError.internalInconsistancyException("Expected http binding type, got \(rpcBinding.type). Please make sure function.json matches the function definition.") 23 | } 24 | case let timer as TimerTrigger: 25 | if rpcBinding.type == TimerTrigger.triggerTypeKey { 26 | let json = try RpcConverter.fromTypedData(data: binding.data) as! [String:Any] 27 | let t = TimerTrigger() 28 | t.userInfo = json 29 | t.name = timer.name 30 | t.schedule = timer.schedule 31 | return t 32 | } else { 33 | throw FunctionError.internalInconsistancyException("Expected timer binding type, got \(rpcBinding.type). Please make sure function.json matches the function definition.") 34 | } 35 | case let b as Blob: 36 | if rpcBinding.type == Blob.typeKey || rpcBinding.type == Blob.triggerTypeKey { 37 | let blob = Blob() 38 | blob.blob = try RpcConverter.fromTypedData(data: binding.data) 39 | 40 | blob.name = b.name 41 | blob.path = b.path 42 | if let meta = metadata, let props = meta["Properties"] { 43 | blob.properties = try (RpcConverter.fromTypedData(data: props) as? [String:Any] ?? [:]) 44 | } 45 | return blob 46 | } else { 47 | throw FunctionError.internalInconsistancyException("Expected blob binding type, got \(rpcBinding.type). Please make sure function.json matches the function definition.") 48 | } 49 | case let sb as ServiceBusMessage: 50 | if rpcBinding.type == ServiceBusMessage.triggerTypeKey { 51 | 52 | let sbMsg = ServiceBusMessage() 53 | sbMsg.name = sb.name 54 | sbMsg.message = try RpcConverter.fromTypedData(data: binding.data, preferJsonInString: true) 55 | sbMsg.queueName = sb.queueName 56 | sbMsg.topicName = sb.topicName 57 | sbMsg.subscriptionName = sb.subscriptionName 58 | 59 | return sbMsg 60 | } else { 61 | throw FunctionError.internalInconsistancyException("Expected blob binding type, got \(rpcBinding.type). Please make sure function.json matches the function definition.") 62 | } 63 | case _ as Queue, _ as Table: 64 | return nil 65 | default: 66 | throw FunctionError.internalInconsistancyException("Cannot build binding for type of '\(binding.name)', type unsupported") 67 | } 68 | } 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/Blob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Blob.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/25/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class Blob: Binding { 11 | 12 | public var name: String = "" 13 | public var connection: String = "" 14 | public var path: String = "" 15 | public var url: String = "" 16 | public var blob: Any? 17 | public var properties: [String:Any] = [:] 18 | 19 | internal init() { 20 | 21 | } 22 | 23 | public init(name: String, path: String, connection: String) { 24 | self.name = name 25 | self.path = path 26 | self.connection = connection 27 | } 28 | 29 | struct Keys { 30 | static let Connection = "connection" 31 | static let Path = "path" 32 | } 33 | } 34 | 35 | extension Blob: BindingCapability { 36 | 37 | var isInput: Bool { 38 | return true 39 | } 40 | 41 | var isOutput: Bool { 42 | return true 43 | } 44 | 45 | var isTrigger: Bool { 46 | return true 47 | } 48 | 49 | 50 | static let triggerTypeKey = "blobTrigger" 51 | static let typeKey = "blob" 52 | 53 | 54 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 55 | 56 | var props: [String: Any] = [ 57 | Definitions.Bindings.Keys.Name: name, 58 | Blob.Keys.Connection: connection, 59 | Blob.Keys.Path: path 60 | ] 61 | 62 | switch direction { 63 | case .trigger: 64 | props[Definitions.Bindings.Keys.TypeKey] = Blob.triggerTypeKey 65 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionIn 66 | break 67 | case .input: 68 | props[Definitions.Bindings.Keys.TypeKey] = Blob.typeKey 69 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionIn 70 | break 71 | case .output: 72 | props[Definitions.Bindings.Keys.TypeKey] = Blob.typeKey 73 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionOut 74 | break 75 | } 76 | 77 | return props 78 | } 79 | 80 | func stringJsonDescription(direction: BindingDirection) throws -> String { 81 | return try jsonDescription(direction: direction).toJsonString() 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/Queue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Queue.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/26/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class Queue: Binding { 11 | 12 | public var name: String = "" 13 | public var queueName: String = "" 14 | public var connection: String = "" 15 | public var metadata: [String:String] = [:] 16 | public var properties: [String:String] = [:] 17 | 18 | internal init() { 19 | 20 | } 21 | 22 | public init(name: String, queueName: String, connection: String) { 23 | self.name = name 24 | self.queueName = queueName 25 | self.connection = connection 26 | } 27 | 28 | struct Keys { 29 | static let Queue = "queueName" 30 | } 31 | } 32 | 33 | extension Queue: BindingCapability { 34 | 35 | var isInput: Bool { 36 | return false 37 | } 38 | 39 | var isOutput: Bool { 40 | return true 41 | } 42 | 43 | var isTrigger: Bool { 44 | return true 45 | } 46 | 47 | 48 | static let triggerTypeKey = "queueTrigger" 49 | static let typeKey = "queue" 50 | 51 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 52 | 53 | var props: [String: Any] = [ 54 | Definitions.Bindings.Keys.Name: name, 55 | Definitions.Bindings.Keys.Connection: connection, 56 | Keys.Queue : queueName 57 | ] 58 | 59 | switch direction { 60 | case .trigger: 61 | props[Definitions.Bindings.Keys.TypeKey] = Queue.triggerTypeKey 62 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionIn 63 | break 64 | case .output: 65 | props[Definitions.Bindings.Keys.TypeKey] = Queue.typeKey 66 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionOut 67 | break 68 | default: 69 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 70 | } 71 | 72 | return props 73 | } 74 | 75 | func stringJsonDescription(direction: BindingDirection) throws -> String { 76 | return try jsonDescription(direction: direction).toJsonString() 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/ServiceBusMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceBusMessage.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/26/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class ServiceBusMessage: Binding { 11 | 12 | public var name: String = "" 13 | public var queueName: String? 14 | public var topicName: String? 15 | public var subscriptionName: String? 16 | public var connection: String = "" 17 | public var message: Any? 18 | 19 | internal init() { 20 | 21 | } 22 | 23 | public init(name: String, queueName: String, connection: String) { 24 | self.name = name 25 | self.queueName = queueName 26 | self.connection = connection 27 | } 28 | 29 | public init(name: String, topicName: String, subscriptionName: String, connection: String) { 30 | self.name = name 31 | self.topicName = topicName 32 | self.subscriptionName = subscriptionName 33 | self.connection = connection 34 | } 35 | 36 | struct Keys { 37 | static let TopicName = "topicName" 38 | static let QueueName = "queueName" 39 | static let SubscriptionName = "subscriptionName" 40 | } 41 | } 42 | 43 | extension ServiceBusMessage: BindingCapability { 44 | 45 | var isInput: Bool { 46 | return false 47 | } 48 | 49 | var isOutput: Bool { 50 | return true 51 | } 52 | 53 | var isTrigger: Bool { 54 | return true 55 | } 56 | 57 | 58 | static let triggerTypeKey = "serviceBusTrigger" 59 | static let typeKey = "serviceBus" 60 | 61 | 62 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 63 | 64 | var props: [String: Any] = [ 65 | Definitions.Bindings.Keys.Name: name, 66 | Definitions.Bindings.Keys.Connection: connection, 67 | ] 68 | 69 | if let topic = self.topicName { 70 | props[Keys.TopicName] = topic 71 | } 72 | 73 | if let queue = self.queueName { 74 | props[Keys.QueueName] = queue 75 | } 76 | 77 | switch direction { 78 | case .trigger: 79 | props[Definitions.Bindings.Keys.TypeKey] = ServiceBusMessage.triggerTypeKey 80 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionIn 81 | if let subscription = self.subscriptionName { 82 | props[Keys.SubscriptionName] = subscription 83 | } 84 | break 85 | case .output: 86 | props[Definitions.Bindings.Keys.TypeKey] = ServiceBusMessage.typeKey 87 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionOut 88 | break 89 | default: 90 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 91 | } 92 | 93 | return props 94 | } 95 | 96 | func stringJsonDescription(direction: BindingDirection) throws -> String { 97 | return try jsonDescription(direction: direction).toJsonString() 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/Table.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Table.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/27/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class Table: Binding { 11 | 12 | public var name: String = "" 13 | public var tableName: String = "" 14 | public var connection: String = "" 15 | public var partitionKey: String? 16 | public var rowKey: String? 17 | public var take: String? 18 | public var filter: String? 19 | 20 | internal init() { 21 | 22 | } 23 | 24 | public init(inputName name: String, tableName: String, connection: String, partitionKey: String? = nil, rowKey: String? = nil, take: String? = nil, filter: String? = nil) { 25 | self.name = name 26 | self.tableName = tableName 27 | self.connection = connection 28 | self.partitionKey = partitionKey 29 | self.rowKey = rowKey 30 | self.take = take 31 | self.filter = filter 32 | } 33 | 34 | public init(outputName name: String, tableName: String, connection: String, partitionKey: String, rowKey: String) { 35 | self.name = name 36 | self.tableName = tableName 37 | self.connection = connection 38 | self.partitionKey = partitionKey 39 | self.rowKey = rowKey 40 | } 41 | 42 | struct Keys { 43 | static let TableName = "tableName" 44 | static let PartitionKey = "partitionKey" 45 | static let RowKey = "rowKey" 46 | static let Take = "take" 47 | static let Filter = "filter" 48 | } 49 | } 50 | 51 | extension Table: BindingCapability { 52 | 53 | 54 | var isInput: Bool { 55 | return true 56 | } 57 | 58 | var isOutput: Bool { 59 | return true 60 | } 61 | 62 | var isTrigger: Bool { 63 | return false 64 | } 65 | 66 | 67 | static let typeKey = "table" 68 | static var triggerTypeKey: String { 69 | return "" 70 | } 71 | 72 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 73 | 74 | var props: [String: Any] = [ 75 | Definitions.Bindings.Keys.Name: name, 76 | Definitions.Bindings.Keys.Connection: connection, 77 | Keys.TableName: tableName 78 | ] 79 | 80 | switch direction { 81 | case .input: 82 | props[Definitions.Bindings.Keys.TypeKey] = Table.typeKey 83 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionIn 84 | 85 | if let partition = partitionKey { 86 | props[Keys.PartitionKey] = partition 87 | } 88 | 89 | if let row = rowKey { 90 | props[Keys.RowKey] = row 91 | } 92 | 93 | if let take = take { 94 | props[Keys.Take] = take 95 | } 96 | 97 | if let filter = filter { 98 | props[Keys.Filter] = filter 99 | } 100 | 101 | break 102 | case .output: 103 | props[Definitions.Bindings.Keys.TypeKey] = Table.typeKey 104 | props[Definitions.Bindings.Keys.Direction] = Definitions.Bindings.DirectionOut 105 | 106 | if let partition = partitionKey { 107 | props[Keys.PartitionKey] = partition 108 | } 109 | 110 | if let row = rowKey { 111 | props[Keys.RowKey] = row 112 | } 113 | 114 | break 115 | default: 116 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 117 | } 118 | 119 | return props 120 | } 121 | 122 | func stringJsonDescription(direction: BindingDirection) throws -> String { 123 | return try jsonDescription(direction: direction).toJsonString() 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/Timer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Timer.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/25/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public final class TimerTrigger : Binding { 11 | 12 | public var name: String = "" 13 | 14 | var schedule: String = "" 15 | 16 | var runOnStartup: Bool? 17 | var useMonitor: Bool? 18 | 19 | var userInfo: [String:Any] = [:] 20 | 21 | internal init () { 22 | 23 | } 24 | 25 | public init(name: String, schedule: String) { 26 | self.schedule = schedule 27 | self.name = name 28 | } 29 | 30 | public init(name: String, schedule: String, runOnStartup: Bool, useMonitor: Bool) { 31 | self.schedule = schedule 32 | self.name = name 33 | self.runOnStartup = runOnStartup 34 | self.useMonitor = useMonitor 35 | } 36 | 37 | struct Keys { 38 | static let Schedule = "schedule" 39 | static let RunOnStartup = "runOnStartup" 40 | static let UseMonitor = "UseMonitor" 41 | } 42 | 43 | } 44 | 45 | extension TimerTrigger: BindingCapability { 46 | 47 | 48 | var isInput: Bool { 49 | return false 50 | } 51 | 52 | var isOutput: Bool { 53 | return false 54 | } 55 | 56 | var isTrigger: Bool { 57 | return true 58 | } 59 | 60 | static let triggerTypeKey = "timerTrigger" 61 | static let typeKey = "" 62 | 63 | 64 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 65 | 66 | var props: [String: Any] = [ 67 | Definitions.Bindings.Keys.TypeKey: TimerTrigger.triggerTypeKey, 68 | Definitions.Bindings.Keys.Name: name, 69 | Definitions.Bindings.Keys.Direction: Definitions.Bindings.DirectionIn, 70 | Keys.Schedule: schedule 71 | ] 72 | 73 | if let runOnStartVal = runOnStartup { 74 | props[Keys.RunOnStartup] = runOnStartVal 75 | } 76 | 77 | if let useMonVal = useMonitor { 78 | props[Keys.UseMonitor] = useMonVal 79 | } 80 | 81 | switch direction { 82 | case .trigger: 83 | return props 84 | default: 85 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 86 | } 87 | 88 | } 89 | 90 | func stringJsonDescription(direction: BindingDirection) throws -> String { 91 | return try jsonDescription(direction: direction).toJsonString() 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/http/HttpRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HttpRequest.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/21/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public final class HttpRequest : Binding { 11 | 12 | public var name: String = "" 13 | public var route: String? 14 | public var methods: [String] = [] 15 | public var method: String = "" 16 | public var url: String = "" 17 | public var originalUrl: String = "" 18 | public var headers: [String:String] = [:] 19 | public var query: [String:String] = [:] 20 | public var params: [String:String] = [:] 21 | public var body: Data? 22 | public var rawBody: Data? 23 | 24 | internal init() { } 25 | 26 | public init(name: String, route: String = "", methods: [String] = []) { 27 | if name != "" { 28 | self.name = name 29 | } else { 30 | self.name = "req" 31 | } 32 | if route != "" { 33 | self.route = route 34 | } 35 | self.methods = methods 36 | } 37 | 38 | 39 | internal init(fromRpcHttp: AzureFunctionsRpcMessages_RpcHttp) { 40 | self.method = fromRpcHttp.method 41 | self.url = fromRpcHttp.url 42 | self.headers = fromRpcHttp.headers 43 | self.query = fromRpcHttp.query 44 | 45 | self.body = RpcConverter.fromBodyTypedData(data: fromRpcHttp.body) 46 | self.rawBody = RpcConverter.fromBodyTypedData(data: fromRpcHttp.rawBody) 47 | 48 | } 49 | 50 | struct Keys { 51 | static let Methods = "methods" 52 | static let Route = "route" 53 | } 54 | 55 | } 56 | 57 | extension HttpRequest: BindingCapability { 58 | static let triggerTypeKey: String = "httpTrigger" 59 | 60 | static let typeKey: String = "" 61 | 62 | var isInput: Bool { 63 | return false 64 | } 65 | 66 | var isOutput: Bool { 67 | return false 68 | } 69 | 70 | var isTrigger: Bool { 71 | return true 72 | } 73 | 74 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 75 | 76 | var props: [String: Any] = [ 77 | Definitions.Bindings.Keys.TypeKey: HttpRequest.triggerTypeKey, 78 | Definitions.Bindings.Keys.Name: name, 79 | Definitions.Bindings.Keys.Direction: Definitions.Bindings.DirectionIn, 80 | ] 81 | 82 | if methods.count > 0 { 83 | props[HttpRequest.Keys.Methods] = methods 84 | } 85 | 86 | if let rt = route { 87 | props[HttpRequest.Keys.Route] = rt 88 | } 89 | 90 | switch direction { 91 | case .trigger: 92 | return props 93 | default: 94 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 95 | } 96 | 97 | } 98 | 99 | func stringJsonDescription(direction: BindingDirection) throws -> String { 100 | return try jsonDescription(direction: direction).toJsonString() 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/http/HttpResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HttpResponse.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/21/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public final class HttpResponse: Binding { 11 | 12 | public var name: String 13 | 14 | public var statusCode: Int? 15 | public var headers: [String:String] = [:] 16 | // TODO var cookies: [Cookie] = [] 17 | public var body: Data? 18 | public var enableContentNegotiation: Bool? 19 | public var params: [String:String] = [:] 20 | public var query: [String:String] = [:] 21 | 22 | public init () { 23 | self.name = "" 24 | self.headers["X-Powered-By"] = "SwiftFunc" 25 | } 26 | 27 | public init(bindingName: String) { 28 | self.name = bindingName 29 | } 30 | 31 | func toRpcHttp() -> AzureFunctionsRpcMessages_RpcHttp { 32 | var rpc = AzureFunctionsRpcMessages_RpcHttp() 33 | rpc.statusCode = "\(statusCode ?? 200)" 34 | rpc.headers = self.headers 35 | 36 | if let data = body { 37 | var typedData = AzureFunctionsRpcMessages_TypedData() 38 | typedData.data = .bytes(data) 39 | rpc.body = typedData 40 | } 41 | 42 | if let contentNegotiation = self.enableContentNegotiation { 43 | rpc.enableContentNegotiation = contentNegotiation 44 | } 45 | 46 | rpc.params = params 47 | rpc.query = query 48 | 49 | return rpc 50 | } 51 | 52 | } 53 | 54 | extension HttpResponse: BindingCapability { 55 | static var triggerTypeKey: String { 56 | return "" 57 | } 58 | 59 | static var typeKey: String = "http" 60 | 61 | 62 | var isInput: Bool { 63 | return false 64 | } 65 | 66 | var isOutput: Bool { 67 | return true 68 | } 69 | 70 | var isTrigger: Bool { 71 | return false 72 | } 73 | 74 | func jsonDescription(direction: BindingDirection) throws -> [String: Any] { 75 | 76 | var props: [String: Any] = [ 77 | Definitions.Bindings.Keys.TypeKey: HttpResponse.typeKey, 78 | Definitions.Bindings.Keys.Direction: Definitions.Bindings.DirectionOut, 79 | ] 80 | 81 | if name != "" { 82 | props[Definitions.Bindings.Keys.Name] = name 83 | } else { 84 | props[Definitions.Bindings.Keys.Name] = "$return" 85 | } 86 | 87 | switch direction { 88 | case .output: 89 | return props 90 | default: 91 | throw FunctionError.internalInconsistancyException("Error generating bidining description") 92 | } 93 | 94 | } 95 | 96 | func stringJsonDescription(direction: BindingDirection) throws -> String { 97 | return try jsonDescription(direction: direction).toJsonString() 98 | } 99 | 100 | 101 | } 102 | 103 | // TODO 104 | class Cookie { 105 | var name: String = "" 106 | var value: String = "" 107 | var domain: String? 108 | var path: String? 109 | var secures: Bool? 110 | var httpOnly: Bool? 111 | var sameSite: String? // "Strict" | "Lax" | undefined; 112 | var maxAge: Int? 113 | 114 | // func toRpcHttp() -> AzureFunctionsRpcMessages_RpcHttp { 115 | // 116 | // } 117 | } 118 | 119 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/models/InvocationRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvocationRequest.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 5/20/20. 6 | // 7 | 8 | import Foundation 9 | import AnyCodable 10 | import Vapor 11 | 12 | public struct InvocationRequest: Content { 13 | 14 | /// Trigger/Bindings data (values). 15 | public var data: [String:AnyCodable]? 16 | /// Trigger/Bindings metadata. 17 | public var metadata: [String:AnyCodable]? 18 | 19 | public enum CodingKeys: String, CodingKey { 20 | 21 | case data = "Data" 22 | case metadata = "Metadata" 23 | } 24 | 25 | public init(from decoder: Decoder) throws { 26 | let values = try decoder.container(keyedBy: CodingKeys.self) 27 | data = try values.decode([String:AnyCodable].self, forKey: .data) 28 | metadata = try values.decode([String:AnyCodable].self, forKey: .metadata) 29 | } 30 | 31 | public func encode(to encoder: Encoder) throws { 32 | var container = encoder.container(keyedBy: CodingKeys.self) 33 | try container.encode(data, forKey: .data) 34 | try container.encode(metadata, forKey: .metadata) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Bindings/models/InvocationResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvocationResponse.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 5/20/20. 6 | // 7 | 8 | import Foundation 9 | import AnyCodable 10 | import Vapor 11 | 12 | public struct InvocationResponse: Content { 13 | 14 | /// Output bindings values dictionary 15 | public var outputs: [String:AnyCodable]? 16 | /// Functions logs array. These will be logged when the Function is executed 17 | public var logs: [String] = [] 18 | /// The $return binding value 19 | public var returnValue: AnyCodable? 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case Output = "Outputs" 23 | case Logs = "Logs" 24 | case ReturnValue = "ReturnValue" 25 | } 26 | 27 | public init(from decoder: Decoder) throws { 28 | let container = try decoder.container(keyedBy: CodingKeys.self) 29 | outputs = try container.decode([String:AnyCodable].self, forKey: .Output) 30 | logs = try container.decode([String].self, forKey: .Logs) 31 | returnValue = try container.decode(AnyCodable.self, forKey: .ReturnValue) 32 | } 33 | 34 | public init() { } 35 | 36 | public init(outputs: [String:AnyCodable], logs: [String] = [], returnValue: AnyCodable? = nil) { 37 | self.outputs = outputs 38 | self.logs = logs 39 | self.returnValue = returnValue 40 | } 41 | 42 | public init(logs: [String] = [], returnValue: AnyCodable? = nil) { 43 | self.outputs = nil 44 | self.logs = logs 45 | self.returnValue = returnValue 46 | } 47 | 48 | public static func response(with outputs: [String:AnyCodable], logs: [String] = [], returnValue: AnyCodable? = nil) -> Data { 49 | let res = InvocationResponse(outputs: outputs, logs: logs, returnValue: returnValue) 50 | return try! JSONEncoder().encode(res) 51 | } 52 | 53 | public func encode(to encoder: Encoder) throws { 54 | var container = encoder.container(keyedBy: CodingKeys.self) 55 | try container.encode(outputs, forKey: .Output) 56 | try container.encode(logs, forKey: .Logs) 57 | try container.encode(returnValue, forKey: .ReturnValue) 58 | } 59 | 60 | } 61 | 62 | public extension InvocationResponse { 63 | 64 | mutating func appendLog(_ log: String) { 65 | logs.append(log) 66 | } 67 | 68 | mutating func removeLastLog() { 69 | logs.removeLast() 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Broker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Broker.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/5/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal class Broker { 11 | 12 | static func run(function: Function, input: Any?, context: inout Context, callback: @escaping callback) throws { 13 | 14 | // handle empty input!! 15 | 16 | guard let inputBinding = input else { 17 | throw FunctionError.internalInconsistancyException("Nil Function Input") 18 | } 19 | 20 | switch inputBinding { 21 | case let http as HttpRequest: 22 | try function.exec(request: http, context: &context, callback: callback) 23 | break 24 | case let timer as TimerTrigger: 25 | try function.exec(timer: timer, context: &context, callback: callback) 26 | break 27 | case let blob as Blob: 28 | try function.exec(blob: blob, context: &context, callback: callback) 29 | break 30 | case let sbMsg as ServiceBusMessage: 31 | try function.exec(sbMessage: sbMsg, context: &context, callback: callback) 32 | break 33 | case let data as Data: 34 | try function.exec(data: data, context: &context, callback: callback) 35 | break 36 | case let string as String: 37 | try function.exec(string: string, context: &context, callback: callback) 38 | break 39 | case let dic as [String:Any]: 40 | try function.exec(dictionary: dic, context: &context, callback: callback) 41 | break 42 | default: 43 | throw FunctionError.internalInconsistancyException("Function Input type cannot be determined") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/CodeGen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeGen.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/15/19. 6 | // 7 | 8 | import Foundation 9 | import Files 10 | import Stencil 11 | 12 | 13 | internal struct CodeGen { 14 | 15 | static func exportScriptRoot(registry: FunctionRegistry,sourceDir: String, rootDir: String, debug: Bool, azureWorkerPath: Bool = false, mode: WorkerMode) throws { 16 | 17 | guard let srcFolder = try? Folder.init(path: sourceDir), srcFolder.containsFile(at: "Package.swift"), let projectName = try? Folder.init(path: "\(sourceDir)/Sources").subfolders.first?.name else { 18 | print("Not a Swift Project") 19 | exit(1) 20 | } 21 | 22 | let environment = Environment() 23 | 24 | let rootFolder = try Folder.init(path: rootDir) 25 | 26 | 27 | let hostRes: String 28 | 29 | var extensionsInfo = ["extensionBundleID": registry.ExtensionBundleId ?? Templates.ProjectFiles.defaultExtensionsBundleId, "extensionBundleVersion": registry.ExtensionBundleVersion ?? Templates.ProjectFiles.defaultExtensionsVersion] 30 | if mode == .Classic { 31 | hostRes = try environment.renderTemplate(string: Templates.ProjectFiles.hostJsonExtensions, context: extensionsInfo) 32 | } else { 33 | extensionsInfo["execPath"] = azureWorkerPath ? Templates.ProjectFiles.defaultHandlerExecutablePath : "\(rootFolder.path)functions" 34 | hostRes = try environment.renderTemplate(string: Templates.ProjectFiles.hostJsonExtensionsHttpWorker, context: extensionsInfo) 35 | } 36 | let hostFile = try rootFolder.createFile(named: "host.json") 37 | try hostFile.write(hostRes) 38 | 39 | 40 | let localSetRes: String 41 | let settingsJsontemplate = mode == .HTTP ? Templates.ProjectFiles.localSettingsJsonHttp : Templates.ProjectFiles.localSettingsJson 42 | if var envVars = registry.EnvironmentVariables, envVars.count > 0 { 43 | if let storage = registry.AzureWebJobsStorage { 44 | envVars["AzureWebJobsStorage"] = storage 45 | } 46 | 47 | var envVarsString = "" 48 | for setting in envVars { 49 | envVarsString.append("\"\(setting.key)\": \"\(setting.value)\",") 50 | } 51 | 52 | localSetRes = try environment.renderTemplate(string: settingsJsontemplate, context: ["envVars": envVarsString]) 53 | 54 | } else if let storage = registry.AzureWebJobsStorage { 55 | localSetRes = try environment.renderTemplate(string: settingsJsontemplate, context: ["envVars": "\"AzureWebJobsStorage\": \"\(storage)\""]) 56 | } else { 57 | localSetRes = try environment.renderTemplate(string: settingsJsontemplate, context: [:]) 58 | } 59 | 60 | let localSetFile = try rootFolder.createFile(named: "local.settings.json") 61 | try localSetFile.write(localSetRes) 62 | 63 | let execDestination: Folder 64 | if mode == .Classic { 65 | let workersFolder = try rootFolder.createSubfolderIfNeeded(withName: "workers") 66 | execDestination = try workersFolder.createSubfolderIfNeeded(withName: "swift") 67 | let workerRes = try environment.renderTemplate(string: Templates.ProjectFiles.workerConfigJson, context: ["execPath": azureWorkerPath ? Templates.ProjectFiles.defaultWorkerExecutablePath : "\(execDestination.path)functions"]) 68 | let workerFile = try execDestination.createFile(named: "worker.config.json") 69 | try workerFile.write(workerRes) 70 | } else { 71 | execDestination = rootFolder 72 | } 73 | 74 | try File.init(path: "\(sourceDir)/.build/release/functions").copy(to: execDestination) 75 | 76 | 77 | for file in try Folder(path: "\(sourceDir)/Sources/\(projectName)/functions").files { 78 | guard file.extension == "swift" else { 79 | break 80 | } 81 | let name = file.nameExcludingExtension 82 | print("Exporting \(name)") 83 | let funcFolder = try rootFolder.createSubfolderIfNeeded(withName: name) 84 | try file.copy(to: funcFolder) 85 | 86 | do { 87 | if let function = registry.byName(name: name) { 88 | let bindings = try function.getFunctionJsonBindings() 89 | let workerRes = try environment.renderTemplate(string: Templates.ProjectFiles.functionJson, context: ["bindings": bindings]) 90 | let workerFile = try funcFolder.createFile(named: "function.json") 91 | try workerFile.write(workerRes) 92 | } else { 93 | print("Function \(name) is not registered.") 94 | if !debug { 95 | print("Error: \(name) exists but is not registered, which is not supported in production. Make sure to register \(name) or remove it") 96 | exit(1) 97 | } else { 98 | print("Warning: \(name) exists but is not registered. This is only allowed while debugging (running locally)") 99 | } 100 | } 101 | } catch { 102 | print("error generating bindings \(error.localizedDescription)") 103 | exit(1) 104 | } 105 | } 106 | 107 | print("Project exported!") 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Context.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Context.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/5/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public final class Context { 11 | 12 | internal var bindings: [String: Any] = [:] 13 | 14 | public var inputBindings: [String: Any] = [:] 15 | public var outputBindings: [String: Any] = [:] 16 | 17 | internal init () { } 18 | 19 | internal func prepBindings() { 20 | self.bindings = inputBindings 21 | self.bindings.merge(self.outputBindings) { (current, _) -> Any in 22 | current 23 | } 24 | } 25 | 26 | public func log(_ message: String) { 27 | Logger.log(message) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Definitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Definitions.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Definitions { 11 | struct Bindings { 12 | static let DirectionIn = "in" 13 | static let DirectionOut = "out" 14 | static let DirectionInOut = "inout" 15 | 16 | struct Keys { 17 | static let Name = "name" 18 | static let Direction = "direction" 19 | static let TypeKey = "type" 20 | static let Connection = "connection" 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Extentions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Converters.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Dictionary { 11 | 12 | func toJsonString() throws -> String { 13 | let jsonData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) 14 | return String(bytes: jsonData, encoding: String.Encoding.utf8)?.backslashCorrected ?? "invalidJson" 15 | } 16 | } 17 | 18 | extension String { 19 | 20 | var backslashCorrected: String { // Workaround 21 | let corrected : String = self.replacingOccurrences(of: "\\/", with: "/") 22 | return corrected 23 | } 24 | 25 | var unescaped: String { 26 | let entities = ["\0", "\t", "\n", "\r", "\"", "\'", "\\"] 27 | var current = self 28 | for entity in entities { 29 | let descriptionCharacters = entity.debugDescription.dropFirst().dropLast() 30 | let description = String(descriptionCharacters) 31 | current = current.replacingOccurrences(of: description, with: entity) 32 | } 33 | return current 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/FunctionInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionInfo.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/5/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal final class FunctionInfo { 11 | 12 | var httpOutputBinding: String? 13 | var bindings: [String: AzureFunctionsRpcMessages_BindingInfo] = [:] 14 | 15 | init() { } 16 | 17 | init(withBindings: [String: AzureFunctionsRpcMessages_BindingInfo]) { 18 | self.bindings = withBindings 19 | getHttpOutputName() 20 | } 21 | 22 | lazy var outputBindings: [String: AzureFunctionsRpcMessages_BindingInfo] = { 23 | return bindings.filter { (key, val) -> Bool in 24 | return val.direction == .out 25 | } 26 | }() 27 | 28 | lazy var inputBindings: [String: AzureFunctionsRpcMessages_BindingInfo] = { 29 | return bindings.filter { (key, val) -> Bool in 30 | return val.direction == .in 31 | } 32 | }() 33 | 34 | fileprivate func getHttpOutputName() { 35 | for binding in outputBindings { 36 | if binding.value.type == "http" { 37 | self.httpOutputBinding = binding.key 38 | } 39 | } 40 | } 41 | 42 | // Unused 43 | static func getInputsAndBindings(input: AzureFunctionsRpcMessages_TypedData) -> Any? { 44 | return nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/FunctionRegistry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionRegistry.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/1/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public final class FunctionRegistry { 11 | 12 | internal var functionsByName: [String: Function] = [:] 13 | private var functionsById: [String: Function] = [:] 14 | private var functionsInfo: [String: FunctionInfo] = [:] 15 | 16 | /// Default Azure Function storage 17 | public var AzureWebJobsStorage: String? 18 | /// Environment variables dictionary 19 | public var EnvironmentVariables: [String: String]? 20 | 21 | /// Extensions Bundle id (do not change unless needed, defaults to Microsoft.Azure.Functions.ExtensionBundle) 22 | public var ExtensionBundleId: String? 23 | /// Extensions Bundle version (do not change unless needed, defaults to [1.*, 2.0.0)) 24 | public var ExtensionBundleVersion: String? 25 | 26 | public init() { } 27 | 28 | public init(AzureWebJobsStorage: String) { 29 | self.AzureWebJobsStorage = AzureWebJobsStorage 30 | } 31 | 32 | public init(AzureWebJobsStorage: String, EnvironmentVariables: [String: String]) { 33 | self.AzureWebJobsStorage = AzureWebJobsStorage 34 | self.EnvironmentVariables = EnvironmentVariables 35 | } 36 | 37 | /// Registers a Function 38 | public func register(_ function: Function.Type) { 39 | let fun = function.init() 40 | self.functionsByName[fun.name] = fun 41 | } 42 | 43 | internal func register(id: String, forName: String) { 44 | functionsByName[forName]?.id = id 45 | self.functionsById[id] = functionsByName[forName] 46 | } 47 | 48 | internal func registerInfo(id: String, info: FunctionInfo) { 49 | self.functionsInfo[id] = info 50 | } 51 | 52 | internal func byId(id: String) -> Function? { 53 | return functionsById[id] 54 | } 55 | 56 | internal func byName(name: String) -> Function? { 57 | return functionsByName[name] 58 | } 59 | 60 | internal func infoById(id: String) -> FunctionInfo? { 61 | return functionsInfo[id] 62 | } 63 | 64 | internal func validateBindings() { 65 | for function in functionsByName.values { 66 | function.validateBindings() 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/HandlerHTTPServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HandlerHTTPServer.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 5/19/20. 6 | // 7 | 8 | import Foundation 9 | import Vapor 10 | 11 | internal final class HandlerHTTPServer { 12 | 13 | internal static let shared = HandlerHTTPServer() 14 | 15 | var app: Application 16 | 17 | private init() { 18 | app = Application.init() 19 | app.environment = .production 20 | } 21 | 22 | func startHttpWorker(port: Int, registry: FunctionRegistry, workerId: String?) throws { 23 | app.http.server.configuration.port = port 24 | 25 | try app.server.start() 26 | dispatchMain() 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/6/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal struct Logger { 11 | 12 | static let LogPrefix: String = "LanguageWorkerConsoleLog" 13 | 14 | static func log(_ message: String) { 15 | print("\(LogPrefix) \(message)") 16 | } 17 | 18 | static func logError(message: String) { 19 | print("\(LogPrefix) [ERROR] \(message)") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/RpcConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RpcConverter.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 10/5/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal final class RpcConverter { 11 | 12 | static func toRpcTypedData(obj: Any) -> AzureFunctionsRpcMessages_TypedData { 13 | var td = AzureFunctionsRpcMessages_TypedData() 14 | // Logger.log(message: obj) 15 | switch obj { 16 | case let string as String: 17 | if string.starts(with: "{") || string.starts(with: "[") { // TODO detect JSON in str 18 | td.json = string 19 | } else { 20 | td.string = string 21 | } 22 | break 23 | case let dic as [String:Any]: 24 | td.json = dic.description 25 | break 26 | case let data as Data: 27 | td.bytes = data 28 | break 29 | case let int as Int: 30 | td.int = Int64(int) 31 | break 32 | case let double as Double: 33 | td.double = double 34 | break 35 | case let httpRes as HttpResponse: 36 | td.http = httpRes.toRpcHttp() 37 | break 38 | case let dataArr as [Data]: 39 | var col = AzureFunctionsRpcMessages_CollectionBytes() 40 | col.bytes = dataArr 41 | td.collectionBytes = col 42 | break 43 | case let strArr as [String]: 44 | var col = AzureFunctionsRpcMessages_CollectionString() 45 | col.string = strArr 46 | td.collectionString = col 47 | break 48 | case let intArr as [Int64]: 49 | var col = AzureFunctionsRpcMessages_CollectionSInt64() 50 | col.sint64 = intArr 51 | td.collectionSint64 = col 52 | break 53 | case let dblArr as [Double]: 54 | var col = AzureFunctionsRpcMessages_CollectionDouble() 55 | col.double = dblArr 56 | td.collectionDouble = col 57 | break 58 | default: 59 | Logger.log("Unsupported data type by worker!") 60 | td.string = "Unsupported data type by worker!" 61 | break 62 | } 63 | return td 64 | } 65 | 66 | static func fromTypedData(data: AzureFunctionsRpcMessages_TypedData, preferJsonInString: Bool = false) throws -> Any? { 67 | var converted: Any? 68 | 69 | switch data.data { 70 | case let .some(.http(http)): 71 | let httpReq = HttpRequest(fromRpcHttp: http) 72 | converted = httpReq 73 | break 74 | case let .some(.json(jsonStr)): 75 | if let data = jsonStr.data(using: .utf8) { 76 | do { 77 | converted = try JSONSerialization.jsonObject(with: data, options: []) 78 | // if jsonStr.starts(with: "[") { 79 | // converted = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] 80 | // } else { 81 | // converted = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] 82 | // } 83 | } catch { 84 | Logger.log(error.localizedDescription) // throw exception 85 | throw FunctionError.JSONSerializationException(error.localizedDescription) 86 | } 87 | } else { 88 | throw FunctionError.JSONSerializationException("Cannot deserialize JSON") 89 | } 90 | break 91 | case let .some(.bytes(data)): 92 | converted = data 93 | break 94 | case let .some(.stream(data)): 95 | converted = data 96 | break 97 | case let .some(.string(string)): 98 | if !preferJsonInString { 99 | converted = string 100 | } else { 101 | if let data = string.data(using: .utf8) { 102 | if string.starts(with: "[") { 103 | converted = (try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: String]]) ?? string 104 | } else { 105 | converted = (try? JSONSerialization.jsonObject(with: data, options: []) as? [String: String]) ?? string 106 | } 107 | } else { 108 | converted = string 109 | } 110 | } 111 | break 112 | case let .some(.double(double)): 113 | converted = double 114 | break 115 | case let .some(.int(int)): 116 | converted = int 117 | break 118 | case let .some(.collectionBytes(colBytes)): 119 | converted = colBytes.bytes 120 | break 121 | case let .some(.collectionDouble(colDouble)): 122 | converted = colDouble.double 123 | break 124 | case let .some(.collectionSint64(colInt)): 125 | converted = colInt.sint64 126 | break 127 | case let .some(.collectionString(colStr)): 128 | converted = colStr.string 129 | break 130 | default: 131 | Logger.log("Unsupported data type by worker!") 132 | converted = "Unsupported data type by worker!" 133 | break 134 | } 135 | 136 | return converted 137 | } 138 | 139 | 140 | static func fromBodyTypedData(data: AzureFunctionsRpcMessages_TypedData) -> Data? { 141 | var converted: Data? = nil 142 | 143 | switch data.data { 144 | case let .some(.stream(data)): 145 | converted = data 146 | break 147 | case let .some(.bytes(data)): 148 | converted = data 149 | break 150 | case let .some(.json(str)), let .some(.string(str)): 151 | if let data = str.data(using: .utf8) { 152 | converted = data 153 | break 154 | } 155 | default: 156 | break 157 | } 158 | return converted 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/Templates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Templates.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 11/14/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal struct Templates { 11 | 12 | struct ProjectFiles { 13 | 14 | static let defaultExtensionsBundleId = "Microsoft.Azure.Functions.ExtensionBundle" 15 | static let defaultExtensionsVersion = "[1.*, 2.0.0)" 16 | static let defaultWorkerExecutablePath = "/home/site/wwwroot/workers/swift/functions" 17 | static let defaultHandlerExecutablePath = "/home/site/wwwroot/functions" 18 | 19 | static let functionJson = """ 20 | {{ bindings }} 21 | """ 22 | 23 | static let hostJsonExtensions = """ 24 | { 25 | "version": "2.0", 26 | "extensionBundle": { 27 | "id": "{{ extensionBundleID }}", 28 | "version": "{{ extensionBundleVersion }}" 29 | } 30 | } 31 | """ 32 | 33 | static let hostJsonExtensionsHttpWorker = """ 34 | { 35 | "version": "2.0", 36 | "extensionBundle": { 37 | "id": "{{ extensionBundleID }}", 38 | "version": "{{ extensionBundleVersion }}" 39 | }, 40 | "customHandler": { 41 | "description": { 42 | "defaultExecutablePath": "{{ execPath }}", 43 | "arguments": [ "run" ] 44 | } 45 | } 46 | } 47 | """ 48 | 49 | static let hostJson = """ 50 | { 51 | "version": "2.0" 52 | } 53 | """ 54 | 55 | static let workerConfigJson = """ 56 | { 57 | "description": { 58 | "arguments": [ 59 | "run" 60 | ], 61 | "defaultExecutablePath": "{{ execPath }}", 62 | "extensions": [ 63 | ".swift" 64 | ], 65 | "language": "swift" 66 | } 67 | } 68 | """ 69 | 70 | static let localSettingsJson = """ 71 | { 72 | "IsEncrypted": false, 73 | "Values": { 74 | "FUNCTIONS_WORKER_RUNTIME": "swift", 75 | "languageWorkers:workersDirectory": "workers", 76 | {{ envVars }} 77 | } 78 | } 79 | """ 80 | 81 | static let localSettingsJsonHttp = """ 82 | { 83 | "IsEncrypted": false, 84 | "Values": { 85 | {{ envVars }} 86 | } 87 | } 88 | """ 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/WorkerChannel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkerChannel.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/6/19. 6 | // 7 | 8 | import Foundation 9 | import GRPC 10 | import NIO 11 | import Dispatch 12 | 13 | 14 | internal final class WorkerChannel: WorkerChannelProtocol { 15 | 16 | var clientService: AzureFunctionsRpcMessages_FunctionRpcClient! 17 | 18 | var workerId: String? 19 | var requestId: String? 20 | var messageLength: Int32? 21 | 22 | var registry: FunctionRegistry! 23 | 24 | var eventStream: BidirectionalStreamingCall! 25 | 26 | var bindings: [String: [String: AzureFunctionsRpcMessages_BindingInfo]] = [:] 27 | 28 | init(workerId: String, requestId: String, messageLength: Int32, registry: FunctionRegistry) { 29 | self.requestId = requestId 30 | self.workerId = workerId 31 | self.messageLength = messageLength 32 | self.registry = registry 33 | } 34 | 35 | 36 | public func runClient(host: String, port: Int) { 37 | 38 | Logger.log("WorkerChannel Starting") 39 | 40 | let clientEventLoopGroup = PlatformSupport.makeEventLoopGroup(loopCount: 1) 41 | let configuration = ClientConnection.Configuration( 42 | target: .hostAndPort(host, port), 43 | eventLoopGroup: clientEventLoopGroup 44 | ) 45 | 46 | let connection = ClientConnection(configuration: configuration) 47 | 48 | self.clientService = AzureFunctionsRpcMessages_FunctionRpcClient(channel: connection) 49 | 50 | eventStream = clientService.eventStream(handler: streamHandler(message:)) 51 | 52 | startStream(requestId: requestId!, msg: AzureFunctionsRpcMessages_StartStream()) 53 | 54 | dispatchMain() 55 | } 56 | 57 | } 58 | 59 | // Channel 60 | extension WorkerChannel { 61 | 62 | func streamHandler(message: AzureFunctionsRpcMessages_StreamingMessage) { 63 | 64 | let reqID = message.requestID 65 | switch message.content { 66 | case let .some(.workerInitRequest(workerInitRequest)): 67 | self.workerInitRequest(requestId: reqID, msg: workerInitRequest) 68 | break 69 | case let .some(.functionLoadRequest(functionLoadRequest)): 70 | self.functionLoadRequest(requestId: reqID, msg: functionLoadRequest) 71 | break 72 | case let .some(.invocationRequest(invocationRequest)): 73 | do { 74 | try self.invocationRequest(requestId: reqID, msg: invocationRequest) 75 | } catch { 76 | Logger.log("Fatal Error: \(error.localizedDescription)") 77 | var res = AzureFunctionsRpcMessages_InvocationResponse() 78 | res.result = failureStatusResult(result: error.localizedDescription, exceptionMessage: error.localizedDescription, source: "Worker") 79 | sendMessage(content: .invocationResponse(res), requestId: reqID) 80 | exit(1) 81 | } 82 | break 83 | case let .some(.functionEnvironmentReloadRequest(envReloadRequest)): 84 | self.functionEnvironmentReloadRequest(requestId: reqID, msg: envReloadRequest) 85 | case .none: 86 | break 87 | default: 88 | break 89 | } 90 | 91 | } 92 | 93 | func startStream(requestId: String, msg: AzureFunctionsRpcMessages_StartStream) { 94 | var ststr: AzureFunctionsRpcMessages_StartStream = AzureFunctionsRpcMessages_StartStream() 95 | ststr.workerID = workerId! 96 | 97 | sendMessage(content: .startStream(ststr), requestId: requestId) 98 | } 99 | 100 | func workerInitRequest(requestId: String, msg: AzureFunctionsRpcMessages_WorkerInitRequest) { 101 | 102 | var res = AzureFunctionsRpcMessages_WorkerInitResponse() 103 | res.workerVersion = "0.1.1" 104 | res.capabilities = ["TypedDataCollection": "TypedDataCollection"] //["RpcHttpBodyOnly": "true" 105 | res.result = successStatusResult(nil) 106 | 107 | sendMessage(content: .workerInitResponse(res), requestId: requestId) 108 | 109 | } 110 | 111 | func workerHeartbeat(requestId: String, msg: AzureFunctionsRpcMessages_WorkerHeartbeat) { 112 | // Not Implemented 113 | } 114 | 115 | func workerTerminate(requestId: String, msg: AzureFunctionsRpcMessages_WorkerTerminate) { 116 | // Not Implemented 117 | } 118 | 119 | func workerStatusRequest(requestId: String, msg: AzureFunctionsRpcMessages_WorkerStatusRequest) { 120 | // Not Implemented 121 | } 122 | 123 | func fileChangeEventRequest(requestId: String, msg: AzureFunctionsRpcMessages_FileChangeEventRequest) { 124 | // Not Implemented 125 | } 126 | 127 | func functionLoadRequest(requestId: String, msg: AzureFunctionsRpcMessages_FunctionLoadRequest) { 128 | 129 | var res = AzureFunctionsRpcMessages_FunctionLoadResponse() 130 | res.functionID = msg.functionID 131 | 132 | if let _ = self.registry.byName(name: msg.metadata.name) { 133 | registry.register(id: msg.functionID, forName: msg.metadata.name) 134 | // self.functions.removeValue(forKey: msg.metadata.name) 135 | let info = FunctionInfo(withBindings: msg.metadata.bindings) 136 | registry.registerInfo(id: msg.functionID, info: info) 137 | registry.byId(id: msg.functionID)?.convertInputsToDictionary() 138 | res.result = successStatusResult(nil) 139 | } else { 140 | res.result = failureStatusResult(result: "Cannot load \(msg.metadata.name): not found", exceptionMessage: nil, source: nil) 141 | Logger.log("Cannot load \(msg.metadata.name): not found") 142 | } 143 | 144 | sendMessage(content: .functionLoadResponse(res), requestId: requestId) 145 | 146 | } 147 | 148 | func invocationRequest(requestId: String, msg: AzureFunctionsRpcMessages_InvocationRequest) throws { 149 | 150 | var res = AzureFunctionsRpcMessages_InvocationResponse() 151 | res.invocationID = msg.invocationID 152 | 153 | if let function: Function = registry.byId(id: msg.functionID), let functionInfo: FunctionInfo = registry.infoById(id: msg.functionID) { 154 | 155 | var inputBindings: [String: Any] = [:] 156 | 157 | var triggerInput: Any? 158 | 159 | for binding in msg.inputData { 160 | 161 | if function.trigger!.name == binding.name { 162 | 163 | if let ti = try BindingFactory.buildBinding(bindingDefinition: function.trigger!, rpcBinding: functionInfo.inputBindings[binding.name]!, binding: binding, metadata: msg.triggerMetadata) { 164 | triggerInput = ti 165 | } else { 166 | triggerInput = try RpcConverter.fromTypedData(data: binding.data) 167 | } 168 | 169 | } else { 170 | if let bindingDef = function.inputBindingsDic[binding.name], let inputBidning = try BindingFactory.buildBinding(bindingDefinition: bindingDef, rpcBinding: functionInfo.inputBindings[binding.name]!, binding: binding, metadata: nil) { 171 | 172 | inputBindings[binding.name] = inputBidning 173 | } else { 174 | inputBindings[binding.name] = try RpcConverter.fromTypedData(data: binding.data) 175 | } 176 | } 177 | 178 | } 179 | 180 | var context = Context() 181 | context.inputBindings = inputBindings 182 | 183 | do { 184 | try Broker.run(function: function, input: triggerInput, context: &context) { [weak context, self] result in 185 | 186 | if (result as? Bool) != true { 187 | res.returnValue = RpcConverter.toRpcTypedData(obj: result) 188 | } 189 | 190 | res.result = self.successStatusResult(nil) 191 | 192 | if let http: String = functionInfo.httpOutputBinding, context!.bindings[http] == nil, let httpRes = result as? HttpResponse { 193 | context!.bindings[http] = httpRes 194 | } 195 | 196 | context!.prepBindings() 197 | 198 | res.outputData = functionInfo.outputBindings 199 | .filter({ (key, val) -> Bool in 200 | return context!.bindings[key] != nil 201 | }).map({ (key, binding) -> AzureFunctionsRpcMessages_ParameterBinding in 202 | 203 | var paramBinding = AzureFunctionsRpcMessages_ParameterBinding() 204 | paramBinding.name = key 205 | 206 | var td = AzureFunctionsRpcMessages_TypedData() 207 | td = RpcConverter.toRpcTypedData(obj:context!.bindings[key]!) 208 | paramBinding.data = td 209 | 210 | return paramBinding 211 | }) 212 | 213 | self.sendMessage(content: .invocationResponse(res), requestId: requestId) 214 | } 215 | } catch { 216 | res.result = failureStatusResult(result: "Exception while executing Function \(function.name ?? "")", exceptionMessage: error.localizedDescription, source: function.name) 217 | sendMessage(content: .invocationResponse(res), requestId: requestId) 218 | } 219 | } else { 220 | res.result = failureStatusResult(result: "Cannot execute Function: not found", exceptionMessage: nil, source: nil) 221 | sendMessage(content: .invocationResponse(res), requestId: requestId) 222 | } 223 | } 224 | 225 | 226 | func invocationCancel(requestId: String, msg: AzureFunctionsRpcMessages_InvocationCancel) { 227 | // Not Implemented 228 | } 229 | 230 | func functionEnvironmentReloadRequest(requestId: String, msg: AzureFunctionsRpcMessages_FunctionEnvironmentReloadRequest) { 231 | var envVars = ProcessInfo.processInfo.environment 232 | 233 | var res = AzureFunctionsRpcMessages_FunctionEnvironmentReloadResponse() 234 | var stRes = AzureFunctionsRpcMessages_StatusResult.init() 235 | 236 | envVars.merge(msg.environmentVariables) { (_, new) -> String in new } 237 | Process().environment = envVars 238 | stRes.status = .success 239 | 240 | res.result = stRes 241 | sendMessage(content: .functionEnvironmentReloadResponse(res), requestId: requestId) 242 | 243 | } 244 | } 245 | 246 | // Helpers 247 | extension WorkerChannel { 248 | 249 | func sendMessage(content: AzureFunctionsRpcMessages_StreamingMessage.OneOf_Content, requestId: String) { 250 | var resMsg = AzureFunctionsRpcMessages_StreamingMessage() 251 | resMsg.requestID = requestId 252 | resMsg.content = content 253 | 254 | eventStream.sendMessage(resMsg, promise: nil) 255 | } 256 | 257 | func successStatusResult(_ result: String?) -> AzureFunctionsRpcMessages_StatusResult { 258 | var stRes = AzureFunctionsRpcMessages_StatusResult.init() 259 | stRes.status = .success 260 | if let res = result { 261 | stRes.result = res 262 | } 263 | return stRes 264 | } 265 | 266 | func failureStatusResult(result: String, exceptionMessage: String?, source: String?) -> AzureFunctionsRpcMessages_StatusResult { 267 | var stRes = AzureFunctionsRpcMessages_StatusResult.init() 268 | stRes.status = .failure 269 | stRes.result = result 270 | 271 | if let ex = exceptionMessage { 272 | var exception = AzureFunctionsRpcMessages_RpcException() 273 | exception.message = ex 274 | if let src = exceptionMessage { 275 | exception.source = src 276 | } 277 | } 278 | 279 | return stRes 280 | } 281 | 282 | } 283 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/WorkerChannelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionsProvider.swift 3 | // SwiftFunc 4 | // 5 | // Created by Saleh on 9/6/19. 6 | // 7 | 8 | import Foundation 9 | 10 | internal protocol WorkerChannelProtocol { 11 | func startStream(requestId: String, msg: AzureFunctionsRpcMessages_StartStream) -> Void 12 | func workerInitRequest(requestId: String, msg: AzureFunctionsRpcMessages_WorkerInitRequest) -> Void 13 | func workerHeartbeat(requestId: String, msg: AzureFunctionsRpcMessages_WorkerHeartbeat) -> Void 14 | func workerTerminate(requestId: String, msg: AzureFunctionsRpcMessages_WorkerTerminate) -> Void 15 | func workerStatusRequest(requestId: String, msg: AzureFunctionsRpcMessages_WorkerStatusRequest) -> Void 16 | func fileChangeEventRequest(requestId: String, msg: AzureFunctionsRpcMessages_FileChangeEventRequest) -> Void 17 | func functionLoadRequest(requestId: String, msg: AzureFunctionsRpcMessages_FunctionLoadRequest) -> Void 18 | func invocationRequest(requestId: String, msg: AzureFunctionsRpcMessages_InvocationRequest) throws -> Void 19 | func invocationCancel(requestId: String, msg: AzureFunctionsRpcMessages_InvocationCancel) -> Void 20 | func functionEnvironmentReloadRequest(requestId: String, msg: AzureFunctionsRpcMessages_FunctionEnvironmentReloadRequest) -> Void 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/protobuf/FunctionRpc.grpc.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DO NOT EDIT. 3 | // 4 | // Generated by the protocol buffer compiler. 5 | // Source: FunctionRpc.proto 6 | // 7 | 8 | // 9 | // Copyright 2018, gRPC Authors All rights reserved. 10 | // 11 | // Licensed under the Apache License, Version 2.0 (the "License"); 12 | // you may not use this file except in compliance with the License. 13 | // You may obtain a copy of the License at 14 | // 15 | // http://www.apache.org/licenses/LICENSE-2.0 16 | // 17 | // Unless required by applicable law or agreed to in writing, software 18 | // distributed under the License is distributed on an "AS IS" BASIS, 19 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | // See the License for the specific language governing permissions and 21 | // limitations under the License. 22 | // 23 | import Foundation 24 | import GRPC 25 | import NIO 26 | import NIOHTTP1 27 | import SwiftProtobuf 28 | 29 | 30 | /// Usage: instantiate AzureFunctionsRpcMessages_FunctionRpcClient, then call methods of this protocol to make API calls. 31 | internal protocol AzureFunctionsRpcMessages_FunctionRpcClientProtocol { 32 | func eventStream(callOptions: CallOptions?, handler: @escaping (AzureFunctionsRpcMessages_StreamingMessage) -> Void) -> BidirectionalStreamingCall 33 | } 34 | 35 | internal final class AzureFunctionsRpcMessages_FunctionRpcClient: GRPCClient, AzureFunctionsRpcMessages_FunctionRpcClientProtocol { 36 | internal let channel: GRPCChannel 37 | internal var defaultCallOptions: CallOptions 38 | 39 | /// Creates a client for the AzureFunctionsRpcMessages.FunctionRpc service. 40 | /// 41 | /// - Parameters: 42 | /// - channel: `GRPCChannel` to the service host. 43 | /// - defaultCallOptions: Options to use for each service call if the user doesn't provide them. 44 | internal init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) { 45 | self.channel = channel 46 | self.defaultCallOptions = defaultCallOptions 47 | } 48 | 49 | /// Bidirectional streaming call to EventStream 50 | /// 51 | /// Callers should use the `send` method on the returned object to send messages 52 | /// to the server. The caller should send an `.end` after the final message has been sent. 53 | /// 54 | /// - Parameters: 55 | /// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`. 56 | /// - handler: A closure called when each response is received from the server. 57 | /// - Returns: A `ClientStreamingCall` with futures for the metadata and status. 58 | internal func eventStream(callOptions: CallOptions? = nil, handler: @escaping (AzureFunctionsRpcMessages_StreamingMessage) -> Void) -> BidirectionalStreamingCall { 59 | return self.makeBidirectionalStreamingCall(path: "/AzureFunctionsRpcMessages.FunctionRpc/EventStream", 60 | callOptions: callOptions ?? self.defaultCallOptions, 61 | handler: handler) 62 | } 63 | 64 | } 65 | 66 | /// To build a server, implement a class that conforms to this protocol. 67 | internal protocol AzureFunctionsRpcMessages_FunctionRpcProvider: CallHandlerProvider { 68 | func eventStream(context: StreamingResponseCallContext) -> EventLoopFuture<(StreamEvent) -> Void> 69 | } 70 | 71 | extension AzureFunctionsRpcMessages_FunctionRpcProvider { 72 | internal var serviceName: String { return "AzureFunctionsRpcMessages.FunctionRpc" } 73 | 74 | /// Determines, calls and returns the appropriate request handler, depending on the request's method. 75 | /// Returns nil for methods not handled by this service. 76 | internal func handleMethod(_ methodName: String, callHandlerContext: CallHandlerContext) -> GRPCCallHandler? { 77 | switch methodName { 78 | case "EventStream": 79 | return BidirectionalStreamingCallHandler(callHandlerContext: callHandlerContext) { context in 80 | return self.eventStream(context: context) 81 | } 82 | 83 | default: return nil 84 | } 85 | } 86 | } 87 | 88 | 89 | // Provides conformance to `GRPCPayload` for request and response messages 90 | extension AzureFunctionsRpcMessages_StreamingMessage: GRPCProtobufPayload {} 91 | 92 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/protobuf/identity/ClaimsIdentityRpc.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // 3 | // Generated by the Swift generator plugin for the protocol buffer compiler. 4 | // Source: ClaimsIdentityRpc.proto 5 | // 6 | // For information on using the generated types, please see the documentation: 7 | // https://github.com/apple/swift-protobuf/ 8 | 9 | /// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 10 | 11 | import Foundation 12 | import SwiftProtobuf 13 | 14 | // If the compiler emits an error on this type, it is because this file 15 | // was generated by a version of the `protoc` Swift plug-in that is 16 | // incompatible with the version of SwiftProtobuf to which you are linking. 17 | // Please ensure that you are building against the same version of the API 18 | // that was used to generate this file. 19 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 20 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 21 | typealias Version = _2 22 | } 23 | 24 | /// Light-weight representation of a .NET System.Security.Claims.ClaimsIdentity object. 25 | /// This is the same serialization as found in EasyAuth, and needs to be kept in sync with 26 | /// its ClaimsIdentitySlim definition, as seen in the WebJobs extension: 27 | /// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimsIdentitySlim.cs 28 | struct RpcClaimsIdentity { 29 | // SwiftProtobuf.Message conformance is added in an extension below. See the 30 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 31 | // methods supported on all messages. 32 | 33 | var authenticationType: NullableString { 34 | get {return _authenticationType ?? NullableString()} 35 | set {_authenticationType = newValue} 36 | } 37 | /// Returns true if `authenticationType` has been explicitly set. 38 | var hasAuthenticationType: Bool {return self._authenticationType != nil} 39 | /// Clears the value of `authenticationType`. Subsequent reads from it will return its default value. 40 | mutating func clearAuthenticationType() {self._authenticationType = nil} 41 | 42 | var nameClaimType: NullableString { 43 | get {return _nameClaimType ?? NullableString()} 44 | set {_nameClaimType = newValue} 45 | } 46 | /// Returns true if `nameClaimType` has been explicitly set. 47 | var hasNameClaimType: Bool {return self._nameClaimType != nil} 48 | /// Clears the value of `nameClaimType`. Subsequent reads from it will return its default value. 49 | mutating func clearNameClaimType() {self._nameClaimType = nil} 50 | 51 | var roleClaimType: NullableString { 52 | get {return _roleClaimType ?? NullableString()} 53 | set {_roleClaimType = newValue} 54 | } 55 | /// Returns true if `roleClaimType` has been explicitly set. 56 | var hasRoleClaimType: Bool {return self._roleClaimType != nil} 57 | /// Clears the value of `roleClaimType`. Subsequent reads from it will return its default value. 58 | mutating func clearRoleClaimType() {self._roleClaimType = nil} 59 | 60 | var claims: [RpcClaim] = [] 61 | 62 | var unknownFields = SwiftProtobuf.UnknownStorage() 63 | 64 | init() {} 65 | 66 | fileprivate var _authenticationType: NullableString? = nil 67 | fileprivate var _nameClaimType: NullableString? = nil 68 | fileprivate var _roleClaimType: NullableString? = nil 69 | } 70 | 71 | /// Light-weight representation of a .NET System.Security.Claims.Claim object. 72 | /// This is the same serialization as found in EasyAuth, and needs to be kept in sync with 73 | /// its ClaimSlim definition, as seen in the WebJobs extension: 74 | /// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimSlim.cs 75 | struct RpcClaim { 76 | // SwiftProtobuf.Message conformance is added in an extension below. See the 77 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 78 | // methods supported on all messages. 79 | 80 | var value: String = String() 81 | 82 | var type: String = String() 83 | 84 | var unknownFields = SwiftProtobuf.UnknownStorage() 85 | 86 | init() {} 87 | } 88 | 89 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 90 | 91 | extension RpcClaimsIdentity: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 92 | static let protoMessageName: String = "RpcClaimsIdentity" 93 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 94 | 1: .standard(proto: "authentication_type"), 95 | 2: .standard(proto: "name_claim_type"), 96 | 3: .standard(proto: "role_claim_type"), 97 | 4: .same(proto: "claims"), 98 | ] 99 | 100 | mutating func decodeMessage(decoder: inout D) throws { 101 | while let fieldNumber = try decoder.nextFieldNumber() { 102 | switch fieldNumber { 103 | case 1: try decoder.decodeSingularMessageField(value: &self._authenticationType) 104 | case 2: try decoder.decodeSingularMessageField(value: &self._nameClaimType) 105 | case 3: try decoder.decodeSingularMessageField(value: &self._roleClaimType) 106 | case 4: try decoder.decodeRepeatedMessageField(value: &self.claims) 107 | default: break 108 | } 109 | } 110 | } 111 | 112 | func traverse(visitor: inout V) throws { 113 | if let v = self._authenticationType { 114 | try visitor.visitSingularMessageField(value: v, fieldNumber: 1) 115 | } 116 | if let v = self._nameClaimType { 117 | try visitor.visitSingularMessageField(value: v, fieldNumber: 2) 118 | } 119 | if let v = self._roleClaimType { 120 | try visitor.visitSingularMessageField(value: v, fieldNumber: 3) 121 | } 122 | if !self.claims.isEmpty { 123 | try visitor.visitRepeatedMessageField(value: self.claims, fieldNumber: 4) 124 | } 125 | try unknownFields.traverse(visitor: &visitor) 126 | } 127 | 128 | static func ==(lhs: RpcClaimsIdentity, rhs: RpcClaimsIdentity) -> Bool { 129 | if lhs._authenticationType != rhs._authenticationType {return false} 130 | if lhs._nameClaimType != rhs._nameClaimType {return false} 131 | if lhs._roleClaimType != rhs._roleClaimType {return false} 132 | if lhs.claims != rhs.claims {return false} 133 | if lhs.unknownFields != rhs.unknownFields {return false} 134 | return true 135 | } 136 | } 137 | 138 | extension RpcClaim: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 139 | static let protoMessageName: String = "RpcClaim" 140 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 141 | 1: .same(proto: "value"), 142 | 2: .same(proto: "type"), 143 | ] 144 | 145 | mutating func decodeMessage(decoder: inout D) throws { 146 | while let fieldNumber = try decoder.nextFieldNumber() { 147 | switch fieldNumber { 148 | case 1: try decoder.decodeSingularStringField(value: &self.value) 149 | case 2: try decoder.decodeSingularStringField(value: &self.type) 150 | default: break 151 | } 152 | } 153 | } 154 | 155 | func traverse(visitor: inout V) throws { 156 | if !self.value.isEmpty { 157 | try visitor.visitSingularStringField(value: self.value, fieldNumber: 1) 158 | } 159 | if !self.type.isEmpty { 160 | try visitor.visitSingularStringField(value: self.type, fieldNumber: 2) 161 | } 162 | try unknownFields.traverse(visitor: &visitor) 163 | } 164 | 165 | static func ==(lhs: RpcClaim, rhs: RpcClaim) -> Bool { 166 | if lhs.value != rhs.value {return false} 167 | if lhs.type != rhs.type {return false} 168 | if lhs.unknownFields != rhs.unknownFields {return false} 169 | return true 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Sources/AzureFunctions/protobuf/shared/NullableTypes.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // 3 | // Generated by the Swift generator plugin for the protocol buffer compiler. 4 | // Source: NullableTypes.proto 5 | // 6 | // For information on using the generated types, please see the documentation: 7 | // https://github.com/apple/swift-protobuf/ 8 | 9 | /// protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 10 | 11 | import Foundation 12 | import SwiftProtobuf 13 | 14 | // If the compiler emits an error on this type, it is because this file 15 | // was generated by a version of the `protoc` Swift plug-in that is 16 | // incompatible with the version of SwiftProtobuf to which you are linking. 17 | // Please ensure that you are building against the same version of the API 18 | // that was used to generate this file. 19 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 20 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 21 | typealias Version = _2 22 | } 23 | 24 | struct NullableString { 25 | // SwiftProtobuf.Message conformance is added in an extension below. See the 26 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 27 | // methods supported on all messages. 28 | 29 | var string: NullableString.OneOf_String? = nil 30 | 31 | var value: String { 32 | get { 33 | if case .value(let v)? = string {return v} 34 | return String() 35 | } 36 | set {string = .value(newValue)} 37 | } 38 | 39 | var unknownFields = SwiftProtobuf.UnknownStorage() 40 | 41 | enum OneOf_String: Equatable { 42 | case value(String) 43 | 44 | #if !swift(>=4.1) 45 | static func ==(lhs: NullableString.OneOf_String, rhs: NullableString.OneOf_String) -> Bool { 46 | switch (lhs, rhs) { 47 | case (.value(let l), .value(let r)): return l == r 48 | } 49 | } 50 | #endif 51 | } 52 | 53 | init() {} 54 | } 55 | 56 | struct NullableDouble { 57 | // SwiftProtobuf.Message conformance is added in an extension below. See the 58 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 59 | // methods supported on all messages. 60 | 61 | var double: NullableDouble.OneOf_Double? = nil 62 | 63 | var value: Double { 64 | get { 65 | if case .value(let v)? = double {return v} 66 | return 0 67 | } 68 | set {double = .value(newValue)} 69 | } 70 | 71 | var unknownFields = SwiftProtobuf.UnknownStorage() 72 | 73 | enum OneOf_Double: Equatable { 74 | case value(Double) 75 | 76 | #if !swift(>=4.1) 77 | static func ==(lhs: NullableDouble.OneOf_Double, rhs: NullableDouble.OneOf_Double) -> Bool { 78 | switch (lhs, rhs) { 79 | case (.value(let l), .value(let r)): return l == r 80 | } 81 | } 82 | #endif 83 | } 84 | 85 | init() {} 86 | } 87 | 88 | struct NullableBool { 89 | // SwiftProtobuf.Message conformance is added in an extension below. See the 90 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 91 | // methods supported on all messages. 92 | 93 | var bool: NullableBool.OneOf_Bool? = nil 94 | 95 | var value: Bool { 96 | get { 97 | if case .value(let v)? = bool {return v} 98 | return false 99 | } 100 | set {bool = .value(newValue)} 101 | } 102 | 103 | var unknownFields = SwiftProtobuf.UnknownStorage() 104 | 105 | enum OneOf_Bool: Equatable { 106 | case value(Bool) 107 | 108 | #if !swift(>=4.1) 109 | static func ==(lhs: NullableBool.OneOf_Bool, rhs: NullableBool.OneOf_Bool) -> Bool { 110 | switch (lhs, rhs) { 111 | case (.value(let l), .value(let r)): return l == r 112 | } 113 | } 114 | #endif 115 | } 116 | 117 | init() {} 118 | } 119 | 120 | struct NullableTimestamp { 121 | // SwiftProtobuf.Message conformance is added in an extension below. See the 122 | // `Message` and `Message+*Additions` files in the SwiftProtobuf library for 123 | // methods supported on all messages. 124 | 125 | var timestamp: NullableTimestamp.OneOf_Timestamp? = nil 126 | 127 | var value: SwiftProtobuf.Google_Protobuf_Timestamp { 128 | get { 129 | if case .value(let v)? = timestamp {return v} 130 | return SwiftProtobuf.Google_Protobuf_Timestamp() 131 | } 132 | set {timestamp = .value(newValue)} 133 | } 134 | 135 | var unknownFields = SwiftProtobuf.UnknownStorage() 136 | 137 | enum OneOf_Timestamp: Equatable { 138 | case value(SwiftProtobuf.Google_Protobuf_Timestamp) 139 | 140 | #if !swift(>=4.1) 141 | static func ==(lhs: NullableTimestamp.OneOf_Timestamp, rhs: NullableTimestamp.OneOf_Timestamp) -> Bool { 142 | switch (lhs, rhs) { 143 | case (.value(let l), .value(let r)): return l == r 144 | } 145 | } 146 | #endif 147 | } 148 | 149 | init() {} 150 | } 151 | 152 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 153 | 154 | extension NullableString: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 155 | static let protoMessageName: String = "NullableString" 156 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 157 | 1: .same(proto: "value"), 158 | ] 159 | 160 | mutating func decodeMessage(decoder: inout D) throws { 161 | while let fieldNumber = try decoder.nextFieldNumber() { 162 | switch fieldNumber { 163 | case 1: 164 | if self.string != nil {try decoder.handleConflictingOneOf()} 165 | var v: String? 166 | try decoder.decodeSingularStringField(value: &v) 167 | if let v = v {self.string = .value(v)} 168 | default: break 169 | } 170 | } 171 | } 172 | 173 | func traverse(visitor: inout V) throws { 174 | if case .value(let v)? = self.string { 175 | try visitor.visitSingularStringField(value: v, fieldNumber: 1) 176 | } 177 | try unknownFields.traverse(visitor: &visitor) 178 | } 179 | 180 | static func ==(lhs: NullableString, rhs: NullableString) -> Bool { 181 | if lhs.string != rhs.string {return false} 182 | if lhs.unknownFields != rhs.unknownFields {return false} 183 | return true 184 | } 185 | } 186 | 187 | extension NullableDouble: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 188 | static let protoMessageName: String = "NullableDouble" 189 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 190 | 1: .same(proto: "value"), 191 | ] 192 | 193 | mutating func decodeMessage(decoder: inout D) throws { 194 | while let fieldNumber = try decoder.nextFieldNumber() { 195 | switch fieldNumber { 196 | case 1: 197 | if self.double != nil {try decoder.handleConflictingOneOf()} 198 | var v: Double? 199 | try decoder.decodeSingularDoubleField(value: &v) 200 | if let v = v {self.double = .value(v)} 201 | default: break 202 | } 203 | } 204 | } 205 | 206 | func traverse(visitor: inout V) throws { 207 | if case .value(let v)? = self.double { 208 | try visitor.visitSingularDoubleField(value: v, fieldNumber: 1) 209 | } 210 | try unknownFields.traverse(visitor: &visitor) 211 | } 212 | 213 | static func ==(lhs: NullableDouble, rhs: NullableDouble) -> Bool { 214 | if lhs.double != rhs.double {return false} 215 | if lhs.unknownFields != rhs.unknownFields {return false} 216 | return true 217 | } 218 | } 219 | 220 | extension NullableBool: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 221 | static let protoMessageName: String = "NullableBool" 222 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 223 | 1: .same(proto: "value"), 224 | ] 225 | 226 | mutating func decodeMessage(decoder: inout D) throws { 227 | while let fieldNumber = try decoder.nextFieldNumber() { 228 | switch fieldNumber { 229 | case 1: 230 | if self.bool != nil {try decoder.handleConflictingOneOf()} 231 | var v: Bool? 232 | try decoder.decodeSingularBoolField(value: &v) 233 | if let v = v {self.bool = .value(v)} 234 | default: break 235 | } 236 | } 237 | } 238 | 239 | func traverse(visitor: inout V) throws { 240 | if case .value(let v)? = self.bool { 241 | try visitor.visitSingularBoolField(value: v, fieldNumber: 1) 242 | } 243 | try unknownFields.traverse(visitor: &visitor) 244 | } 245 | 246 | static func ==(lhs: NullableBool, rhs: NullableBool) -> Bool { 247 | if lhs.bool != rhs.bool {return false} 248 | if lhs.unknownFields != rhs.unknownFields {return false} 249 | return true 250 | } 251 | } 252 | 253 | extension NullableTimestamp: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { 254 | static let protoMessageName: String = "NullableTimestamp" 255 | static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 256 | 1: .same(proto: "value"), 257 | ] 258 | 259 | mutating func decodeMessage(decoder: inout D) throws { 260 | while let fieldNumber = try decoder.nextFieldNumber() { 261 | switch fieldNumber { 262 | case 1: 263 | var v: SwiftProtobuf.Google_Protobuf_Timestamp? 264 | if let current = self.timestamp { 265 | try decoder.handleConflictingOneOf() 266 | if case .value(let m) = current {v = m} 267 | } 268 | try decoder.decodeSingularMessageField(value: &v) 269 | if let v = v {self.timestamp = .value(v)} 270 | default: break 271 | } 272 | } 273 | } 274 | 275 | func traverse(visitor: inout V) throws { 276 | if case .value(let v)? = self.timestamp { 277 | try visitor.visitSingularMessageField(value: v, fieldNumber: 1) 278 | } 279 | try unknownFields.traverse(visitor: &visitor) 280 | } 281 | 282 | static func ==(lhs: NullableTimestamp, rhs: NullableTimestamp) -> Bool { 283 | if lhs.timestamp != rhs.timestamp {return false} 284 | if lhs.unknownFields != rhs.unknownFields {return false} 285 | return true 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /Tests/AzureFunctionsTests/SwiftFuncTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class SwiftFuncTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("SwiftFunc") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Tests/AzureFunctionsTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftFuncTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftFuncTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftFuncTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /protobuf/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /protobuf/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /protobuf/README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions Languge Worker Protobuf 2 | 3 | This repository contains the protobuf definition file which defines the gRPC service which is used between the Azure WebJobs Script host and the Azure Functions language workers. This repo is shared across many repos in many languages (for each worker) by using git commands. 4 | 5 | To use this repo in Azure Functions language workers, follow steps below to add this repo as a subtree (*Adding This Repo*). If this repo is already embedded in a language worker repo, follow the steps to update the consumed file (*Pulling Updates*). 6 | 7 | Learn more about Azure Function's projects on the [meta](https://github.com/azure/azure-functions) repo. 8 | 9 | ## Adding This Repo 10 | 11 | From within the Azure Functions language worker repo: 12 | 1. Define remote branch for cleaner git commands 13 | - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git` 14 | - `git fetch proto-file` 15 | 2. Index contents of azure-functions-worker-protobuf to language worker repo 16 | - `git read-tree --prefix= -u proto-file/` 17 | 3. Add new path in language worker repo to .gitignore file 18 | - In .gitignore, add path in language worker repo 19 | 4. Finalize with commit 20 | - `git commit -m "Added subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Branch: . Commit: "` 21 | - `git push` 22 | 23 | ## Pulling Updates 24 | 25 | From within the Azure Functions language worker repo: 26 | 1. Define remote branch for cleaner git commands 27 | - `git remote add proto-file https://github.com/azure/azure-functions-language-worker-protobuf.git` 28 | - `git fetch proto-file` 29 | 2. Pull a specific release tag 30 | - `git fetch proto-file refs/tags/` 31 | - Example: `git fetch proto-file refs/tags/v1.1.0-protofile` 32 | 3. Merge updates 33 | - Merge with an explicit path to subtree: `git merge -X subtree= --squash --allow-unrelated-histories --strategy-option theirs` 34 | - Example: `git merge -X subtree=src/WebJobs.Script.Grpc/azure-functions-language-worker-protobuf --squash v1.1.0-protofile --allow-unrelated-histories --strategy-option theirs` 35 | 4. Finalize with commit 36 | - `git commit -m "Updated subtree from https://github.com/azure/azure-functions-language-worker-protobuf. Tag: . Commit: "` 37 | - `git push` 38 | 39 | ## Releasing a Language Worker Protobuf version 40 | 41 | 1. Draft a release in the GitHub UI 42 | - Be sure to inculde details of the release 43 | 2. Create a release version, following semantic versioning guidelines ([semver.org](https://semver.org/)) 44 | 3. Tag the version with the pattern: `v..

-protofile` (example: `v1.1.0-protofile`) 45 | 3. Merge `dev` to `master` 46 | 47 | ## Consuming FunctionRPC.proto 48 | *Note: Update versionNumber before running following commands* 49 | 50 | ## CSharp 51 | ``` 52 | set NUGET_PATH="%UserProfile%\.nuget\packages" 53 | set GRPC_TOOLS_PATH=%NUGET_PATH%\grpc.tools\\tools\windows_x86 54 | set PROTO_PATH=.\azure-functions-language-worker-protobuf\src\proto 55 | set PROTO=.\azure-functions-language-worker-protobuf\src\proto\FunctionRpc.proto 56 | set PROTOBUF_TOOLS=%NUGET_PATH%\google.protobuf.tools\\tools 57 | set MSGDIR=.\Messages 58 | 59 | if exist %MSGDIR% rmdir /s /q %MSGDIR% 60 | mkdir %MSGDIR% 61 | 62 | set OUTDIR=%MSGDIR%\DotNet 63 | mkdir %OUTDIR% 64 | %GRPC_TOOLS_PATH%\protoc.exe %PROTO% --csharp_out %OUTDIR% --grpc_out=%OUTDIR% --plugin=protoc-gen-grpc=%GRPC_TOOLS_PATH%\grpc_csharp_plugin.exe --proto_path=%PROTO_PATH% --proto_path=%PROTOBUF_TOOLS% 65 | ``` 66 | ## JavaScript 67 | In package.json, add to the build script the following commands to build .js files and to build .ts files. Use and install npm package `protobufjs`. 68 | 69 | Generate JavaScript files: 70 | ``` 71 | pbjs -t json-module -w commonjs -o azure-functions-language-worker-protobuf/src/rpc.js azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto 72 | ``` 73 | Generate TypeScript files: 74 | ``` 75 | pbjs -t static-module azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto -o azure-functions-language-worker-protobuf/src/rpc_static.js && pbts -o azure-functions-language-worker-protobuf/src/rpc.d.ts azure-functions-language-worker-protobuf/src/rpc_static.js 76 | ``` 77 | 78 | ## Java 79 | Maven plugin : [protobuf-maven-plugin](https://www.xolstice.org/protobuf-maven-plugin/) 80 | In pom.xml add following under configuration for this plugin 81 | ${basedir}//azure-functions-language-worker-protobuf/src/proto 82 | 83 | ## Python 84 | --TODO 85 | 86 | ## Contributing 87 | 88 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 89 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 90 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 91 | 92 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 93 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 94 | provided by the bot. You will only need to do this once across all repos using our CLA. 95 | 96 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 97 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 98 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 99 | -------------------------------------------------------------------------------- /protobuf/src/proto/FunctionRpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 3 | 4 | option java_multiple_files = true; 5 | option java_package = "com.microsoft.azure.functions.rpc.messages"; 6 | option java_outer_classname = "FunctionProto"; 7 | option csharp_namespace = "Microsoft.Azure.WebJobs.Script.Grpc.Messages"; 8 | option go_package ="github.com/Azure/azure-functions-go-worker/internal/rpc"; 9 | 10 | package AzureFunctionsRpcMessages; 11 | 12 | import "google/protobuf/duration.proto"; 13 | import "azure_functions_worker/protos/ClaimsIdentityRpc.proto"; 14 | import "azure_functions_worker/protos/NullableTypes.proto"; 15 | 16 | // Interface exported by the server. 17 | service FunctionRpc { 18 | rpc EventStream (stream StreamingMessage) returns (stream StreamingMessage) {} 19 | } 20 | 21 | message StreamingMessage { 22 | // Used to identify message between host and worker 23 | string request_id = 1; 24 | 25 | // Payload of the message 26 | oneof content { 27 | 28 | // Worker initiates stream 29 | StartStream start_stream = 20; 30 | 31 | // Host sends capabilities/init data to worker 32 | WorkerInitRequest worker_init_request = 17; 33 | // Worker responds after initializing with its capabilities & status 34 | WorkerInitResponse worker_init_response = 16; 35 | 36 | // Worker periodically sends empty heartbeat message to host 37 | WorkerHeartbeat worker_heartbeat = 15; 38 | 39 | // Host sends terminate message to worker. 40 | // Worker terminates if it can, otherwise host terminates after a grace period 41 | WorkerTerminate worker_terminate = 14; 42 | 43 | // Add any worker relevant status to response 44 | WorkerStatusRequest worker_status_request = 12; 45 | WorkerStatusResponse worker_status_response = 13; 46 | 47 | // On file change event, host sends notification to worker 48 | FileChangeEventRequest file_change_event_request = 6; 49 | 50 | // Worker requests a desired action (restart worker, reload function) 51 | WorkerActionResponse worker_action_response = 7; 52 | 53 | // Host sends required metadata to worker to load function 54 | FunctionLoadRequest function_load_request = 8; 55 | // Worker responds after loading with the load result 56 | FunctionLoadResponse function_load_response = 9; 57 | 58 | // Host requests a given invocation 59 | InvocationRequest invocation_request = 4; 60 | 61 | // Worker responds to a given invocation 62 | InvocationResponse invocation_response = 5; 63 | 64 | // Host sends cancel message to attempt to cancel an invocation. 65 | // If an invocation is cancelled, host will receive an invocation response with status cancelled. 66 | InvocationCancel invocation_cancel = 21; 67 | 68 | // Worker logs a message back to the host 69 | RpcLog rpc_log = 2; 70 | 71 | FunctionEnvironmentReloadRequest function_environment_reload_request = 25; 72 | 73 | FunctionEnvironmentReloadResponse function_environment_reload_response = 26; 74 | } 75 | } 76 | 77 | // Process.Start required info 78 | // connection details 79 | // protocol type 80 | // protocol version 81 | 82 | // Worker sends the host information identifying itself 83 | message StartStream { 84 | // id of the worker 85 | string worker_id = 2; 86 | } 87 | 88 | // Host requests the worker to initialize itself 89 | message WorkerInitRequest { 90 | // version of the host sending init request 91 | string host_version = 1; 92 | 93 | // A map of host supported features/capabilities 94 | map capabilities = 2; 95 | 96 | // inform worker of supported categories and their levels 97 | // i.e. Worker = Verbose, Function.MyFunc = None 98 | map log_categories = 3; 99 | } 100 | 101 | // Worker responds with the result of initializing itself 102 | message WorkerInitResponse { 103 | // Version of worker 104 | string worker_version = 1; 105 | // A map of worker supported features/capabilities 106 | map capabilities = 2; 107 | 108 | // Status of the response 109 | StatusResult result = 3; 110 | } 111 | 112 | // Used by the host to determine success/failure/cancellation 113 | message StatusResult { 114 | // Indicates Failure/Success/Cancelled 115 | enum Status { 116 | Failure = 0; 117 | Success = 1; 118 | Cancelled = 2; 119 | } 120 | // Status for the given result 121 | Status status = 4; 122 | 123 | // Specific message about the result 124 | string result = 1; 125 | 126 | // Exception message (if exists) for the status 127 | RpcException exception = 2; 128 | 129 | // Captured logs or relevant details can use the logs property 130 | repeated RpcLog logs = 3; 131 | } 132 | 133 | // TODO: investigate grpc heartbeat - don't limit to grpc implemention 134 | 135 | // Message is empty by design - Will add more fields in future if needed 136 | message WorkerHeartbeat {} 137 | 138 | // Warning before killing the process after grace_period 139 | // Worker self terminates ..no response on this 140 | message WorkerTerminate { 141 | google.protobuf.Duration grace_period = 1; 142 | } 143 | 144 | // Host notifies worker of file content change 145 | message FileChangeEventRequest { 146 | // Types of File change operations (See link for more info: https://msdn.microsoft.com/en-us/library/t6xf43e0(v=vs.110).aspx) 147 | enum Type { 148 | Unknown = 0; 149 | Created = 1; 150 | Deleted = 2; 151 | Changed = 4; 152 | Renamed = 8; 153 | All = 15; 154 | } 155 | 156 | // type for this event 157 | Type type = 1; 158 | 159 | // full file path for the file change notification 160 | string full_path = 2; 161 | 162 | // Name of the function affected 163 | string name = 3; 164 | } 165 | 166 | // Indicates whether worker reloaded successfully or needs a restart 167 | message WorkerActionResponse { 168 | // indicates whether a restart is needed, or reload succesfully 169 | enum Action { 170 | Restart = 0; 171 | Reload = 1; 172 | } 173 | 174 | // action for this response 175 | Action action = 1; 176 | 177 | // text reason for the response 178 | string reason = 2; 179 | } 180 | 181 | // NOT USED 182 | message WorkerStatusRequest{ 183 | } 184 | 185 | // NOT USED 186 | message WorkerStatusResponse { 187 | } 188 | 189 | message FunctionEnvironmentReloadRequest { 190 | // Environment variables from the current process 191 | map environment_variables = 1; 192 | } 193 | 194 | message FunctionEnvironmentReloadResponse { 195 | // Status of the response 196 | StatusResult result = 3; 197 | } 198 | 199 | // Host tells the worker to load a Function 200 | message FunctionLoadRequest { 201 | // unique function identifier (avoid name collisions, facilitate reload case) 202 | string function_id = 1; 203 | 204 | // Metadata for the request 205 | RpcFunctionMetadata metadata = 2; 206 | 207 | // A flag indicating if managed dependency is enabled or not 208 | bool managed_dependency_enabled = 3; 209 | } 210 | 211 | // Worker tells host result of reload 212 | message FunctionLoadResponse { 213 | // unique function identifier 214 | string function_id = 1; 215 | 216 | // Result of load operation 217 | StatusResult result = 2; 218 | // TODO: return type expected? 219 | 220 | // Result of load operation 221 | bool is_dependency_downloaded = 3; 222 | } 223 | 224 | // Information on how a Function should be loaded and its bindings 225 | message RpcFunctionMetadata { 226 | // TODO: do we want the host's name - the language worker might do a better job of assignment than the host 227 | string name = 4; 228 | 229 | // base directory for the Function 230 | string directory = 1; 231 | 232 | // Script file specified 233 | string script_file = 2; 234 | 235 | // Entry point specified 236 | string entry_point = 3; 237 | 238 | // Bindings info 239 | map bindings = 6; 240 | 241 | // Is set to true for proxy 242 | bool is_proxy = 7; 243 | } 244 | 245 | // Host requests worker to invoke a Function 246 | message InvocationRequest { 247 | // Unique id for each invocation 248 | string invocation_id = 1; 249 | 250 | // Unique id for each Function 251 | string function_id = 2; 252 | 253 | // Input bindings (include trigger) 254 | repeated ParameterBinding input_data = 3; 255 | 256 | // binding metadata from trigger 257 | map trigger_metadata = 4; 258 | } 259 | 260 | // Host requests worker to cancel invocation 261 | message InvocationCancel { 262 | // Unique id for invocation 263 | string invocation_id = 2; 264 | 265 | // Time period before force shutdown 266 | google.protobuf.Duration grace_period = 1; // could also use absolute time 267 | } 268 | 269 | // Worker responds with status of Invocation 270 | message InvocationResponse { 271 | // Unique id for invocation 272 | string invocation_id = 1; 273 | 274 | // Output binding data 275 | repeated ParameterBinding output_data = 2; 276 | 277 | // data returned from Function (for $return and triggers with return support) 278 | TypedData return_value = 4; 279 | 280 | // Status of the invocation (success/failure/canceled) 281 | StatusResult result = 3; 282 | } 283 | 284 | // Used to encapsulate data which could be a variety of types 285 | message TypedData { 286 | oneof data { 287 | string string = 1; 288 | string json = 2; 289 | bytes bytes = 3; 290 | bytes stream = 4; 291 | RpcHttp http = 5; 292 | sint64 int = 6; 293 | double double = 7; 294 | CollectionBytes collection_bytes = 8; 295 | CollectionString collection_string = 9; 296 | CollectionDouble collection_double = 10; 297 | CollectionSInt64 collection_sint64 = 11; 298 | } 299 | } 300 | 301 | // Used to encapsulate collection string 302 | message CollectionString { 303 | repeated string string = 1; 304 | } 305 | 306 | // Used to encapsulate collection bytes 307 | message CollectionBytes { 308 | repeated bytes bytes = 1; 309 | } 310 | 311 | // Used to encapsulate collection double 312 | message CollectionDouble { 313 | repeated double double = 1; 314 | } 315 | 316 | // Used to encapsulate collection sint64 317 | message CollectionSInt64 { 318 | repeated sint64 sint64 = 1; 319 | } 320 | 321 | // Used to describe a given binding on invocation 322 | message ParameterBinding { 323 | // Name for the binding 324 | string name = 1; 325 | 326 | // Data for the binding 327 | TypedData data = 2; 328 | } 329 | 330 | // Used to describe a given binding on load 331 | message BindingInfo { 332 | // Indicates whether it is an input or output binding (or a fancy inout binding) 333 | enum Direction { 334 | in = 0; 335 | out = 1; 336 | inout = 2; 337 | } 338 | 339 | // Indicates the type of the data for the binding 340 | enum DataType { 341 | undefined = 0; 342 | string = 1; 343 | binary = 2; 344 | stream = 3; 345 | } 346 | 347 | // Type of binding (e.g. HttpTrigger) 348 | string type = 2; 349 | 350 | // Direction of the given binding 351 | Direction direction = 3; 352 | 353 | DataType data_type = 4; 354 | } 355 | 356 | // Used to send logs back to the Host 357 | message RpcLog { 358 | // Matching ILogger semantics 359 | // https://github.com/aspnet/Logging/blob/9506ccc3f3491488fe88010ef8b9eb64594abf95/src/Microsoft.Extensions.Logging/Logger.cs 360 | // Level for the Log 361 | enum Level { 362 | Trace = 0; 363 | Debug = 1; 364 | Information = 2; 365 | Warning = 3; 366 | Error = 4; 367 | Critical = 5; 368 | None = 6; 369 | } 370 | 371 | // Unique id for invocation (if exists) 372 | string invocation_id = 1; 373 | 374 | // TOD: This should be an enum 375 | // Category for the log (startup, load, invocation, etc.) 376 | string category = 2; 377 | 378 | // Level for the given log message 379 | Level level = 3; 380 | 381 | // Message for the given log 382 | string message = 4; 383 | 384 | // Id for the even associated with this log (if exists) 385 | string event_id = 5; 386 | 387 | // Exception (if exists) 388 | RpcException exception = 6; 389 | 390 | // json serialized property bag, or could use a type scheme like map 391 | string properties = 7; 392 | } 393 | 394 | // Encapsulates an Exception 395 | message RpcException { 396 | // Source of the exception 397 | string source = 3; 398 | 399 | // Stack trace for the exception 400 | string stack_trace = 1; 401 | 402 | // Textual message describing hte exception 403 | string message = 2; 404 | } 405 | 406 | // Http cookie type. Note that only name and value are used for Http requests 407 | message RpcHttpCookie { 408 | // Enum that lets servers require that a cookie shouoldn't be sent with cross-site requests 409 | enum SameSite { 410 | None = 0; 411 | Lax = 1; 412 | Strict = 2; 413 | } 414 | 415 | // Cookie name 416 | string name = 1; 417 | 418 | // Cookie value 419 | string value = 2; 420 | 421 | // Specifies allowed hosts to receive the cookie 422 | NullableString domain = 3; 423 | 424 | // Specifies URL path that must exist in the requested URL 425 | NullableString path = 4; 426 | 427 | // Sets the cookie to expire at a specific date instead of when the client closes. 428 | // It is generally recommended that you use "Max-Age" over "Expires". 429 | NullableTimestamp expires = 5; 430 | 431 | // Sets the cookie to only be sent with an encrypted request 432 | NullableBool secure = 6; 433 | 434 | // Sets the cookie to be inaccessible to JavaScript's Document.cookie API 435 | NullableBool http_only = 7; 436 | 437 | // Allows servers to assert that a cookie ought not to be sent along with cross-site requests 438 | SameSite same_site = 8; 439 | 440 | // Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. 441 | NullableDouble max_age = 9; 442 | } 443 | 444 | // TODO - solidify this or remove it 445 | message RpcHttp { 446 | string method = 1; 447 | string url = 2; 448 | map headers = 3; 449 | TypedData body = 4; 450 | map params = 10; 451 | string status_code = 12; 452 | map query = 15; 453 | bool enable_content_negotiation= 16; 454 | TypedData rawBody = 17; 455 | repeated RpcClaimsIdentity identities = 18; 456 | repeated RpcHttpCookie cookies = 19; 457 | } 458 | -------------------------------------------------------------------------------- /protobuf/src/proto/identity/ClaimsIdentityRpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 3 | 4 | option java_package = "com.microsoft.azure.functions.rpc.messages"; 5 | 6 | import "azure_functions_worker/protos/NullableTypes.proto"; 7 | 8 | // Light-weight representation of a .NET System.Security.Claims.ClaimsIdentity object. 9 | // This is the same serialization as found in EasyAuth, and needs to be kept in sync with 10 | // its ClaimsIdentitySlim definition, as seen in the WebJobs extension: 11 | // https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimsIdentitySlim.cs 12 | message RpcClaimsIdentity { 13 | NullableString authentication_type = 1; 14 | NullableString name_claim_type = 2; 15 | NullableString role_claim_type = 3; 16 | repeated RpcClaim claims = 4; 17 | } 18 | 19 | // Light-weight representation of a .NET System.Security.Claims.Claim object. 20 | // This is the same serialization as found in EasyAuth, and needs to be kept in sync with 21 | // its ClaimSlim definition, as seen in the WebJobs extension: 22 | // https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimSlim.cs 23 | message RpcClaim { 24 | string value = 1; 25 | string type = 2; 26 | } 27 | -------------------------------------------------------------------------------- /protobuf/src/proto/shared/NullableTypes.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | // protobuf vscode extension: https://marketplace.visualstudio.com/items?itemName=zxh404.vscode-proto3 3 | 4 | option java_package = "com.microsoft.azure.functions.rpc.messages"; 5 | 6 | import "google/protobuf/timestamp.proto"; 7 | 8 | message NullableString { 9 | oneof string { 10 | string value = 1; 11 | } 12 | } 13 | 14 | message NullableDouble { 15 | oneof double { 16 | double value = 1; 17 | } 18 | } 19 | 20 | message NullableBool { 21 | oneof bool { 22 | bool value = 1; 23 | } 24 | } 25 | 26 | message NullableTimestamp { 27 | oneof timestamp { 28 | google.protobuf.Timestamp value = 1; 29 | } 30 | } 31 | --------------------------------------------------------------------------------