Erhbase: REST API client-code generators and Content-Type

Hi,

Some OpenEHR REST API methods require the specific Content-Type header, for example, “/rest/openehr/v1/definition/template/adl1.4” (createTemplateClassic)

Let’s take a look at the code generated by NSwag:

createTemplateClassic(openEHR_VERSION: string | undefined, openEHR_AUDIT_DETAILS: string | undefined, content_Type: string, accept: string | undefined, prefer: string | undefined, body: string): Promise<string> {
        let url_ = this.baseUrl + "/rest/openehr/v1/definition/template/adl1.4";
        url_ = url_.replace(/[?&]$/, "");

        const content_ = JSON.stringify(body);

        let options_: RequestInit = {
            body: content_,
            method: "POST",
            headers: {
                "openEHR-VERSION": openEHR_VERSION !== undefined && openEHR_VERSION !== null ? "" + openEHR_VERSION : "",
                "openEHR-AUDIT_DETAILS": openEHR_AUDIT_DETAILS !== undefined && openEHR_AUDIT_DETAILS !== null ? "" + openEHR_AUDIT_DETAILS : "",
                "Content-Type": content_Type !== undefined && content_Type !== null ? "" + content_Type : "", /// !!!
                "Accept": accept !== undefined && accept !== null ? "" + accept : "",
                "Prefer": prefer !== undefined && prefer !== null ? "" + prefer : "",
                "Content-Type": "application/json",  /// !!!
            }
        };

        return this.http.fetch(url_, options_).then((_response: Response) => {
            return this.processCreateTemplateClassic(_response);
        });
    }

Generated code marked with !!! is wrong, as here you can see that user-defined content_Type header will be overwritten with “application/json”.

Here is a code generated with swagger-codegen-cli:

createTemplateClassic: async (body: string, content_type: string, open_ehr_version?: string, open_ehr_audit_details?: string, accept?: string, prefer?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
            // verify required parameter 'body' is not null or undefined
            if (body === null || body === undefined) {
                throw new RequiredError('body','Required parameter body was null or undefined when calling createTemplateClassic.');
            }
            // verify required parameter 'content_type' is not null or undefined
            if (content_type === null || content_type === undefined) {
                throw new RequiredError('content_type','Required parameter content_type was null or undefined when calling createTemplateClassic.');
            }
            const localVarPath = `/rest/openehr/v1/definition/template/adl1.4`;
            // use dummy base URL string because the URL constructor only accepts absolute URLs.
            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
            let baseOptions;
            if (configuration) {
                baseOptions = configuration.baseOptions;
            }
            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
            const localVarHeaderParameter = {} as any;
            const localVarQueryParameter = {} as any;

            if (open_ehr_version !== undefined && open_ehr_version !== null) {
                localVarHeaderParameter['openEHR-VERSION'] = String(open_ehr_version);
            }

            if (open_ehr_audit_details !== undefined && open_ehr_audit_details !== null) {
                localVarHeaderParameter['openEHR-AUDIT_DETAILS'] = String(open_ehr_audit_details);
            }

            if (content_type !== undefined && content_type !== null) {
                localVarHeaderParameter['Content-Type'] = String(content_type); // !!!
            }

            if (accept !== undefined && accept !== null) {
                localVarHeaderParameter['Accept'] = String(accept);
            }

            if (prefer !== undefined && prefer !== null) {
                localVarHeaderParameter['Prefer'] = String(prefer);
            }

            localVarHeaderParameter['Content-Type'] = 'application/json'; /// !!!

            const query = new URLSearchParams(localVarUrlObj.search);
            for (const key in localVarQueryParameter) {
                query.set(key, localVarQueryParameter[key]);
            }
            for (const key in options.params) {
                query.set(key, options.params[key]);
            }
            localVarUrlObj.search = (new URLSearchParams(query)).toString();
            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");

            return {
                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
                options: localVarRequestOptions,
            };
        },

Same problem.
Could that be fixed somehow on server side?

PS NSwag does one more wrong thing:

const content_ = JSON.stringify(body); 

This will not work with XML, so I guess that there is something wrong with server Accept headers as well.

Hi Dmitry,

I’m not really sure what you are doing and what the problem is. :slight_smile: With what source or input files did you run those code generators?
And is this an EHRbase question (as you used the tag) or is this more about the general openEHR REST API?

By the way, we know there are some quirks in the current version of the openEHR REST API (e.g. overlapping path). Thus it is incompatible with certain tools like generators. But, there is some movement around the REST API and there might be a compliant OpenAPI spec in the future (@sebastian.iancu @Seref ).

Hi Jake, my question is about the ehrbase server (GitHub - ehrbase/ehrbase: An open source openEHR server) REST API implementation. Do I use incorrect tag?

No, the tag is fine. I just don’t see the connection from the question to EHRbase.

You would need some input for those generators, don’t you? As far as I remember EHRbase has no prepared swagger file. Did you extract it from the running server or point the tools to the swagger UI?

If this indeed is an EHRbase specific question we will have to wait for feedback from the EHRbase folks.

Did you extract it from the running server
Exactly. As a lazy beginner I prefer not to build requests manually and rely on code generators :innocent:

That’s a totally fair approach :slight_smile: It was a missing piece of information for the question, though. Okay, but then this really depends on the EHRbase implementation and I’m handing over to them.

FYI: The official REST API definition lives here: GitHub - openEHR/specifications-ITS-REST: openEHR REST API Specifications

That’s one way to say it, but you could also say as a software developer you’re expecting to find a computable version of the API specification so that you can minimise mistakes :slight_smile: At this point in time, it’d be up to the good people of EhrBase team to help you though. May I suggest you change the title of the question to indicate it’s related to EhrBase? (I’m assuming that’s possible) That’s likely to get you some attention faster.