google-cloud-platformswagger-2.0google-cloud-api-gateway

GCP API Gateway keep give error "http: body field path \'foo\' must be a non-repeated message."


What could be causing the error "http: body field path 'foo' must be a non-repeated message" when deploying the configuration to GCP API Gateway with the following api-spec.yaml?

I've read documentation swagger 2.0 and I think there's no issue here. I check through swagger editor to check if there is any error but no error regarding error mentioned above.

swagger: '2.0'
info:
  title: API Gateway
  version: 1.0.0
  description: API Gateway
schemes:
  - https
produces:
  - application/json
securityDefinitions:
  firebase:
    scopes: {}
    authorizationUrl: ''
    flow: implicit
    type: oauth2
    x-google-issuer: 'https://securetoken.google.com/project-id'
    x-google-jwks_uri: 'https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com'
paths:
  '/v1/events/{id}/tickets':
    get:
      produces:
        - application/json
      parameters:
        - in: path
          name: id
          required: true
          type: string
      responses:
        '200':
          description: ''
          schema:
            items:
              properties:
                description:
                  type: string
                donationPurpose:
                  type: string
                endDate:
                  format: date-time
                  type: string
                endTime:
                  type: string
                eventId:
                  type: string
                freeTicketType:
                  enum:
                    - donation
                    - infaq
                    - none
                  type: string
                id:
                  type: string
                name:
                  type: string
                price:
                  type: number
                startDate:
                  format: date-time
                  type: string
                startTime:
                  type: string
                totalAvailableTickets:
                  type: number
                totalTickets:
                  type: number
                type:
                  enum:
                    - free
                    - paid
                  type: string
              required:
                - id
                - eventId
                - name
                - description
                - type
                - freeTicketType
                - donationPurpose
                - totalAvailableTickets
                - totalTickets
                - price
                - startDate
                - endDate
                - startTime
                - endTime
              type: object
            type: array
      security:
        - firebase: []
      tags:
        - events
      operationId: EventController_getTicketsByEvent
      summary: Get event tickets
      x-google-backend:
        address: 'https://backend-et.a.run.app/v1/events/{id}/tickets'
    post:
      consumes:
        - application/json
      parameters:
        - in: path
          name: id
          required: true
          type: string
        - in: body
          name: tikets
          required: true
          schema:
            items:
              type: string
            type: array
      responses:
        '200':
          description: ''
      security:
        - firebase: []
      tags:
        - events
      operationId: EventController_createTicket
      summary: Create event tickets
      x-google-backend:
        address: 'https://backend-et.a.run.app/v1/events/{id}/tickets'
    put:
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - in: path
          name: id
          required: true
          type: string
        - in: body
          name: updateTickets
          required: true
          schema:
            items:
              type: string
            type: array
      responses:
        '200':
          description: ''
        '404':
          description: ''
          schema:
            properties:
              error:
                properties:
                  data:
                    type: object
                  details:
                    type: object
                  errorCode:
                    type: number
                  errorName:
                    type: string
                  localizedMessage:
                    type: string
                  message:
                    type: string
                  path:
                    type: string
                  requestId:
                    type: string
                  status:
                    type: string
                  timestamp:
                    type: string
                required:
                  - status
                  - errorCode
                  - message
                  - errorName
                  - details
                  - path
                  - requestId
                  - timestamp
                  - data
                type: object
            required:
              - error
            type: object
      security:
        - firebase: []
      tags:
        - events
      operationId: EventController_updateTicket
      summary: Update event tickets
      x-google-backend:
        address: 'https://backend-et.a.run.app/v1/events/{id}/tickets'

Explanation why it is happening? What should my yaml looks like?


Solution

  • I solved it but forgot what caused the error. I remember that the message error is useless, caused by different reasons. However, I still have my own script to generate spec from openapi3 to swagger2 with no issues.

    const fs = require("fs");
    const path = require("path");
    const yaml = require("js-yaml");
    const minimist = require("minimist");
    
    const TEMP_DIR = path.join(__dirname, "../../tmp");
    
    // Create the temp directory if it doesn't exist.
    if (!fs.existsSync(TEMP_DIR)) {
      fs.mkdirSync(TEMP_DIR);
    }
    const TEMP_SWAGGER_FILE = path.join(TEMP_DIR, "swagger.yaml");
    
    // Use args --source <path to swagger spec> --backend <backend address> --output <path to output file>
    // read args source, backend, output using minimist
    const args = minimist(process.argv.slice(2), {
      string: ["source", "backend", "output"],
      alias: { s: "source", b: "backend", o: "output" },
      default: {
        source: path.join(__dirname, "../../swagger.yaml"),
        backend: "https://your-backend-here",
        output: path.join(__dirname, "../../api.yaml"),
      },
    });
    
    // Get the Swagger spec location from args or use the default. const swaggerSpecPath = args['source'];
    
    // Convert the openapi spec to swagger spec 2.0 format using api-spec-converter
    const apiSpecConverter = require("api-spec-converter");
    apiSpecConverter
      .convert({ from: "openapi_3", to: "swagger_2", source: swaggerSpecPath })
      .then(function (result) {
        result.fillMissing();
        return result.validate().then(function (r) {
          if (r.errors) return console.error(JSON.stringify(r.errors, null, 2));
          if (r.warnings) return console.error(JSON.stringify(r.warnings, null, 2));
    
          // Write the swagger spec to the temp directory.
          fs.writeFileSync(TEMP_SWAGGER_FILE, result.stringify({ syntax: "yaml" }));
        });
      })
      .then(() => {
        // Get x-google-backend address from args or use the default. const backendAddress = args['backend'];
        // Load the Swagger spec from the file system.
        const swaggerSpec = yaml.load(fs.readFileSync(TEMP_SWAGGER_FILE, "utf8"));
    
        // Extract the paths from the Swagger spec.
        const paths = swaggerSpec.paths;
    
        // Generate the API spec.
        const apiSpec = {
          swagger: "2.0",
          info: {
            title: "API Gateway",
            version: "1.0.0",
            description: "API Gateway",
          },
          schemes: ["https"],
          produces: ["application/json"],
          securityDefinitions: {
            bearer: {
              authorizationUrl: "",
              flow: "implicit",
              type: "oauth2",
              "x-google-issuer": "https://securetoken.google.com/qalboo-staging",
              "x-google-jwks_uri":
                "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken@system.gserviceaccount.com",
            },
          },
          paths: {},
          definitions: swaggerSpec.definitions,
          tags: swaggerSpec.tags,
        };
    
        // Iterate over the paths.
        for (const path in paths) {
          // Get the path definition.
          const pathDef = paths[path];
    
          // Iterate over the methods.
          for (const method in pathDef) {
            // Get the method definition.
            const methodDef = pathDef[method];
    
            methodDef["x-google-backend"] = {
              address: `${backendAddress}${path}`,
            };
    
            // // check if the method has a security definition
            // if (!methodDef.security || methodDef.security.length === 0) {
            //   // Add the security definition to the method definition.
            //   methodDef.security = [
            //     {
            //       bearer: [],
            //     },
            //   ];
            // }
    
            pathDef[method] = methodDef;
          }
          // Add the API spec path to the API spec.
          apiSpec.paths[path] = pathDef;
        }
    
        // Write the API spec to the file system.
        console.info(`Writing API spec to ${args["output"]}`);
        fs.writeFileSync(args["output"], yaml.dump(apiSpec));
      });