From e9b86fece53a3d7177afe9b79d6361b2b01b9533 Mon Sep 17 00:00:00 2001 From: ernado Date: Sat, 20 Jan 2024 20:15:54 +0000 Subject: [PATCH] deploy: a96cd57d5f6e797ad010fc0a7d8029eac2ee4844 --- 404.html | 4 ++-- assets/js/b2e2bd1f.13dd70a3.js | 1 - assets/js/b2e2bd1f.2f59d669.js | 1 + .../{runtime~main.4d762683.js => runtime~main.b57e11fc.js} | 2 +- blog/archive/index.html | 4 ++-- blog/index.html | 4 ++-- blog/ogen-intro/index.html | 4 ++-- docs/concepts/convenient_errors/index.html | 4 ++-- docs/concepts/interface_responses/index.html | 4 ++-- docs/concepts/middlewares/index.html | 4 ++-- docs/concepts/static_router/index.html | 4 ++-- docs/faq/index.html | 4 ++-- docs/features/index.html | 4 ++-- docs/intro/index.html | 4 ++-- docs/misc/request_lifecycle/index.html | 4 ++-- docs/spec/extensions/index.html | 4 ++-- docs/spec/file_upload/index.html | 4 ++-- docs/types/any/index.html | 4 ++-- docs/types/map/index.html | 4 ++-- docs/types/optional/index.html | 4 ++-- docs/types/primitive/index.html | 6 +++--- docs/types/sumtype/index.html | 4 ++-- index.html | 4 ++-- markdown-page/index.html | 4 ++-- 24 files changed, 45 insertions(+), 45 deletions(-) delete mode 100644 assets/js/b2e2bd1f.13dd70a3.js create mode 100644 assets/js/b2e2bd1f.2f59d669.js rename assets/js/{runtime~main.4d762683.js => runtime~main.b57e11fc.js} (98%) diff --git a/404.html b/404.html index 2115900..6ce6070 100644 --- a/404.html +++ b/404.html @@ -10,13 +10,13 @@ - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/b2e2bd1f.13dd70a3.js b/assets/js/b2e2bd1f.13dd70a3.js deleted file mode 100644 index 24fcd61..0000000 --- a/assets/js/b2e2bd1f.13dd70a3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(globalThis.webpackChunkdocs_v_2=globalThis.webpackChunkdocs_v_2||[]).push([[450],{3905:(t,e,n)=>{n.d(e,{Zo:()=>o,kt:()=>N});var a=n(7294);function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,a)}return n}function l(t){for(var e=1;e=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}var m=a.createContext({}),d=function(t){var e=a.useContext(m),n=e;return t&&(n="function"==typeof t?t(e):l(l({},e),t)),n},o=function(t){var e=d(t.components);return a.createElement(m.Provider,{value:e},t.children)},k="mdxType",u={inlineCode:"code",wrapper:function(t){var e=t.children;return a.createElement(a.Fragment,{},e)}},g=a.forwardRef((function(t,e){var n=t.components,r=t.mdxType,i=t.originalType,m=t.parentName,o=p(t,["components","mdxType","originalType","parentName"]),k=d(n),g=r,N=k["".concat(m,".").concat(g)]||k[g]||u[g]||i;return n?a.createElement(N,l(l({ref:e},o),{},{components:n})):a.createElement(N,l({ref:e},o))}));function N(t,e){var n=arguments,r=e&&e.mdxType;if("string"==typeof t||r){var i=n.length,l=new Array(i);l[0]=g;var p={};for(var m in e)hasOwnProperty.call(e,m)&&(p[m]=e[m]);p.originalType=t,p[k]="string"==typeof t?t:r,l[1]=p;for(var d=2;d{n.r(e),n.d(e,{assets:()=>m,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>p,toc:()=>d});var a=n(7462),r=(n(7294),n(3905));const i={id:"primitive",title:"Primitives",sidebar_label:"Primitive"},l=void 0,p={unversionedId:"types/primitive",id:"types/primitive",title:"Primitives",description:"String",source:"@site/docs/types/primitive.md",sourceDirName:"types",slug:"/types/primitive",permalink:"/docs/types/primitive",draft:!1,editUrl:"https://github.com/ogen-go/web/edit/main/docs/types/primitive.md",tags:[],version:"current",frontMatter:{id:"primitive",title:"Primitives",sidebar_label:"Primitive"},sidebar:"tutorialSidebar",previous:{title:"Optional",permalink:"/docs/types/optional"},next:{title:"Sum type",permalink:"/docs/types/sumtype"}},m={},d=[{value:"String",id:"string",level:2},{value:"Supported formats",id:"supported-formats",level:3},{value:"Non-standard formats",id:"non-standard-formats",level:4},{value:"Validation",id:"validation",level:3},{value:"Numbers",id:"numbers",level:2}],o={toc:d},k="wrapper";function u(t){let{components:e,...n}=t;return(0,r.kt)(k,(0,a.Z)({},o,n,{components:e,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"string"},"String"),(0,r.kt)("p",null,"String is byte sequence primitive type represent by Go ",(0,r.kt)("inlineCode",{parentName:"p"},"string")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,r.kt)("h3",{id:"supported-formats"},"Supported formats"),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},(0,r.kt)("inlineCode",{parentName:"p"},"duration")," is a Go ",(0,r.kt)("inlineCode",{parentName:"p"},"time.Duration")," format, but JSON Schema defines it as ",(0,r.kt)("a",{parentName:"p",href:"https://datatracker.ietf.org/doc/html/rfc3339#appendix-A"},"RFC 3339 duration"),".")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Format"),(0,r.kt)("th",{parentName:"tr",align:null},"Type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"byte"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"[]byte")),(0,r.kt)("td",{parentName:"tr",align:null},"Base64-encoded string as defined in ",(0,r.kt)("a",{parentName:"td",href:"https://www.rfc-editor.org/rfc/rfc4648.html"},"RFC4648"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"date-time"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Date and time notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2022-02-22T11:22:33Z"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"date"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Date only notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2022-02-22"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"time"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Time only notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"11:22:33"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"duration"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Duration"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Duration"))),(0,r.kt)("td",{parentName:"tr",align:null},"Go duration format")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"uuid"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/github.com/google/uuid#UUID"},(0,r.kt)("inlineCode",{parentName:"a"},"uuid.UUID"))),(0,r.kt)("td",{parentName:"tr",align:null},"UUID")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ip"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net#IP"},(0,r.kt)("inlineCode",{parentName:"a"},"net.IP"))),(0,r.kt)("td",{parentName:"tr",align:null},"Any IP (IPv4, IPv6)")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ipv4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net#IP"},(0,r.kt)("inlineCode",{parentName:"a"},"net.IP"))),(0,r.kt)("td",{parentName:"tr",align:null},"IPv4, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"1.1.1.1"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ipv6"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net#IP"},(0,r.kt)("inlineCode",{parentName:"a"},"net.IP"))),(0,r.kt)("td",{parentName:"tr",align:null},"IPv6, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2001:db8:85a3::8a2e:370:7334"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"uri"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net/url#URL"},(0,r.kt)("inlineCode",{parentName:"a"},"url.URL"))),(0,r.kt)("td",{parentName:"tr",align:null},"URL as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3986"},"RFC 3986"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"email"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Email, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"foo@example.com"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"binary"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Binary string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"hostname"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Hostname as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc1034#section-3.1"},"RFC 1034, section 3.1"))))),(0,r.kt)("h4",{id:"non-standard-formats"},"Non-standard formats"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Format"),(0,r.kt)("th",{parentName:"tr",align:null},"Type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix/unix-seconds"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.Unix"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.Unix()")),", encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/306"},"issue #307"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-nano"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixNano"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixNano()")),", encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/306"},"issue #307"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-micro"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixMicro"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixMicro()")),", encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/306"},"issue #307"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-milli"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixMilli"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixMilli()")),", encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/306"},"issue #307"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"int32"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int32")),(0,r.kt)("td",{parentName:"tr",align:null},"32-bit signed integer, encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/307"},"issue #307"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"int64"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int64")),(0,r.kt)("td",{parentName:"tr",align:null},"64-bit signed integer, encoded as string. See ",(0,r.kt)("a",{parentName:"td",href:"https://github.com/ogen-go/ogen/issues/307"},"issue #307"))))),(0,r.kt)("h3",{id:"validation"},"Validation"),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Note that length validation uses ",(0,r.kt)("a",{parentName:"p",href:"https://pkg.go.dev/unicode/utf8#RuneCountInString"},(0,r.kt)("inlineCode",{parentName:"a"},"utf8.RuneCountInString"))," to count\nstring length.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Validator"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"minLength"),(0,r.kt)("td",{parentName:"tr",align:null},"Minimum length in Unicode code points")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"maxLength"),(0,r.kt)("td",{parentName:"tr",align:null},"Maximum length in Unicode code points")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"pattern"),(0,r.kt)("td",{parentName:"tr",align:null},"Go ",(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/regexp"},"regexp")," pattern")))),(0,r.kt)("h2",{id:"numbers"},"Numbers"),(0,r.kt)("p",null,"OpenAPI/JSON schema has two numeric types, ",(0,r.kt)("inlineCode",{parentName:"p"},"number")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"integer"),", where ",(0,r.kt)("inlineCode",{parentName:"p"},"number")," includes both integer and\nfloating-point numbers."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"type"),(0,r.kt)("th",{parentName:"tr",align:null},"format"),(0,r.kt)("th",{parentName:"tr",align:null},"Go type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"\u2013"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float64")),(0,r.kt)("td",{parentName:"tr",align:null},"Any numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"float"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float32")),(0,r.kt)("td",{parentName:"tr",align:null},"Floating-point numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"double"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float64")),(0,r.kt)("td",{parentName:"tr",align:null},"Floating-point numbers with double precision")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"\u2013"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int")),(0,r.kt)("td",{parentName:"tr",align:null},"Integer numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"int32"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int32")),(0,r.kt)("td",{parentName:"tr",align:null},"Signed 32-bit integers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"int64"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int64")),(0,r.kt)("td",{parentName:"tr",align:null},"Signed 64-bit integers")))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b2e2bd1f.2f59d669.js b/assets/js/b2e2bd1f.2f59d669.js new file mode 100644 index 0000000..0e4b80d --- /dev/null +++ b/assets/js/b2e2bd1f.2f59d669.js @@ -0,0 +1 @@ +"use strict";(globalThis.webpackChunkdocs_v_2=globalThis.webpackChunkdocs_v_2||[]).push([[450],{3905:(t,e,n)=>{n.d(e,{Zo:()=>o,kt:()=>g});var a=n(7294);function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(t);e&&(a=a.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,a)}return n}function l(t){for(var e=1;e=0||(r[n]=t[n]);return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(r[n]=t[n])}return r}var d=a.createContext({}),m=function(t){var e=a.useContext(d),n=e;return t&&(n="function"==typeof t?t(e):l(l({},e),t)),n},o=function(t){var e=m(t.components);return a.createElement(d.Provider,{value:e},t.children)},k="mdxType",u={inlineCode:"code",wrapper:function(t){var e=t.children;return a.createElement(a.Fragment,{},e)}},N=a.forwardRef((function(t,e){var n=t.components,r=t.mdxType,i=t.originalType,d=t.parentName,o=p(t,["components","mdxType","originalType","parentName"]),k=m(n),N=r,g=k["".concat(d,".").concat(N)]||k[N]||u[N]||i;return n?a.createElement(g,l(l({ref:e},o),{},{components:n})):a.createElement(g,l({ref:e},o))}));function g(t,e){var n=arguments,r=e&&e.mdxType;if("string"==typeof t||r){var i=n.length,l=new Array(i);l[0]=N;var p={};for(var d in e)hasOwnProperty.call(e,d)&&(p[d]=e[d]);p.originalType=t,p[k]="string"==typeof t?t:r,l[1]=p;for(var m=2;m{n.r(e),n.d(e,{assets:()=>d,contentTitle:()=>l,default:()=>u,frontMatter:()=>i,metadata:()=>p,toc:()=>m});var a=n(7462),r=(n(7294),n(3905));const i={id:"primitive",title:"Primitives",sidebar_label:"Primitive"},l=void 0,p={unversionedId:"types/primitive",id:"types/primitive",title:"Primitives",description:"String",source:"@site/docs/types/primitive.md",sourceDirName:"types",slug:"/types/primitive",permalink:"/docs/types/primitive",draft:!1,editUrl:"https://github.com/ogen-go/web/edit/main/docs/types/primitive.md",tags:[],version:"current",frontMatter:{id:"primitive",title:"Primitives",sidebar_label:"Primitive"},sidebar:"tutorialSidebar",previous:{title:"Optional",permalink:"/docs/types/optional"},next:{title:"Sum type",permalink:"/docs/types/sumtype"}},d={},m=[{value:"String",id:"string",level:2},{value:"Supported formats",id:"supported-formats",level:3},{value:"Non-standard formats",id:"non-standard-formats",level:4},{value:"Validation",id:"validation",level:3},{value:"Numbers",id:"numbers",level:2}],o={toc:m},k="wrapper";function u(t){let{components:e,...n}=t;return(0,r.kt)(k,(0,a.Z)({},o,n,{components:e,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"string"},"String"),(0,r.kt)("p",null,"String is byte sequence primitive type represent by Go ",(0,r.kt)("inlineCode",{parentName:"p"},"string")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"[]byte"),"."),(0,r.kt)("h3",{id:"supported-formats"},"Supported formats"),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},(0,r.kt)("inlineCode",{parentName:"p"},"duration")," is a Go ",(0,r.kt)("inlineCode",{parentName:"p"},"time.Duration")," format, but JSON Schema defines it as ",(0,r.kt)("a",{parentName:"p",href:"https://datatracker.ietf.org/doc/html/rfc3339#appendix-A"},"RFC 3339 duration"),".")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Format"),(0,r.kt)("th",{parentName:"tr",align:null},"Type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"byte"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"[]byte")),(0,r.kt)("td",{parentName:"tr",align:null},"Base64-encoded string as defined in ",(0,r.kt)("a",{parentName:"td",href:"https://www.rfc-editor.org/rfc/rfc4648.html"},"RFC4648"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"date-time"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Date and time notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2022-02-22T11:22:33Z"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"date"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Date only notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2022-02-22"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"time"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},"Time only notation as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3339#section-5.6"},"RFC 3339, section 5.6"),", for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"11:22:33"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"duration"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Duration"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Duration"))),(0,r.kt)("td",{parentName:"tr",align:null},"Go duration format")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"uuid"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/github.com/google/uuid#UUID"},(0,r.kt)("inlineCode",{parentName:"a"},"uuid.UUID"))),(0,r.kt)("td",{parentName:"tr",align:null},"UUID")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ip"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net/netip#Addr"},(0,r.kt)("inlineCode",{parentName:"a"},"netip.Addr"))),(0,r.kt)("td",{parentName:"tr",align:null},"Any IP (IPv4, IPv6)")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ipv4"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net/netip#Addr"},(0,r.kt)("inlineCode",{parentName:"a"},"netip.Addr"))),(0,r.kt)("td",{parentName:"tr",align:null},"IPv4, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"1.1.1.1"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"ipv6"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net/netip#Addr"},(0,r.kt)("inlineCode",{parentName:"a"},"netip.Addr"))),(0,r.kt)("td",{parentName:"tr",align:null},"IPv6, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"2001:db8:85a3::8a2e:370:7334"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"uri"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/net/url#URL"},(0,r.kt)("inlineCode",{parentName:"a"},"url.URL"))),(0,r.kt)("td",{parentName:"tr",align:null},"URL as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc3986"},"RFC 3986"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"email"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Email, for example, ",(0,r.kt)("inlineCode",{parentName:"td"},"foo@example.com"))),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"binary"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Binary string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"hostname"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"string")),(0,r.kt)("td",{parentName:"tr",align:null},"Hostname as defined by ",(0,r.kt)("a",{parentName:"td",href:"https://datatracker.ietf.org/doc/html/rfc1034#section-3.1"},"RFC 1034, section 3.1"))))),(0,r.kt)("h4",{id:"non-standard-formats"},"Non-standard formats"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Format"),(0,r.kt)("th",{parentName:"tr",align:null},"Type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix/unix-seconds"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.Unix"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.Unix()")),", encoded as string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-nano"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixNano"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixNano()")),", encoded as string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-micro"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixMicro"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixMicro()")),", encoded as string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"unix-milli"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time"},(0,r.kt)("inlineCode",{parentName:"a"},"time.Time"))),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/time#Time.UnixMilli"},(0,r.kt)("inlineCode",{parentName:"a"},"Time.UnixMilli()")),", encoded as string")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"int32"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int32")),(0,r.kt)("td",{parentName:"tr",align:null},"32-bit signed integer")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"int64"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int64")),(0,r.kt)("td",{parentName:"tr",align:null},"64-bit signed integer")))),(0,r.kt)("p",null,"See ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/ogen-go/ogen/issues/307"},"issue #307")," for more information about these formats."),(0,r.kt)("h3",{id:"validation"},"Validation"),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"Note that length validation uses ",(0,r.kt)("a",{parentName:"p",href:"https://pkg.go.dev/unicode/utf8#RuneCountInString"},(0,r.kt)("inlineCode",{parentName:"a"},"utf8.RuneCountInString"))," to count\nstring length.")),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Validator"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"minLength"),(0,r.kt)("td",{parentName:"tr",align:null},"Minimum length in Unicode code points")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"maxLength"),(0,r.kt)("td",{parentName:"tr",align:null},"Maximum length in Unicode code points")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"pattern"),(0,r.kt)("td",{parentName:"tr",align:null},"Go ",(0,r.kt)("a",{parentName:"td",href:"https://pkg.go.dev/regexp"},"regexp")," pattern")))),(0,r.kt)("h2",{id:"numbers"},"Numbers"),(0,r.kt)("p",null,"OpenAPI/JSON schema has two numeric types, ",(0,r.kt)("inlineCode",{parentName:"p"},"number")," and ",(0,r.kt)("inlineCode",{parentName:"p"},"integer"),", where ",(0,r.kt)("inlineCode",{parentName:"p"},"number")," includes both integer and\nfloating-point numbers."),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"type"),(0,r.kt)("th",{parentName:"tr",align:null},"format"),(0,r.kt)("th",{parentName:"tr",align:null},"Go type"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"\u2013"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float64")),(0,r.kt)("td",{parentName:"tr",align:null},"Any numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"float"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float32")),(0,r.kt)("td",{parentName:"tr",align:null},"Floating-point numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"number"),(0,r.kt)("td",{parentName:"tr",align:null},"double"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"float64")),(0,r.kt)("td",{parentName:"tr",align:null},"Floating-point numbers with double precision")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"\u2013"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int")),(0,r.kt)("td",{parentName:"tr",align:null},"Integer numbers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"int32"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int32")),(0,r.kt)("td",{parentName:"tr",align:null},"Signed 32-bit integers")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},"integer"),(0,r.kt)("td",{parentName:"tr",align:null},"int64"),(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("inlineCode",{parentName:"td"},"int64")),(0,r.kt)("td",{parentName:"tr",align:null},"Signed 64-bit integers")))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.4d762683.js b/assets/js/runtime~main.b57e11fc.js similarity index 98% rename from assets/js/runtime~main.4d762683.js rename to assets/js/runtime~main.b57e11fc.js index 5794e5a..a6fc095 100644 --- a/assets/js/runtime~main.4d762683.js +++ b/assets/js/runtime~main.b57e11fc.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,r,a,c,o={},b={};function f(e){var t=b[e];if(void 0!==t)return t.exports;var r=b[e]={id:e,loaded:!1,exports:{}};return o[e].call(r.exports,r,r.exports,f),r.loaded=!0,r.exports}f.m=o,f.c=b,e=[],f.O=(t,r,a,c)=>{if(!r){var o=1/0;for(i=0;i=c)&&Object.keys(f.O).every((e=>f.O[e](r[d])))?r.splice(d--,1):(b=!1,c0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[r,a,c]},f.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return f.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,f.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var c=Object.create(null);f.r(c);var o={};t=t||[null,r({}),r([]),r(r)];for(var b=2&a&&e;"object"==typeof b&&!~t.indexOf(b);b=r(b))Object.getOwnPropertyNames(b).forEach((t=>o[t]=()=>e[t]));return o.default=()=>e,f.d(c,o),c},f.d=(e,t)=>{for(var r in t)f.o(t,r)&&!f.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},f.f={},f.e=e=>Promise.all(Object.keys(f.f).reduce(((t,r)=>(f.f[r](e,t),t)),[])),f.u=e=>"assets/js/"+({17:"0e3b35db",53:"935f2afb",85:"1f391b9e",89:"a6aa9e1f",103:"ccc49370",120:"e8a2f155",169:"d04fbca5",195:"c4f5d8e4",223:"5c7625a6",251:"5c27841a",290:"4126b9e4",302:"f79d6bd4",352:"8abb0e14",361:"c268028b",389:"582263bd",414:"393be207",450:"b2e2bd1f",477:"b2f554cd",514:"1be78505",526:"1d1a8a83",533:"b2b675dd",535:"814f3328",569:"6ec5cf74",608:"9e4087bc",616:"56759394",648:"16970eb8",656:"a04d8747",691:"b2e9e823",794:"f2dfe8f3",816:"7a0b48af",918:"17896441",965:"d400eccc"}[e]||e)+"."+{17:"c54b9e7e",53:"c58fdd64",85:"23b80b3b",89:"8fd46594",103:"5458ac85",120:"1240b784",169:"f45e664e",195:"d106004f",223:"05c5a76a",251:"19ec0840",290:"47ebfdc9",302:"4c9904d4",352:"f6c6e592",361:"3fa1b28d",389:"60b176a8",412:"be6c0726",414:"402bbb41",450:"13dd70a3",477:"b7541ce1",506:"a701d826",514:"8d05e3b1",526:"0e41b6cf",533:"b6eac561",535:"75b7d09d",569:"5e0b3f5b",608:"e4edef2c",616:"e9b5e057",648:"9e28df81",656:"e9241dc4",691:"73cb6d89",794:"a2a5b882",816:"23a19498",918:"4f3f1b4f",965:"59630fdc",972:"b94a7c85"}[e]+".js",f.miniCssF=e=>{},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},c="docs-v-2:",f.l=(e,t,r,o)=>{if(a[e])a[e].push(t);else{var b,d;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i{b.onerror=b.onload=null,clearTimeout(s);var c=a[e];if(delete a[e],b.parentNode&&b.parentNode.removeChild(b),c&&c.forEach((e=>e(r))),t)return t(r)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=u.bind(null,b.onerror),b.onload=u.bind(null,b.onload),d&&document.head.appendChild(b)}},f.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.p="/",f.gca=function(e){return e={17896441:"918",56759394:"616","0e3b35db":"17","935f2afb":"53","1f391b9e":"85",a6aa9e1f:"89",ccc49370:"103",e8a2f155:"120",d04fbca5:"169",c4f5d8e4:"195","5c7625a6":"223","5c27841a":"251","4126b9e4":"290",f79d6bd4:"302","8abb0e14":"352",c268028b:"361","582263bd":"389","393be207":"414",b2e2bd1f:"450",b2f554cd:"477","1be78505":"514","1d1a8a83":"526",b2b675dd:"533","814f3328":"535","6ec5cf74":"569","9e4087bc":"608","16970eb8":"648",a04d8747:"656",b2e9e823:"691",f2dfe8f3:"794","7a0b48af":"816",d400eccc:"965"}[e]||e,f.p+f.u(e)},(()=>{var e={303:0,532:0};f.f.j=(t,r)=>{var a=f.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var c=new Promise(((r,c)=>a=e[t]=[r,c]));r.push(a[2]=c);var o=f.p+f.u(t),b=new Error;f.l(o,(r=>{if(f.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var c=r&&("load"===r.type?"missing":r.type),o=r&&r.target&&r.target.src;b.message="Loading chunk "+t+" failed.\n("+c+": "+o+")",b.name="ChunkLoadError",b.type=c,b.request=o,a[1](b)}}),"chunk-"+t,t)}},f.O.j=t=>0===e[t];var t=(t,r)=>{var a,c,[o,b,d]=r,n=0;if(o.some((t=>0!==e[t]))){for(a in b)f.o(b,a)&&(f.m[a]=b[a]);if(d)var i=d(f)}for(t&&t(r);n{"use strict";var e,t,r,a,c,o={},b={};function f(e){var t=b[e];if(void 0!==t)return t.exports;var r=b[e]={id:e,loaded:!1,exports:{}};return o[e].call(r.exports,r,r.exports,f),r.loaded=!0,r.exports}f.m=o,f.c=b,e=[],f.O=(t,r,a,c)=>{if(!r){var o=1/0;for(i=0;i=c)&&Object.keys(f.O).every((e=>f.O[e](r[d])))?r.splice(d--,1):(b=!1,c0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[r,a,c]},f.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return f.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,f.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var c=Object.create(null);f.r(c);var o={};t=t||[null,r({}),r([]),r(r)];for(var b=2&a&&e;"object"==typeof b&&!~t.indexOf(b);b=r(b))Object.getOwnPropertyNames(b).forEach((t=>o[t]=()=>e[t]));return o.default=()=>e,f.d(c,o),c},f.d=(e,t)=>{for(var r in t)f.o(t,r)&&!f.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},f.f={},f.e=e=>Promise.all(Object.keys(f.f).reduce(((t,r)=>(f.f[r](e,t),t)),[])),f.u=e=>"assets/js/"+({17:"0e3b35db",53:"935f2afb",85:"1f391b9e",89:"a6aa9e1f",103:"ccc49370",120:"e8a2f155",169:"d04fbca5",195:"c4f5d8e4",223:"5c7625a6",251:"5c27841a",290:"4126b9e4",302:"f79d6bd4",352:"8abb0e14",361:"c268028b",389:"582263bd",414:"393be207",450:"b2e2bd1f",477:"b2f554cd",514:"1be78505",526:"1d1a8a83",533:"b2b675dd",535:"814f3328",569:"6ec5cf74",608:"9e4087bc",616:"56759394",648:"16970eb8",656:"a04d8747",691:"b2e9e823",794:"f2dfe8f3",816:"7a0b48af",918:"17896441",965:"d400eccc"}[e]||e)+"."+{17:"c54b9e7e",53:"c58fdd64",85:"23b80b3b",89:"8fd46594",103:"5458ac85",120:"1240b784",169:"f45e664e",195:"d106004f",223:"05c5a76a",251:"19ec0840",290:"47ebfdc9",302:"4c9904d4",352:"f6c6e592",361:"3fa1b28d",389:"60b176a8",412:"be6c0726",414:"402bbb41",450:"2f59d669",477:"b7541ce1",506:"a701d826",514:"8d05e3b1",526:"0e41b6cf",533:"b6eac561",535:"75b7d09d",569:"5e0b3f5b",608:"e4edef2c",616:"e9b5e057",648:"9e28df81",656:"e9241dc4",691:"73cb6d89",794:"a2a5b882",816:"23a19498",918:"4f3f1b4f",965:"59630fdc",972:"b94a7c85"}[e]+".js",f.miniCssF=e=>{},f.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),f.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},c="docs-v-2:",f.l=(e,t,r,o)=>{if(a[e])a[e].push(t);else{var b,d;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i{b.onerror=b.onload=null,clearTimeout(s);var c=a[e];if(delete a[e],b.parentNode&&b.parentNode.removeChild(b),c&&c.forEach((e=>e(r))),t)return t(r)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:b}),12e4);b.onerror=u.bind(null,b.onerror),b.onload=u.bind(null,b.onload),d&&document.head.appendChild(b)}},f.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.p="/",f.gca=function(e){return e={17896441:"918",56759394:"616","0e3b35db":"17","935f2afb":"53","1f391b9e":"85",a6aa9e1f:"89",ccc49370:"103",e8a2f155:"120",d04fbca5:"169",c4f5d8e4:"195","5c7625a6":"223","5c27841a":"251","4126b9e4":"290",f79d6bd4:"302","8abb0e14":"352",c268028b:"361","582263bd":"389","393be207":"414",b2e2bd1f:"450",b2f554cd:"477","1be78505":"514","1d1a8a83":"526",b2b675dd:"533","814f3328":"535","6ec5cf74":"569","9e4087bc":"608","16970eb8":"648",a04d8747:"656",b2e9e823:"691",f2dfe8f3:"794","7a0b48af":"816",d400eccc:"965"}[e]||e,f.p+f.u(e)},(()=>{var e={303:0,532:0};f.f.j=(t,r)=>{var a=f.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var c=new Promise(((r,c)=>a=e[t]=[r,c]));r.push(a[2]=c);var o=f.p+f.u(t),b=new Error;f.l(o,(r=>{if(f.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var c=r&&("load"===r.type?"missing":r.type),o=r&&r.target&&r.target.src;b.message="Loading chunk "+t+" failed.\n("+c+": "+o+")",b.name="ChunkLoadError",b.type=c,b.request=o,a[1](b)}}),"chunk-"+t,t)}},f.O.j=t=>0===e[t];var t=(t,r)=>{var a,c,[o,b,d]=r,n=0;if(o.some((t=>0!==e[t]))){for(a in b)f.o(b,a)&&(f.m[a]=b[a]);if(d)var i=d(f)}for(t&&t(r);n - + - + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html index c298e61..28a816e 100644 --- a/blog/index.html +++ b/blog/index.html @@ -10,14 +10,14 @@ - +

· 10 min read
tdakkota
Aleksandr Razumov

The ogen project generates code from an OpenAPI specification, freeing you from writing hundreds (or even thousands) of lines of boring boilerplate code on Go.

It generates client and server implementations for Go. Developers just need to implement the request handler. No interface{} and no reflect, only strong types and codegen.

In this article I will tell what makes ogen different from other code generators and why you should try it.

Strong types

ogen generates strongly-typed client and server, similar to gRPC. Also, ogen adds endpoint description for each generated method.

For the server, the Handler interface is generated that needs to be implemented:

// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// AddPet implements addPet operation.
//
// Creates a new pet in the store. Duplicates are allowed.
//
// POST /pets
AddPet(ctx context.Context, req NewPet) (AddPetRes, error)
// DeletePet implements deletePet operation.
//
// Deletes a single pet based on the ID supplied.
//
// DELETE /pets/{id}
DeletePet(ctx context.Context, params DeletePetParams) (DeletePetRes, error)
// FindPetByID implements find pet by id operation.
//
// Returns a user based on a single ID, if the user does not have access to the pet.
//
// GET /pets/{id}
FindPetByID(ctx context.Context, params FindPetByIDParams) (FindPetByIDRes, error)
// FindPets implements findPets operation.
//
// Returns all pets from the system that the user has access to
//
// GET /pets
FindPets(ctx context.Context, params FindPetsParams) (FindPetsRes, error)
// PatchPet implements patchPet operation.
//
// Patch a pet.
//
// PATCH /pets/{id}
PatchPet(ctx context.Context, req UpdatePet, params PatchPetParams) (PatchPetRes, error)
}

Client code is quite similar:

func (c *Client) AddPet(ctx context.Context, request NewPet) (res AddPetRes, err error) {}

// PatchPet invokes patchPet operation.
//
// Patch a pet.
//
// PATCH /pets/{id}
func (c *Client) PatchPet(ctx context.Context, request UpdatePet, params PatchPetParams) (res PatchPetRes, err error) {}

Validation

ogen supports maxLength, minLength, pattern (regex), minimum, maximum and other validators for strings, arrays, objects and numbers.

UpdatePet:
type: object
properties:
name:
type: string
maxLength: 25
minLength: 3
pattern: '^[a-zA-Z0-9]+$'
tag:
maxLength: 10
minLength: 1
pattern: '^[a-zA-Z0-9]+$'
nullable: true
type: string

Unknown and required fields

Furthermore, it is checked in the effective way that required fields are set, and unknown (if not allowed) are not passed:

// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfNewPet) {
name = jsonFieldsNameOfNewPet[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}

Enum

The enum validator is fully supported. The enum values are generated as constants, and validation code is generated as well for the client and server:

// Ref: #/components/schemas/Kind
type Kind string

const (
KindCat Kind = "Cat"
KindDog Kind = "Dog"
KindFish Kind = "Fish"
KindBird Kind = "Bird"
KindOther Kind = "Other"
)

func (s Kind) Validate() error {
switch s {
case "Cat":
return nil
case "Dog":
return nil
case "Fish":
return nil
case "Bird":
return nil
case "Other":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}

// Decode decodes Kind from json.
func (s *Kind) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Kind to nil")
}
v, err := d.StrBytes()
if err != nil {
return err
}
// Try to use constant string.
switch Kind(v) {
case KindCat:
*s = KindCat
case KindDog:
*s = KindDog
case KindFish:
*s = KindFish
case KindBird:
*s = KindBird
case KindOther:
*s = KindOther
default:
*s = Kind(v)
}

return nil
}


Unlike ogen, deepmap/oapi-codegen does not check enum values. It only generates the named type and constants.

Without pointers

If it is possible.

In most cases, to represent optional (or nullable) fields in Go, pointers are usually used:

type Pet struct {
// Name of the pet
Name string `json:"name"`

// Type of the pet
Tag *string `json:"tag,omitempty"`
}

This is a common, but hacky way:

  • It is easy to get null pointer exception, hello The Billion Dollar Mistake
  • It increases the GC pressure, especially if there are many objects or they are nested (e.g. []Pet)
  • It is impossible to express nullable optional when three states can be passed: empty, null and filled value. It is especially useful for PATCH operations.

ogen solves this problem by generating generic types:

// Ref: #/components/schemas/NewPet
type NewPet struct {
Name string `json:"name"`
Tag OptString `json:"tag"`
}

// OptString is optional string.
type OptString struct {
Value string
Set bool
}

It seems that deepmap/oapi-codegen cannot handle optional nullable properly:

// UpdatePet defines model for UpdatePet.
type UpdatePet struct {
Name *string `json:"name,omitempty"`
Tag *string `json:"tag"`
}

Whereas ogen generates a special OptNilString type:

// Ref: #/components/schemas/UpdatePet
type UpdatePet struct {
Name OptString `json:"name"`
Tag OptNilString `json:"tag"`
}

// OptNilString is optional nullable string.
type OptNilString struct {
Value string
Set bool
Null bool
}

Using OptNilString you can express any state: the absence of the value, null value, an empty string, and just a string.

Arrays

To represent arrays, a special type may not be generated, changing the semantics of the nil value of the slice depending on the schema. For example, if the field is optional, then nil will mean the absence of the value. If nullable, then null. For optional nullable fields, you will have to generate a wrapper.

JSON implementation without reflection

ogen does not use the standard encoding/json with its limitations in speed and capabilities. Instead, it generates a static code for encoding and decoding JSON.

// Encode encodes string as json.
func (o OptNilString) Encode(e *jx.Encoder) {
if !o.Set {
return
}
if o.Null {
e.Null()
return
}
e.Str(string(o.Value))
}

It lets you make working with json more efficient and flexible. For example, decoding a field in several passes to support oneOf with a discriminator (first the value of the discriminator field is parsed, and then the value as a whole) and without it (first all fields are traversed and the type is selected by unique fields).

Instead of encoding/json, ogen uses go-faster/jx, a heavily modified and optimized fork of jsoniter (can parse almost a gigabyte of json logs in a second per core).

Custom router

ogen uses its own, efficient statically generated router based on radix tree:

// ...
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/pets"
if l := len("/pets"); len(elem) >= l && elem[0:l] == "/pets" {
elem = elem[l:]
} else {
break
}

if len(elem) == 0 {
switch r.Method {
case "GET":
s.handleFindPetsRequest([0]string{}, w, r)
case "POST":
s.handleAddPetRequest([0]string{}, w, r)
default:
s.notAllowed(w, r, "GET,POST")
}

return
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// ...

Static router allows the compiler to make many optimizations: remove unnecessary bounds checks, optimize string comparison, use optimal case search algorithm instead of binary search.

It makes ogen routing much faster than chi and echo (benchmark):

name                        time/op
Router/GithubStatic/ogen-4 18.7ns ± 3%
Router/GithubStatic/chi-4 146ns ± 2%
Router/GithubStatic/echo-4 73.7ns ± 9%
Router/GithubParam/ogen-4 34.0ns ± 3%
Router/GithubParam/chi-4 251ns ± 3%
Router/GithubParam/echo-4 118ns ± 2%
Router/GithubAll/ogen-4 56.6µs ± 3%
Router/GithubAll/chi-4 323µs ± 3%
Router/GithubAll/echo-4 173µs ± 4%

name alloc/op
Router/GithubStatic/ogen-4 0.00B
Router/GithubStatic/chi-4 0.00B
Router/GithubStatic/echo-4 0.00B
Router/GithubParam/ogen-4 0.00B
Router/GithubParam/chi-4 0.00B
Router/GithubParam/echo-4 0.00B
Router/GithubAll/ogen-4 0.00B
Router/GithubAll/chi-4 0.00B
Router/GithubAll/echo-4 0.00B

OneOf

Let's imagine that we have a schema like this:

Dog:
type: object
required:
- kind
properties:
kind:
$ref: '#/components/schemas/Kind'
bark:
type: string
Cat:
type: object
required:
- kind
properties:
kind:
$ref: '#/components/schemas/Kind'
meow:
type: string
SomePet:
type: object
discriminator:
propertyName: kind
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'

ogen will generate code like this:

// Ref: #/components/schemas/Cat
type Cat struct {
Kind Kind `json:"kind"`
Meow OptString `json:"meow"`
}

// Ref: #/components/schemas/Dog
type Dog struct {
Kind Kind `json:"kind"`
Bark OptString `json:"bark"`
}

// Ref: #/components/schemas/SomePet
// SomePet represents sum type.
type SomePet struct {
Type SomePetType // switch on this field
Dog Dog
Cat Cat
}

As you can see, the oneOf case is chosen during the decoding process.

// func (s *SomePet) Decode(d *jx.Decoder) error
if err := d.Capture(func(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
if found {
return d.Skip()
}
switch string(key) {
case "kind":
typ, err := d.Str()
if err != nil {
return err
}
switch typ {
case "Cat":
s.Type = CatSomePet
found = true
case "Dog":
s.Type = DogSomePet
found = true
default:
return errors.Errorf("unknown type %s", typ)
}
return nil
}
return d.Skip()
})
}); err != nil {
return errors.Wrap(err, "capture")
}
if !found {
return errors.New("unable to detect sum type variant")
}
switch s.Type {
case DogSomePet:
if err := s.Dog.Decode(d); err != nil {
return err
}
case CatSomePet:
if err := s.Cat.Decode(d); err != nil {
return err
}
default:
return errors.Errorf("inferred invalid type: %s", s.Type)
}

