github.com/PDOK/gokoala@v0.50.6/internal/ogc/tiles/main_test.go (about)

     1  package tiles
     2  
     3  import (
     4  	"context"
     5  	"log"
     6  	"net"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"os"
    11  	"path"
    12  	"runtime"
    13  	"testing"
    14  
    15  	"github.com/PDOK/gokoala/config"
    16  
    17  	"github.com/PDOK/gokoala/internal/engine"
    18  	"golang.org/x/text/language"
    19  
    20  	"github.com/go-chi/chi/v5"
    21  	"github.com/stretchr/testify/assert"
    22  )
    23  
    24  func init() {
    25  	// change working dir to root, to mimic behavior of 'go run' in order to resolve template files.
    26  	_, filename, _, _ := runtime.Caller(0)
    27  	dir := path.Join(path.Dir(filename), "../../../")
    28  	err := os.Chdir(dir)
    29  	if err != nil {
    30  		panic(err)
    31  	}
    32  }
    33  
    34  func TestNewTiles(t *testing.T) {
    35  	type args struct {
    36  		e *engine.Engine
    37  	}
    38  	tests := []struct {
    39  		name string
    40  		args args
    41  	}{
    42  		{
    43  			name: "Test render templates with OGC Tiles config",
    44  			args: args{
    45  				e: engine.NewEngineWithConfig(&config.Config{
    46  					Version:            "3.3.0",
    47  					Title:              "Test API",
    48  					Abstract:           "Test API description",
    49  					AvailableLanguages: []config.Language{{Tag: language.Dutch}},
    50  					BaseURL:            config.URL{URL: &url.URL{Scheme: "https", Host: "api.foobar.example", Path: "/"}},
    51  					OgcAPI: config.OgcAPI{
    52  						GeoVolumes: nil,
    53  						Tiles: &config.OgcAPITiles{
    54  							TileServer: config.URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset"}},
    55  							Types:      []config.TilesType{config.TilesTypeVector},
    56  							SupportedSrs: []config.SupportedSrs{
    57  								{
    58  									Srs: "EPSG:28992",
    59  									ZoomLevelRange: config.ZoomLevelRange{
    60  										Start: 0,
    61  										End:   6,
    62  									},
    63  								},
    64  							},
    65  						},
    66  						Styles: &config.OgcAPIStyles{
    67  							Default:         "foo",
    68  							SupportedStyles: nil,
    69  						},
    70  					},
    71  				}, "", false, true),
    72  			},
    73  		},
    74  		{
    75  			name: "Test render templates with OGC Tiles config and one SRS",
    76  			args: args{
    77  				e: engine.NewEngineWithConfig(&config.Config{
    78  					Version:            "3.3.0",
    79  					Title:              "Test API",
    80  					Abstract:           "Test API description",
    81  					AvailableLanguages: []config.Language{{Tag: language.Dutch}},
    82  					BaseURL:            config.URL{URL: &url.URL{Scheme: "https", Host: "api.foobar.example", Path: "/"}},
    83  					OgcAPI: config.OgcAPI{
    84  						GeoVolumes: nil,
    85  						Tiles: &config.OgcAPITiles{
    86  							TileServer: config.URL{URL: &url.URL{Scheme: "https", Host: "tiles.foobar.example", Path: "/somedataset"}},
    87  							Types:      []config.TilesType{config.TilesTypeVector},
    88  							SupportedSrs: []config.SupportedSrs{
    89  								{
    90  									Srs: "EPSG:28992",
    91  									ZoomLevelRange: config.ZoomLevelRange{
    92  										Start: 0,
    93  										End:   6,
    94  									},
    95  								},
    96  							},
    97  						},
    98  						Styles: &config.OgcAPIStyles{
    99  							Default:         "foo",
   100  							SupportedStyles: nil,
   101  						},
   102  					},
   103  				}, "", false, true),
   104  			},
   105  		},
   106  	}
   107  	for _, test := range tests {
   108  		t.Run(test.name, func(t *testing.T) {
   109  			tiles := NewTiles(test.args.e)
   110  			assert.NotEmpty(t, tiles.engine.Templates.RenderedTemplates)
   111  		})
   112  	}
   113  }
   114  
   115  func TestTiles_Tile(t *testing.T) {
   116  	type fields struct {
   117  		configFile      string
   118  		url             string
   119  		tileMatrixSetID string
   120  		tileMatrix      string
   121  		tileRow         string
   122  		tileCol         string
   123  	}
   124  	type want struct {
   125  		body       string
   126  		statusCode int
   127  	}
   128  	tests := []struct {
   129  		name   string
   130  		fields fields
   131  		want   want
   132  	}{
   133  		{
   134  			name: "NetherlandsRDNewQuad/0/0/0?f=mvt",
   135  			fields: fields{
   136  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   137  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol?f=mvt",
   138  				tileMatrixSetID: "NetherlandsRDNewQuad",
   139  				tileMatrix:      "0",
   140  				tileRow:         "0",
   141  				tileCol:         "0",
   142  			},
   143  			want: want{
   144  				body:       "/NetherlandsRDNewQuad/0/0/0.pbf",
   145  				statusCode: http.StatusOK,
   146  			},
   147  		},
   148  		{
   149  			name: "NetherlandsRDNewQuad/5/10/15?f=mvt",
   150  			fields: fields{
   151  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   152  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol?f=mvt",
   153  				tileMatrixSetID: "NetherlandsRDNewQuad",
   154  				tileMatrix:      "5",
   155  				tileRow:         "10",
   156  				tileCol:         "15",
   157  			},
   158  			want: want{
   159  				body:       "/NetherlandsRDNewQuad/5/15/10.pbf",
   160  				statusCode: http.StatusOK,
   161  			},
   162  		},
   163  		{
   164  			name: "NetherlandsRDNewQuad/5/10/15?f=pbf",
   165  			fields: fields{
   166  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   167  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol?f=pbf",
   168  				tileMatrixSetID: "NetherlandsRDNewQuad",
   169  				tileMatrix:      "5",
   170  				tileRow:         "10",
   171  				tileCol:         "15",
   172  			},
   173  			want: want{
   174  				body:       "/NetherlandsRDNewQuad/5/15/10.pbf",
   175  				statusCode: http.StatusOK,
   176  			},
   177  		},
   178  		{
   179  			name: "NetherlandsRDNewQuad/5/10/15",
   180  			fields: fields{
   181  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   182  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol",
   183  				tileMatrixSetID: "NetherlandsRDNewQuad",
   184  				tileMatrix:      "5",
   185  				tileRow:         "10",
   186  				tileCol:         "15",
   187  			},
   188  			want: want{
   189  				body:       "/NetherlandsRDNewQuad/5/15/10.pbf",
   190  				statusCode: http.StatusOK,
   191  			},
   192  		},
   193  		{
   194  			name: "NetherlandsRDNewQuad/5/10/15.pbf",
   195  			fields: fields{
   196  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   197  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol",
   198  				tileMatrixSetID: "NetherlandsRDNewQuad",
   199  				tileMatrix:      "5",
   200  				tileRow:         "10",
   201  				tileCol:         "15.pbf",
   202  			},
   203  			want: want{
   204  				body:       "/NetherlandsRDNewQuad/5/15/10.pbf",
   205  				statusCode: http.StatusOK,
   206  			},
   207  		},
   208  		{
   209  			name: "NetherlandsRDNewQuad/5/10/15?f=tilejson",
   210  			fields: fields{
   211  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   212  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol?f=tilejson",
   213  				tileMatrixSetID: "NetherlandsRDNewQuad",
   214  				tileMatrix:      "5",
   215  				tileRow:         "10",
   216  				tileCol:         "15",
   217  			},
   218  			want: want{
   219  				body:       "Specify tile format. Currently only Mapbox Vector Tiles (?f=mvt) tiles are supported",
   220  				statusCode: http.StatusBadRequest,
   221  			},
   222  		},
   223  		{
   224  			name: "different uriTemplateTiles",
   225  			fields: fields{
   226  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles_2.yaml",
   227  				url:             "http://localhost:8080/tiles/:tileMatrixSetId/:tileMatrix/:tileRow/:tileCol?f=mvt",
   228  				tileMatrixSetID: "NetherlandsRDNewQuad",
   229  				tileMatrix:      "5",
   230  				tileRow:         "10",
   231  				tileCol:         "15",
   232  			},
   233  			want: want{
   234  				body:       "/foo/NetherlandsRDNewQuad/5/10/15",
   235  				statusCode: http.StatusOK,
   236  			},
   237  		},
   238  	}
   239  	for _, tt := range tests {
   240  		t.Run(tt.name, func(t *testing.T) {
   241  			req, err := createTileRequest(tt.fields.url, tt.fields.tileMatrixSetID, tt.fields.tileMatrix, tt.fields.tileRow, tt.fields.tileCol)
   242  			if err != nil {
   243  				log.Fatal(err)
   244  			}
   245  			rr, ts := createMockServer()
   246  			defer ts.Close()
   247  
   248  			newEngine, err := engine.NewEngine(tt.fields.configFile, "", false, true)
   249  			assert.NoError(t, err)
   250  			tiles := NewTiles(newEngine)
   251  			handler := tiles.Tile()
   252  			handler.ServeHTTP(rr, req)
   253  
   254  			assert.Equal(t, tt.want.statusCode, rr.Code)
   255  			assert.Contains(t, rr.Body.String(), tt.want.body)
   256  		})
   257  	}
   258  }
   259  
   260  func TestTile_TilesetsList(t *testing.T) {
   261  	type fields struct {
   262  		configFile string
   263  		url        string
   264  	}
   265  	type want struct {
   266  		bodyContains string
   267  		statusCode   int
   268  	}
   269  	tests := []struct {
   270  		name   string
   271  		fields fields
   272  		want   want
   273  	}{
   274  		{
   275  			name: "test NetherlandsRDNewQuad present",
   276  			fields: fields{
   277  				configFile: "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   278  				url:        "http://localhost:8080/tiles",
   279  			},
   280  			want: want{
   281  				bodyContains: "NetherlandsRDNewQuad",
   282  				statusCode:   http.StatusOK,
   283  			},
   284  		},
   285  		{
   286  			name: "test EuropeanETRS89_LAEAQuad present",
   287  			fields: fields{
   288  				configFile: "internal/ogc/tiles/testdata/config_minimal_tiles_2.yaml",
   289  				url:        "http://localhost:8080/tiles",
   290  			},
   291  			want: want{
   292  				bodyContains: "EuropeanETRS89_LAEAQuad",
   293  				statusCode:   http.StatusOK,
   294  			},
   295  		},
   296  	}
   297  	for _, tt := range tests {
   298  		t.Run(tt.name, func(t *testing.T) {
   299  			req, err := createTilesetsListRequest(tt.fields.url)
   300  			if err != nil {
   301  				log.Fatal(err)
   302  			}
   303  			rr, ts := createMockServer()
   304  			defer ts.Close()
   305  
   306  			newEngine, err := engine.NewEngine(tt.fields.configFile, "", false, true)
   307  			assert.NoError(t, err)
   308  			tiles := NewTiles(newEngine)
   309  			handler := tiles.TilesetsList()
   310  			handler.ServeHTTP(rr, req)
   311  
   312  			assert.Equal(t, tt.want.statusCode, rr.Code)
   313  			assert.Contains(t, rr.Body.String(), tt.want.bodyContains)
   314  		})
   315  	}
   316  }
   317  
   318  func TestTile_Tileset(t *testing.T) {
   319  	type fields struct {
   320  		configFile      string
   321  		url             string
   322  		tileMatrixSetID string
   323  	}
   324  	type want struct {
   325  		bodyContains string
   326  		statusCode   int
   327  	}
   328  	tests := []struct {
   329  		name   string
   330  		fields fields
   331  		want   want
   332  	}{
   333  		{
   334  			name: "NetherlandsRDNewQuad",
   335  			fields: fields{
   336  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   337  				url:             "http://localhost:8080/tiles/NetherlandsRDNewQuad",
   338  				tileMatrixSetID: "NetherlandsRDNewQuad",
   339  			},
   340  			want: want{
   341  				bodyContains: "NetherlandsRDNewQuad",
   342  				statusCode:   http.StatusOK,
   343  			},
   344  		},
   345  		{
   346  			name: "EuropeanETRS89_LAEAQuad",
   347  			fields: fields{
   348  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   349  				url:             "http://localhost:8080/tiles/EuropeanETRS89_LAEAQuad",
   350  				tileMatrixSetID: "EuropeanETRS89_LAEAQuad",
   351  			},
   352  			want: want{
   353  				bodyContains: "EuropeanETRS89_LAEAQuad",
   354  				statusCode:   http.StatusOK,
   355  			},
   356  		},
   357  		{
   358  			name: "WebMercatorQuad",
   359  			fields: fields{
   360  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   361  				url:             "http://localhost:8080/tiles/WebMercatorQuad",
   362  				tileMatrixSetID: "WebMercatorQuad",
   363  			},
   364  			want: want{
   365  				bodyContains: "WebMercatorQuad",
   366  				statusCode:   http.StatusOK,
   367  			},
   368  		},
   369  		{
   370  			name: "Invalid",
   371  			fields: fields{
   372  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   373  				url:             "http://localhost:8080/tiles/Invalid",
   374  				tileMatrixSetID: "Invalid",
   375  			},
   376  			want: want{
   377  				bodyContains: "request doesn't conform to OpenAPI spec: parameter \\\"tileMatrixSetId\\\" in path has an error: value is not one of the allowed values",
   378  				statusCode:   http.StatusBadRequest,
   379  			},
   380  		},
   381  	}
   382  	for _, tt := range tests {
   383  		t.Run(tt.name, func(t *testing.T) {
   384  			req, err := createTilesetRequest(tt.fields.url, tt.fields.tileMatrixSetID)
   385  			if err != nil {
   386  				log.Fatal(err)
   387  			}
   388  			rr, ts := createMockServer()
   389  			defer ts.Close()
   390  
   391  			newEngine, err := engine.NewEngine(tt.fields.configFile, "", false, true)
   392  			assert.NoError(t, err)
   393  			tiles := NewTiles(newEngine)
   394  			handler := tiles.Tileset()
   395  			handler.ServeHTTP(rr, req)
   396  
   397  			assert.Equal(t, tt.want.statusCode, rr.Code)
   398  			assert.Contains(t, rr.Body.String(), tt.want.bodyContains)
   399  		})
   400  	}
   401  }
   402  
   403  func TestTile_TilematrixSet(t *testing.T) {
   404  	type fields struct {
   405  		configFile      string
   406  		url             string
   407  		tileMatrixSetID string
   408  	}
   409  	type want struct {
   410  		bodyContains string
   411  		statusCode   int
   412  	}
   413  	tests := []struct {
   414  		name   string
   415  		fields fields
   416  		want   want
   417  	}{
   418  		{
   419  			name: "NetherlandsRDNewQuad",
   420  			fields: fields{
   421  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   422  				url:             "http://localhost:8080/tileMatrixSets/NetherlandsRDNewQuad",
   423  				tileMatrixSetID: "NetherlandsRDNewQuad",
   424  			},
   425  			want: want{
   426  				bodyContains: "NetherlandsRDNewQuad",
   427  				statusCode:   http.StatusOK,
   428  			},
   429  		},
   430  		{
   431  			name: "EuropeanETRS89_LAEAQuad",
   432  			fields: fields{
   433  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   434  				url:             "http://localhost:8080/tileMatrixSets/EuropeanETRS89_LAEAQuad",
   435  				tileMatrixSetID: "EuropeanETRS89_LAEAQuad",
   436  			},
   437  			want: want{
   438  				bodyContains: "EuropeanETRS89_LAEAQuad",
   439  				statusCode:   http.StatusOK,
   440  			},
   441  		},
   442  		{
   443  			name: "WebMercatorQuad",
   444  			fields: fields{
   445  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   446  				url:             "http://localhost:8080/tileMatrixSets/WebMercatorQuad",
   447  				tileMatrixSetID: "WebMercatorQuad",
   448  			},
   449  			want: want{
   450  				bodyContains: "WebMercatorQuad",
   451  				statusCode:   http.StatusOK,
   452  			},
   453  		},
   454  		{
   455  			name: "Invalid",
   456  			fields: fields{
   457  				configFile:      "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   458  				url:             "http://localhost:8080/tileMatrixSets/Invalid",
   459  				tileMatrixSetID: "Invalid",
   460  			},
   461  			want: want{
   462  				bodyContains: "request doesn't conform to OpenAPI spec: parameter \\\"tileMatrixSetId\\\" in path has an error: value is not one of the allowed values",
   463  				statusCode:   http.StatusBadRequest,
   464  			},
   465  		},
   466  	}
   467  	for _, tt := range tests {
   468  		t.Run(tt.name, func(t *testing.T) {
   469  			req, err := createTilematrixSetRequest(tt.fields.url, tt.fields.tileMatrixSetID)
   470  			if err != nil {
   471  				log.Fatal(err)
   472  			}
   473  			rr, ts := createMockServer()
   474  			defer ts.Close()
   475  
   476  			newEngine, err := engine.NewEngine(tt.fields.configFile, "", false, true)
   477  			assert.NoError(t, err)
   478  			tiles := NewTiles(newEngine)
   479  			handler := tiles.TileMatrixSet()
   480  			handler.ServeHTTP(rr, req)
   481  
   482  			assert.Equal(t, tt.want.statusCode, rr.Code)
   483  			assert.Contains(t, rr.Body.String(), tt.want.bodyContains)
   484  		})
   485  	}
   486  }
   487  
   488  func TestTile_TilematrixSets(t *testing.T) {
   489  	type fields struct {
   490  		configFile string
   491  		url        string
   492  	}
   493  	type want struct {
   494  		bodyContains string
   495  		statusCode   int
   496  	}
   497  	tests := []struct {
   498  		name   string
   499  		fields fields
   500  		want   want
   501  	}{
   502  		{
   503  			name: "NetherlandsRDNewQuad",
   504  			fields: fields{
   505  				configFile: "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   506  				url:        "http://localhost:8080/tileMatrixSets",
   507  			},
   508  			want: want{
   509  				bodyContains: "NetherlandsRDNewQuad",
   510  				statusCode:   http.StatusOK,
   511  			},
   512  		},
   513  		{
   514  			name: "EuropeanETRS89_LAEAQuad",
   515  			fields: fields{
   516  				configFile: "internal/ogc/tiles/testdata/config_minimal_tiles_2.yaml",
   517  				url:        "http://localhost:8080/tileMatrixSets",
   518  			},
   519  			want: want{
   520  				bodyContains: "EuropeanETRS89_LAEAQuad",
   521  				statusCode:   http.StatusOK,
   522  			},
   523  		},
   524  		{
   525  			name: "EuropeanETRS89_LAEAQuad",
   526  			fields: fields{
   527  				configFile: "internal/ogc/tiles/testdata/config_minimal_tiles.yaml",
   528  				url:        "http://localhost:8080/tileMatrixSets",
   529  			},
   530  			want: want{
   531  				bodyContains: "WebMercatorQuad",
   532  				statusCode:   http.StatusOK,
   533  			},
   534  		},
   535  	}
   536  	for _, tt := range tests {
   537  		t.Run(tt.name, func(t *testing.T) {
   538  			req, err := createTilematrixSetsRequest(tt.fields.url)
   539  			if err != nil {
   540  				log.Fatal(err)
   541  			}
   542  			rr, ts := createMockServer()
   543  			defer ts.Close()
   544  
   545  			newEngine, err := engine.NewEngine(tt.fields.configFile, "", false, true)
   546  			assert.NoError(t, err)
   547  			tiles := NewTiles(newEngine)
   548  			handler := tiles.TileMatrixSets()
   549  			handler.ServeHTTP(rr, req)
   550  
   551  			assert.Equal(t, tt.want.statusCode, rr.Code)
   552  			assert.Contains(t, rr.Body.String(), tt.want.bodyContains)
   553  		})
   554  	}
   555  }
   556  
   557  func createMockServer() (*httptest.ResponseRecorder, *httptest.Server) {
   558  	rr := httptest.NewRecorder()
   559  	l, err := net.Listen("tcp", "localhost:9090")
   560  	if err != nil {
   561  		log.Fatal(err)
   562  	}
   563  	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   564  		engine.SafeWrite(w.Write, []byte(r.URL.String()))
   565  	}))
   566  	ts.Listener.Close()
   567  	ts.Listener = l
   568  	ts.Start()
   569  	return rr, ts
   570  }
   571  
   572  func createTileRequest(url string, tileMatrixSetID string, tileMatrix string, tileRow string, tileCol string) (*http.Request, error) {
   573  	req, err := http.NewRequest(http.MethodGet, url, nil)
   574  	rctx := chi.NewRouteContext()
   575  	rctx.URLParams.Add("tileMatrixSetId", tileMatrixSetID)
   576  	rctx.URLParams.Add("tileMatrix", tileMatrix)
   577  	rctx.URLParams.Add("tileRow", tileRow)
   578  	rctx.URLParams.Add("tileCol", tileCol)
   579  
   580  	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
   581  	return req, err
   582  }
   583  
   584  func createTilesetsListRequest(url string) (*http.Request, error) {
   585  	req, err := http.NewRequest(http.MethodGet, url, nil)
   586  	rctx := chi.NewRouteContext()
   587  
   588  	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
   589  	return req, err
   590  }
   591  
   592  func createTilesetRequest(url string, tileMatrixSetID string) (*http.Request, error) {
   593  	req, err := http.NewRequest(http.MethodGet, url, nil)
   594  	rctx := chi.NewRouteContext()
   595  	rctx.URLParams.Add("tileMatrixSetId", tileMatrixSetID)
   596  
   597  	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
   598  	return req, err
   599  }
   600  
   601  func createTilematrixSetRequest(url string, tileMatrixSetID string) (*http.Request, error) {
   602  	req, err := http.NewRequest(http.MethodGet, url, nil)
   603  	rctx := chi.NewRouteContext()
   604  	rctx.URLParams.Add("tileMatrixSetId", tileMatrixSetID)
   605  
   606  	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
   607  	return req, err
   608  }
   609  
   610  func createTilematrixSetsRequest(url string) (*http.Request, error) {
   611  	req, err := http.NewRequest(http.MethodGet, url, nil)
   612  	rctx := chi.NewRouteContext()
   613  
   614  	req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
   615  	return req, err
   616  }