loopbackio / loopback-next

LoopBack makes it easy to build modern API applications that require complex integrations.
https://loopback.io
Other
4.92k stars 1.06k forks source link

OpenAPI spec validation error #7403

Open Niks218 opened 3 years ago

Niks218 commented 3 years ago

Steps to reproduce

  1. Create spec enhancer, which add custom extension to #/components/schemas

    @injectable(asSpecEnhancer, { scope: BindingScope.SINGLETON })
    export class ComponentsSpecEnhancer implements OASEnhancer {
    name = 'components';
    
    modifySpec(spec: OpenApiSpec): ValueOrPromise<OpenApiSpec> {
    const components: ComponentsObject = {
      schemas: {
        TupleArray: {
          title: 'TupleArray',
          type: 'array',
          items: {
            type: 'array',
            items: [{ type: 'number' }, { type: 'number' }],
            additionalItems: false,
          },
          example: [[4, 0.2]],
        },
      },
    };
    return mergeOpenAPISpec(spec, { components });
    }
    }
  2. Reference this scheme in route or controller description. In request body, for example.
    @requestBody({
      required: true,
      content: {
        'application/json': {
          schema: {
            allOf: [
              getModelSchemaRef(Transaction),
              {
                type: 'object',
                properties: {
                  contribution: {
                    $ref: '#/components/schemas/TupleArray',
                  },
                },
                required: ['contribution'],
              },
            ],
          },
        },
      },
    })
  3. Call this endpoint with correct request body

Current Behavior

Got error from AJV during validation at @loopback/rest/src/validation/request-body.validator.ts:157

[MissingRefError: can't resolve reference #/components/schemas/TupleArray from id #] {
  missingRef: '#/components/schemas/TupleArray',
  missingSchema: ''
}

Expected Behavior

Passed validation

Additional information

linux x64 14.15.1

├── @loopback/authentication@7.0.7 ├── @loopback/boot@3.3.0 ├── @loopback/context@3.15.1 ├── @loopback/core@2.15.1 ├── @loopback/logging@0.4.7 ├── @loopback/openapi-v3@5.2.1 ├── @loopback/repository-json-schema@3.3.1 ├── @loopback/repository@3.5.1 ├── @loopback/rest-explorer@3.2.0 ├── @loopback/rest@9.2.1 ├── @loopback/security@0.3.7 ├── @loopback/service-proxy@3.1.0

The problem is that resolveControllerSpec at @loopback/openapi-v3/src/controller-spec.ts:71 skips extensions, referenced in api description if $ref doesn't have definitions at the same level (@loopback/openapi-v3/src/controller-spec.ts:493), which IMO, shouldn't happen.

Niks218 commented 3 years ago

As workaround it's possible to add custom schema definition to controller description like that

@api({
  components: {
    schemas: {
      TupleArray: {
        title: 'TupleArray',
        type: 'array',
        items: {
          type: 'array',
          items: [{ type: 'number' }, { type: 'number' }],
          additionalItems: false,
        },
        example: [[4, 0.2]],
      },
    },
  },
})
export class MyController {}
achrinza commented 3 years ago

Thanks for opening the issue! Unfortunately I've not been able to replicate the issue; Please check this minimal sandbox and compare it against your codebase: https://github.com/achrinzatest/loopback4-sandbox-7403

Taking a shot at the dark, was the enhancer registered into the Application (docs)? To register an enhancer, the following line needs to be explicitly added to the Application's constructor:

class MyApplication extends RestApplication {
  constructor() {
    super();
    this.add(createBindingFromClass(MySpecEnhancerClass));
  }
}
Niks218 commented 3 years ago

@achrinza Yes, it was registred and resulting openapi.json was completely valid Hmm, weird. I ran your sandbox and got exact the same error

Request GET /test failed with status code 500. [MissingRefError: can't resolve reference #/components/schemas/TupleArray from id #] {
  missingRef: '#/components/schemas/TupleArray',
  missingSchema: ''
}
Niks218 commented 3 years ago

During debug session I checked logs with DEBUG=loopback:openapi3:metadata:controller-spec env set and also checked resulting controller specs with console.debug(util.inspect(getControllerSpec(TestController), { depth: null }))

stale[bot] commented 2 years ago

This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository. This issue will be closed within 30 days of being stale.