Skip to content

Commit e55652f

Browse files
committed
Artifact Submission
1 parent f509773 commit e55652f

23 files changed

+1011
-117
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ jobs:
1919
name: Build and Test
2020
uses: Apodini/.github/.github/workflows/build-and-test.yml@v1
2121
with:
22-
packagename: ApodiniTemplate
22+
packagename: ApodiniSustainability

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
name: Build and Test
1818
uses: Apodini/.github/.github/workflows/build-and-test.yml@v1
1919
with:
20-
packagename: ApodiniTemplate
20+
packagename: ApodiniSustainability
2121
reuse_action:
2222
name: REUSE Compliance Check
2323
uses: Apodini/.github/.github/workflows/reuse.yml@v1

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ jobs:
1818
name: Generate Docs
1919
uses: Apodini/.github/.github/workflows/docs.yml@v1
2020
with:
21-
packagename: ApodiniTemplate
21+
targetname: ApodiniSustainability

Package.swift

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
11
// swift-tools-version:5.5
2-
3-
//
4-
// This source file is part of the Apodini open source project
5-
//
6-
// SPDX-FileCopyrightText: 2021 Paul Schmiedmayer and the project authors (see CONTRIBUTORS.md) <paul.schmiedmayer@tum.de>
7-
//
8-
// SPDX-License-Identifier: MIT
9-
//
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
103

114
import PackageDescription
125

13-
146
let package = Package(
15-
name: "ApodiniTemplate",
7+
name: "ApodiniSustainability",
168
platforms: [
17-
.macOS(.v11)
9+
.macOS(.v12)
1810
],
1911
products: [
20-
.library(name: "ApodiniTemplate", targets: ["ApodiniTemplate"])
12+
// Products define the executables and libraries a package produces, and make them visible to other packages.
13+
.library(
14+
name: "ApodiniSustainability",
15+
targets: ["ApodiniSustainability"]),
16+
.executable(
17+
name: "ApodiniSustainabilityDemo",
18+
targets: ["ApodiniSustainabilityDemo"])
19+
],
20+
dependencies: [
21+
// Dependencies declare other packages that this package depends on.
22+
// .package(url: /* package url */, from: "1.0.0"),
23+
.package(url: "https://github.com/Apodini/Apodini.git", .upToNextMinor(from: "0.9.0")),
24+
.package(url: "https://github.com/Apodini/ApodiniDocumentExport.git", .upToNextMinor(from: "0.1.0")),
2125
],
2226
targets: [
23-
.target(name: "ApodiniTemplate"),
27+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
28+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
29+
.target(
30+
name: "ApodiniSustainability",
31+
dependencies: [
32+
.product(name: "Apodini", package: "Apodini"),
33+
.product(name: "ApodiniNetworking", package: "Apodini"),
34+
.product(name: "ApodiniDocumentExport", package: "ApodiniDocumentExport")
35+
]),
36+
.executableTarget(
37+
name: "ApodiniSustainabilityDemo",
38+
dependencies: [
39+
.product(name: "Apodini", package: "Apodini"),
40+
.product(name: "ApodiniREST", package: "Apodini"),
41+
.target(name: "ApodiniSustainability")
42+
]),
2443
.testTarget(
25-
name: "ApodiniTemplateTests",
44+
name: "ApodiniSustainabilityTests",
2645
dependencies: [
27-
.target(name: "ApodiniTemplate")
28-
]
29-
)
46+
.target(name: "ApodiniSustainability"),
47+
.product(name: "XCTApodiniNetworking", package: "Apodini")
48+
]),
3049
]
3150
)

README.md

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,36 +8,62 @@ SPDX-License-Identifier: MIT
88
99
-->
1010

