#1 2024-10-14 14:04:10

Georg
Member
Registered: 2016-11-22
Posts: 18

Small problems with OpenAPI conversion

Hello,
I have two small problems with the OpenAPI conversion.
1. For the following spec file https://gist.github.com/georgkeller/325 … 53dd3dc2fd,
the conversion creates the function:

function TTenDukeIDPClient.GetUser(const UserId: TGuid): TUser;
begin
  fClient.Request('GET', '/users/%', [ToUtf8(UserId)], [], [],
    result, TypeInfo(TUser));
end;

The endpoint for the request begins with a slash (/user).
In the constructor of the TJsonClient, the base uri gets a trailing slash:

  fBaseUri := IncludeTrailingUriDelimiter(aBaseUri);

This results in a double slash in the uri and in a http 404 error.

2. In another spec file (which I cannot post unfortunately), the conversion results in the following function definition:

    function ListClients(Offset: Int64 = 0; Limit: integer = 0; const Order: RawUtf8 = '';
      Asc: boolean = false): variantDynArray;

The return type variantDynArray is unknown, I think it should be TvariantDynArray instead.

Offline

#2 2024-10-14 17:18:58

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,662
Website

Re: Small problems with OpenAPI conversion

Hello,

1) should be fixed with https://github.com/synopse/mORMot2/commit/ddba1ae7

2) try with https://github.com/synopse/mORMot2/commit/b979b5ba

Thanks for the feedback!

Offline

#3 2024-10-15 06:33:14

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

Hello,
it works now, thanks for your quick corrections smile.

Offline

#4 2024-10-15 08:46:35

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

