client

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 7, 2022 License: GPL-3.0 Imports: 21 Imported by: 0

README

NHS-FHIR

Go Reference Build report

A Go client for NHS FHIR API.

This client allows you to access the PDS (Patient Demographic Service) which is the national database of NHS patient details.

You can retrieve a patients name, date of birth, address, registered GP and much more.

Installing

To install this library use:

go get github.com/welldigital/nhs-fhir

Getting started

You use this library by creating a new client and calling methods on the client.

package main

import (
	"context"
	"fmt"

	client "github.com/welldigital/nhs-fhir"
)

func main() {
	cli := client.NewClient(nil)
	ctx := context.Background()
	p, resp, err := cli.Patient.Get(ctx, "9000000009")

	if err != nil {
		panic(err)
	}

	fmt.Println(p)

	fmt.Println(resp)
}

Authentication

The easiest and recommended way to do this is using the oauth2 library, but you can always use any other library that provides a http.Client. If you have an OAuth2 access token you can use it like so:

import (
	"golang.org/x/oauth2"
	client "github.com/welldigital/nhs-fhir"
)

func main() {
	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: "... your access token ..."},
	)
	tc := oauth2.NewClient(ctx, ts)

	c := client.NewClient(tc)

	p, resp, err := cli.Patient.Get(ctx, "9000000009")
	// ...
}
Authentication with JWT

This example shows how you can gain access to the restricted API's (integration, prod) using your RSA private key. Make sure you follow the instructions outlined on the NHS website first.


import (
	"log"
	client "github.com/welldigital/nhs-fhir"
)

func main() {
	opts := &client.Options{
			AuthConfigOptions: &client.AuthConfigOptions{
				ClientID:          "your-nhs-app-id",
				Kid:               "test-1",
				BaseURL:           "https://int.api.service.nhs.uk",
				PrivateKeyPemFile: "path/to/private/key/key.pem",
			},
			BaseURL: "https://int.api.service.nhs.uk",
	}
	cli, err := client.NewClientWithOptions(opts)

	if err != nil {
		log.Fatalf("error init client: %v", err)
	}

	p, resp, err := cli.Patient.Get(ctx, "9449304424")
}

Authentication with AWS KMS

This example shows you can authenticate with the AWS Key management service (KMS) by using the KMS to sign your jwt token. Don't forget to pass in your aws credentials!


import(
	"context"
	"log"
	"github.com/aws/aws-sdk-go-v2/service/kms"
	"github.com/golang-jwt/jwt"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/welldigital/jwt-go-aws-kms/jwtkms"
	client "github.com/welldigital/nhs-fhir"
)

func Signer(config *jwtkms.Config) func(token *jwt.Token, key interface{}) (string, error) {
	return func(token *jwt.Token, key interface{}) (string, error) {
		return token.SignedString(config)
	}
}

func main() {
	awsCfg, err := config.LoadDefaultConfig(context.Background(),
		config.WithRegion("REGION"))
		
	if err != nil {
		panic(err)
	}

	kmsClient := kms.NewFromConfig(awsCfg)

	ctx := context.Background()

	kmsConfig := jwtkms.NewKMSConfig(kmsClient, "your private key id from kms", false)

	signerFunc := Signer(kmsConfig.WithContext(ctx))

	opts := &client.Options{
		AuthConfigOptions: &client.AuthConfigOptions{
			ClientID:          "your-nhs-app-id",
			Kid:               "test-1",
			BaseURL:           "https://int.api.service.nhs.uk",
			Signer:            signerFunc,
			SigningMethod:     jwtkms.SigningMethodRS512,
		},
		BaseURL: "https://int.api.service.nhs.uk",
	}
	cli, err := client.NewClientWithOptions(opts)

	if err != nil {
		log.Fatalf("error init client: %v", err)
	}
	p, resp, err := cli.Patient.Get(ctx, "9449304424")

}

Services

The client contains services which can be used to get the data you require.

Patient Service

The patient service contains methods for getting a patient from the PDS either using their NHS number or the PatientSearchOptions.

Roadmap

The following pieces of work still need to be done:

Contributing

If you wish to contribute to the project then open a Pull Request outlining what you want to do and why.

We can then discuss how the feature might be done and then you can create a new branch from which you can develop this feature. Please add tests where appropriate and add documentation where necessary.

Testing

Tests are written preferably in a table driven manner where it makes sense.

Consider using interfaces as it makes the process of testing easier because we can control external parts of the system and only test the parts we are interested in. Read this for more info.

To assist in testing we use a tool called moq which generates a struct from any interface. This then allows us to mock an interface in test code.

Release

Releases are handled automatically by semantic-release which is run whenever a commit is pushed to the branch named 'main'. This is done by the github-action found in .github/workflows/release.yml.

Semantic release requires a github personal access token to make releases to protected branches. Your commit messages need to be in the conventional commits format e.g.

