[{"data":1,"prerenderedAt":1810},["ShallowReactive",2],{"navigation_docs_en":3,"-en-storage-testing":441,"-en-storage-testing-surround":1805},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":169,"body":443,"description":1798,"extension":1799,"links":1800,"meta":1801,"navigation":1802,"path":170,"seo":1803,"stem":171,"__hash__":1804},"docs_en\u002Fen\u002F15.storage\u002F5.testing.md",{"type":444,"value":445,"toc":1791},"minimark",[446,458,463,758,783,788,826,855,859,870,1692,1695,1755,1759,1787],[447,448,449,453,454,457],"p",{},[450,451,452],"code",{},"storage\u002Fstoragetest"," is a shared conformance test suite: the same set of behavioral tests run against every ",[450,455,456],{},"Driver"," implementation, built-in or your own, so \"does this driver actually behave like the others\" is a question you answer by running one function, not by hand-writing a parallel test file per driver.",[459,460,462],"h2",{"id":461},"running-it-against-a-driver","Running it against a Driver",[464,465,470],"pre",{"className":466,"code":467,"language":468,"meta":469,"style":469},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import (\n    \"testing\"\n\n    \"github.com\u002Fgp-system\u002Fstorage\u002Fstoragetest\"\n)\n\nfunc TestMyDriver(t *testing.T) {\n    storagetest.Run(t, func(t *testing.T) storage.Driver {\n        dir := t.TempDir()\n        driver, err := local.New(local.Config{Root: dir, Secret: \"test-secret\"})\n        if err != nil {\n            t.Fatal(err)\n        }\n        return driver\n    }, storagetest.Options{})\n}\n","go","",[450,471,472,485,498,505,515,521,526,560,603,623,686,702,720,726,735,752],{"__ignoreMap":469},[473,474,477,481],"span",{"class":475,"line":476},"line",1,[473,478,480],{"class":479},"s7zQu","import",[473,482,484],{"class":483},"sMK4o"," (\n",[473,486,488,491,495],{"class":475,"line":487},2,[473,489,490],{"class":483},"    \"",[473,492,494],{"class":493},"sBMFI","testing",[473,496,497],{"class":483},"\"\n",[473,499,501],{"class":475,"line":500},3,[473,502,504],{"emptyLinePlaceholder":503},true,"\n",[473,506,508,510,513],{"class":475,"line":507},4,[473,509,490],{"class":483},[473,511,512],{"class":493},"github.com\u002Fgp-system\u002Fstorage\u002Fstoragetest",[473,514,497],{"class":483},[473,516,518],{"class":475,"line":517},5,[473,519,520],{"class":483},")\n",[473,522,524],{"class":475,"line":523},6,[473,525,504],{"emptyLinePlaceholder":503},[473,527,529,532,536,539,543,546,548,551,554,557],{"class":475,"line":528},7,[473,530,531],{"class":483},"func",[473,533,535],{"class":534},"s2Zo4"," TestMyDriver",[473,537,538],{"class":483},"(",[473,540,542],{"class":541},"sHdIc","t",[473,544,545],{"class":483}," *",[473,547,494],{"class":493},[473,549,550],{"class":483},".",[473,552,553],{"class":493},"T",[473,555,556],{"class":483},")",[473,558,559],{"class":483}," {\n",[473,561,563,567,569,572,574,576,579,582,584,586,588,590,592,594,597,599,601],{"class":475,"line":562},8,[473,564,566],{"class":565},"sTEyZ","    storagetest",[473,568,550],{"class":483},[473,570,571],{"class":534},"Run",[473,573,538],{"class":483},[473,575,542],{"class":565},[473,577,578],{"class":483},",",[473,580,581],{"class":483}," func(",[473,583,542],{"class":541},[473,585,545],{"class":483},[473,587,494],{"class":493},[473,589,550],{"class":483},[473,591,553],{"class":493},[473,593,556],{"class":483},[473,595,596],{"class":493}," storage",[473,598,550],{"class":483},[473,600,456],{"class":493},[473,602,559],{"class":483},[473,604,606,609,612,615,617,620],{"class":475,"line":605},9,[473,607,608],{"class":565},"        dir ",[473,610,611],{"class":483},":=",[473,613,614],{"class":565}," t",[473,616,550],{"class":483},[473,618,619],{"class":534},"TempDir",[473,621,622],{"class":483},"()\n",[473,624,626,629,631,634,636,639,641,644,646,649,651,654,657,660,663,666,668,671,673,676,680,683],{"class":475,"line":625},10,[473,627,628],{"class":565},"        driver",[473,630,578],{"class":483},[473,632,633],{"class":565}," err ",[473,635,611],{"class":483},[473,637,638],{"class":565}," local",[473,640,550],{"class":483},[473,642,643],{"class":534},"New",[473,645,538],{"class":483},[473,647,648],{"class":493},"local",[473,650,550],{"class":483},[473,652,653],{"class":493},"Config",[473,655,656],{"class":483},"{",[473,658,659],{"class":565},"Root",[473,661,662],{"class":483},":",[473,664,665],{"class":565}," dir",[473,667,578],{"class":483},[473,669,670],{"class":565}," Secret",[473,672,662],{"class":483},[473,674,675],{"class":483}," \"",[473,677,679],{"class":678},"sfazB","test-secret",[473,681,682],{"class":483},"\"",[473,684,685],{"class":483},"})\n",[473,687,689,692,694,697,700],{"class":475,"line":688},11,[473,690,691],{"class":479},"        if",[473,693,633],{"class":565},[473,695,696],{"class":483},"!=",[473,698,699],{"class":483}," nil",[473,701,559],{"class":483},[473,703,705,708,710,713,715,718],{"class":475,"line":704},12,[473,706,707],{"class":565},"            t",[473,709,550],{"class":483},[473,711,712],{"class":534},"Fatal",[473,714,538],{"class":483},[473,716,717],{"class":565},"err",[473,719,520],{"class":483},[473,721,723],{"class":475,"line":722},13,[473,724,725],{"class":483},"        }\n",[473,727,729,732],{"class":475,"line":728},14,[473,730,731],{"class":479},"        return",[473,733,734],{"class":565}," driver\n",[473,736,738,741,744,746,749],{"class":475,"line":737},15,[473,739,740],{"class":483},"    },",[473,742,743],{"class":493}," storagetest",[473,745,550],{"class":483},[473,747,748],{"class":493},"Options",[473,750,751],{"class":483},"{})\n",[473,753,755],{"class":475,"line":754},16,[473,756,757],{"class":483},"}\n",[447,759,760,763,764,767,768,770,771,774,775,778,779,782],{},[450,761,762],{},"Run(t, factory, opts)"," takes a fresh-driver factory, called once per subtest so tests never share state, and a ",[450,765,766],{},"storagetest.Options",". It then exercises the full ",[450,769,456],{}," surface: put and read round-trips, range reads, stat, list and pagination, copy, delete idempotency, ",[450,772,773],{},"ValidPath"," rejection of traversal attempts, and, where the driver supports it, temporary URLs. A driver that returns ",[450,776,777],{},"ErrUnsupported"," for an optional capability is treated as a valid, if less capable, implementation, not a failure: ",[450,780,781],{},"storagetest"," checks that the error is the right one, not that every driver supports everything.",[784,785,787],"h3",{"id":786},"options-for-drivers-that-dont-preserve-everything","Options for drivers that don't preserve everything",[464,789,791],{"className":466,"code":790,"language":468,"meta":469,"style":469},"type Options struct {\n    PersistsContentType bool\n    PersistsMetadata    bool\n}\n",[450,792,793,806,815,822],{"__ignoreMap":469},[473,794,795,798,801,804],{"class":475,"line":476},[473,796,797],{"class":483},"type",[473,799,800],{"class":493}," Options",[473,802,803],{"class":483}," struct",[473,805,559],{"class":483},[473,807,808,811],{"class":475,"line":487},[473,809,810],{"class":565},"    PersistsContentType ",[473,812,814],{"class":813},"spNyl","bool\n",[473,816,817,820],{"class":475,"line":500},[473,818,819],{"class":565},"    PersistsMetadata    ",[473,821,814],{"class":813},[473,823,824],{"class":475,"line":507},[473,825,757],{"class":483},[447,827,828,829,832,833,836,837,840,841,843,844,836,847,850,851,854],{},"Not every backend round-trips every piece of metadata; a minimal driver (say, one backed by a bare key\u002Fvalue store with no attribute support) might not preserve ",[450,830,831],{},"PutOptions.ContentType"," or other metadata across a ",[450,834,835],{},"Put","\u002F",[450,838,839],{},"Stat"," cycle. Rather than forcing every driver to fake support it doesn't have, ",[450,842,766],{}," lets you say so up front: leave ",[450,845,846],{},"PersistsContentType",[450,848,849],{},"PersistsMetadata"," at their zero value (",[450,852,853],{},"false",") and the suite skips the assertions that would otherwise fail on a driver that's honest about its limits.",[459,856,858],{"id":857},"writing-a-custom-driver","Writing a custom Driver",[447,860,861,862,864,865,869],{},"The ",[450,863,456],{}," interface (see ",[866,867,52],"a",{"href":868},"\u002Fen\u002Fstorage\u002Foverview#driver-the-backend-contract",") is the whole surface a new backend needs to cover. A minimal skeleton:",[464,871,873],{"className":466,"code":872,"language":468,"meta":469,"style":469},"type myDriver struct {\n    name string\n    \u002F\u002F backend-specific fields\n}\n\nfunc (d *myDriver) Name() string { return d.name }\n\nfunc (d *myDriver) Put(ctx context.Context, path string, r io.Reader, opts storage.PutOptions) error {\n    if !storage.ValidPath(path) {\n        return storage.ErrInvalidPath\n    }\n    \u002F\u002F write r to the backend at path\n    return nil\n}\n\nfunc (d *myDriver) Reader(ctx context.Context, path string) (io.ReadCloser, error) {\n    \u002F\u002F return the object at path, or storage.ErrNotExist if it's missing\n}\n\nfunc (d *myDriver) RangeReader(ctx context.Context, path string, offset, length int64) (io.ReadCloser, error) {\n    \u002F\u002F a byte-range read; length \u003C 0 means \"to the end\"\n}\n\nfunc (d *myDriver) Stat(ctx context.Context, path string) (storage.FileInfo, error) { \u002F* ... *\u002F }\nfunc (d *myDriver) List(ctx context.Context, opts storage.ListOptions) (storage.ListPage, error) { \u002F* ... *\u002F }\nfunc (d *myDriver) Copy(ctx context.Context, src, dst string) error { \u002F* ... *\u002F }\nfunc (d *myDriver) Delete(ctx context.Context, path string) error { \u002F* idempotent *\u002F }\n\nfunc (d *myDriver) URL(ctx context.Context, path string) (string, error) { \u002F* ... *\u002F }\n\nfunc (d *myDriver) TemporaryURL(ctx context.Context, path string, opts storage.TemporaryURLOptions) (string, error) {\n    return \"\", storage.ErrUnsupported \u002F\u002F if the backend can't sign URLs\n}\n\nfunc (d *myDriver) TemporaryUploadURL(ctx context.Context, path string, opts storage.TemporaryUploadURLOptions) (storage.UploadURL, error) {\n    return storage.UploadURL{}, storage.ErrUnsupported\n}\n",[450,874,875,886,894,900,904,908,951,955,1024,1047,1058,1063,1068,1076,1080,1084,1137,1143,1148,1153,1218,1224,1229,1234,1293,1355,1405,1450,1455,1508,1513,1572,1592,1597,1602,1666,1687],{"__ignoreMap":469},[473,876,877,879,882,884],{"class":475,"line":476},[473,878,797],{"class":483},[473,880,881],{"class":493}," myDriver",[473,883,803],{"class":483},[473,885,559],{"class":483},[473,887,888,891],{"class":475,"line":487},[473,889,890],{"class":565},"    name ",[473,892,893],{"class":813},"string\n",[473,895,896],{"class":475,"line":500},[473,897,899],{"class":898},"sHwdD","    \u002F\u002F backend-specific fields\n",[473,901,902],{"class":475,"line":507},[473,903,757],{"class":483},[473,905,906],{"class":475,"line":517},[473,907,504],{"emptyLinePlaceholder":503},[473,909,910,912,915,918,921,924,926,929,932,935,938,941,944,946,949],{"class":475,"line":523},[473,911,531],{"class":483},[473,913,914],{"class":483}," (",[473,916,917],{"class":541},"d ",[473,919,920],{"class":483},"*",[473,922,923],{"class":493},"myDriver",[473,925,556],{"class":483},[473,927,928],{"class":534}," Name",[473,930,931],{"class":483},"()",[473,933,934],{"class":813}," string",[473,936,937],{"class":483}," {",[473,939,940],{"class":479}," return",[473,942,943],{"class":565}," d",[473,945,550],{"class":483},[473,947,948],{"class":565},"name ",[473,950,757],{"class":483},[473,952,953],{"class":475,"line":528},[473,954,504],{"emptyLinePlaceholder":503},[473,956,957,959,961,963,965,967,969,972,974,977,980,982,985,987,990,992,994,997,1000,1002,1005,1007,1010,1012,1014,1017,1019,1022],{"class":475,"line":562},[473,958,531],{"class":483},[473,960,914],{"class":483},[473,962,917],{"class":541},[473,964,920],{"class":483},[473,966,923],{"class":493},[473,968,556],{"class":483},[473,970,971],{"class":534}," Put",[473,973,538],{"class":483},[473,975,976],{"class":541},"ctx",[473,978,979],{"class":493}," context",[473,981,550],{"class":483},[473,983,984],{"class":493},"Context",[473,986,578],{"class":483},[473,988,989],{"class":541}," path",[473,991,934],{"class":813},[473,993,578],{"class":483},[473,995,996],{"class":541}," r",[473,998,999],{"class":493}," io",[473,1001,550],{"class":483},[473,1003,1004],{"class":493},"Reader",[473,1006,578],{"class":483},[473,1008,1009],{"class":541}," opts",[473,1011,596],{"class":493},[473,1013,550],{"class":483},[473,1015,1016],{"class":493},"PutOptions",[473,1018,556],{"class":483},[473,1020,1021],{"class":813}," error",[473,1023,559],{"class":483},[473,1025,1026,1029,1032,1034,1036,1038,1040,1043,1045],{"class":475,"line":605},[473,1027,1028],{"class":479},"    if",[473,1030,1031],{"class":483}," !",[473,1033,147],{"class":565},[473,1035,550],{"class":483},[473,1037,773],{"class":534},[473,1039,538],{"class":483},[473,1041,1042],{"class":565},"path",[473,1044,556],{"class":483},[473,1046,559],{"class":483},[473,1048,1049,1051,1053,1055],{"class":475,"line":625},[473,1050,731],{"class":479},[473,1052,596],{"class":565},[473,1054,550],{"class":483},[473,1056,1057],{"class":565},"ErrInvalidPath\n",[473,1059,1060],{"class":475,"line":688},[473,1061,1062],{"class":483},"    }\n",[473,1064,1065],{"class":475,"line":704},[473,1066,1067],{"class":898},"    \u002F\u002F write r to the backend at path\n",[473,1069,1070,1073],{"class":475,"line":722},[473,1071,1072],{"class":479},"    return",[473,1074,1075],{"class":483}," nil\n",[473,1077,1078],{"class":475,"line":728},[473,1079,757],{"class":483},[473,1081,1082],{"class":475,"line":737},[473,1083,504],{"emptyLinePlaceholder":503},[473,1085,1086,1088,1090,1092,1094,1096,1098,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1124,1126,1129,1131,1133,1135],{"class":475,"line":754},[473,1087,531],{"class":483},[473,1089,914],{"class":483},[473,1091,917],{"class":541},[473,1093,920],{"class":483},[473,1095,923],{"class":493},[473,1097,556],{"class":483},[473,1099,1100],{"class":534}," Reader",[473,1102,538],{"class":483},[473,1104,976],{"class":541},[473,1106,979],{"class":493},[473,1108,550],{"class":483},[473,1110,984],{"class":493},[473,1112,578],{"class":483},[473,1114,989],{"class":541},[473,1116,934],{"class":813},[473,1118,556],{"class":483},[473,1120,914],{"class":483},[473,1122,1123],{"class":493},"io",[473,1125,550],{"class":483},[473,1127,1128],{"class":493},"ReadCloser",[473,1130,578],{"class":483},[473,1132,1021],{"class":813},[473,1134,556],{"class":483},[473,1136,559],{"class":483},[473,1138,1140],{"class":475,"line":1139},17,[473,1141,1142],{"class":898},"    \u002F\u002F return the object at path, or storage.ErrNotExist if it's missing\n",[473,1144,1146],{"class":475,"line":1145},18,[473,1147,757],{"class":483},[473,1149,1151],{"class":475,"line":1150},19,[473,1152,504],{"emptyLinePlaceholder":503},[473,1154,1156,1158,1160,1162,1164,1166,1168,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1192,1194,1197,1200,1202,1204,1206,1208,1210,1212,1214,1216],{"class":475,"line":1155},20,[473,1157,531],{"class":483},[473,1159,914],{"class":483},[473,1161,917],{"class":541},[473,1163,920],{"class":483},[473,1165,923],{"class":493},[473,1167,556],{"class":483},[473,1169,1170],{"class":534}," RangeReader",[473,1172,538],{"class":483},[473,1174,976],{"class":541},[473,1176,979],{"class":493},[473,1178,550],{"class":483},[473,1180,984],{"class":493},[473,1182,578],{"class":483},[473,1184,989],{"class":541},[473,1186,934],{"class":813},[473,1188,578],{"class":483},[473,1190,1191],{"class":541}," offset",[473,1193,578],{"class":483},[473,1195,1196],{"class":541}," length",[473,1198,1199],{"class":813}," int64",[473,1201,556],{"class":483},[473,1203,914],{"class":483},[473,1205,1123],{"class":493},[473,1207,550],{"class":483},[473,1209,1128],{"class":493},[473,1211,578],{"class":483},[473,1213,1021],{"class":813},[473,1215,556],{"class":483},[473,1217,559],{"class":483},[473,1219,1221],{"class":475,"line":1220},21,[473,1222,1223],{"class":898},"    \u002F\u002F a byte-range read; length \u003C 0 means \"to the end\"\n",[473,1225,1227],{"class":475,"line":1226},22,[473,1228,757],{"class":483},[473,1230,1232],{"class":475,"line":1231},23,[473,1233,504],{"emptyLinePlaceholder":503},[473,1235,1237,1239,1241,1243,1245,1247,1249,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1279,1281,1283,1285,1287,1290],{"class":475,"line":1236},24,[473,1238,531],{"class":483},[473,1240,914],{"class":483},[473,1242,917],{"class":541},[473,1244,920],{"class":483},[473,1246,923],{"class":493},[473,1248,556],{"class":483},[473,1250,1251],{"class":534}," Stat",[473,1253,538],{"class":483},[473,1255,976],{"class":541},[473,1257,979],{"class":493},[473,1259,550],{"class":483},[473,1261,984],{"class":493},[473,1263,578],{"class":483},[473,1265,989],{"class":541},[473,1267,934],{"class":813},[473,1269,556],{"class":483},[473,1271,914],{"class":483},[473,1273,147],{"class":493},[473,1275,550],{"class":483},[473,1277,1278],{"class":493},"FileInfo",[473,1280,578],{"class":483},[473,1282,1021],{"class":813},[473,1284,556],{"class":483},[473,1286,937],{"class":483},[473,1288,1289],{"class":898}," \u002F* ... *\u002F",[473,1291,1292],{"class":483}," }\n",[473,1294,1296,1298,1300,1302,1304,1306,1308,1311,1313,1315,1317,1319,1321,1323,1325,1327,1329,1332,1334,1336,1338,1340,1343,1345,1347,1349,1351,1353],{"class":475,"line":1295},25,[473,1297,531],{"class":483},[473,1299,914],{"class":483},[473,1301,917],{"class":541},[473,1303,920],{"class":483},[473,1305,923],{"class":493},[473,1307,556],{"class":483},[473,1309,1310],{"class":534}," List",[473,1312,538],{"class":483},[473,1314,976],{"class":541},[473,1316,979],{"class":493},[473,1318,550],{"class":483},[473,1320,984],{"class":493},[473,1322,578],{"class":483},[473,1324,1009],{"class":541},[473,1326,596],{"class":493},[473,1328,550],{"class":483},[473,1330,1331],{"class":493},"ListOptions",[473,1333,556],{"class":483},[473,1335,914],{"class":483},[473,1337,147],{"class":493},[473,1339,550],{"class":483},[473,1341,1342],{"class":493},"ListPage",[473,1344,578],{"class":483},[473,1346,1021],{"class":813},[473,1348,556],{"class":483},[473,1350,937],{"class":483},[473,1352,1289],{"class":898},[473,1354,1292],{"class":483},[473,1356,1358,1360,1362,1364,1366,1368,1370,1373,1375,1377,1379,1381,1383,1385,1388,1390,1393,1395,1397,1399,1401,1403],{"class":475,"line":1357},26,[473,1359,531],{"class":483},[473,1361,914],{"class":483},[473,1363,917],{"class":541},[473,1365,920],{"class":483},[473,1367,923],{"class":493},[473,1369,556],{"class":483},[473,1371,1372],{"class":534}," Copy",[473,1374,538],{"class":483},[473,1376,976],{"class":541},[473,1378,979],{"class":493},[473,1380,550],{"class":483},[473,1382,984],{"class":493},[473,1384,578],{"class":483},[473,1386,1387],{"class":541}," src",[473,1389,578],{"class":483},[473,1391,1392],{"class":541}," dst",[473,1394,934],{"class":813},[473,1396,556],{"class":483},[473,1398,1021],{"class":813},[473,1400,937],{"class":483},[473,1402,1289],{"class":898},[473,1404,1292],{"class":483},[473,1406,1408,1410,1412,1414,1416,1418,1420,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445,1448],{"class":475,"line":1407},27,[473,1409,531],{"class":483},[473,1411,914],{"class":483},[473,1413,917],{"class":541},[473,1415,920],{"class":483},[473,1417,923],{"class":493},[473,1419,556],{"class":483},[473,1421,1422],{"class":534}," Delete",[473,1424,538],{"class":483},[473,1426,976],{"class":541},[473,1428,979],{"class":493},[473,1430,550],{"class":483},[473,1432,984],{"class":493},[473,1434,578],{"class":483},[473,1436,989],{"class":541},[473,1438,934],{"class":813},[473,1440,556],{"class":483},[473,1442,1021],{"class":813},[473,1444,937],{"class":483},[473,1446,1447],{"class":898}," \u002F* idempotent *\u002F",[473,1449,1292],{"class":483},[473,1451,1453],{"class":475,"line":1452},28,[473,1454,504],{"emptyLinePlaceholder":503},[473,1456,1458,1460,1462,1464,1466,1468,1470,1473,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493,1496,1498,1500,1502,1504,1506],{"class":475,"line":1457},29,[473,1459,531],{"class":483},[473,1461,914],{"class":483},[473,1463,917],{"class":541},[473,1465,920],{"class":483},[473,1467,923],{"class":493},[473,1469,556],{"class":483},[473,1471,1472],{"class":534}," URL",[473,1474,538],{"class":483},[473,1476,976],{"class":541},[473,1478,979],{"class":493},[473,1480,550],{"class":483},[473,1482,984],{"class":493},[473,1484,578],{"class":483},[473,1486,989],{"class":541},[473,1488,934],{"class":813},[473,1490,556],{"class":483},[473,1492,914],{"class":483},[473,1494,1495],{"class":813},"string",[473,1497,578],{"class":483},[473,1499,1021],{"class":813},[473,1501,556],{"class":483},[473,1503,937],{"class":483},[473,1505,1289],{"class":898},[473,1507,1292],{"class":483},[473,1509,1511],{"class":475,"line":1510},30,[473,1512,504],{"emptyLinePlaceholder":503},[473,1514,1516,1518,1520,1522,1524,1526,1528,1531,1533,1535,1537,1539,1541,1543,1545,1547,1549,1551,1553,1555,1558,1560,1562,1564,1566,1568,1570],{"class":475,"line":1515},31,[473,1517,531],{"class":483},[473,1519,914],{"class":483},[473,1521,917],{"class":541},[473,1523,920],{"class":483},[473,1525,923],{"class":493},[473,1527,556],{"class":483},[473,1529,1530],{"class":534}," TemporaryURL",[473,1532,538],{"class":483},[473,1534,976],{"class":541},[473,1536,979],{"class":493},[473,1538,550],{"class":483},[473,1540,984],{"class":493},[473,1542,578],{"class":483},[473,1544,989],{"class":541},[473,1546,934],{"class":813},[473,1548,578],{"class":483},[473,1550,1009],{"class":541},[473,1552,596],{"class":493},[473,1554,550],{"class":483},[473,1556,1557],{"class":493},"TemporaryURLOptions",[473,1559,556],{"class":483},[473,1561,914],{"class":483},[473,1563,1495],{"class":813},[473,1565,578],{"class":483},[473,1567,1021],{"class":813},[473,1569,556],{"class":483},[473,1571,559],{"class":483},[473,1573,1575,1577,1580,1582,1584,1586,1589],{"class":475,"line":1574},32,[473,1576,1072],{"class":479},[473,1578,1579],{"class":483}," \"\"",[473,1581,578],{"class":483},[473,1583,596],{"class":565},[473,1585,550],{"class":483},[473,1587,1588],{"class":565},"ErrUnsupported ",[473,1590,1591],{"class":898},"\u002F\u002F if the backend can't sign URLs\n",[473,1593,1595],{"class":475,"line":1594},33,[473,1596,757],{"class":483},[473,1598,1600],{"class":475,"line":1599},34,[473,1601,504],{"emptyLinePlaceholder":503},[473,1603,1605,1607,1609,1611,1613,1615,1617,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644,1647,1649,1651,1653,1655,1658,1660,1662,1664],{"class":475,"line":1604},35,[473,1606,531],{"class":483},[473,1608,914],{"class":483},[473,1610,917],{"class":541},[473,1612,920],{"class":483},[473,1614,923],{"class":493},[473,1616,556],{"class":483},[473,1618,1619],{"class":534}," TemporaryUploadURL",[473,1621,538],{"class":483},[473,1623,976],{"class":541},[473,1625,979],{"class":493},[473,1627,550],{"class":483},[473,1629,984],{"class":493},[473,1631,578],{"class":483},[473,1633,989],{"class":541},[473,1635,934],{"class":813},[473,1637,578],{"class":483},[473,1639,1009],{"class":541},[473,1641,596],{"class":493},[473,1643,550],{"class":483},[473,1645,1646],{"class":493},"TemporaryUploadURLOptions",[473,1648,556],{"class":483},[473,1650,914],{"class":483},[473,1652,147],{"class":493},[473,1654,550],{"class":483},[473,1656,1657],{"class":493},"UploadURL",[473,1659,578],{"class":483},[473,1661,1021],{"class":813},[473,1663,556],{"class":483},[473,1665,559],{"class":483},[473,1667,1669,1671,1673,1675,1677,1680,1682,1684],{"class":475,"line":1668},36,[473,1670,1072],{"class":479},[473,1672,596],{"class":493},[473,1674,550],{"class":483},[473,1676,1657],{"class":493},[473,1678,1679],{"class":483},"{},",[473,1681,596],{"class":565},[473,1683,550],{"class":483},[473,1685,1686],{"class":565},"ErrUnsupported\n",[473,1688,1690],{"class":475,"line":1689},37,[473,1691,757],{"class":483},[447,1693,1694],{},"A few things worth calling out while writing one:",[1696,1697,1698,1706,1712,1726,1744],"ul",{},[1699,1700,1701,1702,1705],"li",{},"Call ",[450,1703,1704],{},"storage.ValidPath"," at the top of every method that takes a path and touches the backend; it's the one thing every driver is expected to share.",[1699,1707,1708,1711],{},[450,1709,1710],{},"Delete"," must be idempotent: deleting a path that's already gone is not an error.",[1699,1713,1714,1715,1718,1719,1725],{},"If a capability genuinely doesn't apply to the backend, return ",[450,1716,1717],{},"storage.ErrUnsupported"," rather than a half-working approximation; callers are expected to handle it (see ",[866,1720,1722],{"href":1721},"\u002Fen\u002Fstorage\u002Fdisk-api#urls",[450,1723,1724],{},"Disk.ProvidesTemporaryURLs",").",[1699,1727,1728,1729,1732,1733,1736,1737,1740,1741,1743],{},"Implement the optional ",[450,1730,1731],{},"Mover"," interface only if the backend has a native, non-copy move; otherwise leave it unimplemented and ",[450,1734,1735],{},"Disk.Move"," falls back to ",[450,1738,1739],{},"Copy"," + ",[450,1742,1710],{}," on your driver's behalf.",[1699,1745,1746,1747,1750,1751,1754],{},"Run it through ",[450,1748,1749],{},"storagetest.Run"," before wiring it into anything real. It's the fastest way to find a corner (an off-by-one in a range read, a ",[450,1752,1753],{},"List"," page that doesn't paginate correctly) that a hand-written smoke test wouldn't catch.",[459,1756,1758],{"id":1757},"related-pages","Related pages",[1696,1760,1761,1772,1777,1782],{},[1699,1762,1763,1765,1766,1768,1769,1771],{},[866,1764,52],{"href":153},": the full ",[450,1767,456],{}," contract and the ",[450,1770,1731],{}," interface.",[1699,1773,1774,1776],{},[866,1775,156],{"href":157},": the methods that end up calling into your driver.",[1699,1778,1779,1781],{},[866,1780,160],{"href":161},": read these two drivers' source for a worked example of the contract.",[1699,1783,1784,1786],{},[866,1785,164],{"href":165},": the third built-in driver, and the one with the most backend-specific logic.",[1788,1789,1790],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":469,"searchDepth":487,"depth":487,"links":1792},[1793,1796,1797],{"id":461,"depth":487,"text":462,"children":1794},[1795],{"id":786,"depth":500,"text":787},{"id":857,"depth":487,"text":858},{"id":1757,"depth":487,"text":1758},"The storagetest conformance suite, and what a custom Driver needs to implement.","md",null,{},{"icon":172},{"title":169,"description":1798},"xerFz6WwY52KpxQ6Wn8kZ1ANNZUfUB8_JGIjETxXTvM",[1806,1808],{"title":164,"path":165,"stem":166,"description":1807,"icon":167,"children":-1},"The S3-compatible driver, a separately versioned module built on minio-go.",{"title":52,"path":180,"stem":181,"description":1809,"icon":175,"children":-1},"The telemetry facade: logging, OpenTelemetry and Sentry in a single call, with one shutdown guarantee.",1785445895460]