Documentation
¶
Index ¶
- Constants
- Variables
- func APIHandlerTLSPreflight(aepr *DXAPIEndPointRequest) (err error)
- func FormatEMailCheckValid(s string) bool
- func FormatNPWPorNIKCheckValid(s string) bool
- func FormatPhoneNumberCheckValid(s string) bool
- func GetIPAddress(r *http.Request) string
- func LogExecutionTrace(ctx context.Context, phase string, requestId string, endpoint string, ...)
- func LogExecutionTraceWithStack(ctx context.Context, phase string, requestId string, endpoint string, ...)
- func NewProblemDetails(statusCode int, body utils.JSON, typeBaseURI string, instance string) utils.JSON
- func NormalizePhoneNumber(s string) (string, error)
- func OpenAPIOperationId(uri string) string
- func OpenAPIOperationIdForMethod(method, uri string) string
- func PeerIdentityFromRequest(r *http.Request) string
- func SetLogDecryptedBody(b bool)
- func TLSPreflightReport(dialAddr string) (report string, ok bool)
- func VerifiedPeerCertificate(r *http.Request) *x509.Certificate
- type DXAPI
- func (a *DXAPI) APIHandlerOpenAPI(aepr *DXAPIEndPointRequest) error
- func (a *DXAPI) APIHandlerPrintSpec(aepr *DXAPIEndPointRequest) (err error)
- func (a *DXAPI) ApplyConfigurations(configurationNameId string) (err error)
- func (a *DXAPI) BindOpenAPI(doc *DXOpenAPIDocument) error
- func (a *DXAPI) FindEndPoint(method, uri string) *DXAPIEndPoint
- func (a *DXAPI) FindEndPointByURI(uri string) *DXAPIEndPoint
- func (a *DXAPI) LoadOpenAPI(doc *DXOpenAPIDocument) error
- func (a *DXAPI) LoadOpenAPIFile(path string) error
- func (a *DXAPI) NewEndPoint(title, description, uri, method string, endPointType DXAPIEndPointType, ...) *DXAPIEndPoint
- func (a *DXAPI) NewWSEndPoint(title, description, uri, method string, onOpen DXAPIEndPointWSOpenFunc, ...) *DXAPIEndPoint
- func (a *DXAPI) OpenAPIAsJSON() ([]byte, error)
- func (a *DXAPI) OpenAPIDocument() (*DXOpenAPIDocument, error)
- func (a *DXAPI) OpenAPIDrift(doc *DXOpenAPIDocument) (specWithoutHandler, handlerWithoutSpec []string)
- func (a *DXAPI) PrintSpec() (s string, err error)
- func (a *DXAPI) RegisterHandler(operationId string, onExecute DXAPIEndPointExecuteFunc, ...)
- func (a *DXAPI) RegisterRawHandler(pattern string, handler http.Handler)
- func (a *DXAPI) RegisterWSHandler(operationId string, handler DXOpenAPIWSHandler)
- func (a *DXAPI) SetEndPointIdempotencyKey(method, uri string, key DXAPIEndPointParameter) error
- func (a *DXAPI) StartAndWait(errorGroup *errgroup.Group) error
- func (a *DXAPI) StartShutdown() (err error)
- type DXAPIAuditLogEntry
- type DXAPIDomainError
- type DXAPIEndPoint
- func (aep *DXAPIEndPoint) NewEndPointRequest(context context.Context, w http.ResponseWriter, r *http.Request) *DXAPIEndPointRequest
- func (aep *DXAPIEndPoint) NewParameter(parent *DXAPIEndPointParameter, nameId string, ...) *DXAPIEndPointParameter
- func (aep *DXAPIEndPoint) PrintSpec() (s string, err error)
- func (p *DXAPIEndPoint) WSBroadcast(message []byte) (delivered int)
- func (p *DXAPIEndPoint) WSClients() []*DXAPIEndPointWebSocketClient
- type DXAPIEndPointExecuteFunc
- type DXAPIEndPointParameter
- type DXAPIEndPointRequest
- func (aepr *DXAPIEndPointRequest) AssignParameterNullableInt64(target *utils.JSON, key string) (isExist bool, v *int64, err error)
- func (aepr *DXAPIEndPointRequest) AssignParameterNullableString(target *utils.JSON, key string) (isExist bool, v *string, err error)
- func (aepr *DXAPIEndPointRequest) DecryptedRequestDumpAsString() string
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsAny(k string) (isExist bool, val any, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsArrayOfAny(k string) (isExist bool, val []any, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsBool(k string, defaultValue ...bool) (isExist bool, val bool, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsDecimal(k string) (isExist bool, val decimal.Decimal, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsFloat32(k string) (isExist bool, val float32, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsFloat64(k string) (isExist bool, val float64, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt32(k string) (isExist bool, val int32, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt64(k string) (isExist bool, val int64, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt64s(k string) (isExist bool, val []int64, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsJSON(k string) (isExist bool, val utils.JSON, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsMapStringString(k string) (isExist bool, val map[string]string, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableInt32(k string, defaultValue ...any) (isExist bool, val *int32, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableInt64(k string, defaultValue ...any) (isExist bool, val *int64, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableString(k string, defaultValue ...any) (isExist bool, val *string, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsString(k string, defaultValue ...any) (isExist bool, val string, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsStrings(k string) (isExist bool, val []string, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueAsTime(k string) (isExist bool, val time.Time, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValueEntry(k string) (val *DXAPIEndPointRequestParameterValue, err error)
- func (aepr *DXAPIEndPointRequest) GetParameterValues() (r utils.JSON)
- func (aepr *DXAPIEndPointRequest) GetResponseWriter() *http.ResponseWriter
- func (aepr *DXAPIEndPointRequest) HTTPClient(method, url string, parameters utils.JSON, headers map[string]string) (responseStatusCode int, responseAsJSON utils.JSON, err error)
- func (aepr *DXAPIEndPointRequest) HTTPClient2(method, url string, parameters utils.JSON, headers map[string]string) (_responseStatusCode int, responseAsJSON utils.JSON, err error)
- func (aepr *DXAPIEndPointRequest) HTTPClientDo(method, url string, parameters utils.JSON, headers map[string]string) (response *http.Response, err error)
- func (aepr *DXAPIEndPointRequest) HTTPClientDoBodyAsJSONString(method, url string, parametersAsJSONString string, headers map[string]string) (response *http.Response, err error)
- func (aepr *DXAPIEndPointRequest) NewAPIEndPointRequestParameter(aepp DXAPIEndPointParameter) *DXAPIEndPointRequestParameterValue
- func (aepr *DXAPIEndPointRequest) PreProcessRequest() (err error)
- func (aepr *DXAPIEndPointRequest) ProxyHTTPAPIClient(method string, url string, bodyParameterAsJSON utils.JSON, ...) (statusCode int, r utils.JSON, err error)
- func (aepr *DXAPIEndPointRequest) RequestDump() ([]byte, error)
- func (aepr *DXAPIEndPointRequest) RequestDumpAsString() (string, error)
- func (aepr *DXAPIEndPointRequest) TranslateMessage(messageKey string) string
- func (aepr *DXAPIEndPointRequest) TranslateMessageWithArgs(messageKey string, args ...any) string
- func (aepr *DXAPIEndPointRequest) WriteResponseAndLogAsError(statusCode int, responseMessage string, err error)
- func (aepr *DXAPIEndPointRequest) WriteResponseAndNewErrorf(statusCode int, responseMessage string, msg string, data ...any) (err error)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsBytes(statusCode int, header map[string]string, bodyAsBytes []byte)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsDomainError(domainErr DXAPIDomainError)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsError(statusCode int, errToSend error)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsErrorMessageNotLogged(statusCode int, errorMsg string, reasonMsg string)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsInternalServerError(label string, err error)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsJSON(statusCode int, header map[string]string, bodyAsJSON utils.JSON)
- func (aepr *DXAPIEndPointRequest) WriteResponseAsString(statusCode int, header map[string]string, s string)
- type DXAPIEndPointRequestParameterValue
- func (aeprpv *DXAPIEndPointRequestParameterValue) GetNameIdPath() (s string)
- func (aeprpv *DXAPIEndPointRequestParameterValue) NewChild(aepp DXAPIEndPointParameter) *DXAPIEndPointRequestParameterValue
- func (aeprpv *DXAPIEndPointRequestParameterValue) SetRawValue(rv any, variablePath string) (err error)
- func (aeprpv *DXAPIEndPointRequestParameterValue) Validate() (err error)
- type DXAPIEndPointResponsePossibilities
- type DXAPIEndPointResponsePossibility
- type DXAPIEndPointType
- type DXAPIEndPointWSCloseFunc
- type DXAPIEndPointWSMessageFunc
- type DXAPIEndPointWSOpenFunc
- type DXAPIEndPointWSPeriodicFunc
- type DXAPIEndPointWebSocketClient
- type DXAPIManager
- type DXAPIUser
- type DXAuditLogHandler
- type DXOpenAPIComponents
- type DXOpenAPIDocument
- type DXOpenAPIHeader
- type DXOpenAPIInfo
- type DXOpenAPIMediaType
- type DXOpenAPIOperation
- type DXOpenAPIOrderedMap
- type DXOpenAPIParameter
- type DXOpenAPIPathItem
- type DXOpenAPIRequestBody
- type DXOpenAPIResponse
- type DXOpenAPISchema
- type DXOpenAPISchemaType
- type DXOpenAPISecurityRequirement
- type DXOpenAPISecurityScheme
- type DXOpenAPIWSHandler
- type DXOpenAPIWebSocketEndPoint
- type DXOpenAPIWebSocketEndPoints
- type ErrUniqueFieldViolation
- func (e *ErrUniqueFieldViolation) DomainErrorCode() string
- func (e *ErrUniqueFieldViolation) DomainErrorHTTPStatusCode() int
- func (e *ErrUniqueFieldViolation) DomainErrorLogDetails() string
- func (e *ErrUniqueFieldViolation) DomainErrorResponseBody() utils.JSON
- func (e *ErrUniqueFieldViolation) Error() string
Constants ¶
const ( DXAPIDefaultWriteTimeoutSec = 300 DXAPIDefaultReadTimeoutSec = 300 )
const ( // NPWP (with separators) can be 20 or 22 characters long // 15 digits: XX.XXX.XXX.X-XXX.XX // 16 digits: XX.XXX.XXX.X-XXX.XXX FormatNPWPWithSeparatorsLengthShort = 20 FormatNPWPWithSeparatorsLengthLong = 22 // NPWP without separators can be 15 or 16 digits long FormatNPWPDigitsLengthShort = 15 FormatNPWPDigitsLengthLong = 16 )
const ( // OpenAPIVersion is the version string the emitter writes and the only // major.minor the reader accepts. 3.0 uses a different schema dialect // (nullable: true, boolean exclusiveMinimum) and is refused rather than // half-read. OpenAPIVersion = "3.1.0" OpenAPIExtensionEndPointType = "x-dxlib-endpoint-type" OpenAPIExtensionPrivileges = "x-dxlib-privileges" OpenAPIExtensionMiddlewares = "x-dxlib-middlewares" OpenAPIExtensionIdempotencyKey = "x-dxlib-idempotency-key" OpenAPIExtensionRateLimitGroup = "x-dxlib-rate-limit-group" OpenAPIExtensionMaxContentLength = "x-dxlib-max-content-length" OpenAPIExtensionRequestContentType = "x-dxlib-request-content-type" OpenAPIExtensionParameters = "x-dxlib-parameters" OpenAPIExtensionType = "x-dxlib-type" OpenAPIExtensionResponseName = "x-dxlib-response-name" OpenAPIExtensionWebSocketEndPoints = "x-dxlib-websocket-endpoints" // OpenAPISecuritySchemeMutualTLS is the one security scheme this dialect // carries: its name under components/securitySchemes and its type, both // spelled as OpenAPI 3.1 spells the type. It is written exactly when the // listener refuses a caller without a valid client certificate; see // openAPIMutualTLSInForce and OPENAPI.md section 2.8. OpenAPISecuritySchemeMutualTLS = "mutualTLS" )
const ContentTypeProblemJSON = "application/problem+json"
ContentTypeProblemJSON is the media type of an RFC 9457 problem document.
const ErrorMessageIncompatibleTypeReceived = "INCOMPATIBLE_TYPE:%s(%v)_BUT_RECEIVED_(%s)=%v"
const FormatEmailMaxLength = 254
const FormatNIKLength = 16
NIK is 16 digits long
const FormatPhoneNumberMaxLength = 25
const FormatPhoneNumberMinimumLength = 3
const OpenAPIMutualTLSSchemeName = "mutualTLS"
OpenAPIMutualTLSSchemeName is the componentsecuritySchemes key used for the mutualTLS requirement. It is fixed so that a document round-trips.
const OpenAPIWebSocketExtensionDescription = "" /* 329-byte string literal not displayed */
OpenAPIWebSocketExtensionDescription is written into every emitted x-dxlib-websocket-endpoints block, so that a reader who has never seen dxlib learns from the document itself why these are not under paths.
Variables ¶
var HTTPClientDoTimeout time.Duration
HTTPClientDoTimeout bounds one outbound exchange made by HTTPClientDo and HTTPClientDoBodyAsJSONString: the dial, the request, the response, and the body read that DumpResponse performs before returning.
The zero value means no timeout, which is what every release up to and including v1.118.2 did -- so this changes nothing for a consumer that does not set it. A consumer that wants its proxy calls bounded assigns this once at start-up, before any request is served; it is not safe to change while requests are in flight.
Without a bound, a downstream service that accepts the connection and never answers holds the calling handler's goroutine and its socket open for ever. The server's own WriteTimeout drops the *caller's* connection but does not unwind the handler, so every retry against a wedged downstream stacks another leaked goroutine.
Keep any value below the server's WriteTimeoutSec (default 300), or the deadline passes before the 502 above can be written and the caller gets a torn connection instead of an answer.
var OnBeforePreProcessRequest func(aepr *DXAPIEndPointRequest) error
OnBeforePreProcessRequest is an OPTIONAL transport-authentication hook invoked by routeHandler IMMEDIATELY BEFORE PreProcessRequest() — i.e. before ANY request body handling, and before the E2EE inner-decrypt that PreProcessRequest triggers (OnE2EEV3Unpack / OnE2EEV4Unpack).
Purpose (F-BE-02 / BUG-SEC-121): let a host authenticate the transport (e.g. validate the api-proxy-v4 X-Proxy-Token) BEFORE the framework decrypts / bootstraps a session / checks replay, so an unauthenticated caller can never reach those primitives.
Contract:
- nil (default) => no-op, fully backward compatible (existing services behave exactly as before).
- non-nil and returns nil => request proceeds to PreProcessRequest as normal.
- non-nil and returns error => routeHandler REJECTS the request with 401 and does NOT call PreProcessRequest (no decrypt, no session create, no replay check). The hook may write its own response; if it hasn't, routeHandler writes a generic 401.
It is a package-level var (mirroring OnE2EEV4Unpack) so a host library (bms-common) wires it once. Only services that wire it are affected; it applies to every routeHandler request in that process, which for the mobile brokers is exactly the E2EE endpoints (health/OAM runs on a separate listener and never passes through routeHandler).
var OnE2EEPrekeyPack func(aepr *DXAPIEndPointRequest, preKeyIndex string, edB0PrivateKeyAsBytes []byte, sharedKey2AsBytes []byte, payloads ...*lv.LV) (dataBlockEnvelopeAsHexString string, err error)
var OnE2EEPrekeyUnPack func(aepr *DXAPIEndPointRequest, prekeyIndex string, dataAsHexString string) (lvPayloadElements []*lv.LV, sharedKey2AsBytes []byte, edB0PrivateKeyAsBytes []byte, preKeyData utils.JSON, err error)
var OnE2EEV3Pack func( aepr *DXAPIEndPointRequest, state utils.JSON, payloads ...*lv.LV, ) (responseBody []byte, err error)
OnE2EEV3Pack encrypts the response and returns the bytes to write to the HTTP response body.
Implementations should:
- Read state to decide which mode to use.
- For bulk: encrypt payloads with the session AES key and HMAC, return { "connection_id": ..., "data": "<base64>" } as JSON bytes.
- For bootstrap: return { "data": "<base64 of L32V bootstrap response>" } as JSON bytes (the bootstrap response is signed but otherwise plaintext — the session key is what /v1/startup_1 establishes).
- For legacy: return plaintext JSON. The published mobile app does not decrypt responses.
var OnE2EEV3Unpack func( aepr *DXAPIEndPointRequest, bodyAsJSON utils.JSON, ) ( lvPayloadElements []*lv.LV, state utils.JSON, err error, )
OnE2EEV3Unpack decrypts an incoming V3 request body and returns the plaintext payload as LV elements plus opaque state for the matching pack call.
Implementations should:
- Detect whether the body is bootstrap, bulk, or legacy.
- For bulk: load the AES + HMAC keys from Redis under connection_id, verify HMAC, AES-CBC decrypt, return the plaintext as LVs.
- For bootstrap: parse the L32V client public keys, run ECDH + optionally ML-KEM, persist the session in Redis, set state["mode"] so Pack knows to return the L32V bootstrap response, and place the L32V bootstrap response into the LV slice OR into state for Pack to retrieve.
- For legacy: run OpenSSL EVP_BytesToKey + AES-CBC-PKCS7 decrypt. Set state["legacy"] = true so Pack returns plaintext (legacy mobile does not decrypt responses).
- Set state with whatever Pack needs.
- On any rejection, return an error with a stable code so the surrounding api endpoint preprocessing can map it to the right HTTP response.
var OnE2EEV4Pack func( aepr *DXAPIEndPointRequest, state utils.JSON, payloads ...*lv.LVLE, ) (responseBody []byte, err error)
OnE2EEV4Pack encrypts the response and returns the bytes to write to the HTTP response body. Payloads are LVLE (little-endian) containers.
var OnE2EEV4Unpack func( aepr *DXAPIEndPointRequest, bodyAsJSON utils.JSON, ) ( lvPayloadElements []*lv.LVLE, state utils.JSON, err error, )
OnE2EEV4Unpack decrypts an incoming V4 request body and returns the plaintext payload as LVLE elements plus opaque state for the matching pack call.
var SpecFormat = "MarkDown"
var UseResponseDataObject = true
Functions ¶
func APIHandlerTLSPreflight ¶
func APIHandlerTLSPreflight(aepr *DXAPIEndPointRequest) (err error)
APIHandlerTLSPreflight serves TLSPreflightReport as text/plain, 200 when every block passed and 503 otherwise, so a deploy pipeline can gate on the status alone.
The dial target is not taken from the request. It comes only from the http-client block's preflight-dial key: a route reachable over the network that dialled whatever host:port a caller named would be a way to open connections from inside the service to addresses of the caller's choosing. The CLI path, TLSPreflightReport(dialAddr), may take the address from a flag, because whoever runs it already has the shell.
Register it on the OAM API with an explicit HTTP method. In this product family an OAM command registered with an empty method panics at call time with OAMCommandConfigurationErrorMethodEmpty; a route that exists only to be called when something is wrong must not itself be the thing that is wrong. This is the shape the services' working ping route uses (a service's module_instance/define_oam_system.go), with only the title, path and handler changed:
oam := dxlibAPI.Manager.APIs["oam"]
oam.NewEndPoint(
"cmdTLSPreflight", "TLS preflight", "/cmdTLSPreflight", "GET",
dxlibAPI.EndPointTypeHTTPJSON, utilsHttp.RequestContentTypeNone,
nil, dxlibAPI.APIHandlerTLSPreflight, nil, nil, nil, nil, 0, "",
)
func FormatEMailCheckValid ¶
func FormatNPWPorNIKCheckValid ¶
FormatNPWPorNIKCheckValid validates if a string is a valid NPWP or NIK
func GetIPAddress ¶
GetIPAddress resolves the client IP for audit logging.
BUG-SEC-118: X-Proxy-Client-IP is checked FIRST. api-proxy-v4 resolves the real client IP against its configured trusted_proxies and sets that header on the upstream request; before this, nothing downstream read it, so the proxy did the work and the backend logged the proxy container's own address instead.
Why preferring it is SAFER, not just more accurate: the proxy builds the upstream request under a strict DEFAULT-DENY allowlist (setUpstreamHeaders in s7-api-proxy/handlers.go - F-PXY-02/F-PXY-NEW-01). Nothing from the client's outer headers or decrypted envelope propagates automatically, so a client-supplied X-Proxy-Client-IP is DROPPED and cannot survive into the upstream request. The header is therefore proxy-owned on any request that came through the proxy, while X-Forwarded-For - what this used to trust first - is attacker-settable.
LIMITATION, stated rather than implied: this makes the value trustworthy for traffic that arrives THROUGH api-proxy-v4. A service reachable by some other ingress could still be sent a forged X-Proxy-Client-IP directly, exactly as it could be sent a forged X-Forwarded-For today - so this is an improvement in the proxied path and no regression elsewhere, not a blanket guarantee. Closing that properly means ensuring the proxy is the only ingress (BUG-SEC-121's territory).
func LogExecutionTrace ¶
func LogExecutionTrace(ctx context.Context, phase string, requestId string, endpoint string, method string, startTime time.Time, statusCode int, errMsg string)
LogExecutionTrace logs execution trace information for Grafana monitoring phase: route_start, preprocess_start, preprocess_end, middleware_start, middleware_end, execute_start, execute_end, response_write, route_end
func LogExecutionTraceWithStack ¶
func LogExecutionTraceWithStack(ctx context.Context, phase string, requestId string, endpoint string, method string, startTime time.Time, statusCode int, errMsg string, stackTrace string)
LogExecutionTraceWithStack logs execution trace information with optional stack trace for Grafana monitoring
func NewProblemDetails ¶ added in v1.126.7
func NewProblemDetails(statusCode int, body utils.JSON, typeBaseURI string, instance string) utils.JSON
NewProblemDetails turns a refusal body in dxlib's own shape ({status, status_code, reason, reason_message, ...}) into an RFC 9457 problem document: type from reason (see problemType), title from the status text, status as the number, detail from reason_message, and instance when it is not empty. A detail that only repeats the status text, as it does when a refusal names no message of its own, is left out, and so is a title for a status Go has no text for. A body that already carries its own type, title, detail or instance, as a handler that writes a problem document does, keeps it. Every other member, such as fields or error_log_ref, is kept as an extension member, and so is a reason_message that is not a string.
func NormalizePhoneNumber ¶
Helper function to normalize phone numbers to E.164 format
func OpenAPIOperationId ¶
OpenAPIOperationId is the operationId of an endpoint, derived from its URI: the leading slash goes, every other slash becomes an underscore, and the braces of a path template are dropped. /cmdX is cmdX; /v2/cmdX is v2_cmdX, which is what keeps the v1 and v2 forms of a command distinct. Handlers register against this id, so the rule is part of the contract. It is the id of an endpoint that is alone on its URI; see OpenAPIOperationIdForMethod.
func OpenAPIOperationIdForMethod ¶ added in v1.126.7
OpenAPIOperationIdForMethod is the operationId of an endpoint that shares its URI with endpoints of other methods: the lower-case method, an underscore, and OpenAPIOperationId. GET and POST on /members are get_members and post_members. Every endpoint on such a URI takes this form, the first registered included, so adding a method to a URI changes the id of the one already there.
func PeerIdentityFromRequest ¶
PeerIdentityFromRequest is utilsTLS.PeerIdentity of VerifiedPeerCertificate, for the audit entry written before the request object exists.
func SetLogDecryptedBody ¶
func SetLogDecryptedBody(b bool)
SetLogDecryptedBody overrides the gate at runtime (used by tests).
func TLSPreflightReport ¶
TLSPreflightReport runs the TLS preflight over every tls block the process is configured with -- each API in the "api" configuration, and the "http-client" block -- and returns one text report. dialAddr, when not empty, is a "host:port" the client configuration is also asked to dial once; when empty, the block's own preflight-dial is used if it has one.
It is the entry point for validating an air-gapped deployment: it reads the configured certificate, key and CA files and nothing else, reports each certificate's subject, SANs, key, validity window and days remaining beside the current clock, checks the leaf chains to the configured CA pool, and prints the effective policy. A service wires it to a CLI flag (--tls-preflight) or to an OAM route through APIHandlerTLSPreflight.
The report is a description of files and settings, never of a running listener. Run it against the same configuration name the service starts with, or it describes a deployment that does not exist.
func VerifiedPeerCertificate ¶
func VerifiedPeerCertificate(r *http.Request) *x509.Certificate
VerifiedPeerCertificate is the client certificate the TLS layer verified for this request, or nil. It reads VerifiedChains, not PeerCertificates: under the "request" migration rung Go fills PeerCertificates with whatever the client sent and verifies none of it, and treating that as an identity would let a caller choose its own. When VerifiedChains is non-empty its first chain's first element is the leaf, and it is the same object as PeerCertificates[0].
Types ¶
type DXAPI ¶
type DXAPI struct {
Version string
NameId string
Address string
WriteTimeoutSec int
ReadTimeoutSec int
CORSAllowedOrigins string // comma-separated allowed origins; empty or "*" = allow all
EnableBrowserSecurityHeaders bool // when true, adds X-Content-Type-Options, HSTS, X-Frame-Options
EndPoints []DXAPIEndPoint
RawHandlers []struct {
Pattern string
Handler http.Handler
}
// TLS is the server's TLS state, built from the "tls" block beside
// "address" in the api configuration. nil means the block is absent and the
// listener is plaintext, exactly as before the block existed. A block with
// enabled=false leaves this non-nil with a nil Config, so the settings can
// still be reported.
TLS *utilsTLS.DXServerTLS
RuntimeIsActive bool
HTTPServer *http.Server
Log log.DXLog
Context context.Context
Cancel context.CancelFunc
OnAuditLogStart DXAuditLogHandler
OnAuditLogUserIdentified DXAuditLogHandler
OnAuditLogEnd DXAuditLogHandler
// ProblemDetailsEnabled answers every refusal (status 400 and above) with an
// RFC 9457 problem document (application/problem+json) instead of
// {status, status_code, reason, reason_message}. Off by default.
ProblemDetailsEnabled bool
// ProblemTypeBaseURI is put in front of the reason code to name a problem
// type, as in "https://example.com/problems/" + "UNIQUE_FIELD_VIOLATION".
ProblemTypeBaseURI string
}
func (*DXAPI) APIHandlerOpenAPI ¶
func (a *DXAPI) APIHandlerOpenAPI(aepr *DXAPIEndPointRequest) error
APIHandlerOpenAPI serves the document as application/json. It is the OpenAPI counterpart of APIHandlerPrintSpec and is registered the same way, as the OnExecute of an endpoint the service chooses the path of. That endpoint is itself in the document, as /spec is in the Markdown one.
func (*DXAPI) APIHandlerPrintSpec ¶
func (a *DXAPI) APIHandlerPrintSpec(aepr *DXAPIEndPointRequest) (err error)
func (*DXAPI) ApplyConfigurations ¶
func (*DXAPI) BindOpenAPI ¶
func (a *DXAPI) BindOpenAPI(doc *DXOpenAPIDocument) error
BindOpenAPI is LoadOpenAPI without the fatal: it returns the error and changes nothing on failure. Everything is checked and every endpoint built before the first one is appended, so a document is bound whole or not at all.
func (*DXAPI) FindEndPoint ¶ added in v1.126.7
func (a *DXAPI) FindEndPoint(method, uri string) *DXAPIEndPoint
FindEndPoint returns a copy of the endpoint registered for method on uri, or nil. Endpoints are keyed by method and URI together, so GET /members and POST /members are two endpoints.
func (*DXAPI) FindEndPointByURI ¶
func (a *DXAPI) FindEndPointByURI(uri string) *DXAPIEndPoint
FindEndPointByURI returns a copy of the first endpoint registered on uri, whatever its method, or nil. A URI may carry one endpoint per method; use FindEndPoint to pick one by method.
func (*DXAPI) LoadOpenAPI ¶
func (a *DXAPI) LoadOpenAPI(doc *DXOpenAPIDocument) error
LoadOpenAPI binds a document already in memory, fatally on any error.
func (*DXAPI) LoadOpenAPIFile ¶
LoadOpenAPIFile reads a document and binds it. Any failure -- a file that does not parse, a construct outside the dialect, a drift between document and handlers, a URI already taken -- is fatal, the way a bad address in ApplyConfigurations is: a service whose privilege declarations did not load as written must not start.
func (*DXAPI) NewEndPoint ¶
func (a *DXAPI) NewEndPoint(title, description, uri, method string, endPointType DXAPIEndPointType, contentType utilsHttp.RequestContentType, parameters []DXAPIEndPointParameter, onExecute DXAPIEndPointExecuteFunc, onWSLoop DXAPIEndPointExecuteFunc, responsePossibilities *DXAPIEndPointResponsePossibilities, middlewares []DXAPIEndPointExecuteFunc, privileges []string, requestMaxContentLength int64, rateLimitGroupNameId string) *DXAPIEndPoint
func (*DXAPI) NewWSEndPoint ¶
func (a *DXAPI) NewWSEndPoint(title, description, uri, method string, onOpen DXAPIEndPointWSOpenFunc, onMessage DXAPIEndPointWSMessageFunc, onClose DXAPIEndPointWSCloseFunc, onPeriodic DXAPIEndPointWSPeriodicFunc, periodicInterval time.Duration, middlewares []DXAPIEndPointExecuteFunc, privileges []string, rateLimitGroupNameId string) *DXAPIEndPoint
NewWSEndPoint registers a WebSocket endpoint served by the library's own lifecycle: it opens, reads, writes and closes, and calls back for the parts that differ between applications.
onMessage is where an application's protocol lives -- the bytes are never inspected here. onPeriodic runs every periodicInterval for anything that has to be pushed unasked; with no periodic hook the same tick sends a ping, which is what stops an idle connection being reaped in between. A zero interval means thirty seconds.
Use NewEndPoint with an onWSLoop instead when an endpoint wants the whole lifecycle to itself.
func (*DXAPI) OpenAPIAsJSON ¶
OpenAPIAsJSON is OpenAPIDocument rendered through DXOpenAPIDocument.AsJSON.
func (*DXAPI) OpenAPIDocument ¶
func (a *DXAPI) OpenAPIDocument() (*DXOpenAPIDocument, error)
OpenAPIDocument builds the document for every endpoint registered on the API so far. It is a pure function of the registration state: two calls with no registration in between produce identical bytes through AsJSON.
func (*DXAPI) OpenAPIDrift ¶
func (a *DXAPI) OpenAPIDrift(doc *DXOpenAPIDocument) (specWithoutHandler, handlerWithoutSpec []string)
OpenAPIDrift is the two-sided comparison behind the startup check: the operationIds in the document with no registered handler, and the registered handlers with no operationId in the document, both sorted. Either list non-empty means the document and the code describe different APIs.
func (*DXAPI) RegisterHandler ¶
func (a *DXAPI) RegisterHandler(operationId string, onExecute DXAPIEndPointExecuteFunc, middlewares ...DXAPIEndPointExecuteFunc)
RegisterHandler names the code that runs for one operationId in the document this API will load. The middlewares are the endpoint's chain, in order, exactly as NewEndPoint would take them. Registering the same id twice is reported when the document is loaded, not here, so that the report is one error with the whole picture rather than a fatal in the middle of a define function.
func (*DXAPI) RegisterRawHandler ¶
RegisterRawHandler registers a raw http.Handler on the API mux. Use this for static file serving, redirects, or other handlers that bypass the DXAPIEndPoint middleware pipeline.
func (*DXAPI) RegisterWSHandler ¶
func (a *DXAPI) RegisterWSHandler(operationId string, handler DXOpenAPIWSHandler)
RegisterWSHandler is RegisterHandler for an entry in x-dxlib-websocket-endpoints.
func (*DXAPI) SetEndPointIdempotencyKey ¶ added in v1.126.7
func (a *DXAPI) SetEndPointIdempotencyKey(method, uri string, key DXAPIEndPointParameter) error
SetEndPointIdempotencyKey declares the idempotency key of the endpoint registered on method and uri. key.NameId is the header name and key.IsMustExist says whether every request must send it. NewEndPoint returns a copy of what it registers, so the key is set here, on the registered endpoint, rather than on that copy.
func (*DXAPI) StartShutdown ¶
type DXAPIAuditLogEntry ¶
type DXAPIAuditLogEntry struct {
StartTime time.Time `json:"start_time,omitempty"`
EndTime time.Time `json:"end_time,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
UserId string `json:"user_id,omitempty"`
UserUid string `json:"user_uid,omitempty"`
UserLoginId string `json:"user_loginid,omitempty"`
UserFullName string `json:"user_fullname,omitempty"`
APIURL string `json:"api_url,omitempty"`
APITitle string `json:"api_title,omitempty"`
Method string `json:"method,omitempty"`
StatusCode int `json:"status_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
// PeerIdentity is the caller as named by the verified client certificate,
// when the request arrived over mTLS; empty otherwise. Under direct mTLS
// there is no proxy in front to set X-Proxy-Client-IP, so IPAddress above
// is whatever the caller put in X-Forwarded-For and this field is the one
// that was actually authenticated. See GetIPAddress.
PeerIdentity string `json:"peer_identity,omitempty"`
}
type DXAPIDomainError ¶
type DXAPIDomainError interface {
error
DomainErrorCode() string
DomainErrorHTTPStatusCode() int
DomainErrorResponseBody() utils.JSON
DomainErrorLogDetails() string
}
DXAPIDomainError is an interface that domain errors implement. The API framework checks for this interface and writes structured HTTP responses. This keeps domain validation errors separate from unexpected server errors.
type DXAPIEndPoint ¶
type DXAPIEndPoint struct {
Owner *DXAPI
Title string
Uri string
Method string
EndPointType DXAPIEndPointType
Description string
RequestContentType utilsHttp.RequestContentType
Parameters []DXAPIEndPointParameter
OnExecute DXAPIEndPointExecuteFunc
OnWSLoop DXAPIEndPointExecuteFunc
// The hooks below drive the built-in WebSocket loop, used when OnWSLoop is
// nil. Each application differs only in what it does on open and close and
// in how it reads and writes a message, so those are all that is left to
// supply; the read loop, write pump, keepalive and client registry are the
// same everywhere and live in the library.
OnWSOpen DXAPIEndPointWSOpenFunc
OnWSMessage DXAPIEndPointWSMessageFunc
OnWSClose DXAPIEndPointWSCloseFunc
OnWSPeriodic DXAPIEndPointWSPeriodicFunc
WSPeriodicInterval time.Duration
ResponsePossibilities *DXAPIEndPointResponsePossibilities
Middlewares []DXAPIEndPointExecuteFunc
Privileges []string
RequestMaxContentLength int64
RateLimitGroupNameId string
// IdempotencyKey is the request header that carries the endpoint's
// idempotency key, nil when it takes none. NameId is the header name,
// IsMustExist whether every request must send it, Type a string type and
// the string bounds its format. It is set with SetEndPointIdempotencyKey
// or bound from a document, and checked by PreProcessRequest; see
// api_endpoint_idempotency.go.
IdempotencyKey *DXAPIEndPointParameter
// contains filtered or unexported fields
}
func (*DXAPIEndPoint) NewEndPointRequest ¶
func (aep *DXAPIEndPoint) NewEndPointRequest(context context.Context, w http.ResponseWriter, r *http.Request) *DXAPIEndPointRequest
func (*DXAPIEndPoint) NewParameter ¶
func (aep *DXAPIEndPoint) NewParameter(parent *DXAPIEndPointParameter, nameId string, aType dxlibTypes.APIParameterType, description string, isMustExist bool) *DXAPIEndPointParameter
func (*DXAPIEndPoint) PrintSpec ¶
func (aep *DXAPIEndPoint) PrintSpec() (s string, err error)
func (*DXAPIEndPoint) WSBroadcast ¶
func (p *DXAPIEndPoint) WSBroadcast(message []byte) (delivered int)
WSBroadcast queues a message for every connected client, skipping any whose buffer is already full rather than blocking the caller on a slow reader.
The read lock is held across the sends. wsUnregister closes Send under the write lock, and a closed channel is ready rather than blocked, so a send racing an unregister would take the default arm on a live client and panic on a closed one. Holding the lock is what keeps the channel open for the send.
func (*DXAPIEndPoint) WSClients ¶
func (p *DXAPIEndPoint) WSClients() []*DXAPIEndPointWebSocketClient
WSClients returns the endpoint's currently connected clients.
type DXAPIEndPointExecuteFunc ¶
type DXAPIEndPointExecuteFunc func(aepr *DXAPIEndPointRequest) (err error)
type DXAPIEndPointParameter ¶
type DXAPIEndPointParameter struct {
Owner *DXAPIEndPoint
Parent *DXAPIEndPointParameter
NameId string
Type dxlibTypes.APIParameterType
Description string
IsMustExist bool
IsNullable bool
Children []DXAPIEndPointParameter
Enum []any
// The JSON Schema bounds Validate checks beyond what Type implies. Each is
// off when nil (or empty, or false), so a declaration that sets none
// behaves as before. A bound applies to the values of its own kind only:
// the numeric bounds to the integer and number types, the length bounds
// and Pattern to the string types, the item bounds to the array types.
// Pattern is Go RE2 syntax and, as in JSON Schema, matches anywhere in the
// string unless anchored. OPENAPI.md section 2.4 has the rules.
Minimum *float64
ExclusiveMinimum *float64
Maximum *float64
ExclusiveMaximum *float64
MultipleOf *float64
MinLength *int
MaxLength *int
Pattern string
MinItems *int
MaxItems *int
UniqueItems bool
// Const holds the one value accepted; a nil pointer means no const.
Const *any
// The JSON Schema annotations. They describe the parameter to a reader of
// the generated document and promise no check: request validation ignores them, a
// Default is not filled in for a parameter left out, a ReadOnly parameter
// is still read from the request (and still required when IsMustExist),
// and nothing strips a WriteOnly value from a response. Default is a
// scalar of the parameter's own JSON type; a nil pointer means none.
// ReadOnly and WriteOnly may not both be set. OPENAPI.md section 2.4.
Title string
Default *any
ReadOnly bool
WriteOnly bool
}
func (*DXAPIEndPointParameter) PrintSpec ¶
func (aep *DXAPIEndPointParameter) PrintSpec(leftIndent int64) (s string)
type DXAPIEndPointRequest ¶
type DXAPIEndPointRequest struct {
Id string
Context context.Context
EndPoint *DXAPIEndPoint
ParameterValues map[string]*DXAPIEndPointRequestParameterValue
Log log.DXLog
Request *http.Request
RequestBodyAsBytes []byte
ResponseWriter *http.ResponseWriter
ResponseStatusCode int
ErrorMessage []string
CurrentUser DXAPIUser
LocalData utils.JSON
ResponseHeaderSent bool
ResponseBodySent bool
SuppressLogDump bool
EncryptionParameters utils.JSON
EffectiveRequestHeader map[string]string
DecryptedRequestBody utils.JSON // E2E decrypted body for debug logging
WSClient *DXAPIEndPointWebSocketClient
// PeerCertificate is the caller's leaf certificate, present only when the
// request arrived over TLS and the server VERIFIED the certificate against
// its ca-trust pool -- mode mtls, enforcing or on its verify-if-given rung.
// It is nil on plaintext, nil when no certificate was sent, and nil under
// mode https or the request rung even if a certificate was sent, because
// there Go leaves it in r.TLS.PeerCertificates unverified and an
// unverified certificate is a claim, not an identity. Middlewares and
// OnBeforePreProcessRequest can make per-caller decisions on it; a
// rejection there is after the handshake, so the allow-list in the tls
// block is the place for anything that should never reach a handler.
PeerCertificate *x509.Certificate
// PeerIdentity is PeerCertificate reduced to one string (first URI SAN,
// else first DNS SAN, else first IP SAN, else CN); "" when PeerCertificate
// is nil. It is what the audit log carries.
PeerIdentity string
// IdempotencyKey is the value of the endpoint's idempotency key header,
// checked against its declaration; "" when the endpoint takes none or the
// request did not send an optional one.
IdempotencyKey string
// contains filtered or unexported fields
}
func (*DXAPIEndPointRequest) AssignParameterNullableInt64 ¶
func (*DXAPIEndPointRequest) AssignParameterNullableString ¶
func (*DXAPIEndPointRequest) DecryptedRequestDumpAsString ¶
func (aepr *DXAPIEndPointRequest) DecryptedRequestDumpAsString() string
DecryptedRequestDumpAsString returns a formatted dump of decrypted request headers and body parameters with sensitive fields masked. This is useful for debugging E2E encrypted requests.
func (*DXAPIEndPointRequest) GetParameterValueAsAny ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsAny(k string) (isExist bool, val any, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsArrayOfAny ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsArrayOfAny(k string) (isExist bool, val []any, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsBool ¶
func (*DXAPIEndPointRequest) GetParameterValueAsDecimal ¶ added in v1.126.4
func (aepr *DXAPIEndPointRequest) GetParameterValueAsDecimal(k string) (isExist bool, val decimal.Decimal, err error)
GetParameterValueAsDecimal reads a money parameter, held as decimal.Decimal.
func (*DXAPIEndPointRequest) GetParameterValueAsFloat32 ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsFloat32(k string) (isExist bool, val float32, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsFloat64 ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsFloat64(k string) (isExist bool, val float64, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsInt32 ¶ added in v1.126.4
func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt32(k string) (isExist bool, val int32, err error)
GetParameterValueAsInt32 reads an int32, int32p, int32zp or nullable-int32 parameter. A nullable-int32 the caller left out answers isExist false; use GetParameterValueAsNullableInt32 to read one as *int32.
func (*DXAPIEndPointRequest) GetParameterValueAsInt64 ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt64(k string) (isExist bool, val int64, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsInt64s ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsInt64s(k string) (isExist bool, val []int64, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsJSON ¶
func (*DXAPIEndPointRequest) GetParameterValueAsMapStringString ¶
func (*DXAPIEndPointRequest) GetParameterValueAsNullableInt32 ¶ added in v1.126.7
func (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableInt32(k string, defaultValue ...any) (isExist bool, val *int32, err error)
GetParameterValueAsNullableInt32 reads a nullable-int32 parameter as *int32. A value sent as JSON null and one left out both mean not given: both answer isExist false and nil (or the int32 default, when one is passed), and the handler decides what not given means.
func (*DXAPIEndPointRequest) GetParameterValueAsNullableInt64 ¶
func (*DXAPIEndPointRequest) GetParameterValueAsNullableString ¶
func (*DXAPIEndPointRequest) GetParameterValueAsString ¶
func (*DXAPIEndPointRequest) GetParameterValueAsStrings ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueAsStrings(k string) (isExist bool, val []string, err error)
func (*DXAPIEndPointRequest) GetParameterValueAsTime ¶
func (*DXAPIEndPointRequest) GetParameterValueEntry ¶
func (aepr *DXAPIEndPointRequest) GetParameterValueEntry(k string) (val *DXAPIEndPointRequestParameterValue, err error)
func (*DXAPIEndPointRequest) GetParameterValues ¶
func (aepr *DXAPIEndPointRequest) GetParameterValues() (r utils.JSON)
func (*DXAPIEndPointRequest) GetResponseWriter ¶
func (aepr *DXAPIEndPointRequest) GetResponseWriter() *http.ResponseWriter
func (*DXAPIEndPointRequest) HTTPClient ¶
func (*DXAPIEndPointRequest) HTTPClient2 ¶
func (*DXAPIEndPointRequest) HTTPClientDo ¶
func (*DXAPIEndPointRequest) HTTPClientDoBodyAsJSONString ¶
func (*DXAPIEndPointRequest) NewAPIEndPointRequestParameter ¶
func (aepr *DXAPIEndPointRequest) NewAPIEndPointRequestParameter(aepp DXAPIEndPointParameter) *DXAPIEndPointRequestParameterValue
func (*DXAPIEndPointRequest) PreProcessRequest ¶
func (aepr *DXAPIEndPointRequest) PreProcessRequest() (err error)
func (*DXAPIEndPointRequest) ProxyHTTPAPIClient ¶
func (*DXAPIEndPointRequest) RequestDump ¶
func (aepr *DXAPIEndPointRequest) RequestDump() ([]byte, error)
func (*DXAPIEndPointRequest) RequestDumpAsString ¶
func (aepr *DXAPIEndPointRequest) RequestDumpAsString() (string, error)
func (*DXAPIEndPointRequest) TranslateMessage ¶
func (aepr *DXAPIEndPointRequest) TranslateMessage(messageKey string) string
TranslateMessage translates a message key using the user's language from session. Falls back to system default language ('id') if user language is not set. Returns the original key if translation is not found.
func (*DXAPIEndPointRequest) TranslateMessageWithArgs ¶
func (aepr *DXAPIEndPointRequest) TranslateMessageWithArgs(messageKey string, args ...any) string
TranslateMessageWithArgs translates a message key and formats it with arguments. Uses fmt.Sprintf for formatting after translation. Falls back to system default language ('id') if user language is not set. Returns formatted original key if translation is not found.
func (*DXAPIEndPointRequest) WriteResponseAndLogAsError ¶
func (aepr *DXAPIEndPointRequest) WriteResponseAndLogAsError(statusCode int, responseMessage string, err error)
func (*DXAPIEndPointRequest) WriteResponseAndNewErrorf ¶
func (*DXAPIEndPointRequest) WriteResponseAsBytes ¶
func (aepr *DXAPIEndPointRequest) WriteResponseAsBytes(statusCode int, header map[string]string, bodyAsBytes []byte)
func (*DXAPIEndPointRequest) WriteResponseAsDomainError ¶ added in v1.126.6
func (aepr *DXAPIEndPointRequest) WriteResponseAsDomainError(domainErr DXAPIDomainError)
WriteResponseAsDomainError answers a domain error the way the route handler answers one returned by OnExecute: the code and the log details go to the log at Warn with the request dump, and the client gets the error's status with a body that holds only its reason code. A helper that writes its own response, such as DXRawTable.DoCreate, uses it so a client sees the same answer whichever path refused the request. Nothing is written when a response has already been sent; the log line is written either way.
func (*DXAPIEndPointRequest) WriteResponseAsError ¶
func (aepr *DXAPIEndPointRequest) WriteResponseAsError(statusCode int, errToSend error)
func (*DXAPIEndPointRequest) WriteResponseAsErrorMessageNotLogged ¶
func (aepr *DXAPIEndPointRequest) WriteResponseAsErrorMessageNotLogged(statusCode int, errorMsg string, reasonMsg string)
func (*DXAPIEndPointRequest) WriteResponseAsInternalServerError ¶ added in v1.126.6
func (aepr *DXAPIEndPointRequest) WriteResponseAsInternalServerError(label string, err error)
WriteResponseAsInternalServerError answers an error that is the server's the way the route handler answers one returned by OnExecute: the full error and the request dump go to the log at Error, with the DB operation context (operation, table, masked data) when the error carries one, and the client gets a 500 whose body names only INTERNAL_SERVER_ERROR and the error_log_ref that finds the log entry. label heads the log line (EXECUTE_ERROR for the route handler) so the log says which path failed. Nothing is written when a response has already been sent; the log line is written either way.
func (*DXAPIEndPointRequest) WriteResponseAsJSON ¶
func (*DXAPIEndPointRequest) WriteResponseAsString ¶
func (aepr *DXAPIEndPointRequest) WriteResponseAsString(statusCode int, header map[string]string, s string)
type DXAPIEndPointRequestParameterValue ¶
type DXAPIEndPointRequestParameterValue struct {
Owner *DXAPIEndPointRequest
Parent *DXAPIEndPointRequestParameterValue
Value any
RawValue any
Metadata DXAPIEndPointParameter
IsArrayChildren bool
Children map[string]*DXAPIEndPointRequestParameterValue
ArrayChildren []DXAPIEndPointRequestParameterValue
// contains filtered or unexported fields
}
func (*DXAPIEndPointRequestParameterValue) GetNameIdPath ¶
func (aeprpv *DXAPIEndPointRequestParameterValue) GetNameIdPath() (s string)
func (*DXAPIEndPointRequestParameterValue) NewChild ¶
func (aeprpv *DXAPIEndPointRequestParameterValue) NewChild(aepp DXAPIEndPointParameter) *DXAPIEndPointRequestParameterValue
func (*DXAPIEndPointRequestParameterValue) SetRawValue ¶
func (aeprpv *DXAPIEndPointRequestParameterValue) SetRawValue(rv any, variablePath string) (err error)
func (*DXAPIEndPointRequestParameterValue) Validate ¶
func (aeprpv *DXAPIEndPointRequestParameterValue) Validate() (err error)
type DXAPIEndPointResponsePossibilities ¶
type DXAPIEndPointResponsePossibilities map[string]DXAPIEndPointResponsePossibility
type DXAPIEndPointResponsePossibility ¶
type DXAPIEndPointResponsePossibility struct {
Owner *DXAPIEndPoint
StatusCode int
Description string
Headers map[string]string
DataTemplate []*DXAPIEndPointParameter
}
type DXAPIEndPointType ¶
type DXAPIEndPointType int
const ( EndPointTypeHTTPJSON DXAPIEndPointType = iota EndPointTypeHTTPUploadStream EndPointTypeHTTPDownloadStream EndPointTypeHTTPDownloadStreamV2 EndPointTypeWS EndPointTypeHTTPEndToEndEncryptionV1 EndPointTypeHTTPEndToEndEncryptionV2 // EndPointTypeHTTPEndToEndEncryptionV3 — persistent-session inner envelope. // Bootstrap at /v1/startup_1 establishes a global connection_id and a // per-session AES key in Redis. Bulk requests carry { connection_id, data }. // See OnE2EEV3Unpack / OnE2EEV3Pack hooks in api/e2ee_v3.go. EndPointTypeHTTPEndToEndEncryptionV3 // EndPointTypeHTTPEndToEndEncryptionV4 — same as V3 but uses LVLE // (4-byte little-endian length prefix) instead of LV (big-endian). // See OnE2EEV4Unpack / OnE2EEV4Pack hooks in api/e2ee_v4.go. EndPointTypeHTTPEndToEndEncryptionV4 )
func (DXAPIEndPointType) String ¶
func (d DXAPIEndPointType) String() string
type DXAPIEndPointWSCloseFunc ¶
type DXAPIEndPointWSCloseFunc func(aepr *DXAPIEndPointRequest)
DXAPIEndPointWSCloseFunc runs once, after the read loop ends, however it ended.
type DXAPIEndPointWSMessageFunc ¶
type DXAPIEndPointWSMessageFunc func(aepr *DXAPIEndPointRequest, message []byte) (response []byte, err error)
DXAPIEndPointWSMessageFunc handles one inbound frame. A non-empty response is written straight back to the sender. This is where an application's own protocol lives -- the library never looks inside the bytes.
type DXAPIEndPointWSOpenFunc ¶
type DXAPIEndPointWSOpenFunc func(aepr *DXAPIEndPointRequest) (err error)
DXAPIEndPointWSOpenFunc runs once per connection, before the read loop starts. Returning an error closes the connection without running OnWSClose.
type DXAPIEndPointWSPeriodicFunc ¶
type DXAPIEndPointWSPeriodicFunc func(aepr *DXAPIEndPointRequest) (err error)
DXAPIEndPointWSPeriodicFunc runs on every WSPeriodicInterval tick for as long as the connection is open, for whatever an application needs to push without being asked.
type DXAPIEndPointWebSocketClient ¶
DXAPIEndPointWebSocketClient represents one connected WebSocket client.
type DXAPIManager ¶
type DXAPIManager struct {
Context context.Context
Cancel context.CancelFunc
APIs map[string]*DXAPI
ErrorGroup *errgroup.Group
ErrorGroupContext context.Context
}
var Manager DXAPIManager
func (*DXAPIManager) LoadFromConfiguration ¶
func (am *DXAPIManager) LoadFromConfiguration(configurationNameId string) (err error)
func (*DXAPIManager) StopAll ¶
func (am *DXAPIManager) StopAll() (err error)
type DXAuditLogHandler ¶
type DXOpenAPIComponents ¶
type DXOpenAPIComponents struct {
Schemas *DXOpenAPIOrderedMap[*DXOpenAPISchema] `json:"schemas,omitempty"`
SecuritySchemes *DXOpenAPIOrderedMap[*DXOpenAPISecurityScheme] `json:"securitySchemes,omitempty"`
}
type DXOpenAPIDocument ¶
type DXOpenAPIDocument struct {
OpenAPI string `json:"openapi"`
Info DXOpenAPIInfo `json:"info"`
Paths *DXOpenAPIOrderedMap[*DXOpenAPIPathItem] `json:"paths"`
Components *DXOpenAPIComponents `json:"components,omitempty"`
// Security is the top-level security requirement. Only one is ever emitted
// here -- mutualTLS, and only when the API is actually served under
// mode: mtls. See openAPISecurityForTLS.
Security []DXOpenAPISecurityRequirement `json:"security,omitempty"`
// WebSocketEndPoints is x-dxlib-websocket-endpoints. OpenAPI 3.1 describes
// request/response operations and has no construct for a socket lifecycle,
// so WebSocket endpoints are listed here rather than under paths, where a
// reader would take them for ordinary GETs.
WebSocketEndPoints *DXOpenAPIWebSocketEndPoints `json:"x-dxlib-websocket-endpoints,omitempty"`
}
func ReadOpenAPI ¶
func ReadOpenAPI(data []byte) (*DXOpenAPIDocument, error)
ReadOpenAPI parses and validates a document held in memory.
func ReadOpenAPIFile ¶
func ReadOpenAPIFile(path string) (*DXOpenAPIDocument, error)
ReadOpenAPIFile reads and validates a document from disk. The syntax is chosen by the first non-blank byte: "{" is JSON, anything else is YAML.
func (*DXOpenAPIDocument) AsJSON ¶
func (doc *DXOpenAPIDocument) AsJSON() ([]byte, error)
AsJSON renders the document with two-space indentation. It is the one serialiser: the emitter, the handler and the round-trip test all go through it, which is what makes two documents comparable byte for byte.
func (*DXOpenAPIDocument) OperationIds ¶
func (doc *DXOpenAPIDocument) OperationIds() []string
OperationIds returns every operationId in the document -- the path operations and the WebSocket endpoints -- in document order.
func (*DXOpenAPIDocument) Validate ¶
func (doc *DXOpenAPIDocument) Validate() error
Validate checks what the walker cannot see one node at a time: the version, that every operationId is unique across paths and the WebSocket list, that path templates and path parameters agree, that references resolve, and that every value drawn from a fixed vocabulary is in it. ReadOpenAPI runs it, and BindOpenAPI runs it again so a document built in code is held to the same rules as one read from a file.
type DXOpenAPIHeader ¶
type DXOpenAPIHeader struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Schema *DXOpenAPISchema `json:"schema,omitempty"`
}
type DXOpenAPIInfo ¶
type DXOpenAPIMediaType ¶
type DXOpenAPIMediaType struct {
Schema *DXOpenAPISchema `json:"schema,omitempty"`
}
type DXOpenAPIOperation ¶
type DXOpenAPIOperation struct {
OperationId string `json:"operationId"`
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
Parameters []*DXOpenAPIParameter `json:"parameters,omitempty"`
RequestBody *DXOpenAPIRequestBody `json:"requestBody,omitempty"`
Responses *DXOpenAPIOrderedMap[*DXOpenAPIResponse] `json:"responses,omitempty"`
// EndPointType is DXAPIEndPointType.String(), always written so that the
// type is read off the document and never defaulted into place.
EndPointType string `json:"x-dxlib-endpoint-type,omitempty"`
// Privileges, RateLimitGroup and MaxContentLength are the endpoint fields
// of the same names. Zero values are omitted and read back as zero.
Privileges []string `json:"x-dxlib-privileges,omitempty"`
RateLimitGroup string `json:"x-dxlib-rate-limit-group,omitempty"`
MaxContentLength int64 `json:"x-dxlib-max-content-length,omitempty"`
// Middlewares names the middleware chain wrapping the handler, in the
// order it runs, by Go function name. It is written for a reader of the
// document and never bound: the chain is code, registered with the
// handler, and that registration is what runs.
Middlewares []string `json:"x-dxlib-middlewares,omitempty"`
// IdempotencyKey names the header parameter that is the endpoint's
// idempotency key. That parameter is the one in: header the dialect
// carries; any other is refused.
IdempotencyKey string `json:"x-dxlib-idempotency-key,omitempty"`
// RequestContentType is written only where the request body cannot carry
// it: a GET or DELETE declared with a content type, which dxlib ignores at
// request time but which the declaration still holds.
RequestContentType string `json:"x-dxlib-request-content-type,omitempty"`
// Parameters is the declared parameter set for a body that is not the
// parameters: application/octet-stream and text/plain, where the values
// arrive in the X-Var header, and a POST with no content type. It is an
// object schema, one property per parameter.
ParametersSchema *DXOpenAPISchema `json:"x-dxlib-parameters,omitempty"`
}
type DXOpenAPIOrderedMap ¶
type DXOpenAPIOrderedMap[V any] struct { // contains filtered or unexported fields }
DXOpenAPIOrderedMap is a string-keyed map that marshals in insertion order. encoding/json sorts map keys, which would put every parameter of every endpoint in alphabetical order and separate a spec's paths from the grouping their author gave them; a slice of keys beside the map keeps the order and costs nothing else.
func NewDXOpenAPIOrderedMap ¶
func NewDXOpenAPIOrderedMap[V any]() *DXOpenAPIOrderedMap[V]
func (*DXOpenAPIOrderedMap[V]) Get ¶
func (m *DXOpenAPIOrderedMap[V]) Get(key string) (value V, ok bool)
func (*DXOpenAPIOrderedMap[V]) Keys ¶
func (m *DXOpenAPIOrderedMap[V]) Keys() []string
func (*DXOpenAPIOrderedMap[V]) Len ¶
func (m *DXOpenAPIOrderedMap[V]) Len() int
func (*DXOpenAPIOrderedMap[V]) MarshalJSON ¶
func (m *DXOpenAPIOrderedMap[V]) MarshalJSON() ([]byte, error)
func (*DXOpenAPIOrderedMap[V]) Set ¶
func (m *DXOpenAPIOrderedMap[V]) Set(key string, value V)
Set adds or replaces a key. A replaced key keeps its original position.
type DXOpenAPIParameter ¶
type DXOpenAPIParameter struct {
Name string `json:"name"`
In string `json:"in"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Schema *DXOpenAPISchema `json:"schema"`
}
type DXOpenAPIPathItem ¶
type DXOpenAPIPathItem struct {
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
Get *DXOpenAPIOperation `json:"get,omitempty"`
Put *DXOpenAPIOperation `json:"put,omitempty"`
Post *DXOpenAPIOperation `json:"post,omitempty"`
Delete *DXOpenAPIOperation `json:"delete,omitempty"`
Options *DXOpenAPIOperation `json:"options,omitempty"`
Head *DXOpenAPIOperation `json:"head,omitempty"`
Patch *DXOpenAPIOperation `json:"patch,omitempty"`
Trace *DXOpenAPIOperation `json:"trace,omitempty"`
}
DXOpenAPIPathItem holds one operation per HTTP method. dxlib keys an endpoint by method and URI, so each operation set here is one endpoint.
func (*DXOpenAPIPathItem) Operations ¶
func (p *DXOpenAPIPathItem) Operations() (methods []string, operations []*DXOpenAPIOperation)
Operations returns the operations set on the item with their upper-case method, in specification order.
type DXOpenAPIRequestBody ¶
type DXOpenAPIRequestBody struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Content *DXOpenAPIOrderedMap[*DXOpenAPIMediaType] `json:"content"`
}
type DXOpenAPIResponse ¶
type DXOpenAPIResponse struct {
Description string `json:"description"`
Headers *DXOpenAPIOrderedMap[*DXOpenAPIHeader] `json:"headers,omitempty"`
Content *DXOpenAPIOrderedMap[*DXOpenAPIMediaType] `json:"content,omitempty"`
// Name is the key the endpoint gave this possibility in its
// ResponsePossibilities map. OpenAPI keys responses by status code, so the
// name travels beside it.
Name string `json:"x-dxlib-response-name,omitempty"`
}
type DXOpenAPISchema ¶
type DXOpenAPISchema struct {
Ref string `json:"$ref,omitempty"`
Type DXOpenAPISchemaType `json:"type,omitempty"`
Format string `json:"format,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Properties *DXOpenAPIOrderedMap[*DXOpenAPISchema] `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
Items *DXOpenAPISchema `json:"items,omitempty"`
AdditionalProperties *DXOpenAPISchema `json:"additionalProperties,omitempty"`
Enum []any `json:"enum,omitempty"`
Minimum *float64 `json:"minimum,omitempty"`
ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
MinLength *int `json:"minLength,omitempty"`
Maximum *float64 `json:"maximum,omitempty"`
ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
MultipleOf *float64 `json:"multipleOf,omitempty"`
MaxLength *int `json:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
MinItems *int `json:"minItems,omitempty"`
MaxItems *int `json:"maxItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty"`
Const *any `json:"const,omitempty"`
// Default, ReadOnly and WriteOnly are annotations: carried, never
// checked or applied. See DXAPIEndPointParameter.
Default *any `json:"default,omitempty"`
ReadOnly bool `json:"readOnly,omitempty"`
WriteOnly bool `json:"writeOnly,omitempty"`
// DXLibType is the exact dxlib parameter type. Several dxlib types share
// one JSON type -- int64, int64p and id are all integer/int64 -- so the
// JSON Schema alone cannot say which validator runs. With it present the
// binder uses it exactly; without it the binder infers the closest dxlib
// type from type and format, as OPENAPI.md tabulates.
DXLibType string `json:"x-dxlib-type,omitempty"`
}
type DXOpenAPISchemaType ¶
type DXOpenAPISchemaType []string
DXOpenAPISchemaType is JSON Schema's type keyword: one name, or a list when the value may also be null. It marshals as a bare string for one name and as an array otherwise, which is how 2020-12 spells nullable.
func (DXOpenAPISchemaType) MarshalJSON ¶
func (t DXOpenAPISchemaType) MarshalJSON() ([]byte, error)
func (DXOpenAPISchemaType) Nullable ¶
func (t DXOpenAPISchemaType) Nullable() bool
func (DXOpenAPISchemaType) Primary ¶
func (t DXOpenAPISchemaType) Primary() string
Primary is the type name that is not "null".
type DXOpenAPISecurityRequirement ¶
DXOpenAPISecurityRequirement is one entry of a security list: a scheme name mapped to its scopes. Only mutualTLS is emitted or accepted, and mutualTLS takes no scopes, so the slice is always empty -- a non-empty one is refused by the reader rather than ignored.
type DXOpenAPISecurityScheme ¶
type DXOpenAPISecurityScheme struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
}
DXOpenAPISecurityScheme is a componentsecuritySchemes entry.
Only type mutualTLS is ever produced. The body token that authenticates these APIs is deliberately NOT modelled here: OpenAPI 3.1 offers apiKey (in: query, header or cookie only), http, oauth2, openIdConnect and mutualTLS, and none of them can express a credential carried inside the request body. Declaring apiKey with in: body would produce a document that is invalid and that every reader outside dxlib would misread. The token is a request-body property, which is what it actually is, and it appears in the requestBody schema like any other parameter. OPENAPI.md states this.
type DXOpenAPIWSHandler ¶
type DXOpenAPIWSHandler struct {
OnOpen DXAPIEndPointWSOpenFunc
OnMessage DXAPIEndPointWSMessageFunc
OnClose DXAPIEndPointWSCloseFunc
OnPeriodic DXAPIEndPointWSPeriodicFunc
OnLoop DXAPIEndPointExecuteFunc
Middlewares []DXAPIEndPointExecuteFunc
}
DXOpenAPIWSHandler is what a WebSocket operation binds to: the hooks NewWSEndPoint takes, or OnLoop for an endpoint that runs the whole lifecycle itself, plus the middleware chain run before the upgrade.
type DXOpenAPIWebSocketEndPoint ¶
type DXOpenAPIWebSocketEndPoint struct {
OperationId string `json:"operationId"`
Path string `json:"path"`
Method string `json:"method"`
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
Privileges []string `json:"privileges,omitempty"`
RateLimitGroup string `json:"rateLimitGroup,omitempty"`
// PeriodicInterval is a Go duration string ("5s"); empty means the
// library default of thirty seconds.
PeriodicInterval string `json:"periodicInterval,omitempty"`
// Middlewares is x-dxlib-middlewares for a WebSocket endpoint, under the
// unprefixed name this block uses for every field.
Middlewares []string `json:"middlewares,omitempty"`
}
type DXOpenAPIWebSocketEndPoints ¶
type DXOpenAPIWebSocketEndPoints struct {
Description string `json:"description"`
EndPoints []*DXOpenAPIWebSocketEndPoint `json:"endpoints"`
}
type ErrUniqueFieldViolation ¶
ErrUniqueFieldViolation is returned when a unique field constraint would be violated. TableName and Values are logged server-side only. Fields are included in the API response.
func (*ErrUniqueFieldViolation) DomainErrorCode ¶
func (e *ErrUniqueFieldViolation) DomainErrorCode() string
func (*ErrUniqueFieldViolation) DomainErrorHTTPStatusCode ¶
func (e *ErrUniqueFieldViolation) DomainErrorHTTPStatusCode() int
func (*ErrUniqueFieldViolation) DomainErrorLogDetails ¶
func (e *ErrUniqueFieldViolation) DomainErrorLogDetails() string
func (*ErrUniqueFieldViolation) DomainErrorResponseBody ¶
func (e *ErrUniqueFieldViolation) DomainErrorResponseBody() utils.JSON
func (*ErrUniqueFieldViolation) Error ¶
func (e *ErrUniqueFieldViolation) Error() string
Source Files
¶
- api.go
- api_domain_error.go
- api_endpoint.go
- api_endpoint_idempotency.go
- api_endpoint_request.go
- api_endpoint_request_error_answer.go
- api_endpoint_request_get_parameter_value.go
- api_endpoint_request_parameter_bounds.go
- api_endpoint_request_parameter_value.go
- api_endpoint_request_proxy.go
- api_endpoint_websocket.go
- api_preauth_hook.go
- api_problem_details.go
- api_tls.go
- decrypted_dump_gate.go
- e2ee.go
- e2ee_v3.go
- e2ee_v4.go
- field_format_check.go
- openapi_annotations.go
- openapi_bind.go
- openapi_bounds.go
- openapi_document.go
- openapi_emit.go
- openapi_read.go
- openapi_types.go