Hello,
I found another caveat with the OpenAPI conversion.
In the following specification for an endpoint, there is an optional query parameter and some header parameters with a default value:

    "/users/{userId}/organization-groups": {
      "get": {
        "tags": [
          "user"
        ],
        "summary": "List a user's user groups",
        "description": "Retrieve the user groups that a user belongs to. You can also limit the query to a specified organization's user groups.",
        "operationId": "listOrganizationGroupsOfUser",
        "parameters": [
          {
            "name": "offset",
            "in": "header",
            "description": "The index of the first user group to include in the results. Indexing is done in the sorting order.",
            "schema": {
              "type": "integer",
              "format": "int64",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "header",
            "description": "The maximum number of user groups to be returned.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 25
            }
          },
          {
            "name": "order",
            "in": "header",
            "description": "The name of the field based on which results are sorted.",
            "schema": {
              "type": "string",
              "default": "name"
            }
          },
          {
            "name": "asc",
            "in": "header",
            "description": "The sorting order of the results: \"true\" = ascending, \"false\" = descending.",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "userId",
            "in": "path",
            "description": "The ID of the user.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "organizationId",
            "in": "query",
            "description": "The organization whose user groups to retrieve.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrganizationGroupOfUser"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {}
          },
          "401": {
            "description": "Unauthorized",
            "content": {}
          },
          "403": {
            "description": "Forbidden",
            "content": {}
          },
          "404": {
            "description": "Resource not found",
            "content": {}
          },
          "500": {
            "description": "Internal server error",
            "content": {}
          }
        }
      },

The resulting pascal code is:

function TAPIClient.ListOrganizationGroupsOfUser(const UserId: TGuid;
  const OrganizationId: TGuid; Offset: Int64; Limit: integer; const Order: RawUtf8;
  Asc: boolean): TOrganizationGroupOfUserDynArray;
begin
  fClient.Request('GET', '/users/%/organization-groups', [ToUtf8(UserId)], [
    'organizationId', ToUtf8(OrganizationId)], [
    'offset', Offset,
    'limit', Limit,
    'order', Order,
    'asc', Asc],
    result, TypeInfo(TOrganizationGroupOfUserDynArray));
end;

In the specification, only the UserId is required, all other parameters are optional or have a default value.

One problem is now that all parameters have to be provided (even if there is a default value).
An even bigger problem is that you have to provide an OrganizationId, there is no possibility to use the request without providing this parameter.
Without the OrganizationId, the call returns all the organization groups of all organizations of the user.

To sum it up, the request should also work like this (without the optional parameters):

  fClient.Request('GET', '/users/%/organization-groups', [ToUtf8(UserId)], [], [], result, TypeInfo(TOrganizationGroupOfUserDynArray));

Offline

#5 2024-10-15 09:56:06

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,662
Website

Re: Small problems with OpenAPI conversion

Difficult to implement this if there is no "null" available in pascal for basic types...
We have "Nullable variants" definition in mormot.db.core - but they may be an overkill, and would need to move from this db unit to one of the core units.

For integer/RawUtf8, if we put 0/'' then it won't appear.

For boolean, there is no default value. We may put a variant here instead, or put a default value.

After https://github.com/synopse/mORMot2/commit/d1f3e4eb
now OrganizationID = GUID_NULL would just ignore the parameter.

Offline

#6 2024-10-15 12:51:19

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

Thanks, the change for the Guid already helps a lot.
The rest is nice to have, but not essential.

Offline

#7 2024-10-16 11:48:53

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

I found another problem with the OpenAPI conversion.

In the spec file, there is the following section:

      "Owner" : {
        "type" : "object",
        "description" : "The owner; an organization or a user.",
        "readOnly" : true,
        "required" : [ "name", "objectType" ],
        "properties" : {
          "objectType" : {
            "type" : "string",
            "description" : "The type of the owner."
          }
        },
        "oneOf" : [ {
          "$ref" : "#/components/schemas/UserProfile"
        }, {
          "$ref" : "#/components/schemas/Organization"
        } ],
        "discriminator" : {
          "propertyName" : "objectType",
          "mapping" : {
            "User" : "#/components/schemas/UserProfile",
            "Organization" : "#/components/schemas/Organization"
          }
        }
      },

This converts to the following type in the dto file:

  // from #/components/schemas/Owner
  TOwner = packed record
    // The type of the owner.
    ObjectType: RawUtf8;
  end;
  POwner = ^TOwner;

The part after the "oneOf" definition in the specification is missing in the conversion.

Last edited by Georg (2024-10-16 11:49:19)

Offline

#8 2024-10-16 12:00:50

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,662
Website

Re: Small problems with OpenAPI conversion

Should not the "oneOf" be part of "properties"?

Offline

#9 2024-10-16 13:10:32

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

I checked the spec file, the Owner structure is exactly as it is defined in the spec. I validated the spec file with the opentapi-generator-cli tool and there were no errors, so such a construction seems to be valid.
But I will check with the creator of the spec file if this section is defined as intended.

Offline

#10 2024-10-16 13:59:35

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

I checked the response when calling the endpoint with Postman, I get this:

        "owner": {
            "objectType": "Organization",
            "id": "test-id",
            "created": "2023-03-27T07:45:50.393Z",
            "authorId": "author-id",
            "name": "xy GmbH",
            "type": "company",
            "description": ""
        },

This corresponds to the TOrganization type that was already created in the conversion:

  TOrganization = packed record
    // The unique ID of the object. If an ID is not specified when the object is created,
    // the system generates an ID.
    Id: TGuid;
    // The date and time (ISO 8601) when the object was created.
    // - Example: "1999-01-01T00:01:02.345Z"
    Created: RawUtf8;
    // The ID of the user who created the object.
    AuthorId: TGuid;
    // The date and time (ISO 8601) when the object was last modified.
    // - Example: "2019-02-02T01:02:03.456Z"
    Modified: RawUtf8;
    // The ID of the user who last modified the object.
    EditorId: TGuid;
    // The type of the license owner.
    ObjectType: RawUtf8;
    // The name of the organization.
    Name: RawUtf8;
    // The type of the organization.
    _Type: RawUtf8;
    // The description of the organization.
    Description: RawUtf8;
  end;
  POrganization = ^TOrganization;

The TUserProfile record is also created in the conversion, but both TOrganization and TUserProfile are not used in the client code because the conversion of "oneOf" is not working.

For comparison, I tried the conversion in Rust and in C++. In Rust, the structure is preserved and an enum with two elements (UserProfile and Organization) is created:

pub enum Owner {
    #[serde(rename="User")]
    User(Box<models::UserProfile>),
    #[serde(rename="Organization")]
    Organization(Box<models::Organization>),
}

In C++, the structure is flattened and an Owner class is created that has all the properties of UserProfile and of Organization (which is clunky but workable):

class LicenseOwner{
public:
...
private:
    std::string objectType{};
    std::string id{};
    std::string created{};
    std::string authorId{};
    std::string modified{};
    std::string editorId{};
    std::string displayName{};
    std::string email{};
    std::string firstName{};
    std::string lastName{};
    std::string validFrom{};
    std::string validUntil{};
    std::string lastSignOn{};
    std::string nickname{};
    std::string picture{};
    std::string phoneNumber{};
    std::string professionalTitle{};
    std::string name{};
    std::string type{};
    std::string description{};
};

Offline

#11 2024-11-13 15:32:45

Georg
Member
Registered: 2016-11-22
Posts: 18

Re: Small problems with OpenAPI conversion

I found another problem with a different OpenAPI specification that uploads and downloads files.
<script src="https://gist.github.com/georgkeller/11a5730d89d69c4815745b81ac6cb908.js"></script>

In this spec, the parameters are submitted in the request body via multipart/form-data

        "/api/Conversion/upload": {
            "post": {
                "tags": [
                    "Conversion"
                ],
                "summary": "Uploads  document to a conversion service.",
                "description": "Note: conversion times might not be constant. Returns ID that you will need to download the document again.",
                "requestBody": {
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "required": [
                                    "Document",
                                    "SourceFormat",
                                    "TargetFormat"
                                ],
                                "type": "object",
                                "properties": {
                                    "Document": {
                                        "type": "string",
                                        "description": "Uploaded document to be parsed/converted",
                                        "format": "binary"
                                    },
                                    "SourceFormat": {
                                        "$ref": "#/components/schemas/DocumentFormatEnumDTO"
                                    },
                                    "TargetFormat": {
                                        "$ref": "#/components/schemas/DocumentFormatEnumDTO"
                                    }
                                }
                            },
                            "encoding": {
                                "Document": {
                                    "style": "form"
                                },
                                "SourceFormat": {
                                    "style": "form"
                                },
                                "TargetFormat": {
                                    "style": "form"
                                }
                            }
                        }
                    }
                },

But in the pascal code, this parameter is missing:

//  [post] /api/Conversion/upload
    //
    // Summary: Uploads  document to a conversion service.
    // Description:
    //   Note: conversion times might not be constant. Returns ID that you will need to download the document again.
    //
    // Responses:
    // - 200 (main): Document uploaded successfully
    // - 400: Invalid request DTO
    // - 500: Internal server error
    function PostApiConversionUpload(): RawUtf8;

It seems to me that the OpenAPI conversion does not work for this kind of content.

Offline

#12 2024-11-13 16:42:32

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,662
Website

Re: Small problems with OpenAPI conversion

You are right: multipart/form-data is indeed not supported yet.
This is clearly documented in the unit.

Any pull request is welcome.

Offline

Board footer

Powered by FluxBB