-
Notifications
You must be signed in to change notification settings - Fork 180
/
ServerCombinedErrorGenerator.kt
156 lines (145 loc) · 6.47 KB
/
ServerCombinedErrorGenerator.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.rust.codegen.server.smithy.generators
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.rustlang.Attribute
import software.amazon.smithy.rust.codegen.rustlang.RustMetadata
import software.amazon.smithy.rust.codegen.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.rustlang.Visibility
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.documentShape
import software.amazon.smithy.rust.codegen.rustlang.rust
import software.amazon.smithy.rust.codegen.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.RustSymbolProvider
import software.amazon.smithy.rust.codegen.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.smithy.generators.error.eventStreamErrorSymbol
import software.amazon.smithy.rust.codegen.smithy.transformers.eventStreamErrors
import software.amazon.smithy.rust.codegen.smithy.transformers.operationErrors
import software.amazon.smithy.rust.codegen.util.isEventStream
import software.amazon.smithy.rust.codegen.util.toSnakeCase
/**
* Generates a unified error enum for [operation]. [ErrorGenerator] handles generating the individual variants,
* but we must still combine those variants into an enum covering all possible errors for a given operation.
*/
open class ServerCombinedErrorGenerator(
private val model: Model,
private val symbolProvider: RustSymbolProvider,
private val operation: OperationShape
) {
fun render(writer: RustWriter) {
val errors = operation.operationErrors(model)
val symbol = operation.errorSymbol(symbolProvider)
val operationSymbol = symbolProvider.toSymbol(operation)
if (errors.isNotEmpty()) {
renderErrors(writer, errors.map { it.asStructureShape().get() }, symbol, operationSymbol)
}
if (operation.isEventStream(model)) {
operation.eventStreamErrors(model)
.forEach { (unionShape, unionErrors) ->
if (unionErrors.isNotEmpty()) {
renderErrors(
writer,
unionErrors,
unionShape.eventStreamErrorSymbol(symbolProvider),
symbolProvider.toSymbol(unionShape)
)
}
}
}
}
private fun renderErrors(
writer: RustWriter,
errors: List<StructureShape>,
errorSymbol: RuntimeType,
operationSymbol: Symbol
) {
val meta = RustMetadata(
derives = Attribute.Derives(setOf(RuntimeType.Debug)),
visibility = Visibility.PUBLIC
)
writer.rust("/// Error type for the `${operationSymbol.name}` operation.")
writer.rust("/// Each variant represents an error that can occur for the `${operationSymbol.name}` operation.")
meta.render(writer)
writer.rustBlock("enum ${errorSymbol.name}") {
errors.forEach { errorVariant ->
documentShape(errorVariant, model)
val errorVariantSymbol = symbolProvider.toSymbol(errorVariant)
write("${errorVariantSymbol.name}(#T),", errorVariantSymbol)
}
}
writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.Display) {
rustBlock("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result") {
delegateToVariants(errors, errorSymbol) {
rust("_inner.fmt(f)")
}
}
}
writer.rustBlock("impl ${errorSymbol.name}") {
errors.forEach { error ->
val errorVariantSymbol = symbolProvider.toSymbol(error)
val fnName = errorVariantSymbol.name.toSnakeCase()
writer.rust("/// Returns `true` if the error kind is `${errorSymbol.name}::${errorVariantSymbol.name}`.")
writer.rustBlock("pub fn is_$fnName(&self) -> bool") {
rust("matches!(&self, ${errorSymbol.name}::${errorVariantSymbol.name}(_))")
}
}
writer.rust("/// Returns the error name string by matching the correct variant.")
writer.rustBlock("pub fn name(&self) -> &'static str") {
delegateToVariants(errors, errorSymbol) {
rust("_inner.name()")
}
}
}
writer.rustBlock("impl #T for ${errorSymbol.name}", RuntimeType.StdError) {
rustBlock("fn source(&self) -> Option<&(dyn #T + 'static)>", RuntimeType.StdError) {
delegateToVariants(errors, errorSymbol) {
rust("Some(_inner)")
}
}
}
for (error in errors) {
val errorVariantSymbol = symbolProvider.toSymbol(error)
writer.rustBlock("impl #T<#T> for #T", RuntimeType.From, errorVariantSymbol, errorSymbol) {
rustBlock("fn from(variant: #T) -> #T", errorVariantSymbol, errorSymbol) {
rust("Self::${errorVariantSymbol.name}(variant)")
}
}
}
}
/**
* Generates code to delegate behavior to the variants, for example:
*
* ```rust
* match &self {
* GreetingWithErrorsError::InvalidGreeting(_inner) => inner.fmt(f),
* GreetingWithErrorsError::ComplexError(_inner) => inner.fmt(f),
* GreetingWithErrorsError::FooError(_inner) => inner.fmt(f),
* GreetingWithErrorsError::Unhandled(_inner) => _inner.fmt(f),
* }
* ```
*
* A [writable] is passed containing the content to be written for each variant.
*
* The field will always be bound as `_inner`.
*/
private fun RustWriter.delegateToVariants(
errors: List<StructureShape>,
symbol: RuntimeType,
writable: Writable,
) {
rustBlock("match &self") {
errors.forEach {
val errorSymbol = symbolProvider.toSymbol(it)
rust("""${symbol.name}::${errorSymbol.name}(_inner) => """)
writable(this)
write(",")
}
}
}
}