Skip to content

Commit

Permalink
Add initial FHIR endpoint for CodeSystem by OID (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickclyde authored Sep 27, 2024
1 parent ea27a93 commit 5ef90ef
Show file tree
Hide file tree
Showing 7 changed files with 880 additions and 4 deletions.
7 changes: 6 additions & 1 deletion docs/examples.http
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ accept: application/json
GET https://localhost:4000/api/value-set-concepts/code-system/1.0.3166.1
accept: application/json

###
###

GET https://localhost:4000/r5/CodeSystem/2.16.840.1.113883.6.238
accept: application/json

###
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
module github.com/skylight-hq/phinvads-go

go 1.23.0
go 1.23.1

require (
github.com/a-h/templ v0.2.771
github.com/google/fhir/go v0.7.4
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.6.0
github.com/joho/godotenv v1.5.1
Expand All @@ -12,11 +13,19 @@ require (
)

require (
bitbucket.org/creachadair/stringset v0.0.9 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.25.0 // indirect
)
676 changes: 676 additions & 0 deletions go.sum

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions internal/app/fhir/r5/CodeSystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package r5

import (
"fmt"

"github.com/google/fhir/go/proto/google/fhir/proto/r5/core/codes_go_proto"
datatypespb "github.com/google/fhir/go/proto/google/fhir/proto/r5/core/datatypes_go_proto"
r5pb "github.com/google/fhir/go/proto/google/fhir/proto/r5/core/resources/code_system_go_proto"
"github.com/skylight-hq/phinvads-go/internal/database/models/xo"
)

func SerializeCodeSystemToFhir(cs *xo.CodeSystem) (*r5pb.CodeSystem, error) {
fhirCS := &r5pb.CodeSystem{
Id: newId(cs.Oid),
Status: &r5pb.CodeSystem_StatusCode{Value: codes_go_proto.PublicationStatusCode_DRAFT},
Version: newString(cs.Version),
Name: newString(cs.Name),
Description: newNullableMarkdown(cs.Definitiontext),
Experimental: newBoolean(cs.Legacyflag),
Url: newNullableUri(cs.Sourceurl),
Date: newDateTime(cs.Statusdate),
Publisher: newNullableString(cs.Distributionsourceversionname),
Title: newNullableString(cs.Assigningauthorityversionname),
Content: &r5pb.CodeSystem_ContentCode{Value: 4},
// TODO: Count
// TODO: Concept
}

fhirCS.Identifier = []*datatypespb.Identifier{
{
System: newUri("urn:ietf:rfc:3986"), // Assuming this system for oid mapping
Value: newString(fmt.Sprintf("urn:oid:%s", cs.Oid)),
},
}

fhirCS.Meta = &datatypespb.Meta{
Profile: []*datatypespb.Canonical{
{Value: "http://hl7.org/fhir/StructureDefinition/shareablecodesystem"},
},
}

fhirCS.Text = &datatypespb.Narrative{
Status: &datatypespb.Narrative_StatusCode{Value: codes_go_proto.NarrativeStatusCode_GENERATED},
Div: newXhtml("<div>Your narrative text here</div>"),
}

fhirCS.Contact = []*datatypespb.ContactDetail{
{
Name: newString("PHIN Vocabulary Services"),
Telecom: []*datatypespb.ContactPoint{
{
System: &datatypespb.ContactPoint_SystemCode{Value: 5},
Value: newString("https://www.cdc.gov/phin/php/phinvads/index.html"),
},
{
System: &datatypespb.ContactPoint_SystemCode{Value: 3},
Value: newString("[email protected]"),
},
},
},
}

return fhirCS, nil
}
66 changes: 66 additions & 0 deletions internal/app/fhir/r5/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package r5

import (
"database/sql"
"time"

datatypespb "github.com/google/fhir/go/proto/google/fhir/proto/r5/core/datatypes_go_proto"
)

func newString(value string) *datatypespb.String {
if value == "" {
return nil
}
return &datatypespb.String{Value: value}
}

func newBoolean(value bool) *datatypespb.Boolean {
return &datatypespb.Boolean{Value: value}
}

func newDateTime(t time.Time) *datatypespb.DateTime {
if t.IsZero() {
return nil
}
return &datatypespb.DateTime{
ValueUs: t.UnixMicro(),
Precision: datatypespb.DateTime_MICROSECOND,
}
}

func newNullableString(ns sql.NullString) *datatypespb.String {
if ns.Valid {
return newString(ns.String)
}
return nil
}

func newId(value string) *datatypespb.Id {
return &datatypespb.Id{Value: value}
}

func newMarkdown(value string) *datatypespb.Markdown {
return &datatypespb.Markdown{Value: value}
}

func newNullableMarkdown(ns sql.NullString) *datatypespb.Markdown {
if ns.Valid {
return newMarkdown(ns.String)
}
return nil
}

func newUri(value string) *datatypespb.Uri {
return &datatypespb.Uri{Value: value}
}

func newNullableUri(ns sql.NullString) *datatypespb.Uri {
if ns.Valid {
return newUri(ns.String)
}
return nil
}

func newXhtml(value string) *datatypespb.Xhtml {
return &datatypespb.Xhtml{Value: value}
}
58 changes: 56 additions & 2 deletions internal/app/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
"net/http"
"strconv"

"github.com/google/fhir/go/fhirversion"
"github.com/google/fhir/go/jsonformat"
"github.com/skylight-hq/phinvads-go/internal/app/fhir/r5"
"github.com/skylight-hq/phinvads-go/internal/database/models"
"github.com/skylight-hq/phinvads-go/internal/database/models/xo"
customErrors "github.com/skylight-hq/phinvads-go/internal/errors"
Expand Down Expand Up @@ -74,6 +77,58 @@ func (app *Application) getCodeSystemByID(w http.ResponseWriter, r *http.Request
json.NewEncoder(w).Encode(codeSystem)
}

func (app *Application) getFHIRCodeSystemByID(w http.ResponseWriter, r *http.Request) {
rp := app.repository

id := r.PathValue("id")
id_type, err := determineIdType(id)
if err != nil {
customErrors.BadRequest(w, r, err, app.logger)
return
}

var codeSystem *xo.CodeSystem
if id_type == "oid" {
codeSystem, err = rp.GetCodeSystemByOID(r.Context(), id)
} else {
codeSystem, err = rp.GetCodeSystemByID(r.Context(), id)
}
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
errorString := fmt.Sprintf("Error: Code System %s not found", id)
dbErr := &customErrors.DatabaseError{
Err: err,
Msg: errorString,
Method: "getCodeSystemById",
Id: id,
}
dbErr.NoRows(w, r, err, app.logger)
} else {
customErrors.ServerError(w, r, err, app.logger)
}
return
}

w.Header().Set("Content-Type", "application/json")

fhirCodeSystem, err := r5.SerializeCodeSystemToFhir(codeSystem)
if err != nil {
customErrors.ServerError(w, r, err, app.logger)
}

marshaller, err := jsonformat.NewMarshaller(false, "", "", fhirversion.R4)
if err != nil {
customErrors.ServerError(w, r, err, app.logger)
}

fhirJson, err := marshaller.MarshalResource(fhirCodeSystem)
if err != nil {
customErrors.ServerError(w, r, err, app.logger)
}

w.Write(fhirJson)
}

