Skip to content

Commit

Permalink
Merge pull request #923 from OpenFn/feature/openmrs-rework-889
Browse files Browse the repository at this point in the history
openmrs: add a generic http request
  • Loading branch information
josephjclark authored Jan 20, 2025
2 parents cafc657 + 106142b commit ed4ec04
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 73 deletions.
8 changes: 8 additions & 0 deletions packages/openmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @openfn/language-openmrs

## 4.2.0

### Minor Changes

- 5d6839e: Implement namespaced http.request() function. The function makes a
call against the `instanceUrl` and the path provided, while allowing
manipulation to the API call as needed.

## 4.1.6

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/openmrs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openfn/language-openmrs",
"version": "4.1.6",
"version": "4.2.0",
"description": "OpenMRS Language Pack for OpenFn",
"homepage": "https://docs.openfn.org",
"repository": {
Expand Down
88 changes: 50 additions & 38 deletions packages/openmrs/src/Adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ export function getPatient(uuid, callback = s => s) {
return async state => {
const [resolvedUuid] = expandReferences(state, uuid);
console.log(`Fetching patient by uuid: ${resolvedUuid}`);
const { instanceUrl: baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/patient/${resolvedUuid}`
`/ws/rest/v1/patient/${resolvedUuid}`,
{
baseUrl,
}
);

console.log(`Retrieved patient with uuid: ${resolvedUuid}...`);
Expand Down Expand Up @@ -73,13 +77,15 @@ export function get(path, query, callback = s => s) {
path,
query
);

const { instanceUrl: baseUrl } = state.configuration;
const response = await request(
state,
'GET',
`/ws/rest/v1/${resolvedPath}`,
{},
resolvedQuery
{
baseUrl,
query: resolvedQuery,
}
);

// TODO: later decide if we want to throw for no-results.
Expand Down Expand Up @@ -109,12 +115,15 @@ export function get(path, query, callback = s => s) {
export function post(path, data, callback = s => s) {
return async state => {
const [resolvedPath, resolvedData] = expandReferences(state, path, data);

const { instanceUrl: baseUrl } = state.configuration;
const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedPath}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

return prepareNextState(state, response, callback);
Expand All @@ -134,16 +143,14 @@ export function post(path, data, callback = s => s) {
export function searchPatient(query, callback = s => s) {
return async state => {
const [resolvedQuery] = expandReferences(state, query);
const { instanceUrl: baseUrl } = state.configuration;

console.log('Searching for patient with query:', resolvedQuery);

const response = await request(
state,
'GET',
'/ws/rest/v1/patient',
{},
resolvedQuery
);
const response = await request(state, 'GET', '/ws/rest/v1/patient', {
baseUrl,
query: resolvedQuery,
});

return prepareNextState(state, response, callback);
};
Expand All @@ -162,16 +169,14 @@ export function searchPatient(query, callback = s => s) {
export function searchPerson(query, callback = s => s) {
return async state => {
const [resolvedQuery = {}] = expandReferences(state, query);
const { instanceUrl: baseUrl } = state.configuration;

console.log(`Searching for person with query:`, resolvedQuery);

const response = await request(
state,
'GET',
'/ws/rest/v1/person',
{},
resolvedQuery
);
const response = await request(state, 'GET', '/ws/rest/v1/person', {
baseUrl,
query: resolvedQuery,
});

console.log(`Found ${response.body.results.length} people`);

Expand All @@ -193,11 +198,15 @@ export function getEncounter(uuid, callback = s => s) {
return async state => {
const [resolvedUuid] = expandReferences(state, uuid);
console.log(`Fetching encounter with UUID: ${resolvedUuid}`);
const { instanceUrl: baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/encounter/${resolvedUuid}`
`/ws/rest/v1/encounter/${resolvedUuid}`,
{
baseUrl,
}
);

console.log(
Expand All @@ -222,14 +231,12 @@ export function getEncounters(query, callback = s => s) {
return async state => {
const [resolvedQuery] = expandReferences(state, query);
console.log('Fetching encounters by query', resolvedQuery);
const { instanceUrl: baseUrl } = state.configuration;

const response = await request(
state,
'GET',
`/ws/rest/v1/encounter/`,
{},
resolvedQuery
);
const response = await request(state, 'GET', `/ws/rest/v1/encounter/`, {
baseUrl,
query: resolvedQuery,
});
console.log(`Found ${response.body.results.length} results`);

return prepareNextState(state, response, callback);
Expand Down Expand Up @@ -308,12 +315,16 @@ export function create(resourceType, data, callback = s => s) {
data
);
console.log('Preparing to create', resolvedResource);
const { instanceUrl: baseUrl } = state.configuration;

const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedResource}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

console.log('Successfully created', resolvedResource);
Expand Down Expand Up @@ -344,12 +355,16 @@ export function update(resourceType, path, data, callback = s => s) {
data
);
console.log('Preparing to update', resolvedResource);
const { instanceUrl: baseUrl } = state.configuration;

const response = await request(
state,
'POST',
`/ws/rest/v1/${resolvedResource}/${resolvedPath}`,
resolvedData
{
baseUrl,
data: resolvedData,
}
);

console.log('Successfully updated', resolvedResource);
Expand Down Expand Up @@ -404,14 +419,11 @@ export function upsert(
"Preparing composed upsert (via 'get' then 'create' OR 'update') on",
resolvedResource
);

return await request(
state,
'GET',
`/ws/rest/v1/${resolvedResource}`,
{},
resolvedQuery
).then(resp => {
const { instanceUrl: baseUrl } = state.configuration;
return await request(state, 'GET', `/ws/rest/v1/${resolvedResource}`, {
baseUrl,
query: resolvedQuery,
}).then(resp => {
const resource = resp.body.results;
if (resource.length > 1) {
throw new RangeError(
Expand Down
48 changes: 35 additions & 13 deletions packages/openmrs/src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,50 @@ export const prepareNextState = (state, response, callback) => {
return callback(nextState);
};

export async function request(state, method, path, data, params) {
const { instanceUrl, username, password } = state.configuration;
const headers = makeBasicAuthHeader(username, password);
export async function request(state, method, path, options = {}) {
const {
baseUrl = state.configuration.instanceUrl,
query = {},
data = {},
headers = { 'content-type': 'application/json' },
parseAs = 'json',
} = options;

const options = {
if (baseUrl.length <= 0) {
throw new Error(
'Invalid instanceUrl. Include instanceUrl in state.configuration'
);
}

const isAbsoluteUrl = /^(https?:|\/\/)/i.test(path);
if (isAbsoluteUrl) {
throw new Error(
`Invalid path argument: "${path}" appears to be an absolute URL. Please provide a relative path.`
);
}

const { username, password } = state.configuration;
const authHeaders = makeBasicAuthHeader(username, password);

const opts = {
body: data,
headers: {
...authHeaders,
...headers,
'content-type': 'application/json',
},
query: params,
parseAs: 'json',
query,
parseAs,
};

const url = `${instanceUrl}${path}`;
const url = `${baseUrl}${path}`;

let allResponses;
let query = options?.query;
let allowPagination = isNaN(query?.startIndex);
let queryParams = opts?.query;
let allowPagination = isNaN(queryParams?.startIndex);

do {
const requestOptions = query ? { ...options, query } : options;
const requestOptions = queryParams ? { ...opts, query: queryParams } : opts;

const response = await commonRequest(method, url, requestOptions);
logResponse(response);

Expand All @@ -56,12 +78,12 @@ export async function request(state, method, path, data, params) {
const params = new URLSearchParams(urlObj.search);
const startIndex = params.get('startIndex');

query = { ...query, startIndex };
queryParams = { ...queryParams, startIndex };
} else {
delete allResponses.body.links;
break;
}
} while (allowPagination);

return allResponses;
}
}
44 changes: 44 additions & 0 deletions packages/openmrs/src/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expandReferences } from '@openfn/language-common/util';
import * as util from './Utils';

/**
* Options object
* @typedef {Object} OpenMRSOptions
* @property {object} query - An object of query parameters to be encoded into the URL
* @property {object} headers - An object of all request headers
* @property {object} body - The request body (as JSON)
* @property {string} [parseAs='json'] - The response format to parse (e.g., 'json', 'text', or 'stream')
*/

/**
* Make a HTTP request to any OpenMRS endpoint
* @example
* request("GET",
* "/ws/rest/v1/patient/d3f7e1a8-0114-4de6-914b-41a11fc8a1a8", {
* query:{
* limit: 1,
* offset: 20
* },
* });
* @function
* @public
* @param {string} method - HTTP method to use
* @param {string} path - Path to resource
* @param {OpenMRSOptions} [options={}] - An object containing query, headers, and body for the request
* @returns {Operation}
*/
export function request(method, path, options = {}, callback = s => s) {
return async state => {
const [resolvedMethod, resolvedPath, resolvedOptions = {}] =
expandReferences(state, method, path, options);

const response = await util.request(
state,
resolvedMethod,
resolvedPath,
resolvedOptions
);

return util.prepareNextState(state, response, callback);
};
}
1 change: 1 addition & 0 deletions packages/openmrs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export default Adaptor;

export * from './Adaptor';
export * as fhir from './fhir';
export * as http from './http'
Loading

0 comments on commit ed4ec04

Please sign in to comment.