-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to specify a constant HTTP header value in a Smithy model? #1002
Comments
Something like this, perhaps, but really hoping it's already supported somehow and doesn't need to be added: /// Configures the HTTP bindings of an operation.
@trait(selector: "operation")
@tags(["diff.error.remove"])
structure http {
/// The HTTP method of the operation.
@required
method: NonEmptyString,
/// The URI pattern of the operation.
///
/// Labels defined in the URI pattern are used to bind operation input
/// members to the URI.
@required
uri: NonEmptyString,
/// The HTTP status code of a successful response.
///
/// Defaults to 200 if not provided.
code: PrimitiveInteger,
+
+ /// Static HTTP headers to add to the request.
+ headers: HttpHeaders,
}
+
+ map HttpHeaders {
+ key: String,
+ value: String,
+ } |
You can't right now, but this is a reasonable feature request. |
@mtdowling thanks! Does this approach make sense (adding a |
That's how I'd imagine this best being described per/operation. There are some things to consider like:
|
Great questions! Here are some proposed answers:
|
We don't allow conflicts today in header bindings, so I think at least starting with that stance here would make sense.
Enforcing at least parts of RFC 7230 makes sense to me. For example, header field names can't be empty, and we'd want to warn if a header name uses non-ASCII characters (even though the RFC allows for obs-text). For header values, we'd ensure they aren't empty strings, they aren't null, and also warn on non-ASCII. We'd likely want to warn if the header value contains commas too since 7230 basically punts on being able to generically parse HTTP headers without knowing the ABNF of every special header that uses commas.
I think that makes sense. There's also the "const" keyword which is basically the same thing but would be more explicit for this case. |
Sounds good. Pointers on how / where this validation should be implemented would be super-helpful as I'm not familiar with the code yet and this would presumably imply cross-checking between the headers used in the trait and the headers used in the request shape.
Makes sense. I'm going to hope that there's already validation for the HTTP binding of headers that I could re-use for the names part. I'm curious about the warning on commas, can you say more about why you'd want to emit a warning there? Is there a way to suppress warnings if the service modeller "knows what they are doing"? Also occurs to me: does it make sense to have a block-list of headers that you can't set, perhaps the same list as for the
Sounds perfect, and seems reasonable to expect an OpenAPI codegen to do the right thing with |
I think I've got the parsing figured out. Next step will be to look at consuming the parsed model in the OpenAPI projection. re: validation: looks like HttpHeaderTraitValidator has a bunch of stuff that I should re-use. Is there a preference between:
I would lean towards option 1 (calling a static I don't like option 3 at all as it seems like a maintenance issue waiting to happen, but again would respect your preference as maintainers. I think I understand enough from the existing validators to make an attempt at conflict detection when I get to the validator. |
Going back to the original problem statement, I think I missed a key requirement: documentation. Using a simple map makes it impossible to have documentation for the headers; you get something like: Generated OpenAPI (excerpt)
which is notably missing a It would be unfortunate if the way we model this makes it impossible to document these static header values. New trait shape suggestion:
or as a diff to the existing structure: @@ -571,6 +571,31 @@ structure http {
///
/// Defaults to 200 if not provided.
code: PrimitiveInteger,
+
+ /// The constant HTTP headers to add to the request.
+ headers: HttpHeaders,
+}
+
+list HttpHeaders {
+ member: HttpHeader,
+}
+
+structure HttpHeader {
+ /// The header name.
+ ///
+ /// The name must be valid according to the rules in RFC 7230 and must not be
+ /// in the restricted headers list.
+ @required
+ name: NonEmptyString,
+
+ /// The constant header value.
+ ///
+ /// The value must not be an empty string.
+ @required
+ value: NonEmptyString,
+
+ /// The header description.
+ description: String,
} Thoughts? |
Seems fine to me, I'm guessing there should probably be some minimal constraints on the description if it's present. OpenAPI's restriction, if any, on the description might be a reasonable starting point. |
An alternative that also solves the documentation problem would be a map where the value is a structure:
Any preferences? |
Got what I was looking for:
not sure why the double description, but that's what I'm seeing other things do so I'm going with it for now. Initial implementation uses the map: map HttpHeaders {
key: NonEmptyString,
+ value: DocumentedHttpHeader,
+}
+
+structure DocumentedHttpHeader {
+ /// The constant header value.
+ ///
+ /// The value must not be an empty string.
+ @required
value: NonEmptyString,
+
+ /// The header description.
+ description: String,
} but I think I understand enough to switch it to a list if people would prefer that. |
I think I've got most things working for this, just need to implement the validator per @mtdowling 's comments earlier:
Will open a draft PR for more detailed discussion once that's done. |
Awesome! I think the map is fine and will be a bit more concise. Should we name this Another option to throw out -- we're very likely going to expand mixins to more shapes, including operations. It might be nice to do something like this to not have to repeat the headers on every operation:
|
Makes sense, will do 👍
@mtdowling I do like the reduced duplication this offers. I am torn by the implication that this implies creating a separate trait † "working" as in it would make correct-ish requests, modulo some annoying behaviours that I need to track down and probably raise issues to discuss once I understand them. That said, it will be some time before my side project is anything but a technology demonstration, so taking the time to get the modelling right is better than building something quickly that's not as useful. I can still build my demo client with what I've done locally and circle back to rebuild this as a separate trait if that's the right path. What I'm hearing is an evolution of the thought process here, that we would not want to extend the
which once mixins are introduced would allow for a mass deletion of duplicated traits. All of the requirements and behaviour we'd discussed would transfer to this new trait. It makes sense. Have I played back your thoughts accurately? |
Naming question: is there a preference for "static" headers vs "constant" headers? I'd lean towards "constant" these days. |
Sorry for the delay! (holidays, got sick, better now). Yeah, your summary is correct, and I like your proposed trait. (One minor point is that I don't think we've ever used "description", but rather always use "documentation")
Exactly. The reduced duplication from mixins is going to be a huge DX improvement, and we should start thinking more about mixins as we design new traits. This means that we should prefer more granular traits like
I don't have a strong preference here, and I polled the Smithy team, and nobody else has a strong preferece. I'll defer to you to make this call :) |
Thanks @mtdowling! Happy new year, glad you're feeling better. When I can get back to this I'll proceed with Any pointers on how to create a brand-new trait and plumb it through end-to-end (just in this repo, not all the way through codegen) would be greatly appreciated; I'll dig through old PRs but a checklist / README would make this process significantly more straightforward. |
Awesome! First you define the trait in the prelude: https://github.com/awslabs/smithy/blob/main/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy. Then you define the trait in code (like this one). Then you register it with Java's SPI (like this). Then write a simple test to make sure it's wired up correctly (like this). Then we usually write trait docs (like this), and test that by running |
That should be a great start, thanks @mtdowling ! Are those steps documented outside your comment or would it make sense for me to include a Markdown file in my PR capturing the steps for the next person to come along? |
Only the process of defining the trait in a model is outlined, that's done so here in the spec. We'd gladly review anything you capture during your development as we plan to build a more full fledged guide. In addition to what @mtdowling outlined above, this warrants a validator to emit events for conflicts with |
Thanks @kstich ! |
@mtdowling earlier you mentioned that mixins for operations are likely coming. I seem to recall something about mixins not merging traits well; I don't know if that's still the proposal but it came to mind as a potential issue. I'm sure you're familiar with some AWS services that take an Building on the example you shared earlier, I was wondering about the feasibility of this, assuming that
or would you have them define a separate trait that also sets a constant header, something like:
To me it feels like it would be good if traits could be merged, so that folks don't need to know the exact implementation details of mixins. It feels a little fragile that someone could do something like
not realizing that This is probably not an issue specific to this trait, happy to split it off to a separate issue about mixin behaviour and the possibility of traits having merge strategies. |
@mtdowling , wouldn't make more sense to allow constant values altogether (as defined in JSON Schema draft 2020-12)? So they can be added as part of any shape and also for documentation purposes (e.g. in the OpenAPI definition). |
@eduardomourar do you have any more thoughts / details on what that would look like in this use case? |
@glb, when I first started using Smithy I expected to do something like this:
As you can see, if the |
This PR is about static HTTP request headers specifically, and not about more general constant values like what's in JSON Schema. The key difference is that static HTTP request headers are not part of the end-user contracts generated by Smithy (they're implementation details of the serialization format) whereas something like constants would be. I haven't heard a strong use case for more general constant values in structures, and I think they come with big tradeoffs. For example, poor integration with structurally typed languages like TypeScript (unless we added constructors to fill in these constant value, because we'd likely be forcing the constant to be reified in code to satisfy the type checker), and a backward compatibility risk for service teams that might need to later change the constant value. As for the Regarding mixin trait merging: they don't support trait merging today, no. It sounds like a lot of complexity to me if we had configurable strategies per mixin (like override vs merge), and would complicate reasoning about the model itself + implementations. If the site at which a mixin is merged into a shape controlled the override behavior, then I worry we'd need a syntactic change to the |
@eduardomourar that's kind of what I expected too 🙂 @mtdowling your last comment, specifically the bit about how I'd probably model this with something like:
I've discovered that there's an example that is basically exactly this in the protocol traits documentation:
Thoughts? |
If you're defining a static value across the API, then it's fair to reuse the restJson1 protocol and use a header. The x-amz-target header is dynamic though and already needs a dedicated protocol implementation. I think another way to address your concern is to allow |
@mtdowling got it, thanks. I like the idea of supporting this at both levels. I'd suggest semantics of "if you specify the same header name on a service and an operation then the operation will take precedence". I don't like the idea of it being impossible to override the default service value for an operation, and I expect that folks won't accidentally add the trait to an operation. Thoughts? |
Hi @glb, did you find a solution, via protocol trait or other avenue, or do you still need this feature request open? |
Hi @syall thanks for checking in! I didn't find a solution as I timed out looking at this activity last year. I would definitely still like to have a capability for modelling an API with a constant header as part of the core Smithy feature set, especially with the expanding set of SDK code generators, but I'm not able to contribute an implementation at this time 😞 I would prefer not to have to build a new protocol binding for each code generator to get this capability, though if the position of the maintainers is that a protocol binding is the right way to model this I'd understand. |
I'd like to model an API operation that requires a specific header value to be provided; I'd prefer not to pass the complexity on to the API consumer.
In my mind this should go into the
@http
trait applied to the operation shape, but from the docs that's not supported.Is there an existing trait that would be an appropriate way to do this?
The text was updated successfully, but these errors were encountered: