Skip to content

Add @sharedRoute decorator and decouple it from @route#1798

Merged
daviwil merged 20 commits into
microsoft:mainfrom
daviwil:split-shared-route
Apr 19, 2023
Merged

Add @sharedRoute decorator and decouple it from @route#1798
daviwil merged 20 commits into
microsoft:mainfrom
daviwil:split-shared-route

Conversation

@daviwil

@daviwil daviwil commented Apr 6, 2023

Copy link
Copy Markdown
Contributor

This PR decouples the concept of a shared route from the @route decorator by adding new @sharedRoute decorator, setSharedRoute and getSharedRoute accessors. I'm also storing the state of shared routes separately so that @autoRoute operations can also have the shared route status set independently.

Note that the old {shared: true} pattern still continues to work and we still return aggregate RoutePath information from getRoutePath so that no breaking change is introduced.

Fixes #1747.

Tasks

  • Deprecate getAction and getCollectionAction
  • Ensure that autoRoute shared route support doesn't break openapi shared route logic
  • Update autorest emitter samples

@github-actions

github-actions Bot commented Apr 6, 2023

Copy link
Copy Markdown
Contributor

Changes in this PR will be published to the following url to try(check status of TypeSpec Pull Request Try It pipeline for publish status):
Playground: https://cadlplayground.z22.web.core.windows.net/prs/1798/

Website: https://cadlwebsite.z1.web.core.windows.net/prs/1798/

@daviwil
daviwil requested review from johanste and tjprescott April 6, 2023 12:52

@tjprescott tjprescott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need documentation for this, but I also am not clear why we are adding this decorator. Presumably you can set shared state in autoroute using the {shared: bool} object? What would this look like in the context of autoroute? If we are adding this, why are we not deprecating the old way?

I would prefer to defer merging this until next sprint.

@daviwil

daviwil commented Apr 6, 2023

Copy link
Copy Markdown
Contributor Author

This is a P0 for client generation for this sprint according to @johanste because we need a better option than the current overloads design.

We decided to break this out as a separate decorator because an Azure.Core user needs to be able to add @sharedRoute to a standard resource operation that already has @autoRoute on it.

Here's an example adapted from a sample that Johan wants to get working:

import "@azure-tools/typespec-azure-core";

alias ContentNegotiate<TMimeType extends string> = 
  Azure.Core.Traits.RequestHeadersTrait< { @TypeSpec.Http.header("Accept") accept: TMimeType; }> & 
  Azure.Core.Traits.ResponseHeadersTrait<{ @TypeSpec.Http.header("Content-Type") contentType: TMimeType;}
>;

@TypeSpec.Rest.resource("things")
model Thing {
  @key
  @visibility("read")
  thingId: string;
}

@sharedRoute
op readThingAsString is Azure.Core.ResourceRead<Thing, ContentNegotiate<"application/xml">>;

@sharedRoute
op readThingAsXml is Azure.Core.ResourceRead<Thing, ContentNegotiate<"application/json">>;

ResourceRead already has @autoRoute applied. I think it makes sense to use a separate decorator so that we don't need to worry about the semantics of re-applying @autoRoute with a different options object for enabling the shared routes.

Comment thread packages/http/src/decorators.ts
Comment thread packages/http/test/routes.test.ts
Comment thread packages/http/lib/http-decorators.tsp Outdated
Comment on lines +212 to +222
* If the namespace or interface that contains the operation is also marked with a `@route` decorator,
* it will be used as a prefix to the route URI of the operation.
*
* `@route` can only be applied to operations, namespaces, and interfaces.
*
* ```typespec
* @route("/widgets")
* op getWidget(@path id: string): Widget;
* ```
*/
extern dec route(target: Namespace | Interface | Operation, path: string, options?: object);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are preserving the options object, we should include that in the documentation. The docstring should also include @param so that it appears in the generated docs.

@timotheeguerin timotheeguerin Apr 10, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also shouldn't the options be a model expression alias instead of just object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, I'm not that good at writing decorator signatures yet. Do you mean this:

extern dec route(target: Namespace | Interface | Operation, path: string, options?: { shared?: boolean});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, or use an alias alias RouteOptions = {...} for the model expression

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the alias might be better for when we have the valueof so we can do: options?: valueof(RouteOptions)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the signature for this but the error reporting on an invalid check isn't so clear. Filed issue #1832 with an example.

Comment on lines +455 to +462
@autoRoute
@sharedRoute
op get1(@path name: string): string;

@test
@autoRoute
op get2(@path name: string): string;
`)) as { get1: Operation; get2: Operation };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it common to actually put @autoRoute on individual operations? Why could we not just have @autoRoute take the same options object as @route? Or just have both make use of this decorator?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the pattern used in Azure.Core. The @autoRoute decorator is placed on ResourceOperation<TResource> which causes it to be applied individually to every operation which uses standard operation signatures via is.

In my earlier comment, I explained that we shouldn't conflate sharedness of routes with @route or @autoRoute because changing an operation signature to go from non-shared to shared means re-applying the original decorator instead of just adding @sharedRoute to the operation, no matter what kind of route it uses. This is the key element of how this PR is intended to affect the design.

Comment thread docs/standard-library/http/reference/decorators.md Outdated
Comment on lines +229 to +231
if (entity.kind === "Operation" && details.shared) {
setSharedRoute(context.program, entity as Operation);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should deprecate this pattern if we are going with an @sharedRoute decorator

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also like to deprecate it if we proceed with @sharedRoute, but I'm not sure how to deprecate a parameter to a decorator at this moment. A simple diagnostic could work, I suppose.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That, and you should be able to mark it with @deprecated in the docstring, I think. @timotheeguerin would know for sure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That, and you should be able to mark it with @deprecated in the docstring, I think. @timotheeguerin would know for sure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just call reportDeprecated but with a condition on the parameter being used instead of just every time for the decorator

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also if you add the type for the options

alias RouteOptions = {
  @deprecated
  shared?: boolean;
}

I don't think it will emit a diagnostic automatically here but we could add that support

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible that just calling reportDeprecated directly is better for now. Thanks!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better, we should add a migration script to do this for people

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added deprecation for the old shared option pattern.

return path
? {
path,
shared: entity.kind === "Operation" && isSharedRoute(program, entity as Operation),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original logic simply plumbed details through without regard to whether it was an operation, interface, etc. so this change would introduce a behavioral change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the original design, applying @route on a namespace or interface does not propagate the sharedness status down to the contained operations, so it never worked in practice. The new decorator just makes this explicit in that @sharedRoute can't be applied anywhere but to an operation.

@tjprescott tjprescott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My overall thoughts are that we should either:

  1. Deprecate the {shared: true} pattern on @route and make use of @sharedRoute the standard or:
  2. We should not introduce a new decorator and just make @autoRoute accept an options object like @route does.

Here is the design issue where we said that the options object was the way to go. #984.

I don't know why this did not also go through design. It seems like we are treating it as a bug fix when it's really not and the current implementation would leave us with multiple ways of doing the same thing, which we try to avoid.

]);
});

describe("shared routes", () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markcowl also left a comment on the original post to make sure that this works for actions, so we should probably add a test that covers that as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a test for that and some extra validation logic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the validation logic and test to verify.

@daviwil

daviwil commented Apr 10, 2023

Copy link
Copy Markdown
Contributor Author

I don't know why this did not also go through design. It seems like we are treating it as a bug fix when it's really not and the current implementation would leave us with multiple ways of doing the same thing, which we try to avoid.

I brought it up in Thursday's design meeting and there wasn't any objection to it at that point. I asked folks to come comment on the PR if they had other feedback but only Mark has left any so far.

@daviwil daviwil closed this Apr 18, 2023
@daviwil daviwil reopened this Apr 18, 2023
@github-actions

Copy link
Copy Markdown
Contributor

Changes in this PR will be published to the following url to try(check status of TypeSpec Pull Request Try It pipeline for publish status):
Playground: https://cadlplayground.z22.web.core.windows.net/prs/1798/

Website: https://cadlwebsite.z1.web.core.windows.net/prs/1798/

@daviwil

daviwil commented Apr 18, 2023

Copy link
Copy Markdown
Contributor Author

This PR is ready for another look, I've addressed all PR feedback and added tests where needed.

@daviwil
daviwil force-pushed the split-shared-route branch from 41589bd to 60d7fbb Compare April 18, 2023 14:44

@tjprescott tjprescott left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good but can we also update the shared routes tests in OpenAPI3 (and Autorest on the Azure side) to use @sharedRoute instead of {shared: true}?

Comment thread packages/http/test/http-decorators.test.ts
Comment thread packages/rest/test/routes.test.ts Outdated
Comment thread packages/rest/test/routes.test.ts
Comment thread packages/rest/src/rest.ts
@daviwil

daviwil commented Apr 18, 2023

Copy link
Copy Markdown
Contributor Author

I've updated the openapi3 tests already, but I'll go check on the autorest emitter tests too.

@daviwil

daviwil commented Apr 19, 2023

Copy link
Copy Markdown
Contributor Author

Sent out the typespec-azure PR to uptake changes here: Azure/typespec-azure#2863

@daviwil
daviwil merged commit df55792 into microsoft:main Apr 19, 2023
@daviwil
daviwil deleted the split-shared-route branch April 19, 2023 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow the "shared = true/false" option to be set independently of route... including for @autoRoute:ed routes

4 participants