Whereas deepmap/oapi-codegen requires an additional manual call (Also, notice that at the moment of posting this article, the generated code is broken):

// SomePet defines model for SomePet.
type SomePet struct {
union json.RawMessage
}

func (t SomePet) Discriminator() (string, error) {
var discriminator struct {
Discriminator string `json:"kind"`
}
err := json.Unmarshal(t.union, &discriminator)
return discriminator.Discriminator, err
}

// AsCat returns the union data inside the SomePet as a Cat
func (t SomePet) AsCat() (Cat, error) {
var body Cat
err := json.Unmarshal(t.union, &body)
return body, err
}

It seems that the developer should write the entire switch-by-discriminator logic by himself.

Without discriminator

Further, ogen can work without the explicit discriminator field at all, choosing the type by unique fields:

var found bool
if err := d.Capture(func(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "bark":
match := DogSomePet
if found && s.Type != match {
s.Type = ""
return errors.Errorf("multiple oneOf matches: (%v, %v)", s.Type, match)
}
found = true
s.Type = match
case "meow":
match := CatSomePet
if found && s.Type != match {
s.Type = ""
return errors.Errorf("multiple oneOf matches: (%v, %v)", s.Type, match)
}
found = true
s.Type = match
}
return d.Skip()
})
}); err != nil {
return errors.Wrap(err, "capture")
}

If there is a meow field, then the type is Cat, if there is a bark field - Dog, and if we didn't find anything, then we will get an error unable to detect sum type variant.

Detailed error messages

ogen provides detailed error messages with context, like this:

$ go generate
- petstore-expanded.yaml:218:17 -> resolve: can't find value for "components/schemas/Do1"
217 | oneOf:
→ 218 | - $ref: '#/components/schemas/Do1'
| ↑
219 | - $ref: '#/components/schemas/Cat'
220 |
221 | UpdatePet:

Summary

Main advantages of ogen:

  • Strict client and server typing
  • Validation
  • oneOf and anyOf support
  • nullable optional support
  • Built-in fast static router
  • Fast JSON handling
  • Detailed error messages
- + \ No newline at end of file diff --git a/blog/ogen-intro/index.html b/blog/ogen-intro/index.html index d127b0b..da46338 100644 --- a/blog/ogen-intro/index.html +++ b/blog/ogen-intro/index.html @@ -10,14 +10,14 @@ - +

ogen: OpenAPI v3 code generator for Go

· 10 min read
tdakkota
Aleksandr Razumov

The ogen project generates code from an OpenAPI specification, freeing you from writing hundreds (or even thousands) of lines of boring boilerplate code on Go.

It generates client and server implementations for Go. Developers just need to implement the request handler. No interface{} and no reflect, only strong types and codegen.

