Skip to content

Commit 41a4422

Browse files
CROSSLINK-183 sync NCIP client (#342)
1 parent d9041df commit 41a4422

File tree

6 files changed

+1129
-0
lines changed

6 files changed

+1129
-0
lines changed

broker/ncipclient/ncipclient.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package ncipclient
2+
3+
import "github.com/indexdata/crosslink/ncip"
4+
5+
type NcipMode string
6+
7+
const (
8+
ModeDisabled NcipMode = "disabled"
9+
ModeAuto NcipMode = "auto"
10+
ModeManual NcipMode = "manual"
11+
)
12+
13+
type NcipProperty string
14+
15+
const (
16+
Ncip NcipProperty = "ncip"
17+
FromAgency NcipProperty = "from_agency"
18+
FromAgencyAuthentication NcipProperty = "from_agency_authentication"
19+
ToAgency NcipProperty = "to_agency"
20+
Address NcipProperty = "address"
21+
LookupUserMode NcipProperty = "lookup_user_mode"
22+
AcceptItemMode NcipProperty = "accept_item_mode"
23+
RequestItemMode NcipProperty = "request_item_mode"
24+
CreateUserFiscalTransactionMode NcipProperty = "create_user_fiscal_transaction_mode"
25+
)
26+
27+
// NcipClient defines the interface for NCIP operations
28+
// customData is from the DirectoryEntry.CustomData field
29+
type NcipClient interface {
30+
// LookupUser performs user authentication.
31+
// Returns true if authentication is successful (disabled or auto and NCIP lookup succeeded)
32+
// Returns false if authentication is manual
33+
// Returns an error otherwise (failed NCIP lookup, misconfiguration, etc)
34+
LookupUser(customData map[string]any, arg ncip.LookupUser) (bool, error)
35+
36+
// AcceptItem accepts an item.
37+
// Returns true if accept is successful (disabled or auto and NCIP accept succeeded)
38+
// Returns false if accept is manual
39+
// Returns an error otherwise (failed NCIP accept, misconfiguration, etc)
40+
AcceptItem(customData map[string]any, arg ncip.AcceptItem) (bool, error)
41+
42+
DeleteItem(customData map[string]any, arg ncip.DeleteItem) error
43+
44+
// RequestItem requests an item.
45+
// Returns true if request is successful (disabled or auto and NCIP request succeeded)
46+
// Returns false if request is manual
47+
// Returns an error otherwise (failed NCIP request, misconfiguration, etc)
48+
RequestItem(customData map[string]any, arg ncip.RequestItem) (bool, error)
49+
50+
CancelRequestItem(customData map[string]any, arg ncip.CancelRequestItem) error
51+
52+
CheckInItem(customData map[string]any, arg ncip.CheckInItem) error
53+
54+
CheckOutItem(customData map[string]any, arg ncip.CheckOutItem) error
55+
56+
// CreateUserFiscalTransaction creates a user fiscal transaction.
57+
// Returns true if creation is successful (disabled or auto and NCIP creation succeeded)
58+
// Returns false if creation is manual
59+
// Returns an error otherwise (failed NCIP creation, misconfiguration, etc)
60+
CreateUserFiscalTransaction(customData map[string]any, arg ncip.CreateUserFiscalTransaction) (bool, error)
61+
}
62+
63+
type NcipError struct {
64+
Message string
65+
Problem ncip.Problem
66+
}
67+
68+
func (e *NcipError) Error() string {
69+
s := e.Message
70+
if e.Problem.ProblemType.Text != "" {
71+
s += ": " + e.Problem.ProblemType.Text
72+
}
73+
if e.Problem.ProblemDetail != "" {
74+
s += ": " + e.Problem.ProblemDetail
75+
}
76+
return s
77+
}
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
package ncipclient
2+
3+
import (
4+
"encoding/xml"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/indexdata/crosslink/httpclient"
9+
"github.com/indexdata/crosslink/ncip"
10+
)
11+
12+
type NcipClientImpl struct {
13+
client *http.Client
14+
}
15+
16+
func CreateNcipClient(client *http.Client) NcipClient {
17+
return &NcipClientImpl{
18+
client: client,
19+
}
20+
}
21+
22+
func (n *NcipClientImpl) LookupUser(customData map[string]any, lookup ncip.LookupUser) (bool, error) {
23+
ncipInfo, err := n.getNcipInfo(customData)
24+
if err != nil {
25+
return false, err
26+
}
27+
handle, ret, err := n.checkMode(ncipInfo, LookupUserMode)
28+
if handle {
29+
return ret, err
30+
}
31+
lookup.InitiationHeader = n.prepareHeader(ncipInfo, lookup.InitiationHeader)
32+
33+
ncipMessage := &ncip.NCIPMessage{
34+
LookupUser: &lookup,
35+
}
36+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
37+
if err != nil {
38+
return false, err
39+
}
40+
lookupUserResponse := ncipResponse.LookupUserResponse
41+
if lookupUserResponse == nil {
42+
return false, fmt.Errorf("invalid NCIP response: missing LookupUserResponse")
43+
}
44+
return true, n.checkProblem("NCIP user lookup", lookupUserResponse.Problem)
45+
}
46+
47+
func (n *NcipClientImpl) AcceptItem(customData map[string]any, accept ncip.AcceptItem) (bool, error) {
48+
ncipInfo, err := n.getNcipInfo(customData)
49+
if err != nil {
50+
return false, err
51+
}
52+
handle, ret, err := n.checkMode(ncipInfo, AcceptItemMode)
53+
if handle {
54+
return ret, err
55+
}
56+
accept.InitiationHeader = n.prepareHeader(ncipInfo, accept.InitiationHeader)
57+
ncipMessage := &ncip.NCIPMessage{
58+
AcceptItem: &accept,
59+
}
60+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
61+
if err != nil {
62+
return false, err
63+
}
64+
acceptItemResponse := ncipResponse.AcceptItemResponse
65+
if acceptItemResponse == nil {
66+
return false, fmt.Errorf("invalid NCIP response: missing AcceptItemResponse")
67+
}
68+
return true, n.checkProblem("NCIP accept item", acceptItemResponse.Problem)
69+
}
70+
71+
func (n *NcipClientImpl) DeleteItem(customData map[string]any, delete ncip.DeleteItem) error {
72+
ncipInfo, err := n.getNcipInfo(customData)
73+
if err != nil {
74+
return err
75+
}
76+
delete.InitiationHeader = n.prepareHeader(ncipInfo, delete.InitiationHeader)
77+
ncipMessage := &ncip.NCIPMessage{
78+
DeleteItem: &delete,
79+
}
80+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
81+
if err != nil {
82+
return err
83+
}
84+
deleteItemResponse := ncipResponse.DeleteItemResponse
85+
if deleteItemResponse == nil {
86+
return fmt.Errorf("invalid NCIP response: missing DeleteItemResponse")
87+
}
88+
return n.checkProblem("NCIP delete item", deleteItemResponse.Problem)
89+
}
90+
91+
func (n *NcipClientImpl) RequestItem(customData map[string]any, request ncip.RequestItem) (bool, error) {
92+
ncipInfo, err := n.getNcipInfo(customData)
93+
if err != nil {
94+
return false, err
95+
}
96+
handle, ret, err := n.checkMode(ncipInfo, RequestItemMode)
97+
if handle {
98+
return ret, err
99+
}
100+
request.InitiationHeader = n.prepareHeader(ncipInfo, request.InitiationHeader)
101+
ncipMessage := &ncip.NCIPMessage{
102+
RequestItem: &request,
103+
}
104+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
105+
if err != nil {
106+
return false, err
107+
}
108+
requestItemResponse := ncipResponse.RequestItemResponse
109+
if requestItemResponse == nil {
110+
return false, fmt.Errorf("invalid NCIP response: missing RequestItemResponse")
111+
}
112+
return true, n.checkProblem("NCIP request item", requestItemResponse.Problem)
113+
}
114+
115+
func (n *NcipClientImpl) CancelRequestItem(customData map[string]any, request ncip.CancelRequestItem) error {
116+
ncipInfo, err := n.getNcipInfo(customData)
117+
if err != nil {
118+
return err
119+
}
120+
request.InitiationHeader = n.prepareHeader(ncipInfo, request.InitiationHeader)
121+
ncipMessage := &ncip.NCIPMessage{
122+
CancelRequestItem: &request,
123+
}
124+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
125+
if err != nil {
126+
return err
127+
}
128+
cancelRequestItemResponse := ncipResponse.CancelRequestItemResponse
129+
if cancelRequestItemResponse == nil {
130+
return fmt.Errorf("invalid NCIP response: missing CancelRequestItemResponse")
131+
}
132+
return n.checkProblem("NCIP cancel request item", cancelRequestItemResponse.Problem)
133+
}
134+
135+
func (n *NcipClientImpl) CheckInItem(customData map[string]any, request ncip.CheckInItem) error {
136+
ncipInfo, err := n.getNcipInfo(customData)
137+
if err != nil {
138+
return err
139+
}
140+
request.InitiationHeader = n.prepareHeader(ncipInfo, request.InitiationHeader)
141+
ncipMessage := &ncip.NCIPMessage{
142+
CheckInItem: &request,
143+
}
144+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
145+
if err != nil {
146+
return err
147+
}
148+
checkInItemResponse := ncipResponse.CheckInItemResponse
149+
if checkInItemResponse == nil {
150+
return fmt.Errorf("invalid NCIP response: missing CheckInItemResponse")
151+
}
152+
return n.checkProblem("NCIP check in item", checkInItemResponse.Problem)
153+
}
154+
155+
func (n *NcipClientImpl) CheckOutItem(customData map[string]any, request ncip.CheckOutItem) error {
156+
ncipInfo, err := n.getNcipInfo(customData)
157+
if err != nil {
158+
return err
159+
}
160+
request.InitiationHeader = n.prepareHeader(ncipInfo, request.InitiationHeader)
161+
ncipMessage := &ncip.NCIPMessage{
162+
CheckOutItem: &request,
163+
}
164+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
165+
if err != nil {
166+
return err
167+
}
168+
checkOutItemResponse := ncipResponse.CheckOutItemResponse
169+
if checkOutItemResponse == nil {
170+
return fmt.Errorf("invalid NCIP response: missing CheckOutItemResponse")
171+
}
172+
return n.checkProblem("NCIP check out item", checkOutItemResponse.Problem)
173+
}
174+
175+
func (n *NcipClientImpl) CreateUserFiscalTransaction(customData map[string]any, request ncip.CreateUserFiscalTransaction) (bool, error) {
176+
ncipInfo, err := n.getNcipInfo(customData)
177+
if err != nil {
178+
return false, err
179+
}
180+
handle, ret, err := n.checkMode(ncipInfo, CreateUserFiscalTransactionMode)
181+
if handle {
182+
return ret, err
183+
}
184+
request.InitiationHeader = n.prepareHeader(ncipInfo, request.InitiationHeader)
185+
186+
ncipMessage := &ncip.NCIPMessage{
187+
CreateUserFiscalTransaction: &request,
188+
}
189+
ncipResponse, err := n.sendReceiveMessage(ncipInfo, ncipMessage)
190+
if err != nil {
191+
return false, err
192+
}
193+
createUserFiscalTransactionResponse := ncipResponse.CreateUserFiscalTransactionResponse
194+
if createUserFiscalTransactionResponse == nil {
195+
return false, fmt.Errorf("invalid NCIP response: missing CreateUserFiscalTransactionResponse")
196+
}
197+
return true, n.checkProblem("NCIP create user fiscal transaction", createUserFiscalTransactionResponse.Problem)
198+
}
199+
200+
func (n *NcipClientImpl) checkProblem(op string, responseProblems []ncip.Problem) error {
201+
if len(responseProblems) > 0 {
202+
return &NcipError{
203+
Message: op + " failed",
204+
Problem: responseProblems[0],
205+
}
206+
}
207+
return nil
208+
}
209+
210+
func (n *NcipClientImpl) getNcipInfo(customData map[string]any) (map[string]any, error) {
211+
ncipInfo, ok := customData["ncip"].(map[string]any)
212+
if !ok {
213+
return nil, fmt.Errorf("missing ncip configuration in customData")
214+
}
215+
return ncipInfo, nil
216+
}
217+
218+
func (n *NcipClientImpl) checkMode(ncipInfo map[string]any, key NcipProperty) (bool, bool, error) {
219+
mode, ok := ncipInfo[string(key)].(string)
220+
if !ok {
221+
return true, false, fmt.Errorf("missing %s in ncip configuration", key)
222+
}
223+
switch mode {
224+
case string(ModeDisabled):
225+
return true, true, nil
226+
case string(ModeManual):
227+
return true, false, nil
228+
case string(ModeAuto):
229+
break
230+
default:
231+
return true, false, fmt.Errorf("unknown value for %s: %s", key, mode)
232+
}
233+
return false, false, nil
234+
}
235+
236+
func (n *NcipClientImpl) prepareHeader(ncipInfo map[string]any, header *ncip.InitiationHeader) *ncip.InitiationHeader {
237+
if header == nil {
238+
header = &ncip.InitiationHeader{}
239+
}
240+
from_agency, ok := ncipInfo[string(FromAgency)].(string)
241+
if !ok || from_agency == "" {
242+
from_agency = "default-from-agency"
243+
}
244+
header.FromAgencyId.AgencyId = ncip.SchemeValuePair{
245+
Text: from_agency,
246+
}
247+
to_agency, ok := ncipInfo[string(ToAgency)].(string)
248+
if !ok || to_agency == "" {
249+
to_agency = "default-to-agency"
250+
}
251+
header.ToAgencyId.AgencyId = ncip.SchemeValuePair{
252+
Text: to_agency,
253+
}
254+
if auth, ok := ncipInfo[string(FromAgencyAuthentication)].(string); ok {
255+
header.FromAgencyAuthentication = auth
256+
}
257+
return header
258+
}
259+
260+
func (n *NcipClientImpl) sendReceiveMessage(ncipInfo map[string]any, message *ncip.NCIPMessage) (*ncip.NCIPMessage, error) {
261+
url, ok := ncipInfo[string(Address)].(string)
262+
if !ok {
263+
return nil, fmt.Errorf("missing NCIP address in customData")
264+
}
265+
message.Version = ncip.NCIP_V2_02_XSD
266+
267+
var respMessage ncip.NCIPMessage
268+
err := httpclient.NewClient().RequestResponse(n.client, http.MethodPost, []string{httpclient.ContentTypeApplicationXml},
269+
url, message, &respMessage, xml.Marshal, xml.Unmarshal)
270+
if err != nil {
271+
return nil, fmt.Errorf("NCIP message exchange failed: %s", err.Error())
272+
}
273+
if len(respMessage.Problem) > 0 {
274+
return nil, &NcipError{
275+
Message: "NCIP message processing failed",
276+
Problem: respMessage.Problem[0],
277+
}
278+
}
279+
return &respMessage, nil
280+
}

0 commit comments

Comments
 (0)