From 95fd826835ecb335e3d0e8512cd4abfaba690c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 23 Nov 2023 19:11:58 +0000 Subject: [PATCH 01/15] Initial draft specs --- specs/kiota.config.md | 240 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 specs/kiota.config.md diff --git a/specs/kiota.config.md b/specs/kiota.config.md new file mode 100644 index 0000000000..18f2c1726d --- /dev/null +++ b/specs/kiota.config.md @@ -0,0 +1,240 @@ +# Kiota Config + +Kiota generates client code for an API and stores parameters in a kiota.lock file. A project can contain multiple API clients, but they are independently managed. Kiota has no awareness that an app has a dependency on multiple APIs, even though that is a core use case. + +## Status + +| Date | Version | Author | Status | +| ------------------- | ------- | ---------------- | ------ | +| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | +| September 24th, 2023 | v0.1 | Darrel Miller | Draft | + +## Current Challenges + +- Client code generation is not reproducible if API description changes +- Kiota doesn’t have a good solution for APIs that use multiple security schemes. +- Kiota doesn’t provide any support for generating auth providers with the required permissions, partially because currently we generate one client for APIs that use different schemes. How would we know which auth provider to generate. +- Kiota doesn’t have a good story for acquiring a client identifier. e.g. apikey or OAuth2 ClientId. This could be possible if the OpenIDConnect URL pointed to a dynamic registration endpoint. +- If an application has multiple kiota clients, there is currently no way perform operations that correspond to all of the clients. + +We have previously described Kiota's approach to managing API dependencies as consistent with the way people manage packages in a project. However, currently our tooling doesn't behave that way. We treat each dependency independently. + +## Proposal + +We should introduce a new Kiota.config file that holds the input parameters required to generate the API Client code. Currently kiota.lock is used to capture what the parameters were at the time of generation and can be used to regenerate based on the parameters in the file. This creates a mixture of purposes for the file. + +We did consider creating one kiota.config file as as a peer of the language project file, however, for someone who wants to generate multiple clients for an API in different languages, this would be a bit annoying. An alternative would be to allow the kiota.config file to move further up the folder structure and support generation in multiple languages from a single file. This is more consistent with what [TypeSpec](https://aka.ms/typespec) are doing and would be helpful for generating CLI and docs as well as a library. + +Here is an example of what the kiota.config file could look like. + +```json +{ + "name": "My application", + "version": "2.0", + "apis": { + "Graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "outputs": [ + { + "language": "csharp", + "outputPath": "./generated/graph", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + "usesBackingStore": true, + "includeAdditionalData": true + } + } + ] + }, + "BusinessCentral": { + "descriptionHash": "810CF81EFDB5D8E065...", + "descriptionLocation": "https://.../bcoas1.0.yaml", + "includePatterns": ["/companies#GET"], + "excludePatterns": [], + "outputs": [ + { + "language": "csharp", + "outputPath": "./generated/business-central" + }, + { + "language": "python", + "outputPath": "./generated/python/business-central" + }, + { + "language": "csharp", + "outputPath": "./generated/business-central-app", + "features": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + } + } + } + ] + } + } +} +``` + +Note that in this example we added suggestions for new parameters related to authentication. If we are to improve the generation experience so that we read the security schemes information from the OpenAPI, then we will need to have some place to configure what providers we will use for those schemes. + +The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota.lock file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. + +## Tooling commands to manage Kiota.config + +| Command | Example | Description | +| ------------------- | ------- | ---------------- | +| init | kiota init --name | Creates a kiota.config file | +| add api | kiota add api --name --openapi | Adds an entry for an API with passed parameters and default values | +| add output | kiota add output --name MyApi --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | +| generate | kiota generate | Outputs kiota.apimanifest and source for each of the output objects | + +In the past we have had both a generate and an update comment. This is because if it was the first time running, generate would set defaults for all the properties that were not set on the command line. However, now that we have add output, it can be used to set the defaults and generate can just read from the kiota.config file. + +## Scenarios using the command line tool + +### Get started to generate an API + +```bash +kiota init --name Myapp +kiota add api --name MyApi --openapi // Can we add using -k ? +kiota add output --name MyApi --lang csharp --outputPath ./csharpClient +kiota generate +``` + +### Add a second language to generate an API + +```bash +kiota add output --name MyApi --lang python --outputPath ./pythonClient +kiota generate --name MyApi --lang python // Generate just the Python client for MyApi +``` + +### Remove a language + +```bash +kiota remove output --name MyApi --lang python +``` + +### Remove an API + +```bash +kiota remove api --name MyApi +``` + +## JSON Schema for Kiota.Config + +```json +{ + "$schema": "", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "apis": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "descriptionLocation": { + "type": "string" + }, + "descriptionHash": { + "type": "string" + } + }, + "descriptionHash": { + "type": "string" + }, + "descriptionLocation": { + "type": "string" + }, + "includePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "excludePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "baseUrl": { + "type": "string" + }, + "output": { + "type": "array", + "items": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "outputPath": { + "type": "string" + }, + "clientClassName": { + "type": "string" + }, + "clientNamespaceName": { + "type": "string" + }, + "features": { + "type": "object", + "properties": { + "authenticationProvider": { + "type": "string" + }, + "authenticationParameters": { + "type": "object" + } + }, + "structuredMediaTypes": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "serializer": { + "type": "string" + }, + "deserializer": { + "type": "string" + } + } + } + } + }, + "usesBackingStore": { + "type": "boolean" + }, + "includeAdditionalData": { + "type": "boolean" + } + } + } + } + } + }, + "disabledValidationRules": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} +``` From accf42025abaa84e8e6ac12fa14024d3b1685c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Mon, 27 Nov 2023 16:58:17 +0000 Subject: [PATCH 02/15] Updated specs --- specs/kiota.config.md | 94 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/specs/kiota.config.md b/specs/kiota.config.md index 18f2c1726d..1097c38df8 100644 --- a/specs/kiota.config.md +++ b/specs/kiota.config.md @@ -4,10 +4,10 @@ Kiota generates client code for an API and stores parameters in a kiota.lock fil ## Status -| Date | Version | Author | Status | -| ------------------- | ------- | ---------------- | ------ | -| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | -| September 24th, 2023 | v0.1 | Darrel Miller | Draft | +| Date | Version | Author | Status | +| -- | -- | -- | -- | +| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | +| September 24th, 2023 | v0.1 | Darrel Miller | Draft | ## Current Challenges @@ -44,9 +44,11 @@ Here is an example of what the kiota.config file could look like. "clientClassName": "GraphClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" + "authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + } }, "usesBackingStore": true, "includeAdditionalData": true @@ -72,9 +74,11 @@ Here is an example of what the kiota.config file could look like. "language": "csharp", "outputPath": "./generated/business-central-app", "features": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" + "authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + } } } } @@ -94,11 +98,79 @@ The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01. | ------------------- | ------- | ---------------- | | init | kiota init --name | Creates a kiota.config file | | add api | kiota add api --name --openapi | Adds an entry for an API with passed parameters and default values | -| add output | kiota add output --name MyApi --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | +| add output | kiota add output --api-name --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | | generate | kiota generate | Outputs kiota.apimanifest and source for each of the output objects | In the past we have had both a generate and an update comment. This is because if it was the first time running, generate would set defaults for all the properties that were not set on the command line. However, now that we have add output, it can be used to set the defaults and generate can just read from the kiota.config file. +## Commands + +### kiota init + +`kiota init` creates a new kiota.config file with the passed parameters. If the file already exists, it should error out and report it to the user. The initialization process has a single required parameter, the name of the application. + +> [!NOTE] +> If a project only needs a single API, using `kiota init` is not required as generating code using the `kiota generate` command should generate a `kiota.config` file with values coming from the `kiota generate` command. See [kiota generate](#kiota-generate) for more information. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--app-name \| -n` | Yes | My application | Name of the application | + +#### Using `kiota init` + +```bash +kiota init --app-name "My application" +``` + +```json +// Creates the following kiota.config file +{ + "name": "My application", + "version": "1.0" +} +``` + +### kiota add api + +`kiota add api` allows a developer to add a new API to the kiota.config file. The command will add a new entry to the apis section of the kiota.config file. The command has two required parameters, the name of the API (key of the api map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the generation process. If not provided, all paths will be assumed. + +When executing, a new API entry will be added and will use the `--api-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--api-name \| -n` | Yes | graph | Name of the API | +| `--open-api \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | +| `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | +| `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | + +#### Using `kiota add api` + +```bash +kiota add api --api-name "graph" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" +``` + +```json +// Adds the following to the kiota.config file +"graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [] +} +``` + +### kiota generate + +In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` as it will create a `kiota.config` file with the values coming from the command parameters. + +#### Using `kiota generate` + +```bash +``` + +```json +``` + ## Scenarios using the command line tool ### Get started to generate an API From 84c7d5eee3433f4277a8a3fbb203ea3b4a688ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 30 Nov 2023 16:30:03 +0000 Subject: [PATCH 03/15] Polishing spec + updated json schema --- specs/kiota.config.md | 483 +++++++++++++++++++++++++++++++++++------- 1 file changed, 409 insertions(+), 74 deletions(-) diff --git a/specs/kiota.config.md b/specs/kiota.config.md index 1097c38df8..906a8fd653 100644 --- a/specs/kiota.config.md +++ b/specs/kiota.config.md @@ -6,6 +6,7 @@ Kiota generates client code for an API and stores parameters in a kiota.lock fil | Date | Version | Author | Status | | -- | -- | -- | -- | +| November 30th, 2023 | v0.3 | Sébastien Levert | Final Draft | | November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | | September 24th, 2023 | v0.1 | Darrel Miller | Draft | @@ -30,17 +31,16 @@ Here is an example of what the kiota.config file could look like. ```json { "name": "My application", - "version": "2.0", "apis": { "Graph": { "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://.../openapi.yaml", "includePatterns": ["/me/chats#GET", "/me#GET"], "excludePatterns": [], - "outputs": [ + "clients": [ { "language": "csharp", - "outputPath": "./generated/graph", + "outputPath": "./generated/graph/csharp", "clientClassName": "GraphClient", "clientNamespaceName": "Contoso.GraphApp", "features": { @@ -96,25 +96,27 @@ The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01. | Command | Example | Description | | ------------------- | ------- | ---------------- | -| init | kiota init --name | Creates a kiota.config file | -| add api | kiota add api --name --openapi | Adds an entry for an API with passed parameters and default values | -| add output | kiota add output --api-name --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | +| init | kiota init --name `appName` | Creates a kiota.config file | +| api add | kiota api add --name `apiName` --openapi | Adds an entry for an API with passed parameters and default values | +| api delete | kiota api delete --api-name `apiName` | Removes the entire API entry with the provided name. | +| output add | kiota output add --api-name `apiName` --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | +| output delete | kiota output delete --api-name `apiName` --client-name `clientName` | Removes the specified output. Has optional parameters to allow deleting the generated output. | | generate | kiota generate | Outputs kiota.apimanifest and source for each of the output objects | -In the past we have had both a generate and an update comment. This is because if it was the first time running, generate would set defaults for all the properties that were not set on the command line. However, now that we have add output, it can be used to set the defaults and generate can just read from the kiota.config file. +In the past we have had both a generate and an update comment. This is because if it was the first time running, generate would set defaults for all the properties that were not set on the command line. However, now that we have output add, it can be used to set the defaults and generate can just read from the kiota.config file. ## Commands ### kiota init -`kiota init` creates a new kiota.config file with the passed parameters. If the file already exists, it should error out and report it to the user. The initialization process has a single required parameter, the name of the application. +`kiota init` creates a new kiota.config file with the provided parameters. If the file already exists, it should error out and report it to the user. The initialization process has a single required parameter, the name of the application. > [!NOTE] -> If a project only needs a single API, using `kiota init` is not required as generating code using the `kiota generate` command should generate a `kiota.config` file with values coming from the `kiota generate` command. See [kiota generate](#kiota-generate) for more information. +> If a project only needs a single API, using `kiota init` is not mandatory as generating code using the `kiota generate` command could generate a `kiota.config` file with values coming from the `kiota generate` command (if no `kiota.config` is present). See [kiota generate](#kiota-generate) for more information. | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--app-name \| -n` | Yes | My application | Name of the application | +| `--app-name \| --an` | Yes | My application | Name of the application | #### Using `kiota init` @@ -122,34 +124,33 @@ In the past we have had both a generate and an update comment. This is because i kiota init --app-name "My application" ``` -```json +```jsonc // Creates the following kiota.config file { - "name": "My application", - "version": "1.0" + "name": "My application" } ``` -### kiota add api +### kiota api add -`kiota add api` allows a developer to add a new API to the kiota.config file. The command will add a new entry to the apis section of the kiota.config file. The command has two required parameters, the name of the API (key of the api map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the generation process. If not provided, all paths will be assumed. +`kiota api add` allows a developer to add a new API to the `kiota.config` file. The command will add a new entry to the `apis` section of the `kiota.config` file. The command has two required parameters, the name of the API (key of the apis map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the future generation process. If not provided, all paths will be assumed. When executing, a new API entry will be added and will use the `--api-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--api-name \| -n` | Yes | graph | Name of the API | -| `--open-api \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | +| `--api-name \| --api` | Yes | graph | Name of the API | +| `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | | `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | -#### Using `kiota add api` +#### Using `kiota api add` ```bash -kiota add api --api-name "graph" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" +kiota api add --api-name "graph" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" ``` -```json +```jsonc // Adds the following to the kiota.config file "graph": { "descriptionHash": "9EDF8506CB74FE44...", @@ -159,46 +160,382 @@ kiota add api --api-name "graph" --openapi "https://raw.githubusercontent.com/mi } ``` +The resulting `kiota.config` file will look like this: + +```jsonc +{ + "name": "My application", + "apis": { + "graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [] + } + } +} +``` + +### kiota api delete + +`kiota api delete` allows a developer to delete an existing API from the `kiota.config` file. The command will remove the entry from the `apis` section of the `kiota.config` file. The command has one required parameter, the name of the API (key of the apis map). The command also has one optional parameter, the ability to remove generated clients. If provided, kiota will delete the folder specified at the `outputPath` from the client configuration. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--api-name \| --api` | Yes | graph | Name of the API | +| `--clean-output \| --co` | No | | Cleans the generated clients from the API | + +#### Using kiota api delete + +```bash +kiota api delete --api-name "graph" --clean-output +``` + +```jsonc +// Removes the following from the kiota.config file +"graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + // All clients + } +} +``` + +The resulting `kiota.config` file will look like this: + +```jsonc +{ + "name": "My application", + "apis": {} +} +``` + +### kiota client add + +`kiota client add` allows a developer to add a new client for a specified API to the `kiota.config` file. The command will add a new entry to the `clients` section of the `kiota.config` file. The command has two required parameters, the name of the API (key of the apis map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the future generation process. If not provided, all paths will be assumed. The `kiota client add` command will never automatically invoke `kiota generate`. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--api-name \| --api` | Yes | graph | Name of the API | +| `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | +| `--language \| -l` | Yes | csharp | The target language for the generated code files or for the information. | +| `--class-name \| -c` | No | GraphClient | The name of the client class. Defaults to `Client`. | +| `--namespace-name \| -n` | No | Contoso.GraphApp | The namespace of the client class. Defaults to `Microsoft.Graph`. | +| `--backing-store \| -b` | No | | Defaults to `false` | +| `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | +| `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | +| `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | +| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | + +> [!NOTE] +> It is not required to use the CLI to ad a new clients. It is possible to add a new client by adding a new entry in the `clients` section of the `kiota.config` file. See [kiota.config](#kiotaconfig) for more information. + +#### Using kiota client add + +```bash +kiota client add --api-name "graph" --client-name "graphDelegated" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +``` + +```jsonc +// Adds the following to the kiota.config file +"clients": { + "graphDelegated": { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } +} +``` + +The resulting `kiota.config` file will look like this: + +```jsonc +{ + "name": "My application", + "apis": { + "graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + "graphDelegated": { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } + } + } +} +``` + +### kiota client delete + +`kiota api client` allows a developer to delete an existing client from the `kiota.config` file. The command will remove the entry from the `clients` section of parent API within the `kiota.config` file. The command has two required parameters, the name of the API and the name of the client. The command also has one optional parameter, the ability to remove the generated client. If provided, kiota will delete the folder specified at the `outputPath` from the client configuration. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--api-name \| --api` | Yes | graph | Name of the API | +| `--client-name \| --cn` | Yes | graphDelegated | Name of the client | +| `--clean-output \| --co` | No | | Cleans the generated client | + +#### Using kiota client delete + +```bash +kiota client delete --api-name "graph" --client-name "graphDelegated" --clean-output +``` + +```jsonc +// Removes the following from the kiota.config file +"graphDelegated": { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } +} +``` + +The resulting `kiota.config` file will look like this: + +```jsonc +{ + "name": "My application", + "version": "1.0", + "apis": { + "graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { } + } + } +} +``` + ### kiota generate -In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` as it will create a `kiota.config` file with the values coming from the command parameters. +Now that we have a `kiota.config` file, all the parameters required to generate the code are stored in the file. The `kiota generate` command will read the `kiota.config` file and generate the code for each of the clients. + +It's also possible to specify for which API and client the code should be generated. This is useful when a project contains multiple APIs and clients. The `kiota generate --api-name "MyAPI" --client-name "MyClient"` command will read the `kiota.config` file and generate the code for the specified API and client. If it can't find the specified API or client, it will throw an error. + +In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` and initialize a `kiota.config` file with the values coming from the command parameters. No breaking changes are required to the existing `kiota generate` command. + +#### kiota generate Parameters -#### Using `kiota generate` +> [!INFO] +> This list is only the new parameters that `kiota generate` should support. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--app-name \| --an` | No | My application | Name of the application | +| `--api-name \| --api` | No | graph | Name of the API | +| `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | + +#### Using `kiota generate` with all parameters ```bash +kiota generate --app-name "My Application" --api-name "graph" --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` ```json +{ + "name": "My application", + "apis": { + "graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + "graphDelegated": { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } + } + } +} ``` -## Scenarios using the command line tool +#### Using kiota generate with parameters inferred from the kiota.config file + +```bash +kiota generate +``` + +#### Using kiota generate with parameters inferred from the kiota.config file for a single API + +```bash +kiota generate --api-name "graph" --client-name "graphDelegated" +``` + +#### Using kiota generate with parameters inferred when there are no kiota.config file + +```bash +kiota generate --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +``` + +```json +// This file gets generated and then `kiota generate` is executed based on these parameters +{ + "name": "Contoso.GraphApp", // Inferred from the provided --namespace-name or its default value + "apis": { + "https://graph.microsoft.com/v1.0": { // Inferred from the first server entry in the OpenAPI description + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + "GraphClient": { // Inferred from the provided --class-name or its default value + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } + } + } +} +``` + +## End-to-end scenarios using the CLI ### Get started to generate an API ```bash -kiota init --name Myapp -kiota add api --name MyApi --openapi // Can we add using -k ? -kiota add output --name MyApi --lang csharp --outputPath ./csharpClient +kiota init --app-name "My Application" +kiota api add --api-name "My API" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" +kiota client add --api-name "My API" --clientName "graphDelegated" --language csharp --outputPath ./csharpClient kiota generate ``` ### Add a second language to generate an API ```bash -kiota add output --name MyApi --lang python --outputPath ./pythonClient -kiota generate --name MyApi --lang python // Generate just the Python client for MyApi +kiota client add --api-name "My API" --clientName "graphPython" --language python --outputPath ./pythonClient +kiota generate --api-name "My API" --client-name "graphPython" ``` -### Remove a language +### Remove a language and delete the generated code ```bash -kiota remove output --name MyApi --lang python +kiota client delete --api-name "My API" --client=name "graphPython" --clean-output ``` ### Remove an API ```bash -kiota remove api --name MyApi +kiota api delete --name "My Api" --clean-output ``` ## JSON Schema for Kiota.Config @@ -245,54 +582,52 @@ kiota remove api --name MyApi "baseUrl": { "type": "string" }, - "output": { - "type": "array", - "items": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "outputPath": { - "type": "string" - }, - "clientClassName": { - "type": "string" - }, - "clientNamespaceName": { - "type": "string" - }, - "features": { - "type": "object", - "properties": { - "authenticationProvider": { - "type": "string" - }, - "authenticationParameters": { - "type": "object" - } + "clients": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "outputPath": { + "type": "string" + }, + "clientClassName": { + "type": "string" }, - "structuredMediaTypes": { + "clientNamespaceName": { + "type": "string" + }, + "features": { "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "serializer": { - "type": "string" - }, - "deserializer": { - "type": "string" - } + "properties": { + "structuredMediaTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "serializers": { + "type": "array", + "items": { + "type": "string" + } + }, + "deserializers": { + "type": "array", + "items": { + "type": "string" } + }, + "usesBackingStore": { + "type": "boolean" + }, + "includeAdditionalData": { + "type": "boolean" } } - }, - "usesBackingStore": { - "type": "boolean" - }, - "includeAdditionalData": { - "type": "boolean" } } } From a89ce5cd7ce98fc26c4eea0caf0cb314c4b76588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 30 Nov 2023 16:39:05 +0000 Subject: [PATCH 04/15] Removing the initial commands table. --- specs/kiota.config.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/specs/kiota.config.md b/specs/kiota.config.md index 906a8fd653..083fb6a702 100644 --- a/specs/kiota.config.md +++ b/specs/kiota.config.md @@ -92,19 +92,6 @@ Note that in this example we added suggestions for new parameters related to aut The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota.lock file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. -## Tooling commands to manage Kiota.config - -| Command | Example | Description | -| ------------------- | ------- | ---------------- | -| init | kiota init --name `appName` | Creates a kiota.config file | -| api add | kiota api add --name `apiName` --openapi | Adds an entry for an API with passed parameters and default values | -| api delete | kiota api delete --api-name `apiName` | Removes the entire API entry with the provided name. | -| output add | kiota output add --api-name `apiName` --lang python --outputPath ./pythonClient | Adds information about a new output artifact that should be generated | -| output delete | kiota output delete --api-name `apiName` --client-name `clientName` | Removes the specified output. Has optional parameters to allow deleting the generated output. | -| generate | kiota generate | Outputs kiota.apimanifest and source for each of the output objects | - -In the past we have had both a generate and an update comment. This is because if it was the first time running, generate would set defaults for all the properties that were not set on the command line. However, now that we have output add, it can be used to set the defaults and generate can just read from the kiota.config file. - ## Commands ### kiota init From 47da551a8e1a1b999097547390c425f5330500cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 30 Nov 2023 16:40:46 +0000 Subject: [PATCH 05/15] markdown edits --- specs/kiota.config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/kiota.config.md b/specs/kiota.config.md index 083fb6a702..2a36381a05 100644 --- a/specs/kiota.config.md +++ b/specs/kiota.config.md @@ -377,7 +377,7 @@ In scenarios where a developer only needs a single API or doesn't want to go thr #### kiota generate Parameters -> [!INFO] +> [!IMPORTANT] > This list is only the new parameters that `kiota generate` should support. | Parameters | Required | Example | Description | From 4f8ba43362ef70612bc5db33ceb4b8af409db157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 30 Nov 2023 16:42:55 +0000 Subject: [PATCH 06/15] Updating code blocks to support comments --- specs/kiota.config.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/kiota.config.md b/specs/kiota.config.md index 2a36381a05..c35d8f4472 100644 --- a/specs/kiota.config.md +++ b/specs/kiota.config.md @@ -111,7 +111,7 @@ The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01. kiota init --app-name "My application" ``` -```jsonc +```javascript // Creates the following kiota.config file { "name": "My application" @@ -137,7 +137,7 @@ When executing, a new API entry will be added and will use the `--api-name` para kiota api add --api-name "graph" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" ``` -```jsonc +```javascript // Adds the following to the kiota.config file "graph": { "descriptionHash": "9EDF8506CB74FE44...", @@ -149,7 +149,7 @@ kiota api add --api-name "graph" --openapi "https://raw.githubusercontent.com/mi The resulting `kiota.config` file will look like this: -```jsonc +```javascript { "name": "My application", "apis": { @@ -178,7 +178,7 @@ The resulting `kiota.config` file will look like this: kiota api delete --api-name "graph" --clean-output ``` -```jsonc +```javascript // Removes the following from the kiota.config file "graph": { "descriptionHash": "9EDF8506CB74FE44...", @@ -193,7 +193,7 @@ kiota api delete --api-name "graph" --clean-output The resulting `kiota.config` file will look like this: -```jsonc +```javascript { "name": "My application", "apis": {} @@ -227,7 +227,7 @@ The resulting `kiota.config` file will look like this: kiota client add --api-name "graph" --client-name "graphDelegated" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` -```jsonc +```javascript // Adds the following to the kiota.config file "clients": { "graphDelegated": { @@ -261,7 +261,7 @@ kiota client add --api-name "graph" --client-name "graphDelegated" --language cs The resulting `kiota.config` file will look like this: -```jsonc +```javascript { "name": "My application", "apis": { @@ -351,7 +351,7 @@ kiota client delete --api-name "graph" --client-name "graphDelegated" --clean-ou The resulting `kiota.config` file will look like this: -```jsonc +```javascript { "name": "My application", "version": "1.0", @@ -392,7 +392,7 @@ In scenarios where a developer only needs a single API or doesn't want to go thr kiota generate --app-name "My Application" --api-name "graph" --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` -```json +```javascript { "name": "My application", "apis": { @@ -452,7 +452,7 @@ kiota generate --api-name "graph" --client-name "graphDelegated" kiota generate --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` -```json +```javascript // This file gets generated and then `kiota generate` is executed based on these parameters { "name": "Contoso.GraphApp", // Inferred from the provided --namespace-name or its default value From 36b10fd16dcdbc29070421fd22124427629b316f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Wed, 6 Dec 2023 20:15:24 +0000 Subject: [PATCH 07/15] Initial restructuring of the spec --- specs/cli/client-add.md | 165 +++++++++ specs/cli/client-edit.md | 160 ++++++++ specs/cli/client-generate.md | 66 ++++ specs/cli/client-remove.md | 49 +++ specs/cli/config-init.md | 28 ++ specs/cli/config-migrate.md | 0 specs/index.md | 19 + specs/kiota.config.md | 634 -------------------------------- specs/scenarios/kiota-config.md | 194 ++++++++++ specs/schemas/kiota.config.md | 107 ++++++ 10 files changed, 788 insertions(+), 634 deletions(-) create mode 100644 specs/cli/client-add.md create mode 100644 specs/cli/client-edit.md create mode 100644 specs/cli/client-generate.md create mode 100644 specs/cli/client-remove.md create mode 100644 specs/cli/config-init.md create mode 100644 specs/cli/config-migrate.md create mode 100644 specs/index.md delete mode 100644 specs/kiota.config.md create mode 100644 specs/scenarios/kiota-config.md create mode 100644 specs/schemas/kiota.config.md diff --git a/specs/cli/client-add.md b/specs/cli/client-add.md new file mode 100644 index 0000000000..b4930d054a --- /dev/null +++ b/specs/cli/client-add.md @@ -0,0 +1,165 @@ +# kiota client add + +## Description + +`kiota client add` allows a developer to add a new API client to the `kiota.config` file. If no `kiota.config` file is found, a new `kiota.config` file would be created. The command will add a new entry to the `clients` section of the `kiota.config` file. Once this is done, a local copy of the OpenAPI description is generated and kepts in the . + +When executing, a new API entry will be added and will use the `--client-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. + +Every time an API client is added, a copy of the OpenAPI description file will be stored in the `./.kiota` folder. The file will be named using the hash of the description. This will allow the CLI to detect changes in the description and avoid downloading the description again if it hasn't changed. + +At the same time, an [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3) file will be generated (if non existing) or edited (if already existing) to represent the surface of the API being used. This file will be named `apimanifest.json` and will be stored in the `./.kiota` folder. This file will be used to generate the code files. + +Once the `kiota.config` file is generated, the OpenAPI description file is saved locally and the API Manifest is available, the code generation will be executed. + +## Parameters + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | +| `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | +| `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | +| `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | +| `--language \| -l` | Yes | csharp | The target language for the generated code files or for the information. | +| `--class-name \| -c` | No | GraphClient | The name of the client class. Defaults to `Client`. | +| `--namespace-name \| -n` | No | Contoso.GraphApp | The namespace of the client class. Defaults to `Microsoft.Graph`. | +| `--backing-store \| -b` | No | | Defaults to `false` | +| `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | +| `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | +| `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | +| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | + +> [!NOTE] +> It is not required to use the CLI to add new clients. It is possible to add a new client by adding a new entry in the `clients` section of the `kiota.config` file. See the [kiota.config schema](../schemas/kiota.config.md) for more information. + +## Using `kiota client add` + +```bash +kiota client add --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "**/users/**" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +``` + +```json +"clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } +} +``` + +_The resulting `kiota.config` file will look like this:_ + +```json +{ + "version": "1.0.0", + "clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } +} +``` + +_The resulting `apimanifest.json` file will look like this:_ + +```json +{ + "publisher": { + "name": "Microsoft Graph", + "contactEmail": "graphsdkpub@microsoft.com" + }, + "apiDependencies": { + "graphDelegated": { + "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "apiDeploymentBaseUrl": "https://graph.microsoft.com", + "apiDescriptionVersion": "v1.0", + "requests": [ + { + "method": "GET", + "uriTemplate": "/users" + }, + { + "method": "POST", + "uriTemplate": "/users" + }, + { + "method": "GET", + "uriTemplate": "/users/$count" + }, + { + "method": "GET", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "PATCH", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "DELETE", + "uriTemplate": "/users/{user-id}" + } + ] + } + } +} +``` + +## File structure +```bash +/ + └─.kiota + └─kiota.config + └─apimanifest.json + └─definitions + └─9EDF8506CB74FE44.yaml + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs +``` + +## Open Questions + +- [ ] How do we determine the `name` and `contactEmail` of the `publisher` in the API Manifest? kiota config --global? +- [ ] Can we automatically generate all `authorizationRequirements` for the endpoints selected or these are left to the developers to add? \ No newline at end of file diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md new file mode 100644 index 0000000000..a46a89030a --- /dev/null +++ b/specs/cli/client-edit.md @@ -0,0 +1,160 @@ +# kiota client edit + +## Description + +`kiota client update` allows a developer to edit an existing API client int the `kiota.config` file. If either the `kiota.config` file or if the `--client-name` client can't be found within the `kiota.config` file, the command should error out and let the developer know. + +When executing, the API entry defined by the `--client-name` parameter will be modified. All parameters should be supported and the only required one is `--client-name`. All others are optional as they would only modify the configuration of the API client. If the OpenAPI description location changed, a new hash of the description will be generated saved as part of the `descriptionHash` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively and would trigger an update to the [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3). + +Once the `kiota.config` file and the API Manifest are updated, the code generation will be executed based on the newly updated API client configuration. + +## Parameters + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | +| `--openapi \| -d` | No | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | +| `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | +| `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | +| `--language \| -l` | No | csharp | The target language for the generated code files or for the information. | +| `--class-name \| -c` | No | GraphClient | The name of the client class. Defaults to `Client`. | +| `--namespace-name \| -n` | No | Contoso.GraphApp | The namespace of the client class. Defaults to `Microsoft.Graph`. | +| `--backing-store \| -b` | No | | Defaults to `false` | +| `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | +| `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | +| `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | +| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | + +> [!NOTE] +> It is not required to use the CLI to edit clients. It is possible to edit a client by modifying its entry in the `clients` section of the `kiota.config` file. See the [kiota.config schema](../schemas/kiota.config.md) for more information. + +## Using `kiota client edit` + +```bash +kiota client edit --client-name "graphDelegated" --class-name "GraphServiceClient" --exclude-path "/users/$count" +``` + +```json +``` + +```json +"clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": ["/users/$count"], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } +} +``` + +_The resulting `kiota.config` file will look like this:_ + +```json +{ + "version": "1.0.0", + "clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } +} +``` + +_The resulting `apimanifest.json` file will look like this:_ + +```json +{ + "publisher": { + "name": "Microsoft Graph", + "contactEmail": "graphsdkpub@microsoft.com" + }, + "apiDependencies": { + "graphDelegated": { + "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "apiDeploymentBaseUrl": "https://graph.microsoft.com", + "apiDescriptionVersion": "v1.0", + "requests": [ + { + "method": "GET", + "uriTemplate": "/users" + }, + { + "method": "POST", + "uriTemplate": "/users" + }, + { + "method": "GET", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "PATCH", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "DELETE", + "uriTemplate": "/users/{user-id}" + } + ] + } + } +} +``` + +## File structure +```bash +/ + └─.kiota + └─kiota.config + └─apimanifest.json + └─definitions + └─9EDF8506CB74FE44.yaml + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs +``` + +## Open Questions + +- [ ] How do we determine the `name` and `contactEmail` of the `publisher` in the API Manifest? kiota config --global? +- [ ] Can we automatically generate all `authorizationRequirements` for the endpoints selected or these are left to the developers to add? \ No newline at end of file diff --git a/specs/cli/client-generate.md b/specs/cli/client-generate.md new file mode 100644 index 0000000000..5fd86967fc --- /dev/null +++ b/specs/cli/client-generate.md @@ -0,0 +1,66 @@ +# kiota client generate + +Now that we have a `kiota.config` file, all the parameters required to generate the code are stored in the file. The `kiota generate` command will read the `kiota.config` file and generate the code for each of the clients. + +It's also possible to specify for which API and client the code should be generated. This is useful when a project contains multiple APIs and clients. The `kiota generate --api-name "MyAPI" --client-name "MyClient"` command will read the `kiota.config` file and generate the code for the specified API and client. If it can't find the specified API or client, it will throw an error. + +In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` and initialize a `kiota.config` file with the values coming from the command parameters. No breaking changes are required to the existing `kiota generate` command. + +#### kiota generate Parameters + +> [!IMPORTANT] +> This list is only the new parameters that `kiota generate` should support. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--app-name \| --an` | No | My application | Name of the application | +| `--api-name \| --api` | No | graph | Name of the API | +| `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | + +#### Using `kiota generate` with all parameters + +```bash +kiota generate --app-name "My Application" --api-name "graph" --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +``` + +```javascript +{ + "name": "My application", + "apis": { + "graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + "graphDelegated": { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } + } + } +} +``` \ No newline at end of file diff --git a/specs/cli/client-remove.md b/specs/cli/client-remove.md new file mode 100644 index 0000000000..d7d857cd6e --- /dev/null +++ b/specs/cli/client-remove.md @@ -0,0 +1,49 @@ +# kiota client remove + +## Description + +`kiota client remove` allows a developer to remove an existing client from the `kiota.config` file. The command will remove the entry from the `clients` section of `kiota.config` file. The command has a single required parameters; the name of the client. + +The command also has one optional parameter, the ability to remove the generated client. If provided, kiota will delete the folder and its content specified at the `outputPath` from the client configuration. It will also remove the local version of the OpenAPI description file (specified by the `descriptionHash` property). The API Manifest is also updated to remove the dependency from the list of dependencies. + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--client-name \| --cn` | Yes | graphDelegated | Name of the client | +| `--clean-output \| --co` | No | | Cleans the generated client | + +#### Using kiota client remove + +```bash +kiota client remove --client-name "graphDelegated" --clean-output +``` + +The resulting `kiota.config` file will look like this: + +```json +{ + "version": "1.0.0", + "clients": { } +} +``` + +_The resulting `apimanifest.json` file will look like this:_ + +```json +{ + "publisher": { + "name": "Microsoft Graph", + "contactEmail": "graphsdkpub@microsoft.com" + }, + "apiDependencies": { } +} +``` + +## File structure +```bash +/ + └─.kiota + └─kiota.config + └─apimanifest.json + └─generated + └─graph +``` \ No newline at end of file diff --git a/specs/cli/config-init.md b/specs/cli/config-init.md new file mode 100644 index 0000000000..f4f3d3c12a --- /dev/null +++ b/specs/cli/config-init.md @@ -0,0 +1,28 @@ +# `kiota config init` + +## Description + +`kiota config init` creates a new kiota.config file with the provided parameters. If the file already exists, it should error out and report it to the user. As the file gets created, it should be adding a `version` property with the value of the `kiota.config` current schema version. + +When `kiota config init` is executed, a `kiota.config` file would be created in the current directory where the command is being executed. If the user wants to create the file in a different directory, they should use the `--config-file` global parameter. + +> [!NOTE] +> If a project only needs a single API, using `kiota config init` is not mandatory as generating code using the `kiota client generate` command could generate a `kiota.config` file with values coming from the `kiota client generate` command (if no `kiota.config` is present). See [kiota client generate](./client-generate.md) for more information. + +## Parameters + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--config-file \| --cf` | No | ../../kiota.config | Path to an existing `kiota.config` file. Defaults to `./` | + +## Using `kiota config init` + +```bash +kiota config init +``` +_Results in the following `kiota.config` file:_ +```json +{ + "version": "1.0.0", +} +``` \ No newline at end of file diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/specs/index.md b/specs/index.md new file mode 100644 index 0000000000..fa305ee49d --- /dev/null +++ b/specs/index.md @@ -0,0 +1,19 @@ +# Kiota Specification Repository + +This repository contains the specifications for the Kiota project. The goal of this repository is to provide a place to discuss and document the Kiota project. It will evolve over time as the project evolves. +config.md) + +## CLI + +* [kiota client add](./cli/client-add.md) +* [kiota client edit](./cli/client-edit.md) +* [kiota client update](./cli/client-update.md) +* [kiota client generate](./cli/client-generate.md) +* [kiota init](./cli/init.md) +* [kiota migrate](./cli/migrate.md) + +## Extension + +## Scenarios + +* [Kiota.Config](./scenarios/kiota. \ No newline at end of file diff --git a/specs/kiota.config.md b/specs/kiota.config.md deleted file mode 100644 index c35d8f4472..0000000000 --- a/specs/kiota.config.md +++ /dev/null @@ -1,634 +0,0 @@ -# Kiota Config - -Kiota generates client code for an API and stores parameters in a kiota.lock file. A project can contain multiple API clients, but they are independently managed. Kiota has no awareness that an app has a dependency on multiple APIs, even though that is a core use case. - -## Status - -| Date | Version | Author | Status | -| -- | -- | -- | -- | -| November 30th, 2023 | v0.3 | Sébastien Levert | Final Draft | -| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | -| September 24th, 2023 | v0.1 | Darrel Miller | Draft | - -## Current Challenges - -- Client code generation is not reproducible if API description changes -- Kiota doesn’t have a good solution for APIs that use multiple security schemes. -- Kiota doesn’t provide any support for generating auth providers with the required permissions, partially because currently we generate one client for APIs that use different schemes. How would we know which auth provider to generate. -- Kiota doesn’t have a good story for acquiring a client identifier. e.g. apikey or OAuth2 ClientId. This could be possible if the OpenIDConnect URL pointed to a dynamic registration endpoint. -- If an application has multiple kiota clients, there is currently no way perform operations that correspond to all of the clients. - -We have previously described Kiota's approach to managing API dependencies as consistent with the way people manage packages in a project. However, currently our tooling doesn't behave that way. We treat each dependency independently. - -## Proposal - -We should introduce a new Kiota.config file that holds the input parameters required to generate the API Client code. Currently kiota.lock is used to capture what the parameters were at the time of generation and can be used to regenerate based on the parameters in the file. This creates a mixture of purposes for the file. - -We did consider creating one kiota.config file as as a peer of the language project file, however, for someone who wants to generate multiple clients for an API in different languages, this would be a bit annoying. An alternative would be to allow the kiota.config file to move further up the folder structure and support generation in multiple languages from a single file. This is more consistent with what [TypeSpec](https://aka.ms/typespec) are doing and would be helpful for generating CLI and docs as well as a library. - -Here is an example of what the kiota.config file could look like. - -```json -{ - "name": "My application", - "apis": { - "Graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": [ - { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - } - }, - "usesBackingStore": true, - "includeAdditionalData": true - } - } - ] - }, - "BusinessCentral": { - "descriptionHash": "810CF81EFDB5D8E065...", - "descriptionLocation": "https://.../bcoas1.0.yaml", - "includePatterns": ["/companies#GET"], - "excludePatterns": [], - "outputs": [ - { - "language": "csharp", - "outputPath": "./generated/business-central" - }, - { - "language": "python", - "outputPath": "./generated/python/business-central" - }, - { - "language": "csharp", - "outputPath": "./generated/business-central-app", - "features": { - "authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - } - } - } - } - ] - } - } -} -``` - -Note that in this example we added suggestions for new parameters related to authentication. If we are to improve the generation experience so that we read the security schemes information from the OpenAPI, then we will need to have some place to configure what providers we will use for those schemes. - -The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota.lock file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. - -## Commands - -### kiota init - -`kiota init` creates a new kiota.config file with the provided parameters. If the file already exists, it should error out and report it to the user. The initialization process has a single required parameter, the name of the application. - -> [!NOTE] -> If a project only needs a single API, using `kiota init` is not mandatory as generating code using the `kiota generate` command could generate a `kiota.config` file with values coming from the `kiota generate` command (if no `kiota.config` is present). See [kiota generate](#kiota-generate) for more information. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--app-name \| --an` | Yes | My application | Name of the application | - -#### Using `kiota init` - -```bash -kiota init --app-name "My application" -``` - -```javascript -// Creates the following kiota.config file -{ - "name": "My application" -} -``` - -### kiota api add - -`kiota api add` allows a developer to add a new API to the `kiota.config` file. The command will add a new entry to the `apis` section of the `kiota.config` file. The command has two required parameters, the name of the API (key of the apis map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the future generation process. If not provided, all paths will be assumed. - -When executing, a new API entry will be added and will use the `--api-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--api-name \| --api` | Yes | graph | Name of the API | -| `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | -| `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | -| `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | - -#### Using `kiota api add` - -```bash -kiota api add --api-name "graph" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" -``` - -```javascript -// Adds the following to the kiota.config file -"graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [] -} -``` - -The resulting `kiota.config` file will look like this: - -```javascript -{ - "name": "My application", - "apis": { - "graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [] - } - } -} -``` - -### kiota api delete - -`kiota api delete` allows a developer to delete an existing API from the `kiota.config` file. The command will remove the entry from the `apis` section of the `kiota.config` file. The command has one required parameter, the name of the API (key of the apis map). The command also has one optional parameter, the ability to remove generated clients. If provided, kiota will delete the folder specified at the `outputPath` from the client configuration. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--api-name \| --api` | Yes | graph | Name of the API | -| `--clean-output \| --co` | No | | Cleans the generated clients from the API | - -#### Using kiota api delete - -```bash -kiota api delete --api-name "graph" --clean-output -``` - -```javascript -// Removes the following from the kiota.config file -"graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - // All clients - } -} -``` - -The resulting `kiota.config` file will look like this: - -```javascript -{ - "name": "My application", - "apis": {} -} -``` - -### kiota client add - -`kiota client add` allows a developer to add a new client for a specified API to the `kiota.config` file. The command will add a new entry to the `clients` section of the `kiota.config` file. The command has two required parameters, the name of the API (key of the apis map) and the location of the OpenAPI description. The command also has two optional parameters, the include and exclude patterns. If provided, these will be used to filter the paths that are included in the future generation process. If not provided, all paths will be assumed. The `kiota client add` command will never automatically invoke `kiota generate`. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--api-name \| --api` | Yes | graph | Name of the API | -| `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | -| `--language \| -l` | Yes | csharp | The target language for the generated code files or for the information. | -| `--class-name \| -c` | No | GraphClient | The name of the client class. Defaults to `Client`. | -| `--namespace-name \| -n` | No | Contoso.GraphApp | The namespace of the client class. Defaults to `Microsoft.Graph`. | -| `--backing-store \| -b` | No | | Defaults to `false` | -| `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | -| `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | -| `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | -| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | -| `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | - -> [!NOTE] -> It is not required to use the CLI to ad a new clients. It is possible to add a new client by adding a new entry in the `clients` section of the `kiota.config` file. See [kiota.config](#kiotaconfig) for more information. - -#### Using kiota client add - -```bash -kiota client add --api-name "graph" --client-name "graphDelegated" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" -``` - -```javascript -// Adds the following to the kiota.config file -"clients": { - "graphDelegated": { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } -} -``` - -The resulting `kiota.config` file will look like this: - -```javascript -{ - "name": "My application", - "apis": { - "graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - "graphDelegated": { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } - } - } -} -``` - -### kiota client delete - -`kiota api client` allows a developer to delete an existing client from the `kiota.config` file. The command will remove the entry from the `clients` section of parent API within the `kiota.config` file. The command has two required parameters, the name of the API and the name of the client. The command also has one optional parameter, the ability to remove the generated client. If provided, kiota will delete the folder specified at the `outputPath` from the client configuration. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--api-name \| --api` | Yes | graph | Name of the API | -| `--client-name \| --cn` | Yes | graphDelegated | Name of the client | -| `--clean-output \| --co` | No | | Cleans the generated client | - -#### Using kiota client delete - -```bash -kiota client delete --api-name "graph" --client-name "graphDelegated" --clean-output -``` - -```jsonc -// Removes the following from the kiota.config file -"graphDelegated": { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } -} -``` - -The resulting `kiota.config` file will look like this: - -```javascript -{ - "name": "My application", - "version": "1.0", - "apis": { - "graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { } - } - } -} -``` - -### kiota generate - -Now that we have a `kiota.config` file, all the parameters required to generate the code are stored in the file. The `kiota generate` command will read the `kiota.config` file and generate the code for each of the clients. - -It's also possible to specify for which API and client the code should be generated. This is useful when a project contains multiple APIs and clients. The `kiota generate --api-name "MyAPI" --client-name "MyClient"` command will read the `kiota.config` file and generate the code for the specified API and client. If it can't find the specified API or client, it will throw an error. - -In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` and initialize a `kiota.config` file with the values coming from the command parameters. No breaking changes are required to the existing `kiota generate` command. - -#### kiota generate Parameters - -> [!IMPORTANT] -> This list is only the new parameters that `kiota generate` should support. - -| Parameters | Required | Example | Description | -| -- | -- | -- | -- | -| `--app-name \| --an` | No | My application | Name of the application | -| `--api-name \| --api` | No | graph | Name of the API | -| `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | - -#### Using `kiota generate` with all parameters - -```bash -kiota generate --app-name "My Application" --api-name "graph" --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" -``` - -```javascript -{ - "name": "My application", - "apis": { - "graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - "graphDelegated": { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } - } - } -} -``` - -#### Using kiota generate with parameters inferred from the kiota.config file - -```bash -kiota generate -``` - -#### Using kiota generate with parameters inferred from the kiota.config file for a single API - -```bash -kiota generate --api-name "graph" --client-name "graphDelegated" -``` - -#### Using kiota generate with parameters inferred when there are no kiota.config file - -```bash -kiota generate --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" -``` - -```javascript -// This file gets generated and then `kiota generate` is executed based on these parameters -{ - "name": "Contoso.GraphApp", // Inferred from the provided --namespace-name or its default value - "apis": { - "https://graph.microsoft.com/v1.0": { // Inferred from the first server entry in the OpenAPI description - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - "GraphClient": { // Inferred from the provided --class-name or its default value - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } - } - } -} -``` - -## End-to-end scenarios using the CLI - -### Get started to generate an API - -```bash -kiota init --app-name "My Application" -kiota api add --api-name "My API" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" -kiota client add --api-name "My API" --clientName "graphDelegated" --language csharp --outputPath ./csharpClient -kiota generate -``` - -### Add a second language to generate an API - -```bash -kiota client add --api-name "My API" --clientName "graphPython" --language python --outputPath ./pythonClient -kiota generate --api-name "My API" --client-name "graphPython" -``` - -### Remove a language and delete the generated code - -```bash -kiota client delete --api-name "My API" --client=name "graphPython" --clean-output -``` - -### Remove an API - -```bash -kiota api delete --name "My Api" --clean-output -``` - -## JSON Schema for Kiota.Config - -```json -{ - "$schema": "", - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "apis": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "descriptionLocation": { - "type": "string" - }, - "descriptionHash": { - "type": "string" - } - }, - "descriptionHash": { - "type": "string" - }, - "descriptionLocation": { - "type": "string" - }, - "includePatterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "excludePatterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "baseUrl": { - "type": "string" - }, - "clients": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "outputPath": { - "type": "string" - }, - "clientClassName": { - "type": "string" - }, - "clientNamespaceName": { - "type": "string" - }, - "features": { - "type": "object", - "properties": { - "structuredMediaTypes": { - "type": "array", - "items": { - "type": "string" - } - }, - "serializers": { - "type": "array", - "items": { - "type": "string" - } - }, - "deserializers": { - "type": "array", - "items": { - "type": "string" - } - }, - "usesBackingStore": { - "type": "boolean" - }, - "includeAdditionalData": { - "type": "boolean" - } - } - } - } - } - } - } - }, - "disabledValidationRules": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } -} -``` diff --git a/specs/scenarios/kiota-config.md b/specs/scenarios/kiota-config.md new file mode 100644 index 0000000000..cc52a50ed2 --- /dev/null +++ b/specs/scenarios/kiota-config.md @@ -0,0 +1,194 @@ +# Kiota Config + +Kiota generates client code for an API and stores parameters in a kiota.lock file. A project can contain multiple API clients, but they are independently managed. Kiota has no awareness that an app has a dependency on multiple APIs, even though that is a core use case. + +## Status + +| Date | Version | Author | Status | +| -- | -- | -- | -- | +| November 30th, 2023 | v0.3 | Sébastien Levert | Final Draft | +| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | +| September 24th, 2023 | v0.1 | Darrel Miller | Draft | + +## Current Challenges + +- Client code generation is not reproducible if API description changes +- Kiota doesn’t have a good solution for APIs that use multiple security schemes. +- Kiota doesn’t provide any support for generating auth providers with the required permissions, partially because currently we generate one client for APIs that use different schemes. How would we know which auth provider to generate. +- Kiota doesn’t have a good story for acquiring a client identifier. e.g. apikey or OAuth2 ClientId. This could be possible if the OpenIDConnect URL pointed to a dynamic registration endpoint. +- If an application has multiple kiota clients, there is currently no way perform operations that correspond to all of the clients. + +We have previously described Kiota's approach to managing API dependencies as consistent with the way people manage packages in a project. However, currently our tooling doesn't behave that way. We treat each dependency independently. + +## Proposal + +We should introduce a new Kiota.config file that holds the input parameters required to generate the API Client code. Currently kiota.lock is used to capture what the parameters were at the time of generation and can be used to regenerate based on the parameters in the file. This creates a mixture of purposes for the file. + +We did consider creating one kiota.config file as as a peer of the language project file, however, for someone who wants to generate multiple clients for an API in different languages, this would be a bit annoying. An alternative would be to allow the kiota.config file to move further up the folder structure and support generation in multiple languages from a single file. This is more consistent with what [TypeSpec](https://aka.ms/typespec) are doing and would be helpful for generating CLI and docs as well as a library. + +Here is an example of what the kiota.config file could look like. + +```json +{ + "name": "My application", + "apis": { + "Graph": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": [ + { + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + } + }, + "usesBackingStore": true, + "includeAdditionalData": true + } + } + ] + }, + "BusinessCentral": { + "descriptionHash": "810CF81EFDB5D8E065...", + "descriptionLocation": "https://.../bcoas1.0.yaml", + "includePatterns": ["/companies#GET"], + "excludePatterns": [], + "outputs": [ + { + "language": "csharp", + "outputPath": "./generated/business-central" + }, + { + "language": "python", + "outputPath": "./generated/python/business-central" + }, + { + "language": "csharp", + "outputPath": "./generated/business-central-app", + "features": { + "authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + } + } + } + } + ] + } + } +} +``` + +Note that in this example we added suggestions for new parameters related to authentication. If we are to improve the generation experience so that we read the security schemes information from the OpenAPI, then we will need to have some place to configure what providers we will use for those schemes. + +The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota.lock file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. + +## Commands + +* [kiota config init](../cli/init.md) +* [kiota client add](../cli/client-add.md) +* [kiota client edit](../cli/client-edit.md) +* [kiota client generate](../cli/client-remove.md) +* [kiota client remove](../cli/client-remove.md) + +## End-to-end experience + +#### Using kiota generate with parameters inferred from the kiota.config file + +```bash +kiota generate +``` + +#### Using kiota generate with parameters inferred from the kiota.config file for a single API + +```bash +kiota generate --api-name "graph" --client-name "graphDelegated" +``` + +#### Using kiota generate with parameters inferred when there are no kiota.config file + +```bash +kiota generate --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +``` + +```javascript +// This file gets generated and then `kiota generate` is executed based on these parameters +{ + "name": "Contoso.GraphApp", // Inferred from the provided --namespace-name or its default value + "apis": { + "https://graph.microsoft.com/v1.0": { // Inferred from the first server entry in the OpenAPI description + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://.../openapi.yaml", + "includePatterns": ["/me/chats#GET", "/me#GET"], + "excludePatterns": [], + "clients": { + "GraphClient": { // Inferred from the provided --class-name or its default value + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + // Adding for future visibility, but not required for now + /*"authentication": { + "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", + "authenticationParameters": { + "clientId": "guid" + }, + },*/ + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } + } + } +} +``` + +## End-to-end scenarios using the CLI + +### Get started to generate an API + +```bash +kiota init --app-name "My Application" +kiota api add --api-name "My API" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" +kiota client add --api-name "My API" --clientName "graphDelegated" --language csharp --outputPath ./csharpClient +kiota generate +``` + +### Add a second language to generate an API + +```bash +kiota client add --api-name "My API" --clientName "graphPython" --language python --outputPath ./pythonClient +kiota generate --api-name "My API" --client-name "graphPython" +``` + +### Remove a language and delete the generated code + +```bash +kiota client delete --api-name "My API" --client=name "graphPython" --clean-output +``` + +### Remove an API + +```bash +kiota api delete --name "My Api" --clean-output +``` diff --git a/specs/schemas/kiota.config.md b/specs/schemas/kiota.config.md new file mode 100644 index 0000000000..a3ff5308ce --- /dev/null +++ b/specs/schemas/kiota.config.md @@ -0,0 +1,107 @@ +## JSON Schema for kiota.config + +```json +{ + "$schema": "", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "apis": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "descriptionLocation": { + "type": "string" + }, + "descriptionHash": { + "type": "string" + } + }, + "descriptionHash": { + "type": "string" + }, + "descriptionLocation": { + "type": "string" + }, + "includePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "excludePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "baseUrl": { + "type": "string" + }, + "clients": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "outputPath": { + "type": "string" + }, + "clientClassName": { + "type": "string" + }, + "clientNamespaceName": { + "type": "string" + }, + "features": { + "type": "object", + "properties": { + "structuredMediaTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "serializers": { + "type": "array", + "items": { + "type": "string" + } + }, + "deserializers": { + "type": "array", + "items": { + "type": "string" + } + }, + "usesBackingStore": { + "type": "boolean" + }, + "includeAdditionalData": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "disabledValidationRules": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} +``` \ No newline at end of file From e805390877d75a9559ead9b47f3d85f8ed63640d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Thu, 7 Dec 2023 19:23:26 +0000 Subject: [PATCH 08/15] Updated version of the spec --- specs/cli/client-add.md | 64 +++++------ specs/cli/client-edit.md | 74 ++++++------- specs/cli/client-generate.md | 73 ++++--------- specs/cli/client-remove.md | 11 +- specs/cli/config-init.md | 19 ++-- specs/cli/config-migrate.md | 185 ++++++++++++++++++++++++++++++++ specs/index.md | 8 +- specs/scenarios/kiota-config.md | 108 ++++--------------- specs/schemas/kiota.config.md | 4 +- 9 files changed, 323 insertions(+), 223 deletions(-) diff --git a/specs/cli/client-add.md b/specs/cli/client-add.md index b4930d054a..3f085e5607 100644 --- a/specs/cli/client-add.md +++ b/specs/cli/client-add.md @@ -2,7 +2,7 @@ ## Description -`kiota client add` allows a developer to add a new API client to the `kiota.config` file. If no `kiota.config` file is found, a new `kiota.config` file would be created. The command will add a new entry to the `clients` section of the `kiota.config` file. Once this is done, a local copy of the OpenAPI description is generated and kepts in the . +`kiota client add` allows a developer to add a new API client to the `kiota-config.json` file. If no `kiota-config.json` file is found, a new `kiota-config.json` file would be created. The command will add a new entry to the `clients` section of the `kiota-config.json` file. Once this is done, a local copy of the OpenAPI description is generated and kepts in the . When executing, a new API entry will be added and will use the `--client-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. @@ -10,12 +10,13 @@ Every time an API client is added, a copy of the OpenAPI description file will b At the same time, an [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3) file will be generated (if non existing) or edited (if already existing) to represent the surface of the API being used. This file will be named `apimanifest.json` and will be stored in the `./.kiota` folder. This file will be used to generate the code files. -Once the `kiota.config` file is generated, the OpenAPI description file is saved locally and the API Manifest is available, the code generation will be executed. +Once the `kiota-config.json` file is generated, the OpenAPI description file is saved locally and the API Manifest is available, the code generation will be executed. ## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | +| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | @@ -28,10 +29,11 @@ Once the `kiota.config` file is generated, the OpenAPI description file is saved | `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | | `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | | `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--skip-generation \| --sg` | No | true | When specified, the generation would be skipped. Defaults to false. | | `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | > [!NOTE] -> It is not required to use the CLI to add new clients. It is possible to add a new client by adding a new entry in the `clients` section of the `kiota.config` file. See the [kiota.config schema](../schemas/kiota.config.md) for more information. +> It is not required to use the CLI to add new clients. It is possible to add a new client by adding a new entry in the `clients` section of the `kiota-config.json` file. See the [kiota-config.json schema](../schemas/kiota-config.json.md) for more information. ## Using `kiota client add` @@ -39,37 +41,39 @@ Once the `kiota.config` file is generated, the OpenAPI description file is saved kiota client add --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "**/users/**" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` -```json -"clients": { - "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", - "includePatterns": ["**/users/**"], - "excludePatterns": [], - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true +```jsonc +{ + "clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } } } } ``` -_The resulting `kiota.config` file will look like this:_ +_The resulting `kiota-config.json` file will look like this:_ -```json +```jsonc { "version": "1.0.0", "clients": { @@ -102,7 +106,7 @@ _The resulting `kiota.config` file will look like this:_ _The resulting `apimanifest.json` file will look like this:_ -```json +```jsonc { "publisher": { "name": "Microsoft Graph", @@ -148,7 +152,7 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota.config + └─kiota-config.json └─apimanifest.json └─definitions └─9EDF8506CB74FE44.yaml diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md index a46a89030a..6968831f40 100644 --- a/specs/cli/client-edit.md +++ b/specs/cli/client-edit.md @@ -2,16 +2,17 @@ ## Description -`kiota client update` allows a developer to edit an existing API client int the `kiota.config` file. If either the `kiota.config` file or if the `--client-name` client can't be found within the `kiota.config` file, the command should error out and let the developer know. +`kiota client update` allows a developer to edit an existing API client int the `kiota-config.json` file. If either the `kiota-config.json` file or if the `--client-name` client can't be found within the `kiota-config.json` file, the command should error out and let the developer know. When executing, the API entry defined by the `--client-name` parameter will be modified. All parameters should be supported and the only required one is `--client-name`. All others are optional as they would only modify the configuration of the API client. If the OpenAPI description location changed, a new hash of the description will be generated saved as part of the `descriptionHash` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively and would trigger an update to the [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3). -Once the `kiota.config` file and the API Manifest are updated, the code generation will be executed based on the newly updated API client configuration. +Once the `kiota-config.json` file and the API Manifest are updated, the code generation will be executed based on the newly updated API client configuration. ## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | +| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | No | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | @@ -24,10 +25,11 @@ Once the `kiota.config` file and the API Manifest are updated, the code generati | `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | | `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | | `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--skip-generation \| --sg` | No | true | When specified, the generation would be skipped. Defaults to false. | | `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | > [!NOTE] -> It is not required to use the CLI to edit clients. It is possible to edit a client by modifying its entry in the `clients` section of the `kiota.config` file. See the [kiota.config schema](../schemas/kiota.config.md) for more information. +> It is not required to use the CLI to edit clients. It is possible to edit a client by modifying its entry in the `clients` section of the `kiota-config.json` file. See the [kiota-config.json schema](../schemas/kiota-config.json.md) for more information. ## Using `kiota client edit` @@ -35,40 +37,39 @@ Once the `kiota.config` file and the API Manifest are updated, the code generati kiota client edit --client-name "graphDelegated" --class-name "GraphServiceClient" --exclude-path "/users/$count" ``` -```json -``` - -```json -"clients": { - "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", - "includePatterns": ["**/users/**"], - "excludePatterns": ["/users/$count"], - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphServiceClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true +```jsonc +{ + "clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": ["/users/$count"], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } } } } ``` -_The resulting `kiota.config` file will look like this:_ +_The resulting `kiota-config.json` file will look like this:_ -```json +```jsonc { "version": "1.0.0", "clients": { @@ -101,7 +102,7 @@ _The resulting `kiota.config` file will look like this:_ _The resulting `apimanifest.json` file will look like this:_ -```json +```jsonc { "publisher": { "name": "Microsoft Graph", @@ -143,7 +144,7 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota.config + └─kiota-config.json └─apimanifest.json └─definitions └─9EDF8506CB74FE44.yaml @@ -152,9 +153,4 @@ _The resulting `apimanifest.json` file will look like this:_ └─csharp └─... # Generated code files └─GraphClient.cs -``` - -## Open Questions - -- [ ] How do we determine the `name` and `contactEmail` of the `publisher` in the API Manifest? kiota config --global? -- [ ] Can we automatically generate all `authorizationRequirements` for the endpoints selected or these are left to the developers to add? \ No newline at end of file +``` \ No newline at end of file diff --git a/specs/cli/client-generate.md b/specs/cli/client-generate.md index 5fd86967fc..73a6274eb9 100644 --- a/specs/cli/client-generate.md +++ b/specs/cli/client-generate.md @@ -1,66 +1,37 @@ # kiota client generate -Now that we have a `kiota.config` file, all the parameters required to generate the code are stored in the file. The `kiota generate` command will read the `kiota.config` file and generate the code for each of the clients. +## Description -It's also possible to specify for which API and client the code should be generated. This is useful when a project contains multiple APIs and clients. The `kiota generate --api-name "MyAPI" --client-name "MyClient"` command will read the `kiota.config` file and generate the code for the specified API and client. If it can't find the specified API or client, it will throw an error. +Now that we have a `kiota-config.json` file, all the parameters required to generate the code are stored in the file. The `kiota client generate` command will read the `kiota-config.json` file and generate the code for each of the available clients. -In scenarios where a developer only needs a single API or doesn't want to go through the ceremony of executing `kiota init`, it's possible to use `kiota generate` and initialize a `kiota.config` file with the values coming from the command parameters. No breaking changes are required to the existing `kiota generate` command. +It's also possible to specify for which API and client the code should be generated. This is useful when a project contains multiple clients. The `kiota client generate --client-name "MyClient"` command will read the `kiota-config.json` file and generate the code for the specified client. If it can't find the specified API or client, it will throw an error. -#### kiota generate Parameters +In general cases, the `kiota client generate` command will generate the code for all the clients in the `kiota-config.json` file based on the cached OpenAPI description. If the `--refresh` parameter is provided, the command will refresh the cached OpenAPI description(s), update the different `descriptionHash` and then generate the code for the specified clients. -> [!IMPORTANT] -> This list is only the new parameters that `kiota generate` should support. +## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--app-name \| --an` | No | My application | Name of the application | -| `--api-name \| --api` | No | graph | Name of the API | +| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | | `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | +| `--refresh \| -r` | No | true | Provided when refreshing the description(s) is required. | -#### Using `kiota generate` with all parameters +## Usage + +### Using `kiota client generate` for a single API client + +```bash +kiota client generate --client-name "graphDelegated" +``` + +### Using `kiota client generate` for all API clients ```bash -kiota generate --app-name "My Application" --api-name "graph" --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +kiota client generate ``` -```javascript -{ - "name": "My application", - "apis": { - "graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - "graphDelegated": { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } - } - } -} -``` \ No newline at end of file +### Using `kiota client generate` for all API clients and refresh their descriptions + +```bash +kiota client generate +``` diff --git a/specs/cli/client-remove.md b/specs/cli/client-remove.md index d7d857cd6e..32cb2fea46 100644 --- a/specs/cli/client-remove.md +++ b/specs/cli/client-remove.md @@ -2,12 +2,13 @@ ## Description -`kiota client remove` allows a developer to remove an existing client from the `kiota.config` file. The command will remove the entry from the `clients` section of `kiota.config` file. The command has a single required parameters; the name of the client. +`kiota client remove` allows a developer to remove an existing client from the `kiota-config.json` file. The command will remove the entry from the `clients` section of `kiota-config.json` file. The command has a single required parameters; the name of the client. The command also has one optional parameter, the ability to remove the generated client. If provided, kiota will delete the folder and its content specified at the `outputPath` from the client configuration. It will also remove the local version of the OpenAPI description file (specified by the `descriptionHash` property). The API Manifest is also updated to remove the dependency from the list of dependencies. | Parameters | Required | Example | Description | | -- | -- | -- | -- | +| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client | | `--clean-output \| --co` | No | | Cleans the generated client | @@ -17,9 +18,9 @@ The command also has one optional parameter, the ability to remove the generated kiota client remove --client-name "graphDelegated" --clean-output ``` -The resulting `kiota.config` file will look like this: +The resulting `kiota-config.json` file will look like this: -```json +```jsonc { "version": "1.0.0", "clients": { } @@ -28,7 +29,7 @@ The resulting `kiota.config` file will look like this: _The resulting `apimanifest.json` file will look like this:_ -```json +```jsonc { "publisher": { "name": "Microsoft Graph", @@ -42,7 +43,7 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota.config + └─kiota-config.json └─apimanifest.json └─generated └─graph diff --git a/specs/cli/config-init.md b/specs/cli/config-init.md index f4f3d3c12a..3e1bc6fa09 100644 --- a/specs/cli/config-init.md +++ b/specs/cli/config-init.md @@ -2,27 +2,34 @@ ## Description -`kiota config init` creates a new kiota.config file with the provided parameters. If the file already exists, it should error out and report it to the user. As the file gets created, it should be adding a `version` property with the value of the `kiota.config` current schema version. +`kiota config init` creates a new kiota-config.json file with the provided parameters. If the file already exists, it should error out and report it to the user. As the file gets created, it should be adding a `version` property with the value of the `kiota-config.json` current schema version. -When `kiota config init` is executed, a `kiota.config` file would be created in the current directory where the command is being executed. If the user wants to create the file in a different directory, they should use the `--config-file` global parameter. +When `kiota config init` is executed, a `kiota-config.json` file would be created in the current directory where the command is being executed. If the user wants to create the file in a different directory, they should use the `--config-file` global parameter. > [!NOTE] -> If a project only needs a single API, using `kiota config init` is not mandatory as generating code using the `kiota client generate` command could generate a `kiota.config` file with values coming from the `kiota client generate` command (if no `kiota.config` is present). See [kiota client generate](./client-generate.md) for more information. +> If a project only needs a single API, using `kiota config init` is not mandatory as generating code using the `kiota client generate` command could generate a `kiota-config.json` file with values coming from the `kiota client generate` command (if no `kiota-config.json` is present). See [kiota client generate](./client-generate.md) for more information. ## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-file \| --cf` | No | ../../kiota.config | Path to an existing `kiota.config` file. Defaults to `./` | +| `--config-file \| --cf` | No | ../../kiota-config.json | Path to an existing `kiota-config.json` file. Defaults to `./` | ## Using `kiota config init` ```bash kiota config init ``` -_Results in the following `kiota.config` file:_ -```json +_Results in the following `kiota-config.json` file:_ +```jsonc { "version": "1.0.0", } +``` + +## File structure +```bash +/ + └─.kiota + └─kiota-config.json ``` \ No newline at end of file diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index e69de29bb2..26f67bfc84 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -0,0 +1,185 @@ +# `kiota config migrate` + +This command is valuable in cases where a code base was created with Kiota v1.0 and needs to be migrated to the latest version of Kiota. The `kiota config migrate` command will identify and locate the closest `kiota-config.json` file available. If a file can't be found, it would initialize a new `kiota-config.json` file. Then, it would identify all `kiota-lock.json` files that are within this folder structure and add each of them to the `kiota-config.json` file. Adding the clients to the `kiota-config.json` file would not trigger the generation as it only affects the `kiota-config.json` file. The `kiota client generate` command would need to be executed to generate the code for the clients. + +## Parameters + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | +| `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. | +| `--client-name \| --cn` | No | graphDelegated | Used with `--lock-location`, it would allow to specify a name for the API client. Else, name is auto-generated as a concatenation of the `language` and `clientClassName`. | + +## Using `kiota config migrate` + +Assuming the following folder structure: +```bash +/ + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs + └─kiota-lock.json + └─python + └─... # Generated code files + └─graph_client.py + └─kiota-lock.json +``` + +```bash +kiota config migrate +``` + +_The resulting `kiota-config.json` file will look like this:_ + +```jsonc +{ + "version": "1.0.0", + "clients": { + "csharpGraphServiceClient": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + }, + "pythonGraphServiceClient": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "python", + "outputPath": "./generated/graph/python", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } +} +``` + +_The resulting `apimanifest.json` file will look like this:_ + +```jsonc +{ + "publisher": { + "name": "Microsoft Graph", + "contactEmail": "graphsdkpub@microsoft.com" + }, + "apiDependencies": { + "graphDelegated": { + "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "apiDeploymentBaseUrl": "https://graph.microsoft.com", + "apiDescriptionVersion": "v1.0", + "requests": [ + { + "method": "GET", + "uriTemplate": "/users" + }, + { + "method": "POST", + "uriTemplate": "/users" + }, + { + "method": "GET", + "uriTemplate": "/users/$count" + }, + { + "method": "GET", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "PATCH", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "DELETE", + "uriTemplate": "/users/{user-id}" + } + ] + } + } +} +``` + +## Using `kiota config migrate` for a specific `kiota-lock.json` file and a specific client name + +Assuming the following folder structure: +```bash +/ + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs + └─kiota-lock.json + └─python + └─... # Generated code files + └─graph_client.py + └─kiota-lock.json +``` + +```bash +kiota config migrate --lock-location ./generated/graph/csharp/kiota-lock.json --client-name graphDelegated +``` + +_The resulting `kiota-config.json` file will look like this:_ + +```jsonc +{ + "version": "1.0.0", + "clients": { + "graphDelegated": { + "descriptionHash": "9EDF8506CB74FE44...", + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], + "excludePatterns": [], + "language": "csharp", + "outputPath": "./generated/graph/csharp", + "clientClassName": "GraphServiceClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } + } + } +} \ No newline at end of file diff --git a/specs/index.md b/specs/index.md index fa305ee49d..efa47455b1 100644 --- a/specs/index.md +++ b/specs/index.md @@ -7,13 +7,13 @@ config.md) * [kiota client add](./cli/client-add.md) * [kiota client edit](./cli/client-edit.md) -* [kiota client update](./cli/client-update.md) +* [kiota client remove](./cli/client-remove.md) * [kiota client generate](./cli/client-generate.md) -* [kiota init](./cli/init.md) -* [kiota migrate](./cli/migrate.md) +* [kiota config init](./cli/config-init.md) +* [kiota config migrate](./cli/config-migrate.md) ## Extension ## Scenarios -* [Kiota.Config](./scenarios/kiota. \ No newline at end of file +* [Kiota Config](./scenarios/kiota.config.md) \ No newline at end of file diff --git a/specs/scenarios/kiota-config.md b/specs/scenarios/kiota-config.md index cc52a50ed2..34372e89ae 100644 --- a/specs/scenarios/kiota-config.md +++ b/specs/scenarios/kiota-config.md @@ -1,34 +1,24 @@ # Kiota Config -Kiota generates client code for an API and stores parameters in a kiota.lock file. A project can contain multiple API clients, but they are independently managed. Kiota has no awareness that an app has a dependency on multiple APIs, even though that is a core use case. - -## Status - -| Date | Version | Author | Status | -| -- | -- | -- | -- | -| November 30th, 2023 | v0.3 | Sébastien Levert | Final Draft | -| November 22nd, 2023 | v0.2 | Sébastien Levert | Draft | -| September 24th, 2023 | v0.1 | Darrel Miller | Draft | +Kiota generates client code for an API and stores parameters in a kiota-lock.json file. A project can contain multiple API clients, but they are independently managed. Kiota has no awareness that an app has a dependency on multiple APIs, even though that is a core use case. ## Current Challenges - Client code generation is not reproducible if API description changes -- Kiota doesn’t have a good solution for APIs that use multiple security schemes. -- Kiota doesn’t provide any support for generating auth providers with the required permissions, partially because currently we generate one client for APIs that use different schemes. How would we know which auth provider to generate. -- Kiota doesn’t have a good story for acquiring a client identifier. e.g. apikey or OAuth2 ClientId. This could be possible if the OpenIDConnect URL pointed to a dynamic registration endpoint. -- If an application has multiple kiota clients, there is currently no way perform operations that correspond to all of the clients. +- Kiota doesn't have an obvious solution for APIs that use multiple security schemes. +- Kiota doesn't have an obvious solution for projects utilizing multiple APIs. We have previously described Kiota's approach to managing API dependencies as consistent with the way people manage packages in a project. However, currently our tooling doesn't behave that way. We treat each dependency independently. ## Proposal -We should introduce a new Kiota.config file that holds the input parameters required to generate the API Client code. Currently kiota.lock is used to capture what the parameters were at the time of generation and can be used to regenerate based on the parameters in the file. This creates a mixture of purposes for the file. +We should introduce a new Kiota.config file that holds the input parameters required to generate the API Client code. Currently kiota-lock.json is used to capture what the parameters were at the time of generation and can be used to regenerate based on the parameters in the file. This creates a mixture of purposes for the file. -We did consider creating one kiota.config file as as a peer of the language project file, however, for someone who wants to generate multiple clients for an API in different languages, this would be a bit annoying. An alternative would be to allow the kiota.config file to move further up the folder structure and support generation in multiple languages from a single file. This is more consistent with what [TypeSpec](https://aka.ms/typespec) are doing and would be helpful for generating CLI and docs as well as a library. +We did consider creating one kiota-config.json file as as a peer of the language project file, however, for someone who wants to generate multiple clients for an API in different languages, this would be a bit annoying. An alternative would be to allow the kiota-config.json file to move further up the folder structure and support generation in multiple languages from a single file. This is more consistent with what [TypeSpec](https://aka.ms/typespec) are doing and would be helpful for generating CLI and docs as well as a library. -Here is an example of what the kiota.config file could look like. +Here is an example of what the kiota-config.json file could look like. -```json +```jsonc { "name": "My application", "apis": { @@ -90,105 +80,51 @@ Here is an example of what the kiota.config file could look like. Note that in this example we added suggestions for new parameters related to authentication. If we are to improve the generation experience so that we read the security schemes information from the OpenAPI, then we will need to have some place to configure what providers we will use for those schemes. -The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota.lock file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. +The [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html) file can be used as a replacement for the kiota-lock.json file as a place to capture a snapshot of what information was used to perform code generation and what APIs that gives the application access to. ## Commands * [kiota config init](../cli/init.md) * [kiota client add](../cli/client-add.md) * [kiota client edit](../cli/client-edit.md) -* [kiota client generate](../cli/client-remove.md) +* [kiota client generate](../cli/client-generate.md) * [kiota client remove](../cli/client-remove.md) ## End-to-end experience -#### Using kiota generate with parameters inferred from the kiota.config file +### Migrate a project that uses Kiota v1.x ```bash -kiota generate +kiota config migrate ``` -#### Using kiota generate with parameters inferred from the kiota.config file for a single API +### Get started to generate an API client ```bash -kiota generate --api-name "graph" --client-name "graphDelegated" +kiota client init +kiota client add --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --language csharp --output "./csharpClient" ``` -#### Using kiota generate with parameters inferred when there are no kiota.config file +### Add a second API client ```bash -kiota generate --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "/me/chats#GET" --include-path "/me#GET" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" +kiota client add --clientName "graphPython" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --language python --outputPath ./pythonClient ``` -```javascript -// This file gets generated and then `kiota generate` is executed based on these parameters -{ - "name": "Contoso.GraphApp", // Inferred from the provided --namespace-name or its default value - "apis": { - "https://graph.microsoft.com/v1.0": { // Inferred from the first server entry in the OpenAPI description - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], - "excludePatterns": [], - "clients": { - "GraphClient": { // Inferred from the provided --class-name or its default value - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - // Adding for future visibility, but not required for now - /*"authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - }, - },*/ - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } - } - } -} -``` - -## End-to-end scenarios using the CLI - -### Get started to generate an API +### Edit an API client ```bash -kiota init --app-name "My Application" -kiota api add --api-name "My API" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" -kiota client add --api-name "My API" --clientName "graphDelegated" --language csharp --outputPath ./csharpClient -kiota generate -``` - -### Add a second language to generate an API - -```bash -kiota client add --api-name "My API" --clientName "graphPython" --language python --outputPath ./pythonClient -kiota generate --api-name "My API" --client-name "graphPython" +kiota client edit --client-name "graphDelegated" --class-name "GraphServiceClient" --exclude-path "/users/$count" ``` ### Remove a language and delete the generated code ```bash -kiota client delete --api-name "My API" --client=name "graphPython" --clean-output +kiota client delete --client=name "graphPython" --clean-output ``` -### Remove an API +### Generate code for all API clients ```bash -kiota api delete --name "My Api" --clean-output -``` +kiota client generate +``` \ No newline at end of file diff --git a/specs/schemas/kiota.config.md b/specs/schemas/kiota.config.md index a3ff5308ce..c48437a85b 100644 --- a/specs/schemas/kiota.config.md +++ b/specs/schemas/kiota.config.md @@ -1,6 +1,6 @@ -## JSON Schema for kiota.config +## JSON Schema for kiota-config.json -```json +```jsonc { "$schema": "", "type": "object", From f304b137c0f964db19e7ea6dc64d6d29f7cca50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Fri, 8 Dec 2023 18:01:27 +0000 Subject: [PATCH 09/15] Updating specs based on review comments --- specs/cli/client-add.md | 61 ++++-------------- specs/cli/client-edit.md | 10 +-- specs/cli/client-generate.md | 2 +- specs/cli/client-remove.md | 6 +- specs/cli/config-init.md | 2 +- specs/cli/config-migrate.md | 86 ++++++++++++++++++++++--- specs/index.md | 6 +- specs/scenarios/kiota-config.md | 73 ++++++++-------------- specs/schemas/kiota-config.json | 87 ++++++++++++++++++++++++++ specs/schemas/kiota.config.md | 107 -------------------------------- 10 files changed, 217 insertions(+), 223 deletions(-) create mode 100644 specs/schemas/kiota-config.json delete mode 100644 specs/schemas/kiota.config.md diff --git a/specs/cli/client-add.md b/specs/cli/client-add.md index 3f085e5607..ef65baa62b 100644 --- a/specs/cli/client-add.md +++ b/specs/cli/client-add.md @@ -2,23 +2,24 @@ ## Description -`kiota client add` allows a developer to add a new API client to the `kiota-config.json` file. If no `kiota-config.json` file is found, a new `kiota-config.json` file would be created. The command will add a new entry to the `clients` section of the `kiota-config.json` file. Once this is done, a local copy of the OpenAPI description is generated and kepts in the . +`kiota client add` allows a developer to add a new API client to the `kiota-config.json` file. If no `kiota-config.json` file is found, a new `kiota-config.json` file would be created in thr current working directory. The command will add a new entry to the `clients` section of the `kiota-config.json` file. Once this is done, a local copy of the OpenAPI description is generated and kept in the `.kiota/descriptions` folder. When executing, a new API entry will be added and will use the `--client-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. -Every time an API client is added, a copy of the OpenAPI description file will be stored in the `./.kiota` folder. The file will be named using the hash of the description. This will allow the CLI to detect changes in the description and avoid downloading the description again if it hasn't changed. +Every time an API client is added, a copy of the OpenAPI description file will be stored in the `./.kiota/{client-name}` folder. The files will be named using the API client name. This will allow the CLI to detect changes in the description and avoid downloading the description again if it hasn't changed. -At the same time, an [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3) file will be generated (if non existing) or edited (if already existing) to represent the surface of the API being used. This file will be named `apimanifest.json` and will be stored in the `./.kiota` folder. This file will be used to generate the code files. +At the same time, an [API Manifest](https://www.ietf.org/archive/id/draft-miller-api-manifest-01.html#section-2.5-3) file will be generated (if non existing) or edited (if already existing) to represent the surface of the API being used. This file will be named `apimanifest.json` and next to the `kiota-config.json`. This file will be used to generate the code files. -Once the `kiota-config.json` file is generated, the OpenAPI description file is saved locally and the API Manifest is available, the code generation will be executed. +Once the `kiota-config.json` file is generated and the OpenAPI description file is saved locally, the code generation will be executed and then the API Manifest would become available. ## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | +| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | +| `--search-key \| --sk` | No | github::microsoftgraph/msgraph-metadata/graph.microsoft.com/v1.0 | The search key used to locate the OpenAPI description. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | | `--exclude-path \| -e` | No | \*\*/users/\*\* | A glob pattern to exclude paths from generation. Accepts multiple values. Defaults to no value which excludes nothing. | | `--language \| -l` | Yes | csharp | The target language for the generated code files or for the information. | @@ -41,36 +42,6 @@ Once the `kiota-config.json` file is generated, the OpenAPI description file is kiota client add --client-name "graphDelegated" --openapi "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml" --include-path "**/users/**" --language csharp --class-name "GraphClient" --namespace-name "Contoso.GraphApp" --backing-store --exclude-backward-compatible --serializer "Contoso.Json.CustomSerializer" --deserializer "Contoso.Json.CustomDeserializer" -structured-mime-types "application/json" --output "./generated/graph/csharp" ``` -```jsonc -{ - "clients": { - "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", - "includePatterns": ["**/users/**"], - "excludePatterns": [], - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } -} -``` - _The resulting `kiota-config.json` file will look like this:_ ```jsonc @@ -108,12 +79,9 @@ _The resulting `apimanifest.json` file will look like this:_ ```jsonc { - "publisher": { - "name": "Microsoft Graph", - "contactEmail": "graphsdkpub@microsoft.com" - }, "apiDependencies": { "graphDelegated": { + "x-ms-apiDescriptionHash": "9EDF8506CB74FE44...", "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "apiDeploymentBaseUrl": "https://graph.microsoft.com", "apiDescriptionVersion": "v1.0", @@ -152,18 +120,13 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota-config.json - └─apimanifest.json └─definitions - └─9EDF8506CB74FE44.yaml + └─graphDelegated.yaml └─generated └─graph └─csharp └─... # Generated code files - └─GraphClient.cs -``` - -## Open Questions - -- [ ] How do we determine the `name` and `contactEmail` of the `publisher` in the API Manifest? kiota config --global? -- [ ] Can we automatically generate all `authorizationRequirements` for the endpoints selected or these are left to the developers to add? \ No newline at end of file + └─GraphClient.cs + └─apimanifest.json + └─kiota-config.json +``` \ No newline at end of file diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md index 6968831f40..f7567efbba 100644 --- a/specs/cli/client-edit.md +++ b/specs/cli/client-edit.md @@ -12,7 +12,7 @@ Once the `kiota-config.json` file and the API Manifest are updated, the code gen | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | +| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | No | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | @@ -144,13 +144,13 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota-config.json - └─apimanifest.json └─definitions - └─9EDF8506CB74FE44.yaml + └─graphDelegated.yaml └─generated └─graph └─csharp └─... # Generated code files - └─GraphClient.cs + └─GraphClient.cs + └─apimanifest.json + └─kiota-config.json ``` \ No newline at end of file diff --git a/specs/cli/client-generate.md b/specs/cli/client-generate.md index 73a6274eb9..0ae06afe91 100644 --- a/specs/cli/client-generate.md +++ b/specs/cli/client-generate.md @@ -12,7 +12,7 @@ In general cases, the `kiota client generate` command will generate the code for | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | +| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | | `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | | `--refresh \| -r` | No | true | Provided when refreshing the description(s) is required. | diff --git a/specs/cli/client-remove.md b/specs/cli/client-remove.md index 32cb2fea46..fdc8fc3520 100644 --- a/specs/cli/client-remove.md +++ b/specs/cli/client-remove.md @@ -8,7 +8,7 @@ The command also has one optional parameter, the ability to remove the generated | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | +| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client | | `--clean-output \| --co` | No | | Cleans the generated client | @@ -43,8 +43,8 @@ _The resulting `apimanifest.json` file will look like this:_ ```bash / └─.kiota - └─kiota-config.json - └─apimanifest.json └─generated └─graph + └─kiota-config.json + └─apimanifest.json ``` \ No newline at end of file diff --git a/specs/cli/config-init.md b/specs/cli/config-init.md index 3e1bc6fa09..939e7c3705 100644 --- a/specs/cli/config-init.md +++ b/specs/cli/config-init.md @@ -13,7 +13,7 @@ When `kiota config init` is executed, a `kiota-config.json` file would be create | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-file \| --cf` | No | ../../kiota-config.json | Path to an existing `kiota-config.json` file. Defaults to `./` | +| `--config-location \| --cf` | No | ../../ | Path to a folder containing an existing `kiota-config.json` file. Defaults to `./` | ## Using `kiota config init` diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index 26f67bfc84..ac418d206c 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -6,8 +6,8 @@ This command is valuable in cases where a code base was created with Kiota v1.0 | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./.kiota/kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./.kiota/kiota-config.json`. | -| `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. | +| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. In the case where conflicting API client names are created, the user would be prompted for a new client name | | `--client-name \| --cn` | No | graphDelegated | Used with `--lock-location`, it would allow to specify a name for the API client. Else, name is auto-generated as a concatenation of the `language` and `clientClassName`. | ## Using `kiota config migrate` @@ -91,12 +91,41 @@ _The resulting `apimanifest.json` file will look like this:_ ```jsonc { - "publisher": { - "name": "Microsoft Graph", - "contactEmail": "graphsdkpub@microsoft.com" - }, "apiDependencies": { - "graphDelegated": { + "csharpGraphServiceClient": { + "x-ms-apiDescriptionHash": "9EDF8506CB74FE44...", + "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "apiDeploymentBaseUrl": "https://graph.microsoft.com", + "apiDescriptionVersion": "v1.0", + "requests": [ + { + "method": "GET", + "uriTemplate": "/users" + }, + { + "method": "POST", + "uriTemplate": "/users" + }, + { + "method": "GET", + "uriTemplate": "/users/$count" + }, + { + "method": "GET", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "PATCH", + "uriTemplate": "/users/{user-id}" + }, + { + "method": "DELETE", + "uriTemplate": "/users/{user-id}" + } + ] + }, + "pythonGraphServiceClient": { + "x-ms-apiDescriptionHash": "9EDF8506CB74FE44...", "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "apiDeploymentBaseUrl": "https://graph.microsoft.com", "apiDescriptionVersion": "v1.0", @@ -131,6 +160,27 @@ _The resulting `apimanifest.json` file will look like this:_ } ``` + +_The resulting file structure will look like this:_ + +```bash +/ + └─.kiota + └─definitions + └─csharpGraphServiceClient.yaml + └─pythonGraphServiceClient.yaml + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs + └─python + └─... # Generated code files + └─graph_client.py + └─apimanifest.json + └─kiota-config.json +``` + ## Using `kiota config migrate` for a specific `kiota-lock.json` file and a specific client name Assuming the following folder structure: @@ -182,4 +232,24 @@ _The resulting `kiota-config.json` file will look like this:_ } } } -} \ No newline at end of file +} +``` + + +```bash +/ + └─.kiota + └─definitions + └─graphDelegated.yaml + └─generated + └─graph + └─csharp + └─... # Generated code files + └─GraphClient.cs + └─python + └─... # Generated code files + └─graph_client.py + └─kiota-lock.json + └─apimanifest.json + └─kiota-config.json +``` \ No newline at end of file diff --git a/specs/index.md b/specs/index.md index efa47455b1..e040b18428 100644 --- a/specs/index.md +++ b/specs/index.md @@ -16,4 +16,8 @@ config.md) ## Scenarios -* [Kiota Config](./scenarios/kiota.config.md) \ No newline at end of file +* [Kiota Config](./scenarios/kiota.config.md) + +## Schemas + +* [kiota-config.json](./schemas/kiota-config.json) \ No newline at end of file diff --git a/specs/scenarios/kiota-config.md b/specs/scenarios/kiota-config.md index 34372e89ae..6a5b3acbd8 100644 --- a/specs/scenarios/kiota-config.md +++ b/specs/scenarios/kiota-config.md @@ -20,59 +20,36 @@ Here is an example of what the kiota-config.json file could look like. ```jsonc { - "name": "My application", - "apis": { - "Graph": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://.../openapi.yaml", - "includePatterns": ["/me/chats#GET", "/me#GET"], + "version": "1.0.0", + "clients": { + "graphDelegated": { + "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", + "includePatterns": ["**/users/**"], "excludePatterns": [], - "clients": [ - { - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - } - }, - "usesBackingStore": true, - "includeAdditionalData": true - } - } - ] + "language": "csharp", + "outputPath": "./generated/graph", + "clientClassName": "GraphClient", + "clientNamespaceName": "Contoso.GraphApp", + "features": { + "serializers": [ + "Contoso.Json.CustomSerializer" + ], + "deserializers": [ + "Contoso.Json.CustomDeserializer" + ], + "structuredMimeTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true + } }, - "BusinessCentral": { - "descriptionHash": "810CF81EFDB5D8E065...", + "businessCentral": { "descriptionLocation": "https://.../bcoas1.0.yaml", "includePatterns": ["/companies#GET"], "excludePatterns": [], - "outputs": [ - { - "language": "csharp", - "outputPath": "./generated/business-central" - }, - { - "language": "python", - "outputPath": "./generated/python/business-central" - }, - { - "language": "csharp", - "outputPath": "./generated/business-central-app", - "features": { - "authentication": { - "authenticationProvider": "Microsoft.Kiota.Authentication.AzureAuthProvider", - "authenticationParameters": { - "clientId": "guid" - } - } - } - } - ] + "language": "csharp", + "outputPath": "./generated/businessCentral" } } } diff --git a/specs/schemas/kiota-config.json b/specs/schemas/kiota-config.json new file mode 100644 index 0000000000..09290bfb3e --- /dev/null +++ b/specs/schemas/kiota-config.json @@ -0,0 +1,87 @@ +{ + "$schema": "", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "clients": { + "type": "object", + "patternProperties": { + ".*": { + "type": "object", + "properties": { + "descriptionLocation": { + "type": "string" + } + }, + "descriptionLocation": { + "type": "string" + }, + "includePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "excludePatterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "baseUrl": { + "type": "string" + }, + "language": { + "type": "string" + }, + "outputPath": { + "type": "string" + }, + "clientClassName": { + "type": "string" + }, + "clientNamespaceName": { + "type": "string" + }, + "features": { + "type": "object", + "properties": { + "structuredMediaTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "serializers": { + "type": "array", + "items": { + "type": "string" + } + }, + "deserializers": { + "type": "array", + "items": { + "type": "string" + } + }, + "usesBackingStore": { + "type": "boolean" + }, + "includeAdditionalData": { + "type": "boolean" + } + } + } + } + }, + "disabledValidationRules": { + "type": "array", + "items": { + "type": "string" + } + } + } + } +} diff --git a/specs/schemas/kiota.config.md b/specs/schemas/kiota.config.md deleted file mode 100644 index c48437a85b..0000000000 --- a/specs/schemas/kiota.config.md +++ /dev/null @@ -1,107 +0,0 @@ -## JSON Schema for kiota-config.json - -```jsonc -{ - "$schema": "", - "type": "object", - "properties": { - "version": { - "type": "string" - }, - "apis": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "descriptionLocation": { - "type": "string" - }, - "descriptionHash": { - "type": "string" - } - }, - "descriptionHash": { - "type": "string" - }, - "descriptionLocation": { - "type": "string" - }, - "includePatterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "excludePatterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "baseUrl": { - "type": "string" - }, - "clients": { - "type": "object", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "outputPath": { - "type": "string" - }, - "clientClassName": { - "type": "string" - }, - "clientNamespaceName": { - "type": "string" - }, - "features": { - "type": "object", - "properties": { - "structuredMediaTypes": { - "type": "array", - "items": { - "type": "string" - } - }, - "serializers": { - "type": "array", - "items": { - "type": "string" - } - }, - "deserializers": { - "type": "array", - "items": { - "type": "string" - } - }, - "usesBackingStore": { - "type": "boolean" - }, - "includeAdditionalData": { - "type": "boolean" - } - } - } - } - } - } - } - }, - "disabledValidationRules": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } -} -``` \ No newline at end of file From 8bd8d950b1c6e20a8a29e5902c3a8dca0234ed7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Fri, 8 Dec 2023 18:03:23 +0000 Subject: [PATCH 10/15] Updating code blocks --- specs/cli/client-edit.md | 32 +------------------------------- specs/cli/client-remove.md | 4 ---- specs/cli/config-init.md | 4 +++- specs/cli/config-migrate.md | 1 - 4 files changed, 4 insertions(+), 37 deletions(-) diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md index f7567efbba..7da4cd696c 100644 --- a/specs/cli/client-edit.md +++ b/specs/cli/client-edit.md @@ -37,36 +37,6 @@ Once the `kiota-config.json` file and the API Manifest are updated, the code gen kiota client edit --client-name "graphDelegated" --class-name "GraphServiceClient" --exclude-path "/users/$count" ``` -```jsonc -{ - "clients": { - "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", - "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", - "includePatterns": ["**/users/**"], - "excludePatterns": ["/users/$count"], - "language": "csharp", - "outputPath": "./generated/graph/csharp", - "clientClassName": "GraphServiceClient", - "clientNamespaceName": "Contoso.GraphApp", - "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true - } - } - } -} -``` - _The resulting `kiota-config.json` file will look like this:_ ```jsonc @@ -77,7 +47,7 @@ _The resulting `kiota-config.json` file will look like this:_ "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], - "excludePatterns": [], + "excludePatterns": ["/users/$count"], "language": "csharp", "outputPath": "./generated/graph/csharp", "clientClassName": "GraphServiceClient", diff --git a/specs/cli/client-remove.md b/specs/cli/client-remove.md index fdc8fc3520..34ad30b1e7 100644 --- a/specs/cli/client-remove.md +++ b/specs/cli/client-remove.md @@ -31,10 +31,6 @@ _The resulting `apimanifest.json` file will look like this:_ ```jsonc { - "publisher": { - "name": "Microsoft Graph", - "contactEmail": "graphsdkpub@microsoft.com" - }, "apiDependencies": { } } ``` diff --git a/specs/cli/config-init.md b/specs/cli/config-init.md index 939e7c3705..b9df504fda 100644 --- a/specs/cli/config-init.md +++ b/specs/cli/config-init.md @@ -20,7 +20,9 @@ When `kiota config init` is executed, a `kiota-config.json` file would be create ```bash kiota config init ``` -_Results in the following `kiota-config.json` file:_ + +_The resulting `kiota-config.json` file will look like this:_ + ```jsonc { "version": "1.0.0", diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index ac418d206c..788eaa8639 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -160,7 +160,6 @@ _The resulting `apimanifest.json` file will look like this:_ } ``` - _The resulting file structure will look like this:_ ```bash From a0fcaeb45ed7a81790cd5ab86235c9a7f0db07ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Fri, 8 Dec 2023 18:11:40 +0000 Subject: [PATCH 11/15] Updates to the --config-location param --- specs/cli/client-add.md | 2 +- specs/cli/client-edit.md | 2 +- specs/cli/client-generate.md | 2 +- specs/cli/client-remove.md | 2 +- specs/cli/config-init.md | 2 +- specs/cli/config-migrate.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/cli/client-add.md b/specs/cli/client-add.md index ef65baa62b..fad5f1f1d1 100644 --- a/specs/cli/client-add.md +++ b/specs/cli/client-add.md @@ -16,7 +16,7 @@ Once the `kiota-config.json` file is generated and the OpenAPI description file | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | Yes | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--search-key \| --sk` | No | github::microsoftgraph/msgraph-metadata/graph.microsoft.com/v1.0 | The search key used to locate the OpenAPI description. | diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md index 7da4cd696c..b802dc94cb 100644 --- a/specs/cli/client-edit.md +++ b/specs/cli/client-edit.md @@ -12,7 +12,7 @@ Once the `kiota-config.json` file and the API Manifest are updated, the code gen | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client. Unique within the parent API. If not provided, defaults to --class-name or its default. | | `--openapi \| -d` | No | https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml | The location of the OpenAPI description in JSON or YAML format to use to generate the SDK. Accepts a URL or a local path. | | `--include-path \| -i` | No | /me/chats#GET | A glob pattern to include paths from generation. Accepts multiple values. Defaults to no value which includes everything. | diff --git a/specs/cli/client-generate.md b/specs/cli/client-generate.md index 0ae06afe91..c86b27cfdb 100644 --- a/specs/cli/client-generate.md +++ b/specs/cli/client-generate.md @@ -12,7 +12,7 @@ In general cases, the `kiota client generate` command will generate the code for | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | | `--client-name \| --cn` | No | graphDelegated | Name of the client. Unique within the parent API. | | `--refresh \| -r` | No | true | Provided when refreshing the description(s) is required. | diff --git a/specs/cli/client-remove.md b/specs/cli/client-remove.md index 34ad30b1e7..2f3f3ba58d 100644 --- a/specs/cli/client-remove.md +++ b/specs/cli/client-remove.md @@ -8,7 +8,7 @@ The command also has one optional parameter, the ability to remove the generated | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | | `--client-name \| --cn` | Yes | graphDelegated | Name of the client | | `--clean-output \| --co` | No | | Cleans the generated client | diff --git a/specs/cli/config-init.md b/specs/cli/config-init.md index b9df504fda..9f93a90ff3 100644 --- a/specs/cli/config-init.md +++ b/specs/cli/config-init.md @@ -13,7 +13,7 @@ When `kiota config init` is executed, a `kiota-config.json` file would be create | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cf` | No | ../../ | Path to a folder containing an existing `kiota-config.json` file. Defaults to `./` | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | ## Using `kiota config init` diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index 788eaa8639..b3db2c9598 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -6,7 +6,7 @@ This command is valuable in cases where a code base was created with Kiota v1.0 | Parameters | Required | Example | Description | | -- | -- | -- | -- | -| `--config-location \| --cl` | No | ./kiota-config.json | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use `./kiota-config.json`. | +| `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | | `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. In the case where conflicting API client names are created, the user would be prompted for a new client name | | `--client-name \| --cn` | No | graphDelegated | Used with `--lock-location`, it would allow to specify a name for the API client. Else, name is auto-generated as a concatenation of the `language` and `clientClassName`. | From 130dfd168ac39ec3bdb8228e1433b892273dfc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Fri, 8 Dec 2023 18:13:51 +0000 Subject: [PATCH 12/15] Update to the migrate conflict use case --- specs/cli/config-migrate.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index b3db2c9598..734bc39353 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -2,12 +2,14 @@ This command is valuable in cases where a code base was created with Kiota v1.0 and needs to be migrated to the latest version of Kiota. The `kiota config migrate` command will identify and locate the closest `kiota-config.json` file available. If a file can't be found, it would initialize a new `kiota-config.json` file. Then, it would identify all `kiota-lock.json` files that are within this folder structure and add each of them to the `kiota-config.json` file. Adding the clients to the `kiota-config.json` file would not trigger the generation as it only affects the `kiota-config.json` file. The `kiota client generate` command would need to be executed to generate the code for the clients. +In the case where conflicting API client names would be migrated, the command will error out and invite the user to re-run the command providing more context for the `--client-name` parameter. + ## Parameters | Parameters | Required | Example | Description | | -- | -- | -- | -- | | `--config-location \| --cl` | No | ../../ | A location where to find or create the `kiota-config.json` file. When not specified it will find an ancestor `kiota-config.json` file and if not found, will use the defaults. Defaults to `./`. | -| `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. In the case where conflicting API client names are created, the user would be prompted for a new client name | +| `--lock-location \| --ll` | No | ./output/pythonClient/kiota-lock.json | Location of the `kiota-lock.json` file. If not specified, all `kiota-lock.json` files within in the current directory tree will be used. | | `--client-name \| --cn` | No | graphDelegated | Used with `--lock-location`, it would allow to specify a name for the API client. Else, name is auto-generated as a concatenation of the `language` and `clientClassName`. | ## Using `kiota config migrate` From 114087e38ca5e28c174c6fd4427b4faabb7ff467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Wed, 13 Dec 2023 19:59:20 +0000 Subject: [PATCH 13/15] Initial spec for self-serve --- specs/cli/info.md | 175 ++++++++++++++++++++++++++++++++++ specs/scenarios/self-serve.md | 53 ++++++++++ 2 files changed, 228 insertions(+) create mode 100644 specs/cli/info.md create mode 100644 specs/scenarios/self-serve.md diff --git a/specs/cli/info.md b/specs/cli/info.md new file mode 100644 index 0000000000..3db070dcb6 --- /dev/null +++ b/specs/cli/info.md @@ -0,0 +1,175 @@ +# `kiota info` + +## Description + +`kiota info` provides information about the available languages in Kiota and the dependencies that are used by the core libraries. This command is useful to get a sense of the maturity of languages in Kiota and to get language-specific information about their dependencies. + +## Parameters + +| Parameters | Required | Example | Description | +| -- | -- | -- | -- | +| `--language \| -l` | No | csharp | The language to get information about. | +| `--json` | No | | When specified, the output will be in JSON format. | +| `--ignore-dependencies` | No | | When specified, ignore the dependencies indicated in the `x-ms-kiota-info` extension (if any). | + + +## Using `kiota info` + +```bash +kiota info +``` + +```bash +Language Maturity Level +CLI Preview +CSharp Stable +Go Stable +Java Preview +PHP Stable +Python Stable +Ruby Experimental +Swift Experimental +TypeScript Experimental + +Hint: use the language argument to get the list of dependencies you need to add to your project. +Example: kiota info -l +``` + +## Using `kiota info -l ` + +```bash +kiota info -l csharp +``` + +```bash +The language CSharp is currently in Stable maturity level. +After generating code for this language, you need to install the following packages: +dotnet add package Microsoft.Kiota.Abstractions --version 1.6.1 +dotnet add package Microsoft.Kiota.Http.HttpClientLibrary --version 1.3.0 +dotnet add package Microsoft.Kiota.Serialization.Form --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Json --version 1.1.1 +dotnet add package Microsoft.Kiota.Authentication.Azure --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Text --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Multipart --version 1.1.0 +``` + +## Using `kiota info -l --json` + +```bash +kiota info -l csharp --json +``` + +```json +{ + "maturityLevel": "Stable", + "dependencyInstallCommand": "dotnet add package {0} --version {1}", + "dependencies": [ + { + "name": "Microsoft.Kiota.Abstractions", + "version": "1.6.1" + }, + { + "name": "Microsoft.Kiota.Http.HttpClientLibrary", + "version": "1.2.0" + }, + { + "name": "Microsoft.Kiota.Serialization.Form", + "version": "1.1.0" + }, + { + "name": "Microsoft.Kiota.Serialization.Json", + "version": "1.1.1" + }, + { + "name": "Microsoft.Kiota.Authentication.Azure", + "version": "1.1.0" + }, + { + "name": "Microsoft.Kiota.Serialization.Text", + "version": "1.1.0" + }, + { + "name": "Microsoft.Kiota.Serialization.Multipart", + "version": "1.1.0" + } + ], + "clientClassName": "", + "clientNamespaceName": "" +} +``` + +## Using `kiota info -l --openapi ` + +```bash +kiota info -l csharp --openapi https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml +``` + +_Assuming these extensions would be available in the OpenAPI description:_ + +```yaml +openapi: 3.0.3 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +x-ms-kiota-info: + languagesInformation: + CSharp: + clientClassName: graphClient + clientNamespaceName: Microsoft.Graph + dependencyInstallCommand: dotnet add package {name} --version {version} + dependencies: + - name: Microsoft.Graph.Core + version: 3.0.0 + structuredMimeTypes: + - application/json +servers: + - url: https://graph.microsoft.com/v1.0 +``` + +```bash +The language CSharp is currently in Stable maturity level. +After generating code for this language, you need to install the following packages: +dotnet add package Microsoft.Graph.Core --version 3.0.0 +``` + +## Using `kiota info -l --openapi --ignore-dependencies` + +```bash +kiota info -l csharp --openapi https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml --ignore-dependencies +``` + +_Assuming these extensions would be available in the OpenAPI description:_ + +```yaml +openapi: 3.0.3 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +x-ms-kiota-info: + languagesInformation: + CSharp: + clientClassName: graphClient + clientNamespaceName: Microsoft.Graph + dependencyInstallCommand: dotnet add package {name} --version {version} + dependencies: + - name: Microsoft.Graph.Core + version: 3.0.0 + structuredMimeTypes: + - application/json +servers: + - url: https://graph.microsoft.com/v1.0 +``` + +```bash +The language CSharp is currently in Stable maturity level. +After generating code for this language, you need to install the following packages: +dotnet add package Microsoft.Kiota.Abstractions --version 1.6.1 +dotnet add package Microsoft.Kiota.Http.HttpClientLibrary --version 1.3.0 +dotnet add package Microsoft.Kiota.Serialization.Form --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Json --version 1.1.1 +dotnet add package Microsoft.Kiota.Authentication.Azure --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Text --version 1.1.0 +dotnet add package Microsoft.Kiota.Serialization.Multipart --version 1.1.0 +``` \ No newline at end of file diff --git a/specs/scenarios/self-serve.md b/specs/scenarios/self-serve.md new file mode 100644 index 0000000000..58d6f458a0 --- /dev/null +++ b/specs/scenarios/self-serve.md @@ -0,0 +1,53 @@ +# Kiota Self-Serve + +Kiota enables developers to self-serve their own API client from an OpenAPI description. This is a proposal for how we can improve the experience to make it easier to use Kiota with specialized APIs that might have core dependencies that would provide a better experience for the developer. + +_This proposal is heavily influenced by scenarios that we have seen in the Microsoft Graph ecosystem. We are looking for feedback on whether this is a good approach for other APIs as well._ + +## Current Challenges + +One of the goals for Microsoft Graph is to shift from providing pre-packaged SDKs to invite developers to self-serve their own API client from the Microsoft Graph OpenAPI description. This brings a number of benefits to the developer: + +- The developer can choose the language that they want to use. +- The developer can choose the features and operations that they want to use, resulting in the smallest possible client. + +This is already possible today and provide "some" value to the developer. However, there are some challenges that we have seen in the ecosystem that we would like to address. + +- The developer loses core capabilities that are provided by our core libraries. For example, the developer would need to implement their own PageIterator to iterate over a collection of paged items, implement their own large file upload logic, their own batch management, etc. +- The developer loses the ability to use the same authentication provider that is used by the core libraries. For example, the developer would need to implement their own authentication provider to account for allowed hosts to be called. +- API producers might lose some telemetry if they use a custom mideleware to capture telemetry via the calls to the API. + +Without these key capabiltiies, Graph developers wouldn't be able to effectively use our self-serve approach. We have seen some developers try to use the self-serve approach and then give up and use the pre-packaged SDKs because they are easier to use. + +## Proposal + +This proposal will focus on how Kiota can be improved to support the scenarios described above. We will focus on the Microsoft Graph scenario, but we believe that this approach could be used for other APIs as well. Another internal document for this proposal will focus on how we can improve the Microsoft Graph ecosystem to support this approach. + +### Making dependencies discoverable + +The first step is to make the dependencies that are used by the core libraries discoverable. We can do this by adding a new parameter to the OpenAPI description that describes the dependencies that are used by the API. We already have [documented](https://github.com/microsoft/OpenAPI/blob/main/extensions/x-ms-kiota-info.md) how to represent language information on the `x-ms-kiota-info` OpenAPI extension and this scenario would leverage it. Here is an example of what this could look like: + +```yaml +openapi: 3.0.3 +info: + title: OData Service for namespace microsoft.graph + description: This OData service is located at https://graph.microsoft.com/v1.0 + version: 1.0.1 +x-ms-kiota-info: + languagesInformation: + CSharp: + clientClassName: graphClient + clientNamespaceName: Microsoft.Graph + dependencyInstallCommand: dotnet add package {name} --version {version} + dependencies: + - name: Microsoft.Graph.Core + version: 3.0.0 + structuredMimeTypes: + - application/json +servers: + - url: https://graph.microsoft.com/v1.0 +``` + +This would allow us to capture the dependencies that are used by the API and the information that is needed to install them. This would also allow to specify the versions of the dependencies that are used by the API producer. For instance, they could roll out their own authentication libraries, their own serialization libraries, etc. and specify the version that they want to use. This would make it easier for the developers to use the right set of functionality without relying on the API producer documentation. + +The metadata related to the `languageInformation` would be used by Kiota to generate the client code. It would be treated as default values but the developers could still override them with their preferred `clientClassName` and `clientNamespaceName`. The metadata related to the `dependencies` would be used by the developer to install the dependencies that are needed by the API. These would not be automatically installed by Kiota. When using `kiota info -l CSharp`, Kiota would use the `dependencies` metadata to generate the `dependencyInstallCommand` that the developer would need to run to install the dependencies. In cases where the API producer doesn't specify the `dependencies` metadata, Kiota would use the default dependencies that are used by the core libraries. It would also be possible to ignore the API Producer dependencies by using the `--ignore-dependencies` flag on the `kiota info -l ` command. \ No newline at end of file From 390b9d6051c251062a13c0869a2f4a80240bb013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Wed, 20 Dec 2023 18:39:14 +0000 Subject: [PATCH 14/15] adding notes for self-serve --- specs/assets/core-diagram.png | Bin 0 -> 93665 bytes specs/scenarios/self-serve.md | 108 +++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 specs/assets/core-diagram.png diff --git a/specs/assets/core-diagram.png b/specs/assets/core-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..9c82ba3d6ebd5d5ca8a647727cc5a4c3090d490a GIT binary patch literal 93665 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 command. \ No newline at end of file +The metadata related to the `languageInformation` would be used by Kiota to generate the client code. It would be treated as default values but the developers could still override them with their preferred `clientClassName` and `clientNamespaceName`. The metadata related to the `dependencies` would be used by the developer to install the dependencies that are needed by the API. These would not be automatically installed by Kiota. When using `kiota info -l CSharp`, Kiota would use the `dependencies` metadata to generate the `dependencyInstallCommand` that the developer would need to run to install the dependencies. In cases where the API producer doesn't specify the `dependencies` metadata, Kiota would use the default dependencies that are used by the core libraries. It would also be possible to ignore the API Producer dependencies by using the `--ignore-dependencies` flag on the `kiota info -l ` command. + +### Defining a core pattern to easily extend the client + +The second step is to define a core pattern that would allow the developer to easily extend the client with the core capabilities that are provided by the core libraries. This would allow the developer to use the same authentication provider, the same telemetry provider, the same large file upload logic, etc. that are used by the core libraries. + +Today, using self-serve, you have to roll out a lot of the internals of the core libraries. This would be a minimal .NET app that would use Kiota to generate the client code. + +Assuming the following `kiota generate` command: + +```bash +kiota generate --openapi https://aka.ms/graph/v1.0/openapi.yaml --language CSharp --output ./generated/graph --client-name "TinyGraphClient" --namespace-name "Contoso.App" --include-path "/me#GET" +``` + +```csharp +using Azure.Identity; +using Contoso.App; +using Microsoft.Kiota.Authentication.Azure; +using Microsoft.Kiota.Http.HttpClientLibrary; + +// The auth provider will only authorize requests to +// the allowed hosts, in this case Microsoft Graph +var allowedHosts = new [] { "graph.microsoft.com" }; +var scopes = new [] { "User.Read" }; + +var options = new DeviceCodeCredentialOptions +{ + ClientId = "c723a528-361b-48a9-99dd-eb9396c00000", + DeviceCodeCallback = (code, cancellation) => + { + Console.WriteLine(code.Message); + return Task.FromResult(0); + }, +}; + +var credential = new DeviceCodeCredential(options); + +var authProvider = new AzureIdentityAuthenticationProvider(credential, allowedHosts, scopes: graphScopes); +var requestAdapter = new HttpClientRequestAdapter(authProvider); +var client = new TinyGraphClient(requestAdapter); + +var me = await client.Me.GetAsync(); +Console.WriteLine($"Hello {me.DisplayName}, your ID is {me.Id}"); +``` + +Compared to using the `Microsoft.Graph` prepackaged SDK, there is a lot of code that the developer has to write and also needs to understand the internals of Kiota to get the same functionality. This is a barrier for developers to use the self-serve approach. We want to make it easier for developers to use the self-serve approach by providing a core pattern that would allow them to easily extend the client with the core capabilities that are provided by the core libraries. + +```csharp +using Microsoft.Graph; +using Azure.Identity; +using Contoso.App; + +var scopes = new[] { "User.Read" }; +var clientId = "c723a528-361b-48a9-99dd-eb9396c00000"; + +var options = new DeviceCodeCredentialOptions +{ + ClientId = "c723a528-361b-48a9-99dd-eb9396c0ffba", + DeviceCodeCallback = (code, cancellation) => + { + Console.WriteLine(code.Message); + return Task.FromResult(0); + }, +}; + +var credential = new DeviceCodeCredential(options); +var client = new GraphServiceClient(credential, scopes); + +var me = await client.Me.GetAsync(); +Console.WriteLine($"Hello {me.DisplayName}, your ID is {me.Id}"); +``` + +The optimal experience would be to generate a client that would have all the necessary code to leverage the same capabilities the core and full libraries provide. This would allow the developer to use the same authentication provider, the same telemetry provider, the same large file upload logic, etc. that are used by the core libraries. This would also allow the API producer to capture the same telemetry that they would capture if the developer was using the pre-packaged SDKs. + +```csharp +using Azure.Identity; +using Contoso.App; + +var scopes = new [] { "User.Read" }; + +var options = new DeviceCodeCredentialOptions +{ + ClientId = "c723a528-361b-48a9-99dd-eb9396c0ffba", + DeviceCodeCallback = (code, cancellation) => + { + Console.WriteLine(code.Message); + return Task.FromResult(0); + }, +}; + +var credential = new DeviceCodeCredential(options); +var client = new TinyGraphClient(credential, scopes); + +var me = await client.Me.GetAsync(); +Console.WriteLine($"Hello {me.DisplayName}, your ID is {me.Id}"); +``` + +* Need an option to use a "core" base class for a generated client +* serializer settings and structured media types are associated to a "core" not a client. Should work with same media types with different serializers. +* We need a default core to simplify getting started for most users +* We need the structured mime type +* This will allow both serializers and backing store registry to be done with their singleton implementation +* Core would take on the ApiClientBuilder calls +* Core is not necessary for the generation. Cores shouldn't be required in the kiota.config. +* In every model file, inject a static property that has the key of the clientName. This would allow the backing store to know the instance it cares about. + +![](../assets/core-diagram.png) \ No newline at end of file From f955ef4165673873e2fe9f1dc0e3e742a54f7e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Levert?= Date: Wed, 20 Dec 2023 18:48:31 +0000 Subject: [PATCH 15/15] Updating based on review --- specs/cli/client-add.md | 21 +++++++-------------- specs/cli/client-edit.md | 20 +++++++------------- specs/cli/config-migrate.md | 27 +++------------------------ specs/scenarios/kiota-config.md | 16 +++++----------- specs/schemas/kiota-config.json | 12 ------------ 5 files changed, 22 insertions(+), 74 deletions(-) diff --git a/specs/cli/client-add.md b/specs/cli/client-add.md index fad5f1f1d1..32b0858e8c 100644 --- a/specs/cli/client-add.md +++ b/specs/cli/client-add.md @@ -4,7 +4,7 @@ `kiota client add` allows a developer to add a new API client to the `kiota-config.json` file. If no `kiota-config.json` file is found, a new `kiota-config.json` file would be created in thr current working directory. The command will add a new entry to the `clients` section of the `kiota-config.json` file. Once this is done, a local copy of the OpenAPI description is generated and kept in the `.kiota/descriptions` folder. -When executing, a new API entry will be added and will use the `--client-name` parameter as the key for the map. When loading the OpenAPI description, it will generate a hash of the description to enable change detection of the description and save it as part of the `descriptionHash` property. It will also store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. +When executing, a new API entry will be added and will use the `--client-name` parameter as the key for the map. When loading the OpenAPI description, it will store the location of the description in the `descriptionLocation` property. If `--include-path` or `--exclude-path` are provided, they will be stored in the `includePatterns` and `excludePatterns` properties respectively. Every time an API client is added, a copy of the OpenAPI description file will be stored in the `./.kiota/{client-name}` folder. The files will be named using the API client name. This will allow the CLI to detect changes in the description and avoid downloading the description again if it hasn't changed. @@ -29,7 +29,7 @@ Once the `kiota-config.json` file is generated and the OpenAPI description file | `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | | `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | | `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | -| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--structured-media-types \| -m` | No | `application/json` |Any valid media type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | | `--skip-generation \| --sg` | No | true | When specified, the generation would be skipped. Defaults to false. | | `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | @@ -49,7 +49,6 @@ _The resulting `kiota-config.json` file will look like this:_ "version": "1.0.0", "clients": { "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], "excludePatterns": [], @@ -58,17 +57,11 @@ _The resulting `kiota-config.json` file will look like this:_ "clientClassName": "GraphClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true + "structuredMediaTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true } } } diff --git a/specs/cli/client-edit.md b/specs/cli/client-edit.md index b802dc94cb..495ec082df 100644 --- a/specs/cli/client-edit.md +++ b/specs/cli/client-edit.md @@ -24,7 +24,7 @@ Once the `kiota-config.json` file and the API Manifest are updated, the code gen | `--exclude-backward-compatible \| --ebc` | No | | Whether to exclude the code generated only for backward compatibility reasons or not. Defaults to `false`. | | `--serializer \| -s` | No | `Contoso.Json.CustomSerializer` | One or more module names that implements ISerializationWriterFactory. Default are documented [here](https://learn.microsoft.com/openapi/kiota/using#--serializer--s). | | `--deserializer \| --ds` | No | `Contoso.Json.CustomDeserializer` | One or more module names that implements IParseNodeFactory. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--deserializer---ds). | -| `--structured-mime-types \| -m` | No | `application/json` |Any valid MIME type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | +| `--structured-media-types \| -m` | No | `application/json` |Any valid media type which will match a request body type or a response type in the OpenAPI description. Default are documented [here](https://learn.microsoft.com/en-us/openapi/kiota/using#--structured-mime-types--m). | | `--skip-generation \| --sg` | No | true | When specified, the generation would be skipped. Defaults to false. | | `--output \| -o` | No | ./generated/graph/csharp | The output directory or file path for the generated code files. Defaults to `./output`. | @@ -44,7 +44,6 @@ _The resulting `kiota-config.json` file will look like this:_ "version": "1.0.0", "clients": { "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], "excludePatterns": ["/users/$count"], @@ -53,17 +52,11 @@ _The resulting `kiota-config.json` file will look like this:_ "clientClassName": "GraphServiceClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true + "structuredMediaTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true } } } @@ -80,6 +73,7 @@ _The resulting `apimanifest.json` file will look like this:_ }, "apiDependencies": { "graphDelegated": { + "x-ms-apiDescriptionHash": "9EDF8506CB74FE44...", "apiDescriptionUrl": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "apiDeploymentBaseUrl": "https://graph.microsoft.com", "apiDescriptionVersion": "v1.0", diff --git a/specs/cli/config-migrate.md b/specs/cli/config-migrate.md index 734bc39353..e97fd50ce5 100644 --- a/specs/cli/config-migrate.md +++ b/specs/cli/config-migrate.md @@ -40,7 +40,6 @@ _The resulting `kiota-config.json` file will look like this:_ "version": "1.0.0", "clients": { "csharpGraphServiceClient": { - "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], "excludePatterns": [], @@ -49,13 +48,7 @@ _The resulting `kiota-config.json` file will look like this:_ "clientClassName": "GraphServiceClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ + "structuredMediaTypes": [ "application/json" ], "usesBackingStore": true, @@ -63,7 +56,6 @@ _The resulting `kiota-config.json` file will look like this:_ } }, "pythonGraphServiceClient": { - "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], "excludePatterns": [], @@ -72,13 +64,7 @@ _The resulting `kiota-config.json` file will look like this:_ "clientClassName": "GraphServiceClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ + "structuredMediaTypes": [ "application/json" ], "usesBackingStore": true, @@ -210,7 +196,6 @@ _The resulting `kiota-config.json` file will look like this:_ "version": "1.0.0", "clients": { "graphDelegated": { - "descriptionHash": "9EDF8506CB74FE44...", "descriptionLocation": "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/openapi.yaml", "includePatterns": ["**/users/**"], "excludePatterns": [], @@ -219,13 +204,7 @@ _The resulting `kiota-config.json` file will look like this:_ "clientClassName": "GraphServiceClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ + "structuredMediaTypes": [ "application/json" ], "usesBackingStore": true, diff --git a/specs/scenarios/kiota-config.md b/specs/scenarios/kiota-config.md index 6a5b3acbd8..8c713ba392 100644 --- a/specs/scenarios/kiota-config.md +++ b/specs/scenarios/kiota-config.md @@ -31,17 +31,11 @@ Here is an example of what the kiota-config.json file could look like. "clientClassName": "GraphClient", "clientNamespaceName": "Contoso.GraphApp", "features": { - "serializers": [ - "Contoso.Json.CustomSerializer" - ], - "deserializers": [ - "Contoso.Json.CustomDeserializer" - ], - "structuredMimeTypes": [ - "application/json" - ], - "usesBackingStore": true, - "includeAdditionalData": true + "structuredMediaTypes": [ + "application/json" + ], + "usesBackingStore": true, + "includeAdditionalData": true } }, "businessCentral": { diff --git a/specs/schemas/kiota-config.json b/specs/schemas/kiota-config.json index 09290bfb3e..70389af1f6 100644 --- a/specs/schemas/kiota-config.json +++ b/specs/schemas/kiota-config.json @@ -54,18 +54,6 @@ "type": "string" } }, - "serializers": { - "type": "array", - "items": { - "type": "string" - } - }, - "deserializers": { - "type": "array", - "items": { - "type": "string" - } - }, "usesBackingStore": { "type": "boolean" },