In this article I will tell what makes ogen different from other code generators and why you should try it.

Strong types

ogen generates strongly-typed client and server, similar to gRPC. Also, ogen adds endpoint description for each generated method.

For the server, the Handler interface is generated that needs to be implemented:

// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// AddPet implements addPet operation.
//
// Creates a new pet in the store. Duplicates are allowed.
//
// POST /pets
AddPet(ctx context.Context, req NewPet) (AddPetRes, error)
// DeletePet implements deletePet operation.
//
// Deletes a single pet based on the ID supplied.
//
// DELETE /pets/{id}
DeletePet(ctx context.Context, params DeletePetParams) (DeletePetRes, error)
// FindPetByID implements find pet by id operation.
//
// Returns a user based on a single ID, if the user does not have access to the pet.
//
// GET /pets/{id}
FindPetByID(ctx context.Context, params FindPetByIDParams) (FindPetByIDRes, error)
// FindPets implements findPets operation.
//
// Returns all pets from the system that the user has access to
//
// GET /pets
FindPets(ctx context.Context, params FindPetsParams) (FindPetsRes, error)
// PatchPet implements patchPet operation.
//
// Patch a pet.
//
// PATCH /pets/{id}
PatchPet(ctx context.Context, req UpdatePet, params PatchPetParams) (PatchPetRes, error)
}

Client code is quite similar:

func (c *Client) AddPet(ctx context.Context, request NewPet) (res AddPetRes, err error) {}

// PatchPet invokes patchPet operation.
//
// Patch a pet.
//
// PATCH /pets/{id}
func (c *Client) PatchPet(ctx context.Context, request UpdatePet, params PatchPetParams) (res PatchPetRes, err error) {}

Validation

ogen supports maxLength, minLength, pattern (regex), minimum, maximum and other validators for strings, arrays, objects and numbers.

UpdatePet:
type: object
properties:
name:
type: string
maxLength: 25
minLength: 3
pattern: '^[a-zA-Z0-9]+$'
tag:
maxLength: 10
minLength: 1
pattern: '^[a-zA-Z0-9]+$'
nullable: true
type: string

Unknown and required fields

Furthermore, it is checked in the effective way that required fields are set, and unknown (if not allowed) are not passed:

// Validate required fields.
var failures []validate.FieldError
for i, mask := range [1]uint8{
0b00000001,
} {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR.
//
// If XOR result is not zero, result is not equal to expected, so some fields are missed.
// Bits of fields which would be set are actually bits of missed fields.
missed := bits.OnesCount8(result)
for bitN := 0; bitN < missed; bitN++ {
bitIdx := bits.TrailingZeros8(result)
fieldIdx := i*8 + bitIdx
var name string
if fieldIdx < len(jsonFieldsNameOfNewPet) {
name = jsonFieldsNameOfNewPet[fieldIdx]
} else {
name = strconv.Itoa(fieldIdx)
}
failures = append(failures, validate.FieldError{
Name: name,
Error: validate.ErrFieldRequired,
})
// Reset bit.
result &^= 1 << bitIdx
}
}
}

Enum

The enum validator is fully supported. The enum values are generated as constants, and validation code is generated as well for the client and server:

// Ref: #/components/schemas/Kind
type Kind string

const (
KindCat Kind = "Cat"
KindDog Kind = "Dog"
KindFish Kind = "Fish"
KindBird Kind = "Bird"
KindOther Kind = "Other"
)

