api

package
v1.128.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 56 Imported by: 9

Documentation

Index

Constants

View Source
const (
	DXAPIDefaultWriteTimeoutSec = 300
	DXAPIDefaultReadTimeoutSec  = 300
)
View Source
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
)
View Source
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"
)
View Source
const ContentTypeProblemJSON = "application/problem+json"

ContentTypeProblemJSON is the media type of an RFC 9457 problem document.

View Source
const ErrorMessageIncompatibleTypeReceived = "INCOMPATIBLE_TYPE:%s(%v)_BUT_RECEIVED_(%s)=%v"
View Source
const FormatEmailMaxLength = 254
View Source
const FormatNIKLength = 16

NIK is 16 digits long

View Source
const FormatPhoneNumberMaxLength = 25
View Source
const FormatPhoneNumberMinimumLength = 3
View Source
const OpenAPIMutualTLSSchemeName = "mutualTLS"

OpenAPIMutualTLSSchemeName is the componentsecuritySchemes key used for the mutualTLS requirement. It is fixed so that a document round-trips.

View Source
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

View Source
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.

View Source
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).

View Source
var OnE2EEPrekeyPack func(aepr *DXAPIEndPointRequest, preKeyIndex string, edB0PrivateKeyAsBytes []byte, sharedKey2AsBytes []byte, payloads ...*lv.LV) (dataBlockEnvelopeAsHexString string, err error)
View Source
var OnE2EEPrekeyUnPack func(aepr *DXAPIEndPointRequest, prekeyIndex string, dataAsHexString string) (lvPayloadElements []*lv.LV, sharedKey2AsBytes []byte, edB0PrivateKeyAsBytes []byte, preKeyData utils.JSON, err error)
View Source
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.
View Source
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.
View Source
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.

View Source
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.

View Source
var SpecFormat = "MarkDown"
View Source
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 ErrorResponseBody added in v1.128.0

func ErrorResponseBody(statusCode_ int, code string, operation string, cause string, next string) utils.JSON

ErrorResponseBody builds the body of an error answer:

error        the one line a caller shows, "<operation>: <cause>"
operation    what was being done when it failed
cause        the underlying error, with credentials masked (RedactErrorText)
next         what the caller can do next (left out when empty)
status_code  the HTTP status, as a number

It also carries dxlib's older members, so existing clients keep working: status (the status text), reason (the code; the status text in upper snake case when code is empty) and reason_message (the same line as error).

func FormatEMailCheckValid

func FormatEMailCheckValid(s string) bool

func FormatNPWPorNIKCheckValid

func FormatNPWPorNIKCheckValid(s string) bool

FormatNPWPorNIKCheckValid validates if a string is a valid NPWP or NIK

func FormatPhoneNumberCheckValid

func FormatPhoneNumberCheckValid(s string) bool

func GetIPAddress

func GetIPAddress(r *http.Request) string

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

func NormalizePhoneNumber(s string) (string, error)

Helper function to normalize phone numbers to E.164 format

func OpenAPIOperationId

func OpenAPIOperationId(uri string) string

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

func OpenAPIOperationIdForMethod(method, uri string) string

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

func PeerIdentityFromRequest(r *http.Request) string

PeerIdentityFromRequest is utilsTLS.PeerIdentity of VerifiedPeerCertificate, for the audit entry written before the request object exists.

func RedactErrorText added in v1.128.0

func RedactErrorText(s string) string

RedactErrorText masks credentials that error text can carry, so the text can go into a response or a log: values of key=value and key: value pairs whose key names a credential (see maskCredentialPair), Bearer and Basic credentials, and the password in a URL's user info. Everything else is left as it is. It works on prose and is not a full masker: a key: value pair whose value is a plain word (no digit or punctuation) stays unless the key names a password, the first word after a password key is masked even when it is prose ("password: must be at least 8 characters"), and JSON in the text is not covered ("{\"token\":\"abc\"}" passes through, since the quote breaks the key match). A dependent that wraps an upstream body or JSON into an error should mask it before it does.

func SetLogDecryptedBody

func SetLogDecryptedBody(b bool)

SetLogDecryptedBody overrides the gate at runtime (used by tests).

func TLSPreflightReport

func TLSPreflightReport(dialAddr string) (report string, ok bool)

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
	// OmitServerErrorCause keeps the underlying error text out of a 5xx answer,
	// for an API that faces callers who must not see table names, SQL or hosts.
	// The answer still names the operation, what to do next and the
	// error_log_ref; its cause says where the full error is logged. A 4xx answer
	// always carries its cause. Off by default ("omit-server-error-cause").
	OmitServerErrorCause bool
}

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 (a *DXAPI) ApplyConfigurations(configurationNameId string) (err error)

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

func (a *DXAPI) LoadOpenAPIFile(path string) error

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

func (a *DXAPI) OpenAPIAsJSON() ([]byte, error)

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) PrintSpec

func (a *DXAPI) PrintSpec() (s string, err error)

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

func (a *DXAPI) RegisterRawHandler(pattern string, handler http.Handler)

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) StartAndWait

func (a *DXAPI) StartAndWait(errorGroup *errgroup.Group) error

func (*DXAPI) StartShutdown

func (a *DXAPI) StartShutdown() (err error)

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 (aepr *DXAPIEndPointRequest) AssignParameterNullableInt64(target *utils.JSON, key string) (isExist bool, v *int64, err error)

func (*DXAPIEndPointRequest) AssignParameterNullableString

func (aepr *DXAPIEndPointRequest) AssignParameterNullableString(target *utils.JSON, key string) (isExist bool, v *string, err error)

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 (aepr *DXAPIEndPointRequest) GetParameterValueAsBool(k string, defaultValue ...bool) (isExist bool, val bool, err error)

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 (aepr *DXAPIEndPointRequest) GetParameterValueAsJSON(k string) (isExist bool, val utils.JSON, err error)

func (*DXAPIEndPointRequest) GetParameterValueAsMapStringString

func (aepr *DXAPIEndPointRequest) GetParameterValueAsMapStringString(k string) (isExist bool, val map[string]string, err error)

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 (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableInt64(k string, defaultValue ...any) (isExist bool, val *int64, err error)

func (*DXAPIEndPointRequest) GetParameterValueAsNullableString

func (aepr *DXAPIEndPointRequest) GetParameterValueAsNullableString(k string, defaultValue ...any) (isExist bool, val *string, err error)

func (*DXAPIEndPointRequest) GetParameterValueAsString

func (aepr *DXAPIEndPointRequest) GetParameterValueAsString(k string, defaultValue ...any) (isExist bool, val string, err error)

func (*DXAPIEndPointRequest) GetParameterValueAsStrings

func (aepr *DXAPIEndPointRequest) GetParameterValueAsStrings(k string) (isExist bool, val []string, err error)

func (*DXAPIEndPointRequest) GetParameterValueAsTime

func (aepr *DXAPIEndPointRequest) GetParameterValueAsTime(k string) (isExist bool, val time.Time, err error)

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 (aepr *DXAPIEndPointRequest) HTTPClient(method, url string, parameters utils.JSON, headers map[string]string) (responseStatusCode int, responseAsJSON utils.JSON, err error)

func (*DXAPIEndPointRequest) HTTPClient2

func (aepr *DXAPIEndPointRequest) HTTPClient2(method, url string, parameters utils.JSON, headers map[string]string) (_responseStatusCode int, responseAsJSON utils.JSON, err error)

func (*DXAPIEndPointRequest) HTTPClientDo

func (aepr *DXAPIEndPointRequest) HTTPClientDo(method, url string, parameters utils.JSON, headers map[string]string) (response *http.Response, err error)

func (*DXAPIEndPointRequest) HTTPClientDoBodyAsJSONString

func (aepr *DXAPIEndPointRequest) HTTPClientDoBodyAsJSONString(method, url string, parametersAsJSONString string, headers map[string]string) (response *http.Response, err error)

func (*DXAPIEndPointRequest) NewAPIEndPointRequestParameter

func (aepr *DXAPIEndPointRequest) NewAPIEndPointRequestParameter(aepp DXAPIEndPointParameter) *DXAPIEndPointRequestParameterValue

func (*DXAPIEndPointRequest) PreProcessRequest

func (aepr *DXAPIEndPointRequest) PreProcessRequest() (err error)

func (*DXAPIEndPointRequest) ProxyHTTPAPIClient

func (aepr *DXAPIEndPointRequest) ProxyHTTPAPIClient(method string, url string, bodyParameterAsJSON utils.JSON, headers map[string]string) (statusCode int, r utils.JSON, err error)

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 (aepr *DXAPIEndPointRequest) WriteResponseAndNewErrorf(statusCode int, responseMessage string, msg string, data ...any) (err error)

func (*DXAPIEndPointRequest) WriteResponseAsAPIError added in v1.128.0

func (aepr *DXAPIEndPointRequest) WriteResponseAsAPIError(statusCode int, operation string, err error, next string)

WriteResponseAsAPIError answers the request with status and an error body (ErrorResponseBody) naming operation, the cause err and next, and logs the same line with the masked request dump (see logAndAnswerAPIError). A handler can use it to answer an error itself and return nil. Nothing is written when a response has already been sent; the log line is written either way.

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 (ErrorResponseBody) names the reason INTERNAL_SERVER_ERROR, the operation (the request's method and endpoint), the cause (the error's text, credentials masked), what to do next and the error_log_ref that finds the log entry. A DXAPIError in err's chain is answered with its own status and body instead, and logged the same way (the whole chain, its stack trace and DB context). 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 (aepr *DXAPIEndPointRequest) WriteResponseAsJSON(statusCode int, header map[string]string, bodyAsJSON utils.JSON)

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 (*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

type DXAPIEndPointWebSocketClient struct {
	Id   string
	Conn *websocket.Conn
	Send chan []byte
}

DXAPIEndPointWebSocketClient represents one connected WebSocket client.

type DXAPIError added in v1.128.0

type DXAPIError struct {
	StatusCode int
	Code       string
	Operation  string
	Cause      error
	Next       string
}

DXAPIError is an error that says how the request should be answered: the HTTP status, a reason code a client can branch on, the operation that failed, the underlying cause and what the caller can do next. A handler or middleware returns it (wrapped or not) and the route handler answers with its status and ErrorResponseBody, instead of the fixed 400 or 500 an untyped error gets.

func NewAPIError added in v1.128.0

func NewAPIError(statusCode int, code string, operation string, cause error, next string) *DXAPIError

NewAPIError returns a DXAPIError. code may be empty; the status text in upper snake case is used then (CONFLICT, SERVICE_UNAVAILABLE).

func (*DXAPIError) Error added in v1.128.0

func (e *DXAPIError) Error() string

func (*DXAPIError) ResponseBody added in v1.128.0

func (e *DXAPIError) ResponseBody() utils.JSON

ResponseBody is the answer body for the error, see ErrorResponseBody. A DB error in the cause is named without its row values (responseCause).

func (*DXAPIError) Unwrap added in v1.128.0

func (e *DXAPIError) Unwrap() error

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) NewAPI

func (am *DXAPIManager) NewAPI(nameId string) (*DXAPI, error)

func (*DXAPIManager) StartAll

func (am *DXAPIManager) StartAll(errorGroup *errgroup.Group, errorGroupContext context.Context) error

func (*DXAPIManager) StopAll

func (am *DXAPIManager) StopAll() (err error)

type DXAPIUser

type DXAPIUser struct {
	Id               string
	Uid              string
	LoginId          string
	FullName         string
	OrganizationId   string
	OrganizationUid  string
	OrganizationName string
}

type DXAuditLogHandler

type DXAuditLogHandler func(ctx context.Context, oldAuditLogId int64, parameters *DXAPIAuditLogEntry) (newAuditLogId int64, err error)

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 DXOpenAPIInfo struct {
	Title       string `json:"title"`
	Version     string `json:"version"`
	Description string `json:"description,omitempty"`
}

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

type DXOpenAPISecurityRequirement map[string][]string

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

type ErrUniqueFieldViolation struct {
	TableName string
	Fields    []string
	Values    utils.JSON
}

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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