From aacde06636f77941dbed3a5c7d2cca2536f50662 Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Wed, 15 Nov 2023 09:51:35 +0100 Subject: [PATCH] triplestore: Use http timeout if possible --- go.mod | 22 +-- go.sum | 22 +++ internal/dis/component/triplestore/backup.go | 2 +- .../dis/component/triplestore/database.go | 13 +- .../dis/component/triplestore/provision.go | 4 +- .../dis/component/triplestore/snapshot.go | 2 +- internal/dis/component/triplestore/update.go | 4 +- internal/phpx/marshal.go | 127 +++++++++++++++++- 8 files changed, 170 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 2be0a69..d0d8938 100644 --- a/go.mod +++ b/go.mod @@ -5,25 +5,25 @@ go 1.21.3 require ( github.com/FAU-CDI/wdresolve v0.0.0-20230108072141-c9c6779d7c41 github.com/alessio/shellescape v1.4.2 - github.com/compose-spec/compose-go v1.20.0 + github.com/compose-spec/compose-go v1.20.1 github.com/docker/docker v24.0.7+incompatible github.com/gliderlabs/ssh v0.3.5 github.com/gorilla/csrf v1.7.2 - github.com/gorilla/sessions v1.2.1 + github.com/gorilla/sessions v1.2.2 github.com/gorilla/websocket v1.5.1 github.com/julienschmidt/httprouter v1.3.0 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.4.0 github.com/rs/zerolog v1.31.0 github.com/tkw1536/goprogram v0.5.0 - github.com/tkw1536/pkglib v0.0.0-20231110192201-b920fd9f7764 + github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025 github.com/yuin/goldmark v1.6.0 github.com/yuin/goldmark-meta v1.1.0 - golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d - golang.org/x/net v0.17.0 - golang.org/x/sync v0.4.0 - golang.org/x/term v0.13.0 + golang.org/x/crypto v0.15.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/net v0.18.0 + golang.org/x/sync v0.5.0 + golang.org/x/term v0.14.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.2 gorm.io/gorm v1.25.5 @@ -63,11 +63,11 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/mod v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 26fde3f..a5d4588 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyX github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/compose-spec/compose-go v1.20.0 h1:h4ZKOst1EF/DwZp7dWkb+wbTVE4nEyT9Lc89to84Ol4= github.com/compose-spec/compose-go v1.20.0/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= +github.com/compose-spec/compose-go v1.20.1 h1:I6gCMGLl96kEf8XZwaozeTwnNfxA2eVsO46W+5ciTEg= +github.com/compose-spec/compose-go v1.20.1/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -51,6 +53,8 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= @@ -122,6 +126,8 @@ github.com/tkw1536/goprogram v0.5.0 h1:7vcIjmMdcZPJyRhgdlCaGfHAoOG3oYlFrno1pWXy1 github.com/tkw1536/goprogram v0.5.0/go.mod h1:MDCwqLmvcc2QryMm6oSC9h/QAdE9PewZ2Mp2Lm7MmAg= github.com/tkw1536/pkglib v0.0.0-20231110192201-b920fd9f7764 h1:rJStxc6PoFUQcsRRqoUNtaM80GZWT69WfWeA+EDbvXM= github.com/tkw1536/pkglib v0.0.0-20231110192201-b920fd9f7764/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= +github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025 h1:t3ewoi0rdqQo0a8zFFpmtsUi+O4C+kCYoOM/QkXV7b0= +github.com/tkw1536/pkglib v0.0.0-20231114141909-8837d3186025/go.mod h1:Qi/vpuxuxo5D40O9jLUSmcUF01B5LmJqDxs8o8Lc6bg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -141,12 +147,18 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -155,11 +167,15 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -180,12 +196,16 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -194,6 +214,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/dis/component/triplestore/backup.go b/internal/dis/component/triplestore/backup.go index 4e41d11..fa8f010 100644 --- a/internal/dis/component/triplestore/backup.go +++ b/internal/dis/component/triplestore/backup.go @@ -32,7 +32,7 @@ func (ts *Triplestore) Backup(scontext *component.StagingContext) error { } func (ts Triplestore) listRepositories(ctx context.Context) (repos []Repository, err error) { - res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "application/json") + res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "application/json", 0) if err != nil { return nil, err } diff --git a/internal/dis/component/triplestore/database.go b/internal/dis/component/triplestore/database.go index c3ea200..da8f27f 100644 --- a/internal/dis/component/triplestore/database.go +++ b/internal/dis/component/triplestore/database.go @@ -27,11 +27,15 @@ type TriplestoreUserAppSettings struct { ExecuteCount bool `json:"EXECUTE_COUNT"` } +// http.Client Timeout to be used for "trivial" triplestore operations. +// This includes e.g. CRUDing a specific repo. +const tsTrivialTimeout = time.Minute + // OpenRaw makes an http request to the triplestore api. // // When bodyName is non-empty, expect body to be a byte slice representing a multipart/form-data upload with the given name. // When bodyName is empty, simply marshal body as application/json -func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body any, bodyName string, accept string) (*http.Response, error) { +func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body any, bodyName string, accept string, timeout time.Duration) (*http.Response, error) { var reader io.Reader // to read the body from var contentType string // content-type of the request being sent @@ -66,6 +70,7 @@ func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body any, // create the request object client := &http.Client{ + Timeout: timeout, Transport: &http.Transport{ DisableKeepAlives: true, }, @@ -92,7 +97,7 @@ func (ts Triplestore) OpenRaw(ctx context.Context, method, url string, body any, // This is achieved using a polling strategy. func (ts Triplestore) Wait(ctx context.Context) error { return timex.TickUntilFunc(func(time.Time) bool { - res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "") + res, err := ts.OpenRaw(ctx, "GET", "/rest/repositories", nil, "", "", tsTrivialTimeout) zerolog.Ctx(ctx).Trace().Err(err).Msg("Triplestore wait") if err != nil { return false @@ -105,7 +110,7 @@ func (ts Triplestore) Wait(ctx context.Context) error { // PurgeUser deletes the specified user from the triplestore. // When the user does not exist, returns no error. func (ts Triplestore) PurgeUser(ctx context.Context, user string) error { - res, err := ts.OpenRaw(ctx, "DELETE", "/rest/security/users/"+user, nil, "", "") + res, err := ts.OpenRaw(ctx, "DELETE", "/rest/security/users/"+user, nil, "", "", tsTrivialTimeout) if err != nil { return err } @@ -118,7 +123,7 @@ func (ts Triplestore) PurgeUser(ctx context.Context, user string) error { // PurgeRepo deletes the specified repo from the triplestore. // When the repo does not exist, returns no error. func (ts Triplestore) PurgeRepo(ctx context.Context, repo string) error { - res, err := ts.OpenRaw(ctx, "DELETE", "/rest/repositories/"+repo, nil, "", "") + res, err := ts.OpenRaw(ctx, "DELETE", "/rest/repositories/"+repo, nil, "", "", tsTrivialTimeout) if err != nil { return err } diff --git a/internal/dis/component/triplestore/provision.go b/internal/dis/component/triplestore/provision.go index f9d42d2..7468c87 100644 --- a/internal/dis/component/triplestore/provision.go +++ b/internal/dis/component/triplestore/provision.go @@ -61,7 +61,7 @@ func (ts *Triplestore) CreateRepository(ctx context.Context, name, domain, user, // do the create! { - res, err := ts.OpenRaw(ctx, "POST", "/rest/repositories", createRepo.Bytes(), "config", "") + res, err := ts.OpenRaw(ctx, "POST", "/rest/repositories", createRepo.Bytes(), "config", "", tsTrivialTimeout) if err != nil { return errTripleStoreFailedRepository.WithMessageF(err) } @@ -87,7 +87,7 @@ func (ts *Triplestore) CreateRepository(ctx context.Context, name, domain, user, "READ_REPO_" + name, "WRITE_REPO_" + name, }, - }, "", "") + }, "", "", tsTrivialTimeout) if err != nil { return errTripleStoreFailedRepository.WithMessageF(err) } diff --git a/internal/dis/component/triplestore/snapshot.go b/internal/dis/component/triplestore/snapshot.go index a5b0dd6..2c8da6c 100644 --- a/internal/dis/component/triplestore/snapshot.go +++ b/internal/dis/component/triplestore/snapshot.go @@ -27,7 +27,7 @@ var errTSBackupWrongStatusCode = errors.New("Triplestore.Backup: Wrong status co // SnapshotDB snapshots the provided repository into dst func (ts Triplestore) SnapshotDB(ctx context.Context, dst io.Writer, repo string) (int64, error) { - res, err := ts.OpenRaw(ctx, "GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads") + res, err := ts.OpenRaw(ctx, "GET", "/repositories/"+repo+"/statements?infer=false", nil, "", "application/n-quads", 0) if err != nil { return 0, err } diff --git a/internal/dis/component/triplestore/update.go b/internal/dis/component/triplestore/update.go index 99f1221..7ca7a3b 100644 --- a/internal/dis/component/triplestore/update.go +++ b/internal/dis/component/triplestore/update.go @@ -30,7 +30,7 @@ func (ts Triplestore) Update(ctx context.Context, progress io.Writer) error { ExecuteCount: true, }, GrantedAuthorities: []string{"ROLE_ADMIN"}, - }, "", "") + }, "", "", tsTrivialTimeout) if err != nil { return fmt.Errorf("failed to create triplestore user: %s", err) } @@ -52,7 +52,7 @@ func (ts Triplestore) Update(ctx context.Context, progress io.Writer) error { logging.LogMessage(progress, "Enabling Triplestore security") { - res, err := ts.OpenRaw(ctx, "POST", "/rest/security", true, "", "") + res, err := ts.OpenRaw(ctx, "POST", "/rest/security", true, "", "", tsTrivialTimeout) if err != nil { return fmt.Errorf("failed to enable triplestore security: %s", err) } diff --git a/internal/phpx/marshal.go b/internal/phpx/marshal.go index f06bfd0..d944739 100644 --- a/internal/phpx/marshal.go +++ b/internal/phpx/marshal.go @@ -2,10 +2,14 @@ package phpx import ( "encoding/json" + "math" + "strconv" "strings" + + "github.com/tkw1536/pkglib/collection" ) -// Marshal marshals data as a PHP expression, meaning it can safely be used inside code. +// Marshal marshals data as a PHP expression, so that it can be safely used inside a php expession. // // Typically data is marshaled using [json.Marshal] and decoded in PHP using 'json_decode'. // Special cases may exist for specific datatypes. @@ -23,11 +27,124 @@ func Marshal(data any) (string, error) { return "json_decode(" + MarshalString(string(bytes)) + ")", nil } -var replacer = strings.NewReplacer("'", "\\'", "\\", "\\\\") +// MarshalJSON marshals a json-value safely as an expression to be used as a php string. +// +// A json value is one returned by calling [json.Unmarshal] on a value of type any. +// These are then marshaled by the appropriate function: +// +// - a nil is turned into [PHPNil] +// - a bool is passed to [MarshalBool] +// - a float64 is passed to [MarshalFloat] +// - a string is passed to [MarshalString] +// - an []any is passed to [MarshalSlice] +// - an map[string]any is passed to [MarshalMap] +// +// All marshaling attempts to minify the length of the returned string, meaning compact encodings +// are prefered over length ones. +// +// If a value is none of these types, an empty string is returned. +// No valid value ever returns the empty string +func MarshalJSON(v any) string { + switch v := v.(type) { + case nil: + return PHPNil + case bool: + return MarshalBool(v) + case float64: + return MarshalFloat(v) + case string: + return MarshalString(v) + case []any: + return MarshalSlice(v) + case map[string]any: + return MarshalMap(v) + } + return "" +} + +const ( + // PHPNil represents the equivalent of a nil value in php + PHPNil = "null" + + phpTrue = "!0" + phpFalse = "!1" + + phpNaN = "NAN" + phpPositiveInfinity = "INF" + phpNegativeInfinity = "-INF" +) + +// MarshalBool marshals b as a boolean to be used in php code. +// This corresponds to the strings "true" or "false". +func MarshalBool(b bool) string { + if b { + return phpTrue + } + return phpFalse +} + +// MarshalFloat marshals a floating point number or integer +func MarshalFloat(f float64) string { + // if we actually have an integer, return it! + if i := int64(f); f == float64(i) { + return MarshalInt(i) + } + + // special cases + if math.IsNaN(f) { + return phpNaN + } + if math.IsInf(f, 1) { + return phpPositiveInfinity + } + if math.IsInf(f, -1) { + return phpNegativeInfinity + } + + // all other cases + return strconv.FormatFloat(f, 'E', -1, 64) +} + +// MarshalInt marshals an integer as a string to be used inside a php literal +func MarshalInt(i int64) string { + return strconv.FormatInt(i, 10) +} + +var stringReplacer = strings.NewReplacer("'", "\\'", "\\", "\\\\") // MarshalString marshals s as a php string that can be used safely as a PHP expression. -// -// See [https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single]. func MarshalString(s string) string { - return "'" + replacer.Replace(s) + "'" + // See [https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single] + // we just escape + return "'" + stringReplacer.Replace(s) + "'" +} + +func MarshalSlice(slice []any) string { + var builder strings.Builder + + builder.WriteRune('[') + { + for _, v := range slice { + builder.WriteString(MarshalJSON(v)) + builder.WriteRune(',') + } + } + builder.WriteRune(']') + + return builder.String() +} + +func MarshalMap(m map[string]any) string { + var builder strings.Builder + + builder.WriteString("array(") + collection.IterateSorted(m, func(k string, v any) { + builder.WriteString(MarshalString(k)) + builder.WriteString("=>") + builder.WriteString(MarshalJSON(v)) + builder.WriteString(",") + }) + builder.WriteString(")") + + return builder.String() }