Skip to content

Instantly share code, notes, and snippets.

@tadelesh
Last active June 26, 2024 07:49
Show Gist options
  • Save tadelesh/a61883724a5cd072ef18f500826c7a7f to your computer and use it in GitHub Desktop.
Save tadelesh/a61883724a5cd072ef18f500826c7a7f to your computer and use it in GitHub Desktop.

Requirement

In swagger, we have testmodeler to extends m4 model to help to link swagger example file to corresponding operation, as well as map example value to its type schema. When we switch to TypeSpec, we currently still use swagger example file to show the payload example of an operation. Also, TCGC is the middle model layer for TypeSpec, just like m4 for swagger. So, TCGC need to do what testmodeler do.

Basic rules

Current example/test generation way of all languages could be grouped into two kinds:

  1. Use example model in testmodeler to generate the example/test codes: Go MPG from swagger, JS HLC, .NET MPG from swagger
  2. Use self-defined example model and do example value mapping/fake value generation by language's code generator: Java, .NET DPG, Python

So, in order to support two patten, TCGC will do the following works:

  1. Map example file to its operation schema. Since current example file is HTTP payload, the example will be mapped to SdkHttpOperation type in TCGC.
  2. Map example's parameters and responses to SdkHttpParameter and SdkHttpResponse.
  3. Map each value of the example to corresponding TCGC types.

Design

Interfaces

export interface SdkHttpOperation extends SdkServiceOperationBase {
  __raw: HttpOperation;
  kind: "http";
  path: string;
  verb: HttpVerb;
  parameters: (SdkPathParameter | SdkQueryParameter | SdkHeaderParameter)[];
  bodyParam?: SdkBodyParameter;
  responses: Map<HttpStatusCodeRange | number, SdkHttpResponse>;
  exceptions: Map<HttpStatusCodeRange | number | "*", SdkHttpResponse>;
  examples?: SdkHttpOperationExample[];
}

export type SdkExampleBase = {
  kind: string;
  name: string;
  description: string;
  filePath: string;
  rawExample: any;
};

export interface SdkHttpOperationExample extends SdkExampleBase {
  kind: "http";
  parameters: SdkHttpParameterExample[];
  responses: Map<number, SdkHttpResponseExample>;
}

export interface SdkHttpParameterExample {
  parameter: SdkHttpParameter;
  value: SdkTypeExample;
}

export interface SdkHttpResponseExample {
  response: SdkHttpResponse;
  headers: SdkHttpResponseHeaderExample[];
  value?: SdkTypeExample;
}

export interface SdkHttpResponseHeaderExample {
  header: SdkServiceResponseHeader;
  value: SdkTypeExample;
}

export type SdkTypeExample =
  | SdkStringExample
  | SdkNumberExample
  | SdkBooleanExample
  | SdkNullExample
  | SdkAnyExample
  | SdkArrayExample
  | SdkDictionaryExample
  | SdkUnionExample
  | SdkModelExample;

export interface SdkExampleTypeBase {
  kind: string;
  type: SdkType | SdkModelPropertyType;
  value: unknown;
}

export interface SdkStringExample extends SdkExampleTypeBase {
  kind: "string";
  type: SdkBuiltInType | SdkDatetimeType | SdkDurationType | SdkEnumType | SdkConstantType;
  value: string;
}

export interface SdkNumberExample extends SdkExampleTypeBase {
  kind: "number";
  type: SdkBuiltInType | SdkDatetimeType | SdkDurationType | SdkEnumType | SdkConstantType;
  value: number;
}

export interface SdkBooleanExample extends SdkExampleTypeBase {
  kind: "boolean";
  type: SdkBuiltInType | SdkConstantType;
  value: boolean;
}

export interface SdkNullExample extends SdkExampleTypeBase {
  kind: "null";
  type: SdkNullableType;
  value: null;
}

export interface SdkAnyExample extends SdkExampleTypeBase {
  kind: "any";
  type: SdkBuiltInType;
  value: unknown;
}

export interface SdkArrayExample extends SdkExampleTypeBase {
  kind: "array";
  type: SdkArrayType;
  value: SdkTypeExample[];
}

export interface SdkDictionaryExample extends SdkExampleTypeBase {
  kind: "dict";
  type: SdkDictionaryType;
  value: Record<string, SdkTypeExample>;
}

export interface SdkUnionExample extends SdkExampleTypeBase {
  kind: "union";
  type: SdkUnionType;
  value: unknown;
}

export interface SdkModelExample extends SdkExampleTypeBase {
  kind: "model";
  type: SdkModelType;
  value: Record<string, SdkTypeExample>;
  additionalProperties?: Record<string, SdkTypeExample>;
}

Special mapping

  1. For SdkUnionType, since it is hard to do mapping to the union variant, TCGC will just map the value to the union type, not to the union variant type.
  2. For discriminated models, TCGC will map the value to the exact subtype based on the discriminator.
  3. For additional properties, TCGC will only map the the outermost model with additional properties, not to find the model with the right type.

Helper functions

function getExamples(operation: HttpOperation): SdkHttpOperationExample[] {}

Diagnostics

If example value type is not right, add warnings to diagnostics.

Options

Use same config from typespec-autorest emitter: examples-directory.

Example

Basic

{
  "kind": "http",
  "name": "TestResources_TestAction",
  "description": "Action example for specific test resource",
  "parameters": [
    {
      "parameter": {
        "kind": "path",
        "serializedName": "subscriptionId"
      },
      "value": {
        "kind": "string",
        "type": {
          "kind": "string"
        },
        "value": "00000000-0000-0000-0000-000000000000"
      }
    },
    {
      "parameter": {
        "kind": "body",
      },
      "value": {
        "kind": "model",
        "type": {
          "kind": "model",
          "name": "Foo",
          "properties": []
        },
        "value": {
          "prop": {
            {
              "kind": "string",
              "type": {
                "kind": "string"
              },
              "value": "test"
            }
          }
        },
      }
    }
  ],
  "responses": {
    200: {
      "response": {
        "kind": "http",
      },
      "value": {
        "kind": "number",
        "type": {
          "kind": "int32"
        },
        "value": 3
      }
    }
  }
}
@weidongxu-microsoft
Copy link

weidongxu-microsoft commented Jun 17, 2024

question:

  1. is there concern about not hooking it directly under SdkServiceMethod?

suggestion:

If we need user to use emitter-option, consider examples-directory as it is used in typespec-autorest (and java)

@tadelesh
Copy link
Author

question:

  1. is there concern about not hooking it directly under SdkServiceMethod?

Since example is based on http payload, so I map it to SdkServiceOperation. You could easily map it back to SdkServiceMethod.

suggestion:

If we need user to use emitter-option, consider examples-directory as it is used in typespec-autorest (and java)

Got it.

@lirenhe
Copy link

lirenhe commented Jun 26, 2024

Please note this work is in progress, microsoft/typespec#2700 (comment), please ensure the above structure could also work well with @example

@tadelesh
Copy link
Author

tadelesh commented Jun 26, 2024

@lirenhe SdkTypeExample level type is for all kinds of example values. SdkHttpOperationExample is for Http operation. After TypeSpec support native example definition, I could add SdkServiceMethodExample for opExample, examples property for all types that could have examples. I believe the mapping logic is common.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment