Skip to content

Commit fe37dde

Browse files
committed
Allow telling whether an Operation specifies security
When an operation does not include the `security` key, it means "use the value of the top-level `security` key." When an operation *does* specify `security`, its value overrides the top-level `security` value. We need to be able to tell these cases apart in order to support operations overriding the global security setting. Here's how the spec documents this, in its description of the `security` field, at https://swagger.io/specification/v3/#operation-object: > A declaration of which security mechanisms can be used for this operation. [...] This definition overrides any declared top-level security. To remove a top-level security declaration, an empty array can be used.
1 parent a38fe2e commit fe37dde

File tree

3 files changed

+42
-8
lines changed

3 files changed

+42
-8
lines changed

src/OpenApi/Operation.elm

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,12 @@ deprecated (Operation operation_) =
135135
operation_.deprecated
136136

137137

138-
{-| -}
139-
security : Operation -> List SecurityRequirement
138+
{-| SecurityRequirements the operation specifies (possibly an empty list), or Nothing if the operation does not specify any.
139+
140+
If an operation specifies `security`, the given value (even if it's an empty array) overrides the schema's top-level `security`. This is documented in the OpenAPI specification here: <https://swagger.io/specification/v3/#operation-object>.
141+
142+
-}
143+
security : Operation -> Maybe (List SecurityRequirement)
140144
security (Operation operation_) =
141145
operation_.security
142146

src/OpenApi/Types.elm

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ type alias OperationInternal =
10831083
, responses : Dict String (ReferenceOr Response)
10841084
, callbacks : Dict String (ReferenceOr Callback)
10851085
, deprecated : Bool
1086-
, security : List SecurityRequirement
1086+
, security : Maybe (List SecurityRequirement)
10871087
, servers : List Server
10881088
}
10891089

@@ -1116,7 +1116,7 @@ decodeOperation =
11161116
|> decodeOptionalDict "responses" (decodeRefOr decodeResponse)
11171117
|> Json.Decode.Pipeline.optional "callbacks" (Json.Decode.dict (decodeRefOr decodeCallback)) Dict.empty
11181118
|> Json.Decode.Pipeline.optional "deprecated" Json.Decode.bool False
1119-
|> Json.Decode.Pipeline.optional "security" (Json.Decode.list decodeSecurityRequirement) []
1119+
|> optionalNothing "security" (Json.Decode.list decodeSecurityRequirement)
11201120
|> Json.Decode.Pipeline.optional "servers" (Json.Decode.list decodeServer) []
11211121
|> Json.Decode.andThen
11221122
(\operation ->
@@ -1140,7 +1140,8 @@ encodeOperation (Operation operation) =
11401140
, Just ( "responses", Json.Encode.dict identity (encodeRefOr encodeResponse) operation.responses )
11411141
, Internal.maybeEncodeDictField ( "callbacks", identity, encodeRefOr encodeCallback ) operation.callbacks
11421142
, Just ( "deprecated", Json.Encode.bool operation.deprecated )
1143-
, Internal.maybeEncodeListField ( "security", encodeSecurityRequirement ) operation.security
1143+
, operation.security
1144+
|> Maybe.map (\security -> ( "security", Json.Encode.list encodeSecurityRequirement security ))
11441145
, Internal.maybeEncodeListField ( "servers", encodeServer ) operation.servers
11451146
]
11461147
|> List.filterMap identity

tests/Test/OpenApi/Operation.elm

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ module Test.OpenApi.Operation exposing (suite)
33
import Dict
44
import Expect
55
import Json.Decode
6-
import OpenApi.Operation
6+
import Json.Encode
7+
import OpenApi.Operation exposing (security)
78
import Test exposing (..)
89

910

@@ -68,8 +69,22 @@ suite =
6869
, test "security" <|
6970
\() ->
7071
decodedOperation
71-
|> Result.map (OpenApi.Operation.security >> List.length)
72-
|> Expect.equal (Ok 1)
72+
|> Result.map (OpenApi.Operation.security >> Maybe.map List.length)
73+
|> Expect.equal (Ok (Just 1))
74+
, describe "when 'security' is unspecified" <|
75+
[ test "it decodes to Nothing" <|
76+
\() ->
77+
Json.Decode.decodeString OpenApi.Operation.decode securityUnspecifiedExample
78+
|> Result.map OpenApi.Operation.security
79+
|> Expect.equal (Ok Nothing)
80+
, test "it is not encoded" <|
81+
\() ->
82+
Json.Decode.decodeString OpenApi.Operation.decode securityUnspecifiedExample
83+
|> Result.map (OpenApi.Operation.encode >> Json.Encode.encode 0)
84+
|> Result.andThen (Json.Decode.decodeString OpenApi.Operation.decode)
85+
|> Result.map OpenApi.Operation.security
86+
|> Expect.equal (Ok Nothing)
87+
]
7388
, test "servers" <|
7489
\() ->
7590
decodedOperation
@@ -148,6 +163,20 @@ example =
148163
}"""
149164

150165

166+
securityUnspecifiedExample : String
167+
securityUnspecifiedExample =
168+
"""{
169+
"summary": "Updates a pet in the store with form data",
170+
"operationId": "updatePetWithForm",
171+
"responses": {
172+
"200": {
173+
"description": "Pet updated.",
174+
"content": {}
175+
}
176+
}
177+
}"""
178+
179+
151180
failingExample : String
152181
failingExample =
153182
"""{

0 commit comments

Comments
 (0)