This was the project page for the Webservices.rest Module while it was being created. See that page for up-to-date documentation
Conventions
Because there are so many options when creating REST urls, we have laid out a set of conventions that all REST developers should follow. This will keep our web service api looking neat and uniform across all different types of objects and modules.
- For resource,
- GET /ws/rest/resource?q=query = search
- GET /ws/rest/resource/uuid = retrieve
- POST /ws/rest/resource = create
- POST /ws/rest/resource/uuid = update the fields specified in body content
- PUT = replace value of entire object (we don't plan to use this for now)
- DELETE /ws/rest/resource/uuid = void for data, retire for metadata
- DELETE /ws/rest/resource/uuid?purge=true = purge
- Create and Update should not use query string params, but rather send instructions in the request body
- Do not put verbs into the URL. Represent this idea with sub-resources.
- For example, instead of addNameToPerson we POST to person/uuid/names.
- URLS:
- Must be prefixed with /ws/rest/
- All lowercase
- No special characters (e.g. no spaces or underscores)
- Hyphens are ok if absolutely necessary
- No extensions allowed (extension will be used to specify json or xml content)
- Sub-resources (i.e. things like personname or patientidentifier which belong to a parent resource) should have their URIs inside the URI of their parent (like /ws/rest/person/uuid/names/nameuuid)
- Domain objects that are separately managed get their own URI. (e.g. /ws/rest/enrollment/uuid instead of /ws/rest/program/uuid/enrollments/uuid)
- Hide (aka don't expose) "helper" classes as must as possible (e.g. web service clients should never see ConceptSet, etc)
- Resource names should usually be the same as the domain objects they represent, but they may differ if the domain object name is confusing.
- For example org.openmrs.PatientProgram will be /ws/rest/enrollment
Domain Objects
Rather than creating lightweight & heavyweight versions of our objects, trying to anticipate which properties might be needed by web service clients, we imagine creating a single version of each domain object with optional filling of some properties.
When creating objects, there will be required properties and optional properties. Required properties must be provided in order to create the object.
When requesting objects, there will be default properties that are always fetched vs. extra properties that are only fetched when specifically requested. "Extra" properties will be missing/null if not requested. (See below)
Object References ("Ref")
When an object is a subresource on another object (e.g. conceptDatatype property on Concept object) the full ConceptDatatype object is not returned. Instead a "Ref" kind of class with String properties for uuid
, uri
, and a display
fills the concept.conceptDatatype property. The ref looks like this:
concept.conceptDatatype \-> { display: "Numeric", uuid: "8d4a4488-c2cc-11de-8d13-0010c6dffd0f", uri: "/ws/rest/conceptdatatype/8d4a4488-c2cc-11de-8d13-0010c6dffd0f" }
To fetch the full ConceptDatatype data, a second call to /ws/rest/conceptdatatype/8d4a4488-c2cc-11de-8d13-0010c6dffd0f
is needed.
When saving a "concept" object, the conceptDatatype property can be a ref or simply the uuid. For some metadata, the "name" is unique across all active metadata, so that can also be used in place of the uuid when saving (POST or PUT).
Retrieving optional properties
Not all properties are returned when a call is performed. There is a set of default properties for each resource/object. The default properties returned should cover the 90% use-case. This keeps the bandwidth use to a minimum.
If a webservice user wants different properties returned, they must specify a different type of representation. The request parameter "v" is used to specify this.
If all properties are desired, use the ?v=full representation. Adding ?v=default is invalid, simply leave off the parameter.
Not yet implemented: If you want only certain attributes returned, then ask for custom(prop1, prop2, prop3, subprop.prop1). If a custom property is requested that does not exist or is not allowed, that property is silently ignored.
Adding A Web Service
See this page on Adding a Web Service Step by Step Guide for Core Developers (REST 1.x)
Adding a Web Service to Your Module
See this page on Adding a Web Service Step by Step Guide for Module Developers
Concept Example
Concept Properties
// required for creation Ref conceptDatatype Ref conceptClass String name // primary name String locale // locale of primary name // always returned (in addition to above) String uuid boolean retired // optional (null unless requested) String description AuditInfo auditInfo List<Ref> names List<Ref> shortNames List<Ref> descriptions List<Ref> setMembers List<ConceptAnswer> answers // intentionally not exposed: conceptId, isSet, version
{ conceptDatatype: {display:"Numeric", uuid: "2348321402134", link:"/ws/rest/conceptdatatype/2348321402134"}, conceptClass: {display:"Finding", uuid: "nhj32fdnm2-23nmd32", link:"/ws/rest/conceptdatatype/2348321402134"}, name: "Weight (KG)", locale: "en_US", uuid: 3219fnmj23r9nmfd, retired: false, description: "A patient's weight as determined in kilograms", /\* (only returned if asked for) \*/ names: \[{display:"Weight (KG)", uuid: "3n23dnk23r", link:"ws/rest/concept/3219fnmj23r9nmfd/name/3n23dnk23r"}, {display:"Uzito (KG)", uuid: "39i324i8abf-234", link:"ws/rest/concept/3219fnmj23r9nmfd/name/39i324i8abf-234"}\], descriptions: \[\], setMembers: \[{...},{...},{...}\] }
Ref creator Date dateCreated Ref changedBy Date dateChanged Ref retiredBy Date dateRetired String retireReason Ref voidedBy Date dateVoided String voidReason
//properties String answerType // "concept", "drug", "conceptSet", or "conceptClass" Ref answer // currently answers are only allowed to be concept or drug, but we want to be future-proof // example usage if (question.answers[0].answerType == "set") { Concept\[\] members = api.getConceptSetMembers(question.answers[0].answer.uuid); // do something with each }
Standard CRUD, applied to Concept
Create a concept
POST to /ws/rest/concept
with content:
conceptDatatype: Numeric
conceptClass: test
name: HEMOGLOBIN
locale: en
description: "Stuff in the blood" (<-- this is an optional include)
returns:
success = 201 CREATED
location: http://.../ws/rest/concept/newconceptuuid
content: default rep of created concept
(on failure returns either 400 or 500 error codes)
Read a concept
GET to /ws/rest/concept?fully-specified-name=HEMOGLOBIN&locale=en
or
GET to /ws/rest/concept/2dw23-z113-234234-2sdfr3
or
GET to /ws/rest/concept?partial-name=HEMOGL&locale=en (<-- gets multiple concepts)
Update a concept
PUT to /ws/rest/concept/2dw23-z113-234234-2sdfr3
with content:
conceptClass: "Symptom"
description: "a new description"
returns:
success = 204 NO CONTENT
or no concept found for given uuid = 404 NOT FOUND error code returned
or on failure = 400 or 500 error code returned
Delete a concept
DELETE to /ws/rest/concept/2dw23-z113-234234-2sdfr3?reason=because
(delete calls do not have bodies)
returns:success = 204 NO CONTENT
Purge a concept
DELETE to /ws/rest/concept/2dw23-z113-234234-2sdfr3?purge=true
returns:
success = 204 NO CONTENT
Fuzzy/partial name search
GET to /ws/rest/concept?partial-name=weig&locale=en&concept-class=Symptom
returns:
success = 200 OK with a list of minimal patient representations as content
Find by fully specified name and locale
GET to /ws/rest/concept?fully-specified-namethe%20fully%20specified%20name?locale=en
returns:
success: 200 OK
content: default json representation of concept
no concept found for given uuid = 404 NOT FOUND
Other service calls
GET to /ws/rest/concept/thesetuuid/setmembers
GET to /ws/rest/concept/theconceptquestionuuid/answers
GET to /ws/rest/concept?mapping=sourceuuid&code=thecodeinthemapping
Collection properties of Concept
Add a name to a concept
POST to /ws/rest/concept/theconceptsuuid/name
with content:
name:a name
locale:en_US
preferred:true
type:Fully Specified
...other name properties optional
Remove a name from a concept (void the name)
DELETE to /ws/rest/concept/theconceptuuid/name/thenameuuid?reason=thereason
Add a short name to a concept
POST to /ws/rest/concept/theconceptuuid/shortname
with request body content:
name:a short name
locale:en_US
Remove a short name from a concept:
(same options as Add Name To Concept/Remove Name From Concept. The point is that short name is a separate property, not an alternate name)
Edit an existing name (only props sent will get changed)
PUT to /ws/rest/concept/theconceptuuid/name/thenameuuid
localePreferred:true
locale:en_US
name:a modified name
Add member to set needs work
PUT to /ws/rest/concept/theconceptuuid/members
beforeOtherMember:othermemberuuid (assigns isSet=true on the set if not already specified. if beforeOtherMember is null, thememberuuid is put at the end of the list)
Add many members to a set
POST to /ws/rest/concept/thesetconceptuuid/members
memberUuids:2342349323, 2349234923420394, snmcnmw4923409234234, 2349284347nhj3
(Replaces all with the given order)
Remove member from set
DELETE to /ws/rest/concept/conceptuuid/members/memberuuid
Concept subclasses (ConceptNumeric, ConceptComplex)
Are treated as optional properties on the concept class:
GET to /ws/rest/concept/349882349823
with representation: v=custom(default,conceptNumeric)
will return:
name: Weight (KG)
locale: en
retired: false
conceptNumeric:
{ hiAbsolute: 1000
hiCritical: 150
....
units: kg
precise: false
}