More conventional OpenAPI schema output?

We are in the process of rewriting a gRPC service in ServiceStack. grpc-gateway generated the OpenAPI definitions for us based on the .proto definition and this is how we exposed the application to web clients. In particular, our existing Angular application uses ng-swagger-gen to consume these definitions and generates a typed service client.

As a stop-gap prior to trying out typed SS clients, we are attempting to configure SS to generate definitions that match as close as possible what we originally had coming out of grpc-gateway.

With that in mind we have a few questions/roadblocks:

  1. Regardless of whether HostConfig.DefaultContentType is set, a required Accept parameter is appended to every OpenApiPath definition (changes the signature of all generated functions to include this non-optional header param) and doesn’t appear to be configurable.
  2. Tries to generate unique OperationIds by default instead of the conventional DTO name. Luckily ours are pretty consistent so we can knock out most of them with a filter along the lines of operation.OperationId = Regex.Replace(operation.OperationId, "Request.*$", "") but it would be nice if GetOperationName tried to use the conventional name first unless there is a conflict.
  3. There appears to be no way to globally default to emitting enum types instead of strings in the definition. I understand we can manually annotate DTOs with [ApiAllowableValues(...)], but this is tricky to do everywhere, particularly in cases where we are reusing OrmLite domain models in request/response DTOs. Is there a better option?
  4. It appears that query and path parameter names do not respect the UseCamelCaseSchemaPropertyNames config option, nor is there a more specific option. Is this intentional? It results in inconsistent casing for parameters on the same request.

I am most concerned with (3) and (4) as these would be the most difficult to hack around with the available operation filters. We could really use a solution around (3) in particular.

Thanks in advance and I’d be happy to contribute some of these changes to OpenApiFeature if we can agree upon behavior!

So you’re still trying to generate clients from Open API? Any reason why you can’t just give them TypeScript Add ServiceStack Reference with the typed generic @servicestack/client? the end result is going to be much cleaner, richer & simpler.

For 1. & 2. we can add more configurability/filters as needed, happy to accept PR’s for these, they should just be opt-in with existing behavior preserved as much as possible.

For 3. there’s a SchemaPropertyFilter you should be able to use to populate the enums similar to how they’re populated:

If needed I can add a non-serializable PropertyInfo to the schemaProperty that you can access. We can do this for other types as well. I’ve just started doing this for other Metadata DTOs where more Type info is required.

  1. The UseCamelCaseSchemaPropertyNames config was initially only meant for property names, but I’m ok with changing it to using it elsewhere.

I think this will be our plan longer-term but we are on a bit of a tight deadline for the rewrite and wanted to alleviate FE impact as much as possible. The two primary differences between ng-swagger-gen and @servicestack/client that impact us are:

  1. The service methods are not generic so the client doesn’t have to care about the underlying HTTP method/options
    • ex. service.AutocompleteOrganizations(q?: string) would have to be rewritten to client.get(new AutocompleteOrganizationsRequest({ q: query }), not a huge deal but time consuming.
  2. All service methods return an Observable<T> rather than Promise<T>. Again, this would mostly be ok just a lot to tweak/validate.

I think it would be relatively easy to generate a more Angular-specific client with the semantics we’re used to on top of @servicestack/client + SS TS references so we may look into that down the road as well.

@mythz that would be great! I think that’s all we’d need to achieve (3) for now and would allow us to work around (4) in a more robust way. Once you expose the PropertyInfo I’ll get all this working with filters for our project before I get the PR going to implement all this opt-in as discussed.

The only other caveat to (3) is that I think ideally we’d want to generate top-level definitions for all referenced enum types vs inlining them. That’s what I will plan on implementing as part of the PR.

FYI this has been added in this commit where now SchemaPropertyFilter will be called with an OpenApiProperty.PropertyInfo.

Note that all OpenApiProperty types will have PropertyType populated with the Type of the property but not all will have a PropertyInfo as they’re not always populated from Type properties, e.g. it’s used in dictionary, but all the properties called from SchemaPropertyFilter will have PropertyInfo populated.

This change is available from the latest v5.8.1 that’s now available on MyGet.

1 Like