Small formatting and syntax highlighting improvements (#73)
1. Format *.cadl on save in our repo. This required some extra knobs in .vscode/settings.json and .prettierrc.json. These are not required when referencing the prettier plugin via npm, but we need a workaround to consume the plugin this way from source. 2. Fix a formatter bug with operations that return anonymous models where the braces around the return model were dropped. 3. Add cadl syntax highlighting to markdown ```cadl blocks
This commit is contained in:
Родитель
53cbecc69c
Коммит
e7d656075b
|
@ -7,5 +7,13 @@
|
|||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"pluginSearchDirs": ["./packages/compiler"],
|
||||
"plugins": ["./packages/prettier-plugin-cadl"]
|
||||
"plugins": ["./packages/prettier-plugin-cadl"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.cadl",
|
||||
"options": {
|
||||
"parser": "cadl"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -57,9 +57,12 @@
|
|||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[cadl]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"editor.tabSize": 2
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[csharp]": {
|
||||
"editor.insertSpaces": true,
|
||||
|
@ -96,6 +99,8 @@
|
|||
"CADL_VERBOSE_TEST_OUTPUT": "true",
|
||||
"NODE_OPTIONS": "--stack-trace-limit=50"
|
||||
},
|
||||
"prettier.prettierPath": "./packages/compiler/node_modules/prettier",
|
||||
"prettier.documentSelectors": ["**/*.cadl"],
|
||||
"testExplorer.errorDecoration": false,
|
||||
"cadl.cadl-server.path": "${workspaceRoot}/packages/compiler"
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ Here is a very small Cadl example that uses the `@cadl-lang/openapi3` library to
|
|||
|
||||
#### sample.cadl
|
||||
|
||||
```
|
||||
```cadl
|
||||
import "@cadl-lang/rest";
|
||||
import "@cadl-lang/openapi3";
|
||||
|
||||
|
@ -109,6 +109,7 @@ namespace Example {
|
|||
@get("/message")
|
||||
op getMessage(): string;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can compile it to OpenAPI 3.0 by using the following command:
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "@cadl-lang/compiler",
|
||||
"comment": "Fix formatting bug with operations returning anonymous models",
|
||||
"type": "patch"
|
||||
}
|
||||
],
|
||||
"packageName": "@cadl-lang/compiler"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"changes": [
|
||||
{
|
||||
"packageName": "cadl-vscode",
|
||||
"comment": "Add syntax highlighting code-fenced cadl blocks in markdown",
|
||||
"type": "minor"
|
||||
}
|
||||
],
|
||||
"packageName": "cadl-vscode"
|
||||
}
|
142
docs/tutorial.md
142
docs/tutorial.md
|
@ -30,11 +30,12 @@ In addition, Cadl comes with a standard library for describing REST APIs and gen
|
|||
|
||||
Cadl models are used to describe data shapes or schemas. Models have any number of members and can extend and be composed with other models. Members are required by default, but can made optional by appending a "?" to the member name. The following defines a data shape with two members:
|
||||
|
||||
```
|
||||
```cadl
|
||||
model Dog {
|
||||
name: string;
|
||||
favoriteToy?: string;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Built-in Models
|
||||
|
@ -59,7 +60,7 @@ Cadl comes with built-in models for common data types:
|
|||
|
||||
The spread operator takes the members of a source model and copies them into a target model. Spread doesn't create any nominal relationship between source and target, and so it's useful when you want to reuse common properties without reasoning about or generating complex inheritence relationships.
|
||||
|
||||
```
|
||||
```cadl
|
||||
model Animal {
|
||||
species: string;
|
||||
}
|
||||
|
@ -78,44 +79,43 @@ model Dog {
|
|||
species: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Extends
|
||||
|
||||
Sometimes you want to create an explicit relationship between two models, for example when you want to emit class definitions in languages which support inheritance. The `extends` keyword can be used to establish such a relationship.
|
||||
|
||||
```
|
||||
```cadl
|
||||
model Animal {
|
||||
species: string;
|
||||
}
|
||||
|
||||
model Pet {
|
||||
name: string;
|
||||
}
|
||||
model Dog extends Animal {}
|
||||
|
||||
model Dog extends Pet, Animal { }
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
Enums define a type which can hold one of a set of constant values.
|
||||
|
||||
```
|
||||
```cadl
|
||||
enum Color {
|
||||
Red,
|
||||
Blue,
|
||||
Green,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In this case, we haven't specified how the constants will be represented, allowing for different choices in different scenarios. For example, the OpenAPI emitter will choose string values "Red", "Green", "Blue". Another protocol might prefer to assign incrementing numeric values 0, 1, 2.
|
||||
|
||||
We can also specify explicit string or numeric values:
|
||||
|
||||
```
|
||||
```cadl
|
||||
enum Color {
|
||||
Red: "red",
|
||||
Blue: "blue"
|
||||
Blue: "blue",
|
||||
Green: "green",
|
||||
}
|
||||
|
||||
|
@ -123,13 +123,14 @@ enum Priority {
|
|||
High: 100,
|
||||
Low: 0,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Templates
|
||||
|
||||
It is often useful to let the users of a model fill in certain details. Model templates enable this pattern. Similar to generics found in other languages, model templates declare template parameters that users provide when referencing the model.
|
||||
|
||||
```
|
||||
```cadl
|
||||
model Page<T> {
|
||||
size: number;
|
||||
item: T[];
|
||||
|
@ -138,14 +139,16 @@ model Page<T> {
|
|||
model DogPage {
|
||||
... Page<Dog>;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Type Aliases
|
||||
|
||||
Sometimes it's convenient to alias a model template instantiation or type produced via type operators (covered later) as a convenient name. Aliases allow this:
|
||||
|
||||
```
|
||||
```cadl
|
||||
alias DogPage = Page<Dog>;
|
||||
|
||||
```
|
||||
|
||||
Unlike `model`, `alias` does not create a new entity, and as such will not change generated code in any way. An alias merely describes a source code shorthand to avoid repeating the right-hand side in multiple places.
|
||||
|
@ -154,24 +157,26 @@ Unlike `model`, `alias` does not create a new entity, and as such will not chang
|
|||
|
||||
API authors often need to describe API shapes in terms of specific literal values. For example, this operation returns this specific integer status code, or this model member can be one of a few specific string values. It is also often useful to pass specific literal values to decorators. Cadl supports string, number, and boolean literal values to support these cases:
|
||||
|
||||
```
|
||||
```cadl
|
||||
model BestDog {
|
||||
name: "Suki",
|
||||
age: 14,
|
||||
best: true
|
||||
name: "Suki";
|
||||
age: 14;
|
||||
best: true;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
String literal types can also be created using the triple-quote syntax which enables multi-line strings:
|
||||
|
||||
```
|
||||
```cadl
|
||||
model Dog {
|
||||
favoriteFoods: """
|
||||
McDonalds
|
||||
Chipotle
|
||||
And so on
|
||||
"""
|
||||
""";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Type Operators
|
||||
|
@ -182,65 +187,72 @@ Cadl supports a few type operators that make it easy to compose new models from
|
|||
|
||||
Unions describe a type that must be exactly one of the union's constituents. Create a union with the `|` operator.
|
||||
|
||||
```
|
||||
```cadl
|
||||
alias GoodBreed = Beagle | GermanShepherd | GoldenRetriever;
|
||||
|
||||
```
|
||||
|
||||
#### Intersection
|
||||
|
||||
Intersections describe a type that must include all of the intersection's constituents. Create an intersection with the `&` operator.
|
||||
|
||||
```
|
||||
```cadl
|
||||
alias Dog = Animal & Pet;
|
||||
|
||||
```
|
||||
|
||||
#### Array
|
||||
|
||||
Arrays describe lists of things. Create an Array type with the `[]` operator.
|
||||
|
||||
```
|
||||
```cadl
|
||||
alias Pack = Dog[];
|
||||
|
||||
```
|
||||
|
||||
### Operations
|
||||
|
||||
Operations describe service endpoints and consist of an operation name, parameters, and return type. Operations are declared using the `op` keyword:
|
||||
|
||||
```
|
||||
```cadl
|
||||
op getDog(name: string): Dog;
|
||||
|
||||
```
|
||||
|
||||
The operation's parameters describe a model, so anything you can do in a model you can do in a parameter list as well, including using the spread operator:
|
||||
|
||||
```
|
||||
```cadl
|
||||
op getDog(... commonParams, name: string): Dog;
|
||||
|
||||
```
|
||||
|
||||
Often an endpoint point return one of any number of models. For example, there might be return type for when an item is found, and a return type for when an item isn't found. Unions are used to describe this pattern:
|
||||
|
||||
```
|
||||
```cadl
|
||||
model DogNotFound {
|
||||
error: "Not Found"
|
||||
error: "Not Found";
|
||||
}
|
||||
|
||||
op getDog(name: string): Dog | DogNotFound;
|
||||
|
||||
```
|
||||
|
||||
### Namespaces & Usings
|
||||
|
||||
Namespaces let you group related types together into namespaces. This helps organize your types making them easier to find and prevents name conflicts. Namespaces are merged across files, so you can reference any type anywhere in your Cadl program via its namespace. You can create namespace blocks like the following:
|
||||
|
||||
```
|
||||
```cadl
|
||||
namespace Models {
|
||||
model Dog { }
|
||||
model Dog {}
|
||||
}
|
||||
|
||||
operation getDog(): Models.Dog;
|
||||
op getDog(): Models.Dog;
|
||||
|
||||
```
|
||||
|
||||
You can also put an entire Cadl file into a namespace by using the blockless namespace syntax:
|
||||
|
||||
```
|
||||
```cadl
|
||||
// models.cadl
|
||||
namespace Models;
|
||||
model Dog { };
|
||||
|
@ -252,17 +264,22 @@ operation getDog(): Models.Dog;
|
|||
|
||||
Namespace declarations can declare multiple namespaces at once by using a dotted member expression. There's no need to declare nested namespace blocks if you don't want to.
|
||||
|
||||
```
|
||||
```cadl
|
||||
namespace A.B;
|
||||
namespace C.D {}
|
||||
namespace C.D.E { model M { }}
|
||||
namespace C.D {
|
||||
|
||||
}
|
||||
namespace C.D.E {
|
||||
model M {}
|
||||
}
|
||||
|
||||
alias M = A.B.C.D.E.M;
|
||||
|
||||
```
|
||||
|
||||
It can be convenient to add references to a namespace's declarations to your local namespace, especially when namespaces can become deeply nested. The `using` statement lets us do this:
|
||||
|
||||
```
|
||||
```cadl
|
||||
// models.cadl
|
||||
namespace Service.Models;
|
||||
model Dog { };
|
||||
|
@ -275,9 +292,9 @@ operation getDog(): Dog; // here we can use Dog directly.
|
|||
|
||||
The bindings introduced by a `using` statement are local to the namespace they are declared in. They do not become part of the namespace themselves.
|
||||
|
||||
```
|
||||
```cadl
|
||||
namespace Test {
|
||||
model A { }
|
||||
model A {}
|
||||
}
|
||||
|
||||
namespace Test2 {
|
||||
|
@ -287,6 +304,7 @@ namespace Test2 {
|
|||
|
||||
alias C = Test2.A; // not ok
|
||||
alias C = Test2.B; // ok
|
||||
|
||||
```
|
||||
|
||||
### Imports
|
||||
|
@ -446,14 +464,17 @@ namespace PetStore;
|
|||
|
||||
Resources are operations that are grouped in a namespace. You declare such a namespace by adding the `@resource` decorator and providing the path to that resource:
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets")
|
||||
namespace Pets { }
|
||||
namespace Pets {
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To define an operation on this resource, you need to provide the HTTP verb for the route using the `@get`, `@head` `@post`, `@put`, `@patch`, or `@delete` decorators. Alternatively, you can name your operation `list`, `create`, `read`, `update`, `delete`, or `deleteAll` and the appropriate verb will be used automatically. Lets add an operation to our `Pets` resource:
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets")
|
||||
namespace Pets {
|
||||
op list(): Pet[];
|
||||
|
@ -461,35 +482,38 @@ namespace Pets {
|
|||
// or you could also use
|
||||
@get op listPets(): Pet[];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Path and query parameters
|
||||
|
||||
Model properties and parameters which should be passed as path and query parameters use the `@path` and `@query` parameters respectively. Let's modify our list route to support pagination, and add a read route to our Pets resource:
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets")
|
||||
namespace Pets {
|
||||
op list(@query skip: int32, @query top: int32): Pet[];
|
||||
|
||||
op read(@path petId: int32): Pet;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Path parameters are appended to the URL unless a substitution with that parameter name exists on the resource path. For example, we might define a sub-resource using the following Cadl. Note how the path parameter for our sub-resource's list operation corresponds to the substitution in the URL.
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets/{petId}/toys")
|
||||
namespace PetToys {
|
||||
op list(@path petId: int32): Toy[];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Headers
|
||||
|
||||
Model properties and parameters that should be passed in a header use the `@header` decorator. The decorator takes the header name as a parameter. If a header name is not provided, it is inferred from the property or parameter name. Let's add `etag` support to our pet store's read operation:
|
||||
|
||||
```
|
||||
```cadl
|
||||
model PetWithETag {
|
||||
... Pet;
|
||||
@header eTag: string;
|
||||
|
@ -500,44 +524,62 @@ namespace Pets {
|
|||
op list(@query skip: int32, @query top: int32): Pet[];
|
||||
op read(@path petId: int32, @header ifMatch?: string): PetWithETag;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Request & response bodies
|
||||
|
||||
Request and response bodies are declared using the `@body` decorator. Let's add an endpoint to create a pet. Let's also use this decorator for the responses, although this doesn't change anything about the API.
|
||||
|
||||
```
|
||||
```cadl
|
||||
alias Foo = {};
|
||||
@resource("/pets")
|
||||
namespace Pets {
|
||||
op list(@query skip: int32, @query top: int32): { @body pets: Pet[] };
|
||||
op read(@path petId: int32, @header ifMatch?: string): { @body pet: PetWithETag };
|
||||
op list(@query skip: int32, @query top: int32): {
|
||||
@body pets: Pet[];
|
||||
};
|
||||
op read(@path petId: int32, @header ifMatch?: string): {
|
||||
@body pet: PetWithETag;
|
||||
};
|
||||
op create(@body pet: Pet): {};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Status codes
|
||||
|
||||
Use the `@status` decorator to declare a status code for a response. Generally, setting this to just `int32` isn't particularly useful. Instead, use number literal types to create a discriminated union of response types. Let's add status codes to our responses, and add a 404 response to our read endpoint.
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets")
|
||||
namespace Pets {
|
||||
op list(@query skip: int32, @query top: int32): { @status code: 200, @body pets: Pet[] };
|
||||
op read(@path petId: int32, @header ifMatch?: string):
|
||||
{ @status code: 200, @body pet: PetWithETag } | { @status code: 404 }
|
||||
op create(@body pet: Pet): { @status code: 200 };
|
||||
op list(@query skip: int32, @query top: int32): {
|
||||
@status code: 200;
|
||||
@body pets: Pet[];
|
||||
};
|
||||
op read(@path petId: int32, @header ifMatch?: string): {
|
||||
@status code: 200;
|
||||
@body pet: PetWithETag;
|
||||
} | {
|
||||
@status code: 404;
|
||||
};
|
||||
op create(@body pet: Pet): {
|
||||
@status code: 200;
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Built-in request shapes
|
||||
|
||||
Since status codes are so common for REST APIs, Cadl comes with some built-in types for common status codes so you don't need to declare status codes so frequently. Lets update our sample one last time to use these built-in response types:
|
||||
|
||||
```
|
||||
```cadl
|
||||
@resource("/pets")
|
||||
namespace Pets {
|
||||
op list(@query skip: int32, @query top: int32): OkResponse<Pet[]>;
|
||||
op read(@path petId: int32, @header ifMatch?: string): OkResponse<PetWithETag> | NotFoundResponse;
|
||||
op create(@body pet: Pet): OkResponse<{}>;
|
||||
}
|
||||
|
||||
```
|
||||
|
|
|
@ -10,5 +10,6 @@
|
|||
!dist/**/*.js.map
|
||||
!extension-shim.js
|
||||
!language-configuration.json
|
||||
!markdown-cadl.json
|
||||
!README.md
|
||||
!ThirdPartyNotices.txt
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"fileTypes": [],
|
||||
"injectionSelector": "L:text.html.markdown",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#cadl-code-block"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"cadl-code-block": {
|
||||
"begin": "(^|\\G)(\\s*)(\\`{3,}|~{3,})\\s*(?i:(cadl)(\\s+[^`~]*)?$)",
|
||||
"name": "markup.fenced_code.block.markdown",
|
||||
"end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$",
|
||||
"beginCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
},
|
||||
"4": {
|
||||
"name": "fenced_code.block.language.markdown"
|
||||
},
|
||||
"5": {
|
||||
"name": "fenced_code.block.language.attributes.markdown"
|
||||
}
|
||||
},
|
||||
"endCaptures": {
|
||||
"3": {
|
||||
"name": "punctuation.definition.markdown"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "(^|\\G)(\\s*)(.*)",
|
||||
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
|
||||
"contentName": "meta.embedded.block.cadl",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "source.cadl"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scopeName": "markdown.cadl.codeblock"
|
||||
}
|
|
@ -65,6 +65,17 @@
|
|||
"language": "cadl",
|
||||
"scopeName": "source.cadl",
|
||||
"path": "./dist/cadl.tmLanguage"
|
||||
},
|
||||
{
|
||||
"language": "cadl",
|
||||
"scopeName": "markdown.cadl.codeblock",
|
||||
"path": "./markdown-cadl.json",
|
||||
"injectTo": [
|
||||
"text.html.markdown"
|
||||
],
|
||||
"embeddedLanguages": {
|
||||
"meta.embedded.block.cadl": "cadl"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -605,6 +605,7 @@ function isModelAValue(path: AstPath<Node>): boolean {
|
|||
switch (node.kind) {
|
||||
case SyntaxKind.ModelStatement:
|
||||
case SyntaxKind.AliasStatement:
|
||||
case SyntaxKind.OperationStatement:
|
||||
return false;
|
||||
case SyntaxKind.DecoratorExpression:
|
||||
return true;
|
||||
|
@ -644,7 +645,7 @@ function isModelExpressionInBlock(path: AstPath<ModelExpressionNode>) {
|
|||
|
||||
switch (parent?.kind) {
|
||||
case SyntaxKind.OperationStatement:
|
||||
return false;
|
||||
return parent.parameters !== path.getNode();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -37,6 +37,20 @@ import "@azure-tools/cadl-rpaas";
|
|||
});
|
||||
});
|
||||
|
||||
it("formats returns of anonymous models", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
op test(): { a: string; b: string; };
|
||||
`,
|
||||
expected: `
|
||||
op test(): {
|
||||
a: string;
|
||||
b: string;
|
||||
};
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it("format using", () => {
|
||||
assertFormat({
|
||||
code: `
|
||||
|
|
Загрузка…
Ссылка в новой задаче