feat(feature-name): your message

or

fix: your message
  • feat stands for feature. Other acceptable values are feat, fix, chore, docs and BREAKING CHANGE. This determines what version release is made.
  • feature-name (optional) is the name of the feature, must be lower case with no gaps. This will be included as items in the change log.
  • your message is the changes you've made in the commit. This will make up most of the auto generated change log.
Pre-releases

This repo supports the use of pre-releases. Any work which will have a lot of breaking changes should be done on either an alpha or beta branch which are both marked as prerelease branches as shown in .releaserc. This is to avoid creating a lot of un-necessary versions.

For more information on how pre-release branches work see the documentation.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrBaseUrlMissing = errors.New("auth base url is missing but required")
View Source
var ErrClientIdMissing = errors.New("client id is missing but required")
View Source
var ErrInvalidSigningMethodAlg = errors.New("signing method must be RSA")
View Source
var ErrKeyMissing = errors.New("private key or private key file must be specified")
View Source
var ErrKidMissing = errors.New("kid is missing but required")
View Source
var ErrUrlHostMissing error = errors.New("url host is empty")
View Source
var ErrUrlSchemeMissing error = errors.New("url scheme is empty")

Functions

func IsAbsoluteUrl added in v1.1.0

func IsAbsoluteUrl(str string) error

Types

type AccessTokenRequest added in v1.1.0

type AccessTokenRequest struct {
	GrantType           string `url:"grant_type"`
	ClientAssertionType string `url:"client_assertion_type"`
	JWT                 string `url:"client_assertion"`
}

type AccessTokenResponse added in v1.1.0

type AccessTokenResponse struct {
	// AccessToken used to call NHS restricted API's
	AccessToken string `json:"access_token"`
	// ExpiresIn the time in seconds that the token will expire in.
	ExpiresIn int64 `json:"expires_in,string"`
	// TokenType = "bearer"
	TokenType string `json:"token_type"`
	// timestamp of when the token was issued in milliseconds
	IssuedAt int64 `json:"issued_at,string"`
}

func (AccessTokenResponse) ExpiryTime added in v1.1.0

func (a AccessTokenResponse) ExpiryTime() time.Time

func (AccessTokenResponse) HasExpired added in v1.1.0

func (a AccessTokenResponse) HasExpired() bool

type AuthConfigOptions added in v1.1.0

type AuthConfigOptions struct {
	// BaseURL the url for auth
	BaseURL string

	// ClientID the api key of your nhs application
	ClientID string
	// Kid is a header used as a key identifier to identify which key to look up to sign a particular token
	// When used with a JWK, the kid value is used to match a JWK kid parameter value.
	Kid string
	// PrivateKeyPemFile file location to your private RSA key
	PrivateKeyPemFile string

	// PrivateKey the value of your private key
	PrivateKey []byte

	// Signer is a function you can use to sign your own tokens
	Signer SigningFunc

	// SigningMethod to be used when signing/verifing tokens, must be RSA
	SigningMethod jwt.SigningMethod
}

AuthConfigOptions the options used for JWT Auth

func (AuthConfigOptions) Validate added in v1.1.0

func (c AuthConfigOptions) Validate() error

type Claims added in v1.1.0

type Claims struct {
	*jwt.StandardClaims
}

type Client

type Client struct {
	BaseURL   *url.URL
	UserAgent string

	Patient *PatientService
	// contains filtered or unexported fields
}

Client manages communication with the NHS FHIR API.

func NewClient

func NewClient(httpClient *http.Client) *Client

NewClient returns a new FHIR client. If a nil httpClient is provided then a new http.client will be used. To use API methods requiring auth then provide a http.Client which will perform the authentication for you e.g. oauth2

func NewClientWithOptions added in v1.1.0

func NewClientWithOptions(opts *Options) (*Client, error)

NewClientWithOptions takes in some options to create the client with. If no options are given then its treated the same as NewClient(nil)

type DateParam

type DateParam struct {
	Prefix Prefix
	Value  time.Time
}

DateParam is a struct containing a prefix and a timestamp.

func (*DateParam) String

func (d *DateParam) String() string

String converts DateParam to a string. Useful for logging or as parameters to other funcs

type Gender

type Gender string

Gender the gender that the person is born as

const (
	Male    Gender = "male"
	Female  Gender = "female"
	Other   Gender = "other"
	Unknown Gender = "unknown"
)

List of genders

func (Gender) String

func (g Gender) String() string

String returns gender as a string

type IClient

type IClient interface {
	// contains filtered or unexported methods
}

IClient interface for Client

type IClientMock

type IClientMock struct {
	// contains filtered or unexported fields
}

IClientMock is a mock implementation of IClient.

func TestSomethingThatUsesIClient(t *testing.T) {

	// make and configure a mocked IClient
	mockedIClient := &IClientMock{
		baseURLGetterFunc: func() *url.URL {
			panic("mock out the baseURLGetter method")
		},
		doFunc: func(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
			panic("mock out the do method")
		},
		dumpHTTPFunc: func(req *http.Request, resp *http.Response) error {
			panic("mock out the dumpHTTP method")
		},
		newRequestFunc: func(method string, path string, body interface{}) (*http.Request, error) {
			panic("mock out the newRequest method")
		},
		postFormFunc: func(ctx context.Context, urlMoqParam string, data url.Values, v interface{}) (*Response, error) {
			panic("mock out the postForm method")
		},
	}

	// use mockedIClient in code that requires IClient
	// and then make assertions.

}

type Options added in v1.1.0

type Options struct {
	*http.Client
	*AuthConfigOptions
	BaseURL   string
	UserAgent string
	*TracingOptions
}

Options to configure the client with

type PatientSearchOptions

type PatientSearchOptions struct {
	// A fuzzy search is performed, including checks for homophones, transposed names and historic information.
	// You cant use wildcards with fuzzy search
	FuzzyMatch *bool `url:"_fuzzy-match,omitempty"`
	// The search only returns results where the score field is 1.0. Use this with care - it is unlikely to work with fuzzy search or wildcards.
	ExactMatch *bool `url:"_exact-match,omitempty"`
	// The search looks for matches in historic information such as previous names and addresses.
	// This parameter has no effect for a fuzzy search, which always includes historic information.
	History *bool `url:"_history,omitempty"`
	// For application-restricted access, this must be 1
	MaxResults int `url:"_max-results"`
	// if used with wildcards, fuzzy match must be false. Wildcards must contain at least two characters, this matches Smith, Smythe. Not case-sensitive.
	Family *string `url:"family,omitempty"`
	// The patients given name, can be used with wildcards. E.g. Jane Anne Smith
	// Use * as a wildcard but not in the first two characters and not in fuzzy search mode
	Given  *[]string `url:"given,omitempty"`
	Gender *Gender   `url:"gender,omitempty"`
	// Format: <eq|ge|le>yyyy-mm-dd e.g. eq2021-08-01
	BirthDate []*string `url:"birthdate,omitempty"`
	// For a fuzzy search, this is ignored for matching but included in the score calculation.
	// Format: <eq|ge|le>yyyy-mm-dd e.g. eq2021-08-01
	DeathDate *[]string `url:"death-date,omitempty"`
	// Not case sensitive. Spaces are ignored, for example LS16AE and LS1 6AE both match LS1 6AE
	Postcode *string `url:"address-postcode,omitempty"`
	// The Organisation Data Service (ODS) code of the patient's registered GP practice.
	// Not case sensitive. For a fuzzy search, this is ignored for matching but included in the score calculation.
	// Example: Y12345
	GeneralPractioner *string `url:"general-practitioner,omitempty"`
}

PatientSearchOptions is the options we pass into the request for searching a patient

type PatientService

type PatientService = service

PatientService service used to interact with patient details

func (*PatientService) Get

Get gets a patient from the PDS using the patients NHS number as the id. id = The patient's NHS number. The primary identifier of a patient, unique within NHS England and Wales. Always 10 digits and must be a valid NHS number.

func (*PatientService) Search

Search searches for a patient in the PDS The behaviour of this endpoint depends on your access mode: https://digital.nhs.uk/developer/api-catalogue/personal-demographics-service-fhir#api-Default-search-patient

type Prefix

type Prefix string

Prefix is an enum representing FHIR parameter prefixes. The following description is from the FHIR DSTU2 specification:

For the ordered parameter types number, date, and quantity, a prefix to the parameter value may be used to control the nature of the matching.

const (
	EQ Prefix = "eq"
	GE Prefix = "ge"
	LE Prefix = "le"
)

Constant values for the Prefix enum.

func (Prefix) String

func (p Prefix) String() string

String returns the prefix as a string.

type RateLimitError added in v1.1.0

type RateLimitError struct {
}

RateLimitError contains information relating to this type of error

func (*RateLimitError) Error added in v1.1.0

func (e *RateLimitError) Error() string

type Response

type Response struct {
	*http.Response
	// RequestID contains a string which is used to uniquely identify the request
	// Used for debugging or support
	RequestID string
}

Response is for all API responses, it contains the http response.

type SigningFunc added in v1.1.0

type SigningFunc = func(token *jwt.Token, key interface{}) (string, error)

SigningFunc used to sign your own JWT tokens returns a signed token

type TracingOptions added in v1.1.0

type TracingOptions struct {
	// Enabled set to true to enable ALL tracing
	Enabled bool
	// TraceErrorsOnly set to true to limit tracing to errors only
	TraceErrorsOnly bool
	// Output allows you to configure where the log output is outputted to. Defaults to os.Stdout
	Output io.Writer
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL