This page is a working document to hash out a solution for #182
What is the Job to be done? What is the Cadl author trying to accomplish?
To define a “logical model" for an entity that may be represented in different ways based on context.
- Used as a parameter or return type
- Sent in an http protocol vs gRPC protocol
Note that not all metadata is necessarily part of the "logical model". E.g. the Cadl author may consider the response status code as part of the "reponse" but separate from the "logical model".
Some specific use cases
1. Etag for a model.
This could be defined in the model, but
a. In http response to “get” request Etag is returned in a header
b. In http response to “list” request, Etag may be returned in list item (or not)
c. As a parameter to “create” request Etag is probably omitted
d. In http response to "create" or "update" request, Etag may or may not be returned in a header
e. In gRPC Etag is probably included in both “get” response and items “list” response
2. “id” for a model.
a. In http “create” this is sent as the final path segment of URL and not in the body
- [MC] In some http create scenarios, id is in request payload (path ends at the collection level)
b. In http “get” it is returned in the body (or not)
c. In gRPC it is probably in the body of both requests and responses
3. Batch APIs
(Good description by Nick of a specific case in Text Analytics)
a. Single request (GET) passes request data in query params
b. Batch request (POST) passes and array of single request elements in request body
c. Response to single request uses status code, response headers, and response body
d. Response to batch request passes an array that encapsulates status, headers and body in each item
4. [MC] Logical model mapped entirely to http metadata (Storage blob properties)
In some representations, data that is logically part of a model is represented on the wire as http metadata
a. Blob properties passed in request headers
b. Blob Properties returned in response headers
c. In come cases, a request or response may consist of only http metadata
d. In gRPC, all of these properties would have to be be represented in the model
Desiderata
- A good solution will require a minimum of extra decoration of fields. We are already decorating fields with
@header
,@query
,@statusCode
,@path
. We have the@body
decorator but I think we'd prefer a solution that does not require it.
Brian's proposal
Visibility controls whether a field is present in the model for a given part of the lifecycle. For OA, if a model property's visibility matches the current visibility rules, then it is present in the request or response somewhere, whether in metadata or in the request body.
======== Work in progress =====
There is a visibility implied for the parameters and return type of a method based on its method type:
"create", "replace", "update", "read", "list".
The openapi3 emitter will determine method type by http verb:
"create" == post, "replace" == put, "update" == patch, "read" == get, "list" == get
For "list": parameters must have visibility("list") and return type properties must have visibility("list").
For "<x>": parameters must have visibility("<x>") and return type properties must have visibility("<x>").
For OA, when determining whether a property of a model should marshal via the http body (i.e. be part of the schema) or in metadata, we extract from the request or response model all applicable and valid metadata. All other properties of the request or response model are part of the schema. We use heuristics to discover a "good name" for these the remaining schemas in OpenAPI.
Applicable metadata are members whose metadata types can be sent as metadata for the type of request. For example, @query is applicable for requests, @status is applicable for responses).
Valid metadata are members whose metadata can successfully be extracted. A common example of invalid metadata are multiple model properties sharing the same header field. Likewise, metadata properties of a model that is part of a collection are invalid. No errors are thrown for invalid metadata, they merely become part of the schema.
1. Etag for a model.
model Pet {
id: string;
name: string;
@header @visibility("read", "list") Etag: string;
}
model Pets {
value: Pet[];
}
a. In http response to “get” request Etag is returned in a header
@route("/Pets")
namespace root {
@get
op read(@path id: string): Pet;
}
b. In http response to “list” request, Etag may be returned in list item (or not)
This flavor will return Etag in each list element.
@route("/Pets")
namespace root {
@get
op list(): Pets;
}
This could be represented in OpenAPI in a few ways:
- Include
Etag
in thePet
schema as optional. This is not quite right because we want to express that Etag is always returned in list elements. - Define a separate schema for
Pet
list elements. This would probablyallOf: [ { $ref: Pet } ]
and add theEtag
property. We'd probably call this schemaPetItem
.
To exclude the Etag from list elements, Pet
would be defined a little differently:
model Pet {
id: string;
name: string;
@header @visibility("get") Etag: string;
}
c. As a parameter to “create” request Etag is probably omitted
@route("/Pets")
namespace root {
@put
op create(@path id: string, @body body: Pet): Pet;
}
In OpenAPI the Pet schema would not have an Etag property, and this would work just fine.
d. In http response to "create" or "update" request, Etag may or may not be returned in a header
If Etag
is in the model returned by a create or update, then it will be returned by create or update -- as a header if so specified.
How to specify that Etag
is not returned by create or update even if included in the model? Maybe this is an anti-pattern: we should say "don't include it in the model if you want to exclude it from some responses".
e. In gRPC Etag is probably included in both “get” response and items “list” response
Presumably gRPC would ignore the @header decorator and always put the header in responses.
What about create or update response?
2. “id” for a model.
model Pet {
@path id: string;
name: string;
@header Etag: string;
}
a. In http “create” this is sent as the final path segment of URL and not in the body
@route("/Pets")
namespace root {
@put
op create(...Pet): Pet;
}
b. In http “get” it is returned in the body (or not)
@route("/Pets")
namespace root {
@get
op read(@path id: string): Pet;
}
Since a "path" element is not "applicable metadata" for a response, it is included in the response of the Get.
If the Cadl author wanted to exclude it from the response ... ???
Maybe we call this an anti-pattern. And if this is what they really want then they can't include id
in the model.
c. In gRPC it is probably in the body of both requests and responses
Presumably gRPC would ignore the @path decorator and include the id in both the request and response payload.
3. Batch APIs
a. Single request (GET) passes request data in query params
b. Batch request (POST) passes and array of single request elements in request body
c. Response to single request uses status code, response headers, and response body
d. Response to batch request passes an array that encapsulates status, headers and body in each item
4. [MC] Logical model mapped entirely to http metadata (Storage blob properties)
Add an @logical
decorator to indicate which pieces of valid metadata should be part of the model in generated clients. Map this into a swagger extension when emitting OpenAPI.
The request or response model might look like this
@logical
model BlobProperties {
@header x-ms-blob-type?: string;
@header x-ms-creation-time: datetime;
}
Note that, in this case an additional mechanism is needed to match the actual header name to a good client-side name, which we can handle separately from this proposal.