func (app *Application) getAllViews(w http.ResponseWriter, r *http.Request) {
views, err := models.GetAllViews(r.Context(), app.db)
if err != nil {
Expand Down Expand Up @@ -420,7 +475,6 @@ func (app *Application) getAllHotTopics(w http.ResponseWriter, r *http.Request)
rp := app.repository

hotTopics, err := rp.GetAllHotTopics(r.Context())

if err != nil {
var (
method = r.Method
Expand Down Expand Up @@ -477,7 +531,7 @@ func (app *Application) search(w http.ResponseWriter, r *http.Request, searchTer
rp := app.repository
logger := app.logger

var result = &models.CodeSystemResultRow{}
result := &models.CodeSystemResultRow{}
defaultPageCount := 5

// retrieve code system
Expand Down
2 changes: 2 additions & 0 deletions internal/app/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func (app *Application) routes() http.Handler {
mux.HandleFunc("GET /api/value-set-concepts/value-set-version/{valueSetVersionId}", app.getValueSetConceptsByVersionID)
mux.HandleFunc("GET /api/value-set-concepts/code-system/{codeSystemOid}", app.getValueSetConceptsByCodeSystemOID)

mux.HandleFunc("GET /r5/CodeSystem/{id}", app.getFHIRCodeSystemByID)

mux.HandleFunc("GET /toggle-banner/{action}", app.handleBannerToggle)
mux.HandleFunc("GET /load-hot-topics", app.getAllHotTopics)

Expand Down

0 comments on commit 5ef90ef

Please sign in to comment.