func (s Kind) Validate() error {
switch s {
case "Cat":
return nil
case "Dog":
return nil
case "Fish":
return nil
case "Bird":
return nil
case "Other":
return nil
default:
return errors.Errorf("invalid value: %v", s)
}
}

// Decode decodes Kind from json.
func (s *Kind) Decode(d *jx.Decoder) error {
if s == nil {
return errors.New("invalid: unable to decode Kind to nil")
}
v, err := d.StrBytes()
if err != nil {
return err
}
// Try to use constant string.
switch Kind(v) {
case KindCat:
*s = KindCat
case KindDog:
*s = KindDog
case KindFish:
*s = KindFish
case KindBird:
*s = KindBird
case KindOther:
*s = KindOther
default:
*s = Kind(v)
}

return nil
}


Unlike ogen, deepmap/oapi-codegen does not check enum values. It only generates the named type and constants.

Without pointers

If it is possible.

In most cases, to represent optional (or nullable) fields in Go, pointers are usually used:

type Pet struct {
// Name of the pet
Name string `json:"name"`

// Type of the pet
Tag *string `json:"tag,omitempty"`
}

This is a common, but hacky way:

  • It is easy to get null pointer exception, hello The Billion Dollar Mistake
  • It increases the GC pressure, especially if there are many objects or they are nested (e.g. []Pet)
  • It is impossible to express nullable optional when three states can be passed: empty, null and filled value. It is especially useful for PATCH operations.

ogen solves this problem by generating generic types:

// Ref: #/components/schemas/NewPet
type NewPet struct {
Name string `json:"name"`
Tag OptString `json:"tag"`
}

// OptString is optional string.
type OptString struct {
Value string
Set bool
}

It seems that deepmap/oapi-codegen cannot handle optional nullable properly:

// UpdatePet defines model for UpdatePet.
type UpdatePet struct {
Name *string `json:"name,omitempty"`
Tag *string `json:"tag"`
}

Whereas ogen generates a special OptNilString type:

// Ref: #/components/schemas/UpdatePet
type UpdatePet struct {
Name OptString `json:"name"`
Tag OptNilString `json:"tag"`
}

// OptNilString is optional nullable string.
type OptNilString struct {
Value string
Set bool
Null bool
}

Using OptNilString you can express any state: the absence of the value, null value, an empty string, and just a string.

Arrays

To represent arrays, a special type may not be generated, changing the semantics of the nil value of the slice depending on the schema. For example, if the field is optional, then nil will mean the absence of the value. If nullable, then null. For optional nullable fields, you will have to generate a wrapper.

JSON implementation without reflection

ogen does not use the standard encoding/json with its limitations in speed and capabilities. Instead, it generates a static code for encoding and decoding JSON.

// Encode encodes string as json.
func (o OptNilString) Encode(e *jx.Encoder) {
if !o.Set {
return
}
if o.Null {
e.Null()
return
}
e.Str(string(o.Value))
}

It lets you make working with json more efficient and flexible. For example, decoding a field in several passes to support oneOf with a discriminator (first the value of the discriminator field is parsed, and then the value as a whole) and without it (first all fields are traversed and the type is selected by unique fields).

Instead of encoding/json, ogen uses go-faster/jx, a heavily modified and optimized fork of jsoniter (can parse almost a gigabyte of json logs in a second per core).

Custom router

ogen uses its own, efficient statically generated router based on radix tree:

// ...
// Static code generated router with unwrapped path search.
switch {
default:
if len(elem) == 0 {
break
}
switch elem[0] {
case '/': // Prefix: "/pets"
if l := len("/pets"); len(elem) >= l && elem[0:l] == "/pets" {
elem = elem[l:]
} else {
break
}

if len(elem) == 0 {
switch r.Method {
case "GET":
s.handleFindPetsRequest([0]string{}, w, r)
case "POST":
s.handleAddPetRequest([0]string{}, w, r)
default:
s.notAllowed(w, r, "GET,POST")
}

return
}
switch elem[0] {
case '/': // Prefix: "/"
if l := len("/"); len(elem) >= l && elem[0:l] == "/" {
elem = elem[l:]
} else {
break
}
// ...

Static router allows the compiler to make many optimizations: remove unnecessary bounds checks, optimize string comparison, use optimal case search algorithm instead of binary search.

It makes ogen routing much faster than chi and echo (benchmark):

name                        time/op
Router/GithubStatic/ogen-4 18.7ns ± 3%
Router/GithubStatic/chi-4 146ns ± 2%
Router/GithubStatic/echo-4 73.7ns ± 9%
Router/GithubParam/ogen-4 34.0ns ± 3%
Router/GithubParam/chi-4 251ns ± 3%
Router/GithubParam/echo-4 118ns ± 2%
Router/GithubAll/ogen-4 56.6µs ± 3%
Router/GithubAll/chi-4 323µs ± 3%
Router/GithubAll/echo-4 173µs ± 4%

name alloc/op
Router/GithubStatic/ogen-4 0.00B
Router/GithubStatic/chi-4 0.00B
Router/GithubStatic/echo-4 0.00B
Router/GithubParam/ogen-4 0.00B
Router/GithubParam/chi-4 0.00B
Router/GithubParam/echo-4 0.00B
Router/GithubAll/ogen-4 0.00B
Router/GithubAll/chi-4 0.00B
Router/GithubAll/echo-4 0.00B

OneOf

Let's imagine that we have a schema like this:

Dog:
type: object
required:
- kind
properties:
kind:
$ref: '#/components/schemas/Kind'
bark:
type: string
Cat:
type: object
required:
- kind
properties:
kind:
$ref: '#/components/schemas/Kind'
meow:
type: string
SomePet:
type: object
discriminator:
propertyName: kind
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'

ogen will generate code like this:

// Ref: #/components/schemas/Cat
type Cat struct {
Kind Kind `json:"kind"`
Meow OptString `json:"meow"`
}

// Ref: #/components/schemas/Dog
type Dog struct {
Kind Kind `json:"kind"`
Bark OptString `json:"bark"`
}

// Ref: #/components/schemas/SomePet
// SomePet represents sum type.
type SomePet struct {
Type SomePetType // switch on this field
Dog Dog
Cat Cat
}

As you can see, the oneOf case is chosen during the decoding process.

// func (s *SomePet) Decode(d *jx.Decoder) error
if err := d.Capture(func(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
if found {
return d.Skip()
}
switch string(key) {
case "kind":
typ, err := d.Str()
if err != nil {
return err
}
switch typ {
case "Cat":
s.Type = CatSomePet
found = true
case "Dog":
s.Type = DogSomePet
found = true
default:
return errors.Errorf("unknown type %s", typ)
}
return nil
}
return d.Skip()
})
}); err != nil {
return errors.Wrap(err, "capture")
}
if !found {
return errors.New("unable to detect sum type variant")
}
switch s.Type {
case DogSomePet:
if err := s.Dog.Decode(d); err != nil {
return err
}
case CatSomePet:
if err := s.Cat.Decode(d); err != nil {
return err
}
default:
return errors.Errorf("inferred invalid type: %s", s.Type)
}

Whereas deepmap/oapi-codegen requires an additional manual call (Also, notice that at the moment of posting this article, the generated code is broken):

// SomePet defines model for SomePet.
type SomePet struct {
union json.RawMessage
}

func (t SomePet) Discriminator() (string, error) {
var discriminator struct {
Discriminator string `json:"kind"`
}
err := json.Unmarshal(t.union, &discriminator)
return discriminator.Discriminator, err
}

// AsCat returns the union data inside the SomePet as a Cat
func (t SomePet) AsCat() (Cat, error) {
var body Cat
err := json.Unmarshal(t.union, &body)
return body, err
}

It seems that the developer should write the entire switch-by-discriminator logic by himself.

Without discriminator

Further, ogen can work without the explicit discriminator field at all, choosing the type by unique fields:

var found bool
if err := d.Capture(func(d *jx.Decoder) error {
return d.ObjBytes(func(d *jx.Decoder, key []byte) error {
switch string(key) {
case "bark":
match := DogSomePet
if found && s.Type != match {
s.Type = ""
return errors.Errorf("multiple oneOf matches: (%v, %v)", s.Type, match)
}
found = true
s.Type = match
case "meow":
match := CatSomePet
if found && s.Type != match {
s.Type = ""
return errors.Errorf("multiple oneOf matches: (%v, %v)", s.Type, match)
}
found = true
s.Type = match
}
return d.Skip()
})
}); err != nil {
return errors.Wrap(err, "capture")
}

If there is a meow field, then the type is Cat, if there is a bark field - Dog, and if we didn't find anything, then we will get an error unable to detect sum type variant.

Detailed error messages

ogen provides detailed error messages with context, like this:

$ go generate
- petstore-expanded.yaml:218:17 -> resolve: can't find value for "components/schemas/Do1"
217 | oneOf:
→ 218 | - $ref: '#/components/schemas/Do1'
| ↑
219 | - $ref: '#/components/schemas/Cat'
220 |
221 | UpdatePet:

Summary

Main advantages of ogen:

  • Strict client and server typing
  • Validation
  • oneOf and anyOf support
  • nullable optional support
  • Built-in fast static router
  • Fast JSON handling
  • Detailed error messages
- + \ No newline at end of file diff --git a/docs/concepts/convenient_errors/index.html b/docs/concepts/convenient_errors/index.html index e75afef..c28ca7e 100644 --- a/docs/concepts/convenient_errors/index.html +++ b/docs/concepts/convenient_errors/index.html @@ -10,13 +10,13 @@ - +

Convenient errors

If spec meets all following requirements:

  • Every operation defines the same default response
  • This response defines only one application/json media

then ogen generates special handler for errors returned by Handler implementation and does not generate default response variant.

For example:

openapi: 3.0.3
info:
title: Convenient errors example
version: 0.1.0
paths:
/me:
get:
responses:
"200":
description: User
content:
application/json:
schema:
$ref: "#/components/schemas/User"
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/users:
post:
requestBody:
description: User to create
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/User"
responses:
"201":
description: OK
default:
description: General Error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
User:
type: object
required:
- id
properties:
id:
type: integer
Error:
description: Represents error object
type: object
properties:
code:
type: integer
format: int64
message:
type: string
required:
- code
- message
// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
...
// NewError creates ErrorStatusCode from error returned by handler.
//
// Used for common default response.
NewError(ctx context.Context, err error) ErrorStatusCode
}

Force or Disable Convenient errors

Use --convenient-errors option

  • auto (default) generates NewError if possible
  • on tells generator to fail if spec does not meet requirements
  • off disables Convenient errors at all
- + \ No newline at end of file diff --git a/docs/concepts/interface_responses/index.html b/docs/concepts/interface_responses/index.html index 4287bc2..fa308ec 100644 --- a/docs/concepts/interface_responses/index.html +++ b/docs/concepts/interface_responses/index.html @@ -10,14 +10,14 @@ - +

Interface responses

OpenAPI allows to define multiple responses by using HTTP codes and content types.

Multiple responses schema
      responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
'405':
description: Invalid input

To represents them, ogen generates an interface-based sum type (or tagged union) of different response types.

addPet operation response interface
type AddPetRes interface {
addPetRes()
}
addPet operation response variants
// AddPetMethodNotAllowed is response for AddPet operation.
type AddPetMethodNotAllowed struct{}

func (*AddPetMethodNotAllowed) addPetRes() {}

