JSON Schema to Tapir Schema

I have a dynamically generated JSON Schema representation in json-sKema, and would like to build/derive a corresponding Tapir schema from it to a) describe and validate a request body and b) render full OpenAPI endpoint docs. I can identify some bits and pieces, i.e. I can create a Tapir Validator based on the json-Skema API and put this into a Schema[Json] to override the default one, but I can’t see how to put everything together, particularly how I can get the JSON Schema to be rendered into OpenAPI. Can anybody point me in the right direction, please?

I don’t know json-sKema, but once you have translated the json-sKema object into a sttp.tapir.Schema[Whatever], you can then create a body description using either:

  1. one of Tapir’s json integrations, that is using a jsonBody[Json], providing the translated schema implicitly
  2. use stringBodyUtf8AnyFormat by providing a Codec from String to your desired type. It might be helpful to create the codec using Codec.json. See one of the json integration implementations as an example, e.g. the circe one.

If you’d need more help, some code snippet to reproduce the problem would be great.

Thanks for the quick reply! So far I’ve been trying something like this…

type SchemaEndpoint = PublicEndpoint[Json, String, Unit, Any]

private def tapirValidator(schemaValidator: SchemaValidator): Validator[Json] =
  def validationLogic(json: Json): ValidationResult = ???
  Validator.Custom[Json](validationLogic)

private def tapirSchema(tapirValidator: Validator[Json]): Schema[Json] =
  summon[Schema[Json]].copy(validator = tapirValidator)

def postEndpoint(tapirSchema: Schema[Json]): SchemaEndpoint =
  endpoint
    .post
    .in(
      jsonBody[Json](using summon[Encoder[Json]], summon[Decoder[Json]], tapirSchema)
    )
    .out(
      statusCode(StatusCode.Created)
    )
    .errorOut(
      statusCode(StatusCode.BadRequest)
    )

This should cover request body validation, according to a naive BackendStub test. But I don’t see how to inject the JSON Schema spec into the OpenAPI output - should this be done at Tapir Schema level or at OpenAPIDocsInterpreter level, is this the right approach at all to start with…?

EDIT: Just to clarify, I can convert the json-sKema representation to a String or a Circe Json, but there’s no URL for the schema document that I could use with a $ref.

Ah I think I see where the misunderstanding might be. The JSON Schemas (which are part of the OpenAPI specs) are generated when the OpenAPIDocsInterpreter is being run, basing on sttp.tapir.Schema. There’s (currently) no way to attach a schema that is already in JSON and use that instead.

I suppose we could support that via an attribute - attaching a parsed sttp.apispec.Schema (which is a parsed representation of JSON Schema, see the GitHub - softwaremill/sttp-apispec: OpenAPI, AsyncAPI and JSON Schema Scala models. project) and using that, but this would require some code changes.

Ok, I see, thanks. Naively, I see two options to cover OpenAPI, then: Write a transformation from some JSON Schema representation to Tapir Schema (which doesn’t feel like rocket science, but certainly like a non-trivial amount of work), or bypass Tapir for the API docs and do some text level processing instead (which is error-prone and goes against the grain of the Tapir mindset in general). What’s the code changes you’re alluding to - would that be a third option? (Not expecting this to be tackled, just curious.)

The third option is to

  1. parse the JSON schema using the circe decoders from sttp-apispec, yielding a sttp.apispec.Schema
  2. attach that parsed schema to the input describing the body using an attribute (here’s an example of an attribute attached to a schema)
  3. in the interpreter, use the attached attribute as the schema instead of generating one: probably somewhere here, maybe it would be better to attach the parsed schema to a codec instead. But that’s just a sketch :slight_smile: