From 6ae301a8c5da08dda5d9dee5fbc460e780ba7169 Mon Sep 17 00:00:00 2001 From: Diogo Castro Date: Sat, 20 Aug 2022 15:38:39 +0100 Subject: [PATCH] [#114] Add examples --- docs/swagger.json | 76 ++++++++++++++++++++++++++++++++++++++--- lib/CLI/Types.hs | 1 + lib/Coffer/Directory.hs | 9 +++-- lib/Coffer/Path.hs | 19 ++++++++--- lib/Entry.hs | 52 +++++++++++++++++++++++++--- 5 files changed, 141 insertions(+), 16 deletions(-) diff --git a/docs/swagger.json b/docs/swagger.json index 0eb24df7..d8d8a5cd 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -27,6 +27,7 @@ }, "EntryPath": { "type": "string", + "example": "/accounts/sre/gmail", "pattern": "(/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_]*)+" }, "Directory": { @@ -35,6 +36,41 @@ "entries", "subdirs" ], + "example": { + "subdirs": { + "accounts": { + "subdirs": { + "sre": { + "subdirs": {}, + "entries": [ + { + "tags": [ + "some-tag" + ], + "masterField": null, + "path": "/accounts/sre/gmail", + "fields": { + "password": { + "contents": "some-password", + "visibility": "private", + "dateModified": "2016-07-22T00:00:00Z" + }, + "username": { + "contents": "some-username", + "visibility": "public", + "dateModified": "2016-07-22T00:00:00Z" + } + }, + "dateModified": "2016-07-22T00:00:00Z" + } + ] + } + }, + "entries": [] + } + }, + "entries": [] + }, "properties": { "subdirs": { "type": "object", @@ -57,6 +93,7 @@ }, "FieldName": { "type": "string", + "example": "password", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" }, "Field": { @@ -68,7 +105,8 @@ ], "properties": { "contents": { - "type": "string" + "type": "string", + "example": "some-password" }, "visibility": { "$ref": "#/components/schemas/FieldVisibility" @@ -101,6 +139,7 @@ }, "EntryTag": { "type": "string", + "example": "some-tag", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" }, "NewField": { @@ -111,7 +150,8 @@ ], "properties": { "contents": { - "type": "string" + "type": "string", + "example": "some-password" }, "visibility": { "$ref": "#/components/schemas/FieldVisibility" @@ -133,6 +173,26 @@ "fields", "tags" ], + "example": { + "tags": [ + "some-tag" + ], + "masterField": null, + "path": "/accounts/sre/gmail", + "fields": { + "password": { + "contents": "some-password", + "visibility": "private", + "dateModified": "2016-07-22T00:00:00Z" + }, + "username": { + "contents": "some-username", + "visibility": "public", + "dateModified": "2016-07-22T00:00:00Z" + } + }, + "dateModified": "2016-07-22T00:00:00Z" + }, "properties": { "tags": { "items": { @@ -206,7 +266,8 @@ "required": false, "in": "query", "schema": { - "format": "name:\ndate:\n=[asc, desc]\n" + "format": "name:\ndate:\n=[asc, desc]\n", + "example": "name:asc" } }, { @@ -295,6 +356,7 @@ "in": "query", "schema": { "type": "string", + "example": "password", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } @@ -303,7 +365,8 @@ "content": { "application/json;charset=utf-8": { "schema": { - "type": "string" + "type": "string", + "example": "some-password" } } } @@ -518,6 +581,7 @@ "in": "query", "schema": { "type": "string", + "example": "password", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } @@ -562,6 +626,7 @@ "in": "query", "schema": { "type": "string", + "example": "some-tag", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } @@ -604,6 +669,7 @@ "in": "query", "schema": { "type": "string", + "example": "some-tag", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } @@ -648,6 +714,7 @@ "in": "query", "schema": { "type": "string", + "example": "password", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } @@ -692,6 +759,7 @@ "in": "query", "schema": { "type": "string", + "example": "password", "pattern": "[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_;]*" } } diff --git a/lib/CLI/Types.hs b/lib/CLI/Types.hs index 95d8797a..7935c550 100644 --- a/lib/CLI/Types.hs +++ b/lib/CLI/Types.hs @@ -309,6 +309,7 @@ instance ToParamSchema (Sort, Direction) where toParamSchema _ = mempty & format ?~ sortDirectionFormat + & example ?~ "name:asc" where sortDirectionFormat = T.unlines [ "name:" diff --git a/lib/Coffer/Directory.hs b/lib/Coffer/Directory.hs index 43fe3dc7..f144bc84 100644 --- a/lib/Coffer/Directory.hs +++ b/lib/Coffer/Directory.hs @@ -19,13 +19,15 @@ module Coffer.Directory import Coffer.Path (PathSegment, entryPathParentDir, pathSegments) import Control.Lens +import Data.Aeson (toJSON) import Data.Aeson.Casing import Data.Aeson.TH import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HashMap import Data.Maybe qualified as Maybe import Data.OpenApi -import Entry (Entry) +import Data.OpenApi.Lens qualified as Schema +import Entry (Entry, exampleEntry) import Entry qualified as E import GHC.Generics (Generic) @@ -43,7 +45,10 @@ deriveToJSON (aesonPrefix camelCase) ''Directory makeLensesWith abbreviatedFields 'Directory instance ToSchema Directory where - declareNamedSchema = genericDeclareNamedSchema $ fromAesonOptions (aesonPrefix camelCase) + declareNamedSchema proxy = + genericDeclareNamedSchema (fromAesonOptions (aesonPrefix camelCase)) proxy <&> \schema -> + schema + & Schema.schema . example ?~ toJSON (insertEntry exampleEntry emptyDir) emptyDir :: Directory emptyDir = Directory mempty mempty diff --git a/lib/Coffer/Path.hs b/lib/Coffer/Path.hs index 752d1755..965b87c0 100644 --- a/lib/Coffer/Path.hs +++ b/lib/Coffer/Path.hs @@ -14,6 +14,7 @@ module Coffer.Path , Path(..) , mkPath , EntryPath(..) + , exampleEntryPath , mkEntryPath , entryPathName , entryPathParentDir @@ -28,7 +29,7 @@ module Coffer.Path import BackendName (BackendName, newBackendName) import Control.Lens import Control.Monad ((>=>)) -import Data.Aeson (ToJSON, Value(String)) +import Data.Aeson (ToJSON, Value(String), toJSON) import Data.Aeson qualified as A import Data.Data (Typeable) import Data.Hashable (Hashable) @@ -67,14 +68,13 @@ data DirectoryContents = DirectoryContents makeLensesWith abbreviatedFields ''DirectoryContents instance ToSchema PathSegment where - declareNamedSchema _ = pure $ NamedSchema (Just "PathSegment") + declareNamedSchema _ = pure $ NamedSchema (Just "PathSexxgment") $ mempty & Schema.pattern ?~ pathSegmentPattern & type_ ?~ OpenApiString + & Schema.example ?~ "accounts" where - pathSegmentPattern = [int|s| - [#{pathSegmentAllowedCharacters}] - |] + pathSegmentPattern = "aaa" mkPathSegment :: Text -> Either Text PathSegment mkPathSegment segment @@ -141,6 +141,14 @@ newtype EntryPath = EntryPath { unEntryPath :: NonEmpty PathSegment } deriving stock (Show, Eq, Generic) deriving anyclass (Hashable) +exampleEntryPath :: EntryPath +exampleEntryPath = + EntryPath $ + UnsafeMkPathSegment "accounts" :| + [ UnsafeMkPathSegment "sre" + , UnsafeMkPathSegment "gmail" + ] + instance A.ToJSON EntryPath where toJSON = String . pretty @@ -149,6 +157,7 @@ instance ToParamSchema EntryPath where mempty & Schema.pattern ?~ entryPathPattern & type_ ?~ OpenApiString + & example ?~ toJSON exampleEntryPath where segmentPattern :: Pattern segmentPattern = [int|s| diff --git a/lib/Entry.hs b/lib/Entry.hs index 7d564442..40d633d2 100644 --- a/lib/Entry.hs +++ b/lib/Entry.hs @@ -21,6 +21,7 @@ module Entry , visibility , contents , Entry + , exampleEntry , newEntry , path , masterField @@ -30,8 +31,9 @@ module Entry ) where -import Coffer.Path (EntryPath) +import Coffer.Path (EntryPath, exampleEntryPath) import Control.Lens +import Data.Aeson (toJSON) import Data.Aeson qualified as A import Data.Aeson.Casing import Data.Aeson.TH @@ -44,10 +46,10 @@ import Data.Set (Set) import Data.Set qualified as S import Data.Text (Text) import Data.Text qualified as T -import Data.Time (UTCTime) +import Data.Time (UTCTime(..), fromGregorian) import Fmt (Buildable, build) import GHC.Generics (Generic) -import Servant (FromHttpApiData, ToHttpApiData) +import Servant (FromHttpApiData, Proxy(..), ToHttpApiData) import System.Console.ANSI (SGR(Reset), setSGRCode) import System.Console.ANSI.Codes (csi) @@ -63,6 +65,7 @@ instance ToParamSchema FieldName where mempty & Schema.pattern ?~ fieldNamePattern & type_ ?~ OpenApiString + & example ?~ toJSON (UnsafeFieldName "password") where fieldNamePattern = "[" <> T.pack allowedCharSet <> "]*" @@ -95,12 +98,16 @@ instance ToParamSchema EntryTag where mempty & Schema.pattern ?~ entryTagPattern & type_ ?~ OpenApiString + & example ?~ toJSON exampleEntryTag where entryTagPattern = "[" <> T.pack allowedCharSet <> "]*" newtype BadEntryTag = BadEntryTag { unBadEntryTag :: Text } deriving newtype Buildable +exampleEntryTag :: EntryTag +exampleEntryTag = UnsafeEntryTag "some-tag" + newEntryTag :: Text -> Either BadEntryTag EntryTag newEntryTag tag | T.null tag = @@ -139,9 +146,14 @@ instance A.FromJSON FieldVisibility where newtype FieldContents = FieldContents { unFieldContents :: Text } deriving stock (Show, Eq, Ord) - deriving newtype (Hashable, A.FromJSON, A.ToJSON, A.FromJSONKey, A.ToJSONKey, ToSchema) + deriving newtype (Hashable, A.FromJSON, A.ToJSON, A.FromJSONKey, A.ToJSONKey) makeLensesFor [("unFieldContents", "fieldContents")] ''FieldContents +instance ToSchema FieldContents where + declareNamedSchema _ = pure $ NamedSchema Nothing $ mempty + & type_ ?~ OpenApiString + & example ?~ toJSON (FieldContents "some-password") + -- | User can use ANSI control sequences in field contents. -- If some ANSI control sequence contain in field contents then we append @reset@ ANSI control sequence. -- Otherwise, we just return wrapped text. @@ -189,7 +201,37 @@ deriveToJSON (aesonPrefix camelCase) ''Entry makeLensesWith abbreviatedFields ''Entry instance ToSchema Entry where - declareNamedSchema = genericDeclareNamedSchema $ fromAesonOptions (aesonPrefix camelCase) + declareNamedSchema proxy = + genericDeclareNamedSchema (fromAesonOptions (aesonPrefix camelCase)) proxy <&> \schema -> + schema + & Schema.schema . example ?~ toJSON exampleEntry + +exampleEntry :: Entry +exampleEntry = + Entry + { ePath = exampleEntryPath + , eDateModified = exampleDate + , eMasterField = Nothing + , eFields = HS.fromList + [ ( UnsafeFieldName "username" + , Field + { fDateModified = exampleDate + , fVisibility = Public + , fContents = FieldContents "some-username" + } + ) + , ( UnsafeFieldName "password" + , Field + { fDateModified = exampleDate + , fVisibility = Private + , fContents = FieldContents "some-password" + } + ) + ] + , eTags = S.singleton exampleEntryTag + } + where + exampleDate = UTCTime (fromGregorian 2016 7 22) 0 newEntry :: EntryPath -> UTCTime -> Entry newEntry path time =