// Ref: #/components/schemas/Pet
type Pet struct {
ID OptInt64 `json:"id"`
Name string `json:"name"`
Category OptCategory `json:"category"`
PhotoUrls []string `json:"photoUrls"`
Tags []Tag `json:"tags"`
// Pet status in the store.
Status OptPetStatus `json:"status"`
}

func (*Pet) addPetRes() {}
- + \ No newline at end of file diff --git a/docs/concepts/middlewares/index.html b/docs/concepts/middlewares/index.html index 861fddd..7937dc2 100644 --- a/docs/concepts/middlewares/index.html +++ b/docs/concepts/middlewares/index.html @@ -10,7 +10,7 @@ - + @@ -20,7 +20,7 @@ Using net/http middlewares section.

ogen provides a high-level middleware API that allows you to perform custom logic before and after the request is handled by the server.

Unlike the net/http middleware, ogen middleware gets an already parsed and validated request, and must return a typed response.

package middlewares

import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"

"github.com/ogen-go/ogen/middleware"
)

func Logging(logger *zap.Logger) middleware.Middleware {
return func(
req middleware.Request,
next func(req middleware.Request) (middleware.Response, error),
) (middleware.Response, error) {
logger := logger.With(
zap.String("operation", req.OperationName),
zap.String("operationId", req.OperationID),
)
logger.Info("Handling request")
resp, err := next(req)
if err != nil {
logger.Error("Fail", zap.Error(err))
} else {
var fields []zapcore.Field
// Some response types may have a status code.
// ogen provides a getter for it.
//
// You can write your own interface to match any response type.
if tresp, ok := resp.Type.(interface{ GetStatusCode() int }); ok {
fields = []zapcore.Field{
zap.Int("status_code", tresp.GetStatusCode()),
}
}
logger.Info("Success", fields...)
}
return resp, err
}
}
- + \ No newline at end of file diff --git a/docs/concepts/static_router/index.html b/docs/concepts/static_router/index.html index 2177d6c..1e7e5fa 100644 --- a/docs/concepts/static_router/index.html +++ b/docs/concepts/static_router/index.html @@ -10,7 +10,7 @@ - + @@ -22,7 +22,7 @@ and Allow header with comma-separated allowed methods list.

You can change the default handler by using WithMethodNotAllowed option.

caution

The HTTP spec requires that the Allow header is sent with the status code 405 Method Not Allowed.

You should add the Allow header to the response by yourself.

h := myHandler{}
srv, err := api.NewServer(h, api.WithMethodNotAllowed(func(w http.ResponseWriter, r *http.Request, allowed string) {
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Allow", allowed)
w.WriteHeader(http.StatusMethodNotAllowed)
_, _ = fmt.Fprintf(w, "use one of the following methods: %s\n", allowed)
}))
if err != nil {
panic(err)
}

Using net/http middlewares

The Server type implements http.Handler interface. Any net/http-compatible middleware can be used.

Applying middleware to all routes

package main

import (
"net/http"

"github.com/klauspost/compress/gzhttp"

api "<your_api_package>"
)

func main() {
srv, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
http.ListenAndServe(":8080", gzhttp.GzipHandler(srv))
}

Using FindRoute method

The Server type defines a FindRoute method that finds and returns the Route for the request, if any.

It's useful for middleware that needs to find the route for the request.

package main

import (
"net/http"

api "<your_api_package>"
)

type myMiddleware struct {
Next *api.Server
}

func (m myMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
route, ok := m.Next.FindRoute(r.Method, r.URL.Path)
if !ok {
// There is no route for the request.
//
// Let server handle 404/405.
m.Next.ServeHTTP(w, r)
return
}
// Match operation by spec operation ID.
// Notice that the operation ID is optional and may be empty.
//
// You can also use `route.Name()` to get the ogen operation name.
// Unlike the operation ID, the name is guaranteed to be unique and non-empty.
switch route.OperationID() {
case "operation1", "operation2":
// Middleware logic:
args := route.Args()
if args[0] == "debug" {
w.Header().Set("X-Debug", "true")
}
}
m.Next.ServeHTTP(w, r)
}

func main() {
s, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
http.ListenAndServe(":8080", myMiddleware{Next: s})
}

Adding profiler, metrics or static content route

To serve other routes, you can use any upper-level router.