11-
## How to use this repository
12-
### Template
13-
14-
When creating a new repository, make sure to select this repository as a repository template.
15-
16-
### Customize the repository
17-
18-
Enter your repository-specific configuration
19-
- Replace the "Package.swift", "Sources" and "Tests" folder with your Swift Package
20-
- Enter the correct Swift Package name (currently "ApodiniTemplate") in the build.yml, pull_request.yml and release.yml files.
21-
- Update the DocC documentation to reflect the name of the new Swift package and adapt the docs and build and test GitHub Actions where the documentation is generated to the updated names to be sure the DocC generation works as expected
22-
- Update the README with your information and replace the links to the license with the new repository.
23-
- Update the status badges to point to the GitHub actions of your repository.
24-
- If you create a new repository in the Apodini organization, you do not need to add a personal access token named "ACCESS_TOKEN". If you create the repo outside the Apodini organization, you need to create such a token with write access to the repo for all GitHub Actions to work. You will need to give the `ApodiniBot` user write access to the repository.
25-
26-
### ⬆️ Remove everything up to here ⬆️
27-
28-
# Project Name
29-
30-
[![Build](https://github.com/Apodini/Template-Repository/actions/workflows/build.yml/badge.svg)](https://github.com/Apodini/Template-Repository/actions/workflows/build.yml)
31-
[![codecov](https://codecov.io/gh/Apodini/Template-Repository/branch/develop/graph/badge.svg?token=5MMKMPO5NR)](https://codecov.io/gh/Apodini/Template-Repository)
32-
33-
## Requirements
34-
35-
## Installation/Setup/Integration
36-
37-
## Usage
11+
# Apodini Sustainability
12+
13+
[![Build](https://github.com/Apodini/ApodiniSustainability/actions/workflows/build.yml/badge.svg)](https://github.com/Apodini/ApodiniSustainability/actions/workflows/build.yml)
14+
[![codecov](https://codecov.io/gh/Apodini/ApodiniSustainability/branch/develop/graph/badge.svg?token=5MMKMPO5NR)](https://codecov.io/gh/Apodini/ApodiniSustainability)
15+
16+
ApodiniSustainability includes metadata definitions for annotating Apodini web services with additional information to enable sustainability in cloud-native applications and the interface exporter to share the information.
17+
18+
## Hello World
19+
20+
This `HelloWorld` example shows the annotations of the Apodini web service with metadata and includes the `Sustainability` interface exporter in the configuration of the web service.
21+
22+
```
23+
struct Greeter: Handler {
24+
@Parameter var country: String?
25+
26+
func handle() -> String {
27+
"Hello, \(country ?? "World")! 🚀"
28+
}
29+
30+
var metadata: Metadata {
31+
ExecutionModality(.highPerformance)
32+
ResponseTime(value: 1)
33+
InstanceType(.small)
34+
}
35+
}
36+
37+
struct GreeterService: Component {
38+
39+
var content: some Component {
40+
Greeter()
41+
.description("Say 'Hello' to a country.")
42+
}
43+
44+
var metadata: Metadata {
45+
Optional()
46+
ServiceIdentifier("greeter")
47+
}
48+
}
49+
50+
struct HelloWorld: WebService {
51+
52+
@OptionGroup
53+
var options: SustainabilityDocumentExportOptions
54+
55+
var configuration: Configuration {
56+
Sustainability(options)
57+
}
58+
59+
var content: some Component {
60+
GreeterService()
61+
}
62+
}
63+
```
3864

3965
## Contributing
4066
Contributions to this project are welcome. Please make sure to read the [contribution guidelines](https://github.com/Apodini/.github/blob/main/CONTRIBUTING.md) and the [contributor covenant code of conduct](https://github.com/Apodini/.github/blob/main/CODE_OF_CONDUCT.md) first.
4167

4268
## License
43-
This project is licensed under the MIT License. See [Licenses](https://github.com/Apodini/Template-Repository/tree/develop/LICENSES) for more information.
69+
This project is licensed under the MIT License. See [Licenses](https://github.com/Apodini/ApodiniSustainability/tree/develop/LICENSES) for more information.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# ``ApodiniSustainability``
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import Apodini
2+
import ApodiniDocumentExport
3+
import ApodiniNetworking
4+
@_implementationOnly import Logging
5+
6+
final class ApodiniSustainabilityInterfaceExporter: InterfaceExporter {
7+
8+
private let app: Application
9+
private let configuration: SustainabilityDocumentConfiguration
10+
private var services: [String: Service] = .init()
11+
private let logger = Logger(label: "org.apodini.sustainability")
12+
13+
init(_ app: Application, configuration: SustainabilityDocumentConfiguration) {
14+
self.app = app
15+
self.configuration = configuration
16+
}
17+
18+
func export<H>(_ endpoint: Endpoint<H>) -> () where H : Handler {
19+
20+
guard let key = endpoint[Context.self].get(valueFor: ServiceIdentifierComponentMetadata.self) else { return }
21+
22+
let requirements = Requirements(responseTime: endpoint[Context.self].get(valueFor: ResponseTimeHandlerMetadata.self), instanceType: endpoint[Context.self].get(valueFor: InstanceTypeHandlerMetadata.self))
23+
24+
let serviceVersion = ServiceVersion(id: endpoint[AnyHandlerIdentifier.self], name: endpoint[HandlerDescription.self].rawValue, description: endpoint[Context.self].get(valueFor: HandlerDescriptionMetadata.self), requirements: requirements)
25+
26+
let modalities: ExecutionModalities
27+
28+
switch endpoint[Context.self].get(valueFor: ExecutionModalityHandlerMetadata.self) {
29+
case .highPerformance:
30+
modalities = ExecutionModalities(highPerformance: serviceVersion)
31+
case .lowPower:
32+
modalities = ExecutionModalities(lowPower: serviceVersion)
33+
default:
34+
modalities = ExecutionModalities(standard: serviceVersion)
35+
}
36+
37+
let optional : Bool? = endpoint[Context.self].get(valueFor: OptionalComponentMetadata.self)
38+
39+
services.merge([key : Service(
40+
id: key,
41+
name: key.replacingOccurrences(of: "-", with: " ").capitalized(with: nil),
42+
description: String.init(),
43+
optional: optional,
44+
modalities: modalities
45+
)]) { current, new in
46+
var service = current
47+
service.modalities = service.modalities.merge(with: new.modalities)
48+
if new.optional ?? false {
49+
service.optional = true
50+
}
51+
return service
52+
}
53+
}
54+
55+
func export<H>(blob endpoint: Endpoint<H>) -> () where H : Handler, H.Response.Content == Blob {
56+
export(endpoint)
57+
}
58+
59+
func finishedExporting(_ webService: WebServiceModel) {
60+
61+
let document = SustainabilityDocument(
62+
serverPath: app.httpConfiguration.uriPrefix,
63+
version: webService.context.get(valueFor: APIVersionContextKey.self).semVerString,
64+
description: webService.context.get(valueFor: WebServiceDescriptionMetadata.self),
65+
services: [Service](services.values)
66+
)
67+
68+
app.storage.set(SustainabilityDocumentStorageKey.self, to: document)
69+
70+
guard let options = configuration.exportOptions else {
71+
return logger.notice("No configuration provided to handle the ApodiniSustainability document of the current version")
72+
}
73+
74+
if let directory = options.directory {
75+
do {
76+
let filePath = try document.write(at: directory, outputFormat: options.format, fileName: "sustainability_\(document.version)")
77+
logger.info("ApodiniSustainability document exported at \(filePath) in \(options.format.rawValue)")
78+
} catch {
79+
logger.error("ApodiniSustainability export at \(directory) failed with error: \(error)")
80+
}
81+
}
82+
83+
if let endpoint = options.endpoint {
84+
// app.httpServer.registerRoute(.GET, endpoint.httpPathComponents) { _ -> String in
85+
// options.format.string(of: document)
86+
// }
87+
app.httpServer.registerRoute(.GET, endpoint.httpPathComponents) { req in
88+
HTTPResponse(
89+
version: req.version,
90+
status: .ok,
91+
headers: HTTPHeaders {
92+
$0[.accessControlAllowOrigin] = .wildcard
93+
},
94+
bodyStorage: .buffer(initialValue: options.format.string(of: document))
95+
)
96+
}
97+
logger.info("ApodiniSustainability document served at \(endpoint) in \(options.format.rawValue) format")
98+
}
99+
}
100+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Apodini
2+
import ApodiniDocumentExport
3+
4+
public enum ExecutionModalityOption: String, Value {
5+
case standard
6+
case highPerformance
7+
case lowPower
8+
}
9+
10+
public struct ExecutionModalityMetadataContextKey: ContextKey {
11+
public static var defaultValue = ExecutionModalityOption.standard
12+
public typealias Value = ExecutionModalityOption
13+
}
14+
15+
extension HandlerMetadataNamespace {
16+
/// Name definition for the ``ExecutionModalityHandlerMetadata``
17+
public typealias ExecutionModality = ExecutionModalityHandlerMetadata
18+
}
19+
20+
/// The ``ExecutionModalityHandlerMetadata`` can be used to describe the execution modality of a ``Handler``.
21+
///
22+
/// The Metadata is available under the ``HandlerMetadataNamespace/ExecutionModality`` name and can be used like the following:
23+
/// ```swift
24+
/// struct ExampleHandler: Handler {
25+
/// // ...
26+
/// var metadata: Metadata {
27+
/// ExecutionModality(.highPerformance)
28+
/// }
29+
/// }
30+
/// ```
31+
public struct ExecutionModalityHandlerMetadata: HandlerMetadataDefinition {
32+
public typealias Key = ExecutionModalityMetadataContextKey
33+
public let value: ExecutionModalityOption
34+
35+
/// Creates a new `ExecutionModality` metadata
36+
/// - Parameter value: The execution modality option for the Component.
37+
public init(_ value: ExecutionModalityOption) {
38+
self.value = value
39+
}
40+
}
41+
42+
extension Handler {
43+
/// A `executionModality` modifier can be used to specify the `ExecutionModalityHandlerMetadata` via a `HandlerModifier`.
44+
/// - Parameter value: The `executionModality` that is used for the `Handler`
45+
/// - Returns: The modified `Handler` with the `ExecutionModalityHandlerMetadata` added.
46+
public func executionModality(_ value: ExecutionModalityOption) -> HandlerMetadataModifier<Self> {
47+
HandlerMetadataModifier(modifies: self, with: ExecutionModalityHandlerMetadata(value))
48+
}
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Apodini
2+
import ApodiniDocumentExport
3+
4+
public enum InstanceTypeOption: String, Value {
5+
case large
6+
case medium
7+
case small
8+
}
9+
10+
public struct InstanceTypeMetadataContextKey: OptionalContextKey {
11+
public typealias Value = InstanceTypeOption
12+
}
13+
14+
extension HandlerMetadataNamespace {
15+
/// Name definition for the ``InstanceTypeHandlerMetadata``
16+
public typealias InstanceType = InstanceTypeHandlerMetadata
17+
}
18+
19+
/// The ``InstanceTypeHandlerMetadata`` can be used to describe the instance type of a ``Handler``.
20+
///
21+
/// The Metadata is available under the ``HandlerMetadataNamespace/InstanceType`` name and can be used like the following:
22+
/// ```swift
23+
/// struct ExampleHandler: Handler {
24+
/// // ...
25+
/// var metadata: Metadata {
26+
/// InstanceType(.small)
27+
/// }
28+
/// }
29+
/// ```
30+
public struct InstanceTypeHandlerMetadata: HandlerMetadataDefinition {
31+
public typealias Key = InstanceTypeMetadataContextKey
32+
public let value: InstanceTypeOption
33+
34+
/// Creates a new `InstanceType` metadata
35+
/// - Parameter value: The instance type option for the Component.
36+
public init(_ value: InstanceTypeOption) {
37+
self.value = value
38+
}
39+
}
40+
41+
extension Handler {
42+
/// A `instanceType` modifier can be used to specify the `InstanceTypeHandlerMetadata` via a `HandlerModifier`.
43+
/// - Parameter value: The `instanceType` that is used for the `Handler`
44+
/// - Returns: The modified `Handler` with the `InstanceTypeHandlerMetadata` added.
45+
public func instanceType(_ value: InstanceTypeOption) -> HandlerMetadataModifier<Self> {
46+
HandlerMetadataModifier(modifies: self, with: InstanceTypeHandlerMetadata(value))
47+
}
48+
}

0 commit comments

Comments
 (0)