Example would serve

  • API routes at /api/v1/*
  • pprof handlers on /debug/pprof/*
  • Files from static folder on /static/*
  • Prometheus metrics handler on /metrics
package main

import (
"embed"
"net/http"
"net/http/pprof"

"github.com/prometheus/client_golang/prometheus/promhttp"

api "<your_api_package>"
)

//go:embed static
var static embed.FS

func main() {
srv, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", srv))
// Register static files.
mux.Handle("/static/", http.FileServer(http.FS(static)))
// Register metrics handler.
mux.Handle("/metrics", promhttp.Handler())
{
// Register pprof handlers.
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
http.ListenAndServe(":8080", mux)
}
- + \ No newline at end of file diff --git a/docs/faq/index.html b/docs/faq/index.html index be39d7f..17cb5d8 100644 --- a/docs/faq/index.html +++ b/docs/faq/index.html @@ -10,13 +10,13 @@ - +

Frequently Asked Questions (FAQ)

How to set 404 Not Found handler?

Use WithNotFound option:

h := myHandler{}
srv, err := api.NewServer(h, api.WithNotFound(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `{"error": "not found"}`)
}))
if err != nil {
panic(err)
}

See Static router section for more details about router errors.

How to set error handler?

Use WithErrorHandler option:

h := myHandler{}
srv, err := api.NewServer(h, api.WithErrorHandler(func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) {
var (
code = http.StatusInternalServerError
ogenErr ogenerrors.Error
)
switch {
case errors.Is(err, ht.ErrNotImplemented):
code = http.StatusNotImplemented
case errors.As(err, &ogenErr):
code = ogenErr.Code()
}
_, _ = io.WriteString(w, http.StatusText(code))
}))
if err != nil {
panic(err)
}

Also, check out the Convenient errors concept.

How to use net/http middlewares?

The Server type implements http.Handler interface, so you can use any net/http middleware with it.

type Middleware func(next http.Handler) http.Handler

func Wrap(h http.Handler, mw Middleware) http.Handler {
return mw(h)
}

// TraceMiddleware adds traces.
func TraceMiddleware(lg *zap.Logger, tp trace.TracerProvider) middleware.Middleware {
return func(next http.Handler) http.Handler {
t := tp.Tracer("http")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
start := time.Now()
w := &writerProxy{ResponseWriter: rw}

ctx, span := t.Start(r.Context(), "HandleRequest")
defer span.End()
spanCtx := span.SpanContext()
ctx = With(ctx, lg.With(
zap.String("trace_id", spanCtx.TraceID().String()),
zap.String("span_id", spanCtx.SpanID().String()),
))

defer func() {
if r := recover(); r != nil {
lg.Error("Panic", zap.Stack("stack"))
if w.status == 0 {
w.WriteHeader(http.StatusInternalServerError)
}
span.AddEvent("Panic recovered",
trace.WithStackTrace(true),
)
span.SetStatus(codes.Error, "Panic recovered")
}
lg.Debug("Request",
zap.Duration("duration", time.Since(start)),
zap.Int("http.status", w.status),
zap.Int64("http.response.size", w.wrote),
zap.String("http.path", r.URL.String()),
zap.String("http.method", r.Method),
)
}()

next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

func newServer() *http.Server {
h, err := oas.NewServer(h,
oas.WithMeterProvider(m.MeterProvider()),
oas.WithTracerProvider(m.TracerProvider()),
)
if err != nil {
return errors.Wrap(err, "oas server")
}
return &http.Server{
Handler: middleware.Wrap(h, TraceMiddleware(lg, m.TracerProvider())),
Addr: addr,
}
}

See Static router section for more details.

How to use prometheus?

To instrument server or client with OpenTelemetry and prometheus exporter, pass oas.Option:

package main

import (
"github.com/go-faster/errors"
promClient "github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/exporters/prometheus"
"go.opentelemetry.io/otel/sdk/export/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/aggregator/histogram"
controller "go.opentelemetry.io/otel/sdk/metric/controller/basic"
processor "go.opentelemetry.io/otel/sdk/metric/processor/basic"
selector "go.opentelemetry.io/otel/sdk/metric/selector/simple"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"

"application/oas"
)

// Resource returns new resource for application.
func Resource(name string) *resource.Resource {
r, _ := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(name),
),
)
return r
}

func newPrometheus(config prometheus.Config, options ...controller.Option) (*prometheus.Exporter, error) {
c := controller.New(
processor.NewFactory(
selector.NewWithHistogramDistribution(
histogram.WithExplicitBoundaries(config.DefaultHistogramBoundaries),
),
aggregation.CumulativeTemporalitySelector(),
processor.WithMemory(true),
),
options...,
)
return prometheus.New(config, c)
}

// newMeterProviderOption returns oas.Option for prometheus instrumentation.
func newMeterProviderOption() (oas.Option, error) {
registry := promClient.NewPedanticRegistry()
res := Resource("app")
promExporter, err := newPrometheus(prometheus.Config{
DefaultHistogramBoundaries: promClient.DefBuckets,

Registry: registry,
Gatherer: registry,
Registerer: registry,
},
controller.WithCollectPeriod(0),
controller.WithResource(res),
)
if err != nil {
return nil, errors.Wrap(err, "prometheus")
}
return oas.WithMeterProvider(promExporter.MeterProvider()), nil
}

How to add some tags to generated structures?

Use x-oapi-codegen-extra-tags extension.

- + \ No newline at end of file diff --git a/docs/features/index.html b/docs/features/index.html index 1e398f5..67326e5 100644 --- a/docs/features/index.html +++ b/docs/features/index.html @@ -10,13 +10,13 @@ - +

Features

caution

Work in progress, so some features are missing

  • No reflection or interface{}/any
    • The json encoding is code-generated, optimized and uses go-faster/jx for speed and overcoming encoding/json limitations
    • Validation is code-generated according to spec
  • Code-generated static radix router
  • No more boilerplate
    • Structures are generated from OpenAPI v3 specification
    • Arguments, headers, url queries are parsed according to specification into structures
    • String formats like uuid, date, date-time, uri are represented by go types directly
  • Statically typed client and server
  • Convenient support for optional, nullable and optional nullable fields
    • No more pointers
    • Generated Optional[T], Nullable[T] or OptionalNullable[T] wrappers with helpers
    • Special case for array handling with nil semantics relevant to specification
      • When array is optional, nil denotes absence of value
      • When nullable, nil denotes that value is nil
      • When required, nil currently the same as [], but is actually invalid
      • If both nullable and required, wrapper will be generated (TODO)
  • Generated sum types for oneOf
    • Primitive types (string, number) are detected by type
    • Discriminator field is used if defined in schema
    • Type is inferred by unique fields if possible
  • Extra Go struct field tags in the generated types
  • OpenTelemetry tracing and metrics

Not implemented

  • Content-types
    • XML

Incompatibility

  • duration is a Go time.Duration format, but JSON Schema defines it as RFC 3339 duration.
- + \ No newline at end of file diff --git a/docs/intro/index.html b/docs/intro/index.html index 307c7eb..af9da65 100644 --- a/docs/intro/index.html +++ b/docs/intro/index.html @@ -10,13 +10,13 @@ - +

Quick start

ogen is a powerful and fast OpenAPI v3 code generator for Go.

Prepare environment

Create Go module.

go mod init <project>

Installation

Then, install the ogen tool.

go install -v github.com/ogen-go/ogen/cmd/ogen@latest

Generate code

Setup generator

Download petstore example.

wget https://raw.githubusercontent.com/ogen-go/web/main/examples/petstore.yml

Create generate.go file:

generate.go
package project

//go:generate go run github.com/ogen-go/ogen/cmd/ogen@latest --target petstore --clean petstore.yml

Then go to the root directory of your module, and run:

go generate ./...

ogen will generate new folder with several files

petstore
├── oas_cfg_gen.go
├── oas_client_gen.go
├── oas_handlers_gen.go
├── oas_interfaces_gen.go
├── oas_json_gen.go
├── oas_middleware_gen.go
├── oas_parameters_gen.go
├── oas_request_decoders_gen.go
├── oas_request_encoders_gen.go
├── oas_response_decoders_gen.go
├── oas_response_encoders_gen.go
├── oas_router_gen.go
├── oas_schemas_gen.go
├── oas_server_gen.go
├── oas_unimplemented_gen.go
├── oas_validators_gen.go

Using generated server

To get started, implement Handler interface generated by ogen tool.

// Handler handles operations described by OpenAPI v3 specification.
type Handler interface {
// AddPet implements addPet operation.
//
// Add a new pet to the store.
//
// POST /pet
AddPet(ctx context.Context, req *Pet) (*Pet, error)
// DeletePet implements deletePet operation.
//
// Deletes a pet.
//
// DELETE /pet/{petId}
DeletePet(ctx context.Context, params DeletePetParams) error
// GetPetById implements getPetById operation.
//
// Returns a single pet.
//
// GET /pet/{petId}
GetPetById(ctx context.Context, params GetPetByIdParams) (GetPetByIdRes, error)
// UpdatePet implements updatePet operation.
//
// Updates a pet in the store.
//
// POST /pet/{petId}
UpdatePet(ctx context.Context, params UpdatePetParams) error
}
package main

import (
"context"
"sync"

petstore "<project>/petstore"
)

type petsService struct {
pets map[int64]petstore.Pet
id int64
mux sync.Mutex
}

func (p *petsService) AddPet(ctx context.Context, req *petstore.Pet) (*petstore.Pet, error) {
p.mux.Lock()
defer p.mux.Unlock()

p.pets[p.id] = *req
p.id++
return req, nil
}

func (p *petsService) DeletePet(ctx context.Context, params petstore.DeletePetParams) error {
p.mux.Lock()
defer p.mux.Unlock()

delete(p.pets, params.PetId)
return nil
}

func (p *petsService) GetPetById(ctx context.Context, params petstore.GetPetByIdParams) (petstore.GetPetByIdRes, error) {
p.mux.Lock()
defer p.mux.Unlock()

pet, ok := p.pets[params.PetId]
if !ok {
// Return Not Found.
return &petstore.GetPetByIdNotFound{}, nil
}
return &pet, nil
}

func (p *petsService) UpdatePet(ctx context.Context, params petstore.UpdatePetParams) error {
p.mux.Lock()
defer p.mux.Unlock()

pet := p.pets[params.PetId]
pet.Status = params.Status
if val, ok := params.Name.Get(); ok {
pet.Name = val
}
p.pets[params.PetId] = pet

return nil
}

Run server

Now, we are ready to run our server!

main.go
package main

import (
"log"
"net/http"

petstore "<project>/petstore"
)

func main() {
// Create service instance.
service := &petsService{
pets: map[int64]petstore.Pet{},
}
// Create generated server.
srv, err := petstore.NewServer(service)
if err != nil {
log.Fatal(err)
}
if err := http.ListenAndServe(":8080", srv); err != nil {
log.Fatal(err)
}
}

Check that server started by creating pet

curl -X "POST" -H "Content-Type: application/json" --data "{\"name\":\"Cat\"}" http://localhost:8080/pet
{"name":"Cat"}

Using generated client

Let's make a simple app that gets the pet by id.

main.go
package main

import (
"bytes"
"context"
"encoding/json"
"flag"
"os"

"github.com/fatih/color"

petstore "<project>/petstore"
)

func run(ctx context.Context) error {
var arg struct {
BaseURL string
ID int64
}
flag.StringVar(&arg.BaseURL, "url", "http://localhost:8080", "target server url")
flag.Int64Var(&arg.ID, "id", 1, "pet id to request")
flag.Parse()

client, err := petstore.NewClient(arg.BaseURL)
if err != nil {
return fmt.Errorf("create client: %w", err)
}

res, err := client.GetPetById(ctx, petstore.GetPetByIdParams{
PetId: arg.ID,
})
if err != nil {
return fmt.Errorf("get pet %d: %w", arg.ID, err)
}

switch p := res.(type) {
case *petstore.Pet:
data, err := p.MarshalJSON()
if err != nil {
return err
}
var out bytes.Buffer
if err := json.Indent(&out, data, "", " "); err != nil {
return err
}
color.New(color.FgGreen).Println(out.String())
case *petstore.GetPetByIdNotFound:
return errors.New("not found")
}

return nil
}

func main() {
if err := run(context.Background()); err != nil {
color.New(color.FgRed).Printf("%+v\n", err)
os.Exit(2)
}
}
- + \ No newline at end of file diff --git a/docs/misc/request_lifecycle/index.html b/docs/misc/request_lifecycle/index.html index e9bdae6..c815795 100644 --- a/docs/misc/request_lifecycle/index.html +++ b/docs/misc/request_lifecycle/index.html @@ -10,7 +10,7 @@ - + @@ -21,7 +21,7 @@ It is done to be sure that every scheme value is validated and checked.

For example, let's say we have a specification like this:

{
"security": [
{
"basicAuth": [],
"headerKey": []
},
{
"bearerToken": [],
"headerKey": []
}
],
"components": {
"securitySchemes": {
"headerKey": {
"type": "apiKey",
"in": "header",
"name": "X-Api-Key"
},
"basicAuth": {
"type": "http",
"scheme": "basic"
},
"bearerToken": {
"type": "http",
"scheme": "bearer"
}
}
}
}

Basically, this specification defines two possible requirements: (bearerToken && headerKey) || (basicAuth && headerKey).

headerKey schemebasicAuth schemebearerToken schemeResult
Present ✅Present ✅Not present❌OK
Present ✅Not present❌Present ✅OK
Present ✅Present ✅Present ✅OK
Not present❌Present ✅Present ✅Error
Present ✅Not present❌Not present❌Error

Parameters decoder

If any error occured during decoding, parameter decoder generates a ogenerrors.DecodeParamsError, then calls error handler

Request body decoder

At first body decoder tries to match given Content-Type with defined in spec. If there is no match, decoder generates a validate.InvalidContentTypeError

caution

Note that request decoder reads entire request to the memory except for big multipart files.

If any error occured during decoding, body decoder generates a ogenerrors.DecodeRequestError, then calls error handler

multipart/form-data

If form file is too big, multipart reader temporarily stores it on disk. Limit is defined by MaxMultipartMemory. These files are available until handler returns.

If any error occured during decoding, handler closes and deletes all temporary files.

Handler

When all parts of request are parsed, user-defined Handler is called.

If any middleware set via WithMiddleware option, Handler passed to the middleware as next parameter.

Convenient errors

In case if handler returns an error and Convenient errors are available, behavior depends on error type:

  • If error type is equal to convenient error schema (e.g. ErrorStatusCode), it passed directly to response encoder
  • If error is ht.ErrNotImplemented, error handler will be called
  • Otherwise, NewError method will be called

Error handling

If any occured during request handling, handler does this:

- + \ No newline at end of file diff --git a/docs/spec/extensions/index.html b/docs/spec/extensions/index.html index 11653c1..7cdd85c 100644 --- a/docs/spec/extensions/index.html +++ b/docs/spec/extensions/index.html @@ -10,7 +10,7 @@ - + @@ -18,7 +18,7 @@

Generator extensions

OpenAPI enables Specification Extensions, which are implemented as patterned fields that are always prefixed by x-.

Server name

Optionally, server name can be specified by x-ogen-server-name, for example:

{
"openapi": "3.0.3",
"servers": [
{
"x-ogen-server-name": "production",
"url": "https://{region}.example.com/{val}/v1",
},
{
"x-ogen-server-name": "prefix",
"url": "/{val}/v1",
},
{
"x-ogen-server-name": "const",
"url": "https://cdn.example.com/v1"
}
],
(...)

Generator will use this name to generate a server URL builder:

// ProductionServer is a server URL template.
//
// Production server.
type ProductionServer struct {
Region string `json:"region" yaml:"region"`
Val string `json:"val" yaml:"val"`
}

// Build returns the computed server URL.
//
// If variable is empty, it uses the default value.
// If spec defines an enum and given value is not in the enum, it returns an error.
//
// Notice that given values will not be escaped and may cause invalid URL.
func (s ProductionServer) Build() (string, error) {
zeroOr := func(s string, def string) string {
if s == "" {
return def
}
return s
}
s.Region = zeroOr(s.Region, "us")
// Validate "region"
switch s.Region {
case "us":
case "eu":
default:
return "", errors.Errorf("param %q: unexpected value %q", "region", s.Region)
}
s.Val = zeroOr(s.Val, "prod")
// Validate "val"
switch s.Val {
case "prod":
case "test":
default:
return "", errors.Errorf("param %q: unexpected value %q", "val", s.Val)
}
return fmt.Sprintf("https://%s.example.com/%s/v1",
s.Region,
s.Val,
), nil
}

Custom type name

Optionally, type name can be specified by x-ogen-name, for example:

{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"x-ogen-name": "Name",
"properties": {
"foobar": {
"$ref": "#/$defs/FooBar"
}
},
"$defs": {
"FooBar": {
"x-ogen-name": "FooBar",
"type": "object",
"properties": {
"foo": {
"type": "string"
}
}
}
}
}

Extra struct field tags

Optionally, additional Go struct field tags can be specified by x-oapi-codegen-extra-tags, for example:

components:
schemas:
Pet:
type: object
required:
- id
properties:
id:
type: integer
format: int64
x-oapi-codegen-extra-tags:
gorm: primaryKey
valid: customIdValidator

The generated source code looks like:

// Ref: #/components/schemas/Pet
type Pet struct {
ID int64 `gorm:"primaryKey" valid:"customNameValidator" json:"id"`
}

Streaming JSON encoding

By default, ogen loads the entire JSON body into memory before decoding it. Optionally, streaming JSON encoding can be enabled by x-ogen-json-streaming, for example:

requestBody:
required: true
content:
application/json:
x-ogen-json-streaming: true
schema:
type: array
items:
type: number
- + \ No newline at end of file diff --git a/docs/spec/file_upload/index.html b/docs/spec/file_upload/index.html index 32d8932..ef12823 100644 --- a/docs/spec/file_upload/index.html +++ b/docs/spec/file_upload/index.html @@ -10,14 +10,14 @@ - +

File upload

OpenAPI 3.0 provides two ways to upload files:

File upload via request body

File would be uploaded via request body. The request body should be of type string and format binary.

openapi: 3.0.3
info:
title: Example
version: 1.0.0
paths:
/avatar:
post:
summary: Upload avatar
operationId: uploadAvatar
requestBody:
required: true
content:
image/png:
schema:
type: string
format: binary
responses:
'200':
description: OK

File upload via multipart request

Multiple files can be uploaded via multipart request, along with other fields.

openapi: 3.0.3
info:
title: Example
version: 1.0.0
paths:
/post:
post:
summary: Creates new post
operationId: newPost
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [ title, content ]
properties:
title:
type: string
content:
type: string
thumbnail:
type: string
format: binary
attachments:
type: array
items:
type: string
format: binary
responses:
'200':
description: OK
- + \ No newline at end of file diff --git a/docs/types/any/index.html b/docs/types/any/index.html index 2f8620d..92e3692 100644 --- a/docs/types/any/index.html +++ b/docs/types/any/index.html @@ -10,13 +10,13 @@ - +

Any type

Description

To represent untyped and empty schemas, ogen uses "any" jx.Raw type.

Checking JSON type

raw := jx.Raw(`[1, 2, 3]`)
fmt.Println(raw.Type())
// Output:
// array

Getting values from jx.Raw

raw := jx.Raw(`[1, 2, 3]`)
d := jx.GetDecoder()
d.DecodeBytes(raw)

var values []int
if err := d.Arr(func(d *jx.Decoder) error {
v, err := d.Int()
if err != nil {
return err
}
values = append(values, v)
return nil
}); err != nil {
return err
}

fmt.Println(values)
// Output:
// [1 2 3]
- + \ No newline at end of file diff --git a/docs/types/map/index.html b/docs/types/map/index.html index e3145a5..a8ffb5f 100644 --- a/docs/types/map/index.html +++ b/docs/types/map/index.html @@ -10,7 +10,7 @@ - + @@ -18,7 +18,7 @@

Map type

Plain map

To represent object with additionalProperties ogen uses Go map type.

Map:
type: object
additionalProperties:
type: string
type Map map[string]string

Map with fixed-keys

Map with fixed-keys represent by struct with AdditionalProps field.

Map:
type: object
properties:
default:
type: integer
required:
- default
additionalProperties:
type: string
type Map struct {
Default int `json:"default"`
AdditionalProps map[string]string
}

Map with key pattern

ogen also supports pattenProperties. Decoder will populate map only with keys that matches given pattern.

Map:
type: object
patternProperties:
foo.*:
type: string
type Map map[string]string

Free-form map

Map with unspecified schema represent by map with jx.Raw values.

Map:
type: object
additionalProperties: true
type Map map[string]jx.Raw
- + \ No newline at end of file diff --git a/docs/types/optional/index.html b/docs/types/optional/index.html index bf09366..44fe535 100644 --- a/docs/types/optional/index.html +++ b/docs/types/optional/index.html @@ -10,13 +10,13 @@ - +

Optional type

Description

Instead of using pointers, ogen generates generic wrappers.

For example, OptNilString is string that is optional (no value) and can be null.

// OptNilString is optional nullable string.
type OptNilString struct {
Value string
Set bool
Null bool
}

Multiple convenience helper methods and functions are generated, some of them:

// IsSet returns true if OptNilString was set.
func (o OptNilString) IsSet() bool
// Reset unsets value.
func (o *OptNilString) Reset()
// SetTo sets value to v.
func (o *OptNilString) SetTo(v string)
// IsSet returns true if value is Null.
func (o OptNilString) IsNull() bool
// Get returns value and boolean that denotes whether value was set.
func (o OptNilString) Get() (v string, ok bool)
// Or returns value if set, or given parameter if does not.
func (o OptNilString) Or(d string) string

func NewOptNilString(v string) OptNilString

Semantic

To implement decoder, ogen follows the rules below

Type

SchemaType
requiredT
optionalOpt[#T]
nullable, but requiredNil[#T]
optional & nullableOptNil[#T]

Decoder behavior

Schemano valuenullvalue
requiredErrorErrorOK
optionalSet=falseErrorSet=true
nullable, but requiredErrorNull=trueOK
optional & nullableSet=falseSet=true,Null=trueSet=true,Null=false

Semantic for arrays

For arrays semantic is slightly different

Type

SchemaType
required[]T
optional[]T
nullable, but required[]T
optional & nullableOptNil[#T]Array

Decoder behavior

Schemano valuenull[]non-empty array
requiredErrorErrorEmpty non-nil sliceNon-empty slice
optionalnil sliceErrorEmpty non-nil sliceNon-empty slice
nullable, but requiredErrornil sliceEmpty non-nil sliceNon-empty slice
optional & nullableSet=falseSet=true,Null=true, empty sliceSet=true,Null=false, empty sliceSet=true,Null=false, non-empty slice
- + \ No newline at end of file diff --git a/docs/types/primitive/index.html b/docs/types/primitive/index.html index 309b71c..4ee0031 100644 --- a/docs/types/primitive/index.html +++ b/docs/types/primitive/index.html @@ -10,15 +10,15 @@ - +
-

Primitives

String

String is byte sequence primitive type represent by Go string or []byte.

Supported formats

caution

duration is a Go time.Duration format, but JSON Schema defines it as RFC 3339 duration.

FormatTypeDescription
byte[]byteBase64-encoded string as defined in RFC4648
date-timetime.TimeDate and time notation as defined by RFC 3339, section 5.6, for example, 2022-02-22T11:22:33Z
datetime.TimeDate only notation as defined by RFC 3339, section 5.6, for example, 2022-02-22
timetime.TimeTime only notation as defined by RFC 3339, section 5.6, for example, 11:22:33
durationtime.DurationGo duration format
uuiduuid.UUIDUUID
ipnet.IPAny IP (IPv4, IPv6)
ipv4net.IPIPv4, for example, 1.1.1.1
ipv6net.IPIPv6, for example, 2001:db8:85a3::8a2e:370:7334
uriurl.URLURL as defined by RFC 3986
emailstringEmail, for example, foo@example.com
binarystringBinary string
hostnamestringHostname as defined by RFC 1034, section 3.1

Non-standard formats

FormatTypeDescription
unix/unix-secondstime.TimeTime.Unix(), encoded as string. See issue #307
unix-nanotime.TimeTime.UnixNano(), encoded as string. See issue #307
unix-microtime.TimeTime.UnixMicro(), encoded as string. See issue #307
unix-millitime.TimeTime.UnixMilli(), encoded as string. See issue #307
int32int3232-bit signed integer, encoded as string. See issue #307
int64int6464-bit signed integer, encoded as string. See issue #307

Validation

note

Note that length validation uses utf8.RuneCountInString to count +

Primitives

String

String is byte sequence primitive type represent by Go string or []byte.

Supported formats

caution

duration is a Go time.Duration format, but JSON Schema defines it as RFC 3339 duration.

FormatTypeDescription
byte[]byteBase64-encoded string as defined in RFC4648
date-timetime.TimeDate and time notation as defined by RFC 3339, section 5.6, for example, 2022-02-22T11:22:33Z
datetime.TimeDate only notation as defined by RFC 3339, section 5.6, for example, 2022-02-22
timetime.TimeTime only notation as defined by RFC 3339, section 5.6, for example, 11:22:33
durationtime.DurationGo duration format
uuiduuid.UUIDUUID
ipnetip.AddrAny IP (IPv4, IPv6)
ipv4netip.AddrIPv4, for example, 1.1.1.1
ipv6netip.AddrIPv6, for example, 2001:db8:85a3::8a2e:370:7334
uriurl.URLURL as defined by RFC 3986
emailstringEmail, for example, foo@example.com
binarystringBinary string
hostnamestringHostname as defined by RFC 1034, section 3.1

Non-standard formats

FormatTypeDescription
unix/unix-secondstime.TimeTime.Unix(), encoded as string
unix-nanotime.TimeTime.UnixNano(), encoded as string
unix-microtime.TimeTime.UnixMicro(), encoded as string
unix-millitime.TimeTime.UnixMilli(), encoded as string
int32int3232-bit signed integer
int64int6464-bit signed integer

See issue #307 for more information about these formats.

Validation

note

Note that length validation uses utf8.RuneCountInString to count string length.

ValidatorDescription
minLengthMinimum length in Unicode code points
maxLengthMaximum length in Unicode code points
patternGo regexp pattern

Numbers

OpenAPI/JSON schema has two numeric types, number and integer, where number includes both integer and floating-point numbers.

typeformatGo typeDescription
numberfloat64Any numbers
numberfloatfloat32Floating-point numbers
numberdoublefloat64Floating-point numbers with double precision
integerintInteger numbers
integerint32int32Signed 32-bit integers
integerint64int64Signed 64-bit integers
- + \ No newline at end of file diff --git a/docs/types/sumtype/index.html b/docs/types/sumtype/index.html index 29d4089..40edd1e 100644 --- a/docs/types/sumtype/index.html +++ b/docs/types/sumtype/index.html @@ -10,7 +10,7 @@ - + @@ -18,7 +18,7 @@

Sum type

Sum type (also known as tagged union) is a type that can be one of multiple possible variants. ogen uses sum types to represent oneOf and some anyOf schemas.

oneOf schema
Sum:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
Generated sum type
// Sum represents sum type.
type Sum struct {
Type SumType // switch on this field
Cat Cat
Dog Dog
}

// SumType is oneOf type of Sum.
type SumType string

// Possible values for SumType.
const (
CatSum SumType = "Cat"
DogSum SumType = "Dog"
)

Discriminator inference

To distinguish different cases, decoder need some pattern called discriminator.

Explicit discriminator

Generator may distinguish variants by special type field.

oneOf schema with explicit discriminator mapping
Sum:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'

Type discriminator

Type discriminator is discriminator based on JSON type.

oneOf schema with type discriminator
ID:
oneOf:
- type: string
- type: integer

Unique fields discriminator

Unique fields discriminator is discriminator based on unique schema fields. In that case, Decoder select variant by field that available only in one of all variants.

For example, given that schema:

oneOf schema with unique fields discriminator
Sum:
oneOf:
- type: object
required:
- common-1
- unique-1
properties:
common-1:
type: string
unique-1:
type: string
- type: object
required:
- common-1
- unique-2
properties:
common-1:
type: string
unique-2:
type: string

a payload like this:

{"common-1": "foo", "unique-1": "bar"}

will indicate that the first variant be used.

- + \ No newline at end of file diff --git a/index.html b/index.html index 2ee1a41..5c16ad2 100644 --- a/index.html +++ b/index.html @@ -10,13 +10,13 @@ - +

ogen

OpenAPI v3 code generator for Go

No reflection

  • The json encoding is code-generated, optimized and uses jx for speed and overcoming encoding/json limitations
  • Validation is code-generated according to specification

No boilerplate

  • Structures are generated from OpenAPI v3 specification
  • Arguments, headers, url queries are parsed according to specification into structures
  • String formats like uuid, date, date-time, uri are represented by go types directly
  • Sum types are generated for OneOf, with discriminator or implicit type inference
  • Optional and nullable are supported without pointers if possible

OpenTelemetry

Tracing and metrics support that is compatible with OpenTelemetry

- + \ No newline at end of file diff --git a/markdown-page/index.html b/markdown-page/index.html index 7152162..cd8f4d4 100644 --- a/markdown-page/index.html +++ b/markdown-page/index.html @@ -10,13 +10,13 @@ - +

Markdown page example

You don't need React to write simple standalone pages.

- + \ No newline at end of file