Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/2019.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export {
AsyncValidateFunction,
ErrorObject,
ErrorNoParams,
Context,
} from "./types"

export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core"
Expand Down
1 change: 1 addition & 0 deletions lib/2020.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export {
AsyncValidateFunction,
ErrorObject,
ErrorNoParams,
Context,
} from "./types"

export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core"
Expand Down
1 change: 1 addition & 0 deletions lib/ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export {
SchemaValidateFunction,
ErrorObject,
ErrorNoParams,
Context,
} from "./types"

export {Plugin, Options, CodeOptions, InstanceOptions, Logger, ErrorsTextOptions} from "./core"
Expand Down
4 changes: 4 additions & 0 deletions lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,7 @@ export interface UriResolver {
resolve(base: string, path: string): string
serialize(component: URIComponent): string
}

export interface Context {
apiContext?: "request" | "response"
}
6 changes: 3 additions & 3 deletions lib/vocabularies/oasContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export function getSkipCondition(schema: AnySchemaObject, prop: string): Code |
if (!hasReadOnly && !hasWriteOnly) return undefined

const conditions: Code[] = []
const oasContext = _`typeof ${N.this} == "object" && ${N.this} && ${N.this}.oas`
const apiContext = _`typeof ${N.this} == "object" && ${N.this} && ${N.this}.apiContext`

if (hasReadOnly) {
conditions.push(_`${oasContext} && ${N.this}.oas.mode === "request"`)
conditions.push(_`${apiContext} === "request"`)
}

if (hasWriteOnly) {
conditions.push(_`${oasContext} && ${N.this}.oas.mode === "response"`)
conditions.push(_`${apiContext} === "response"`)
}

return conditions.length === 1 ? conditions[0] : or(...conditions)
Expand Down
16 changes: 5 additions & 11 deletions lib/vocabularies/validation/readOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import {_, str} from "../../compile/codegen"
import N from "../../compile/names"

const error: KeywordErrorDefinition = {
message: ({params}) =>
str`must NOT be present in ${params.mode || "this context"}${
params.location ? str` (${params.location})` : ""
}`,
params: ({params}) => _`{mode: ${params.mode}, location: ${params.location}}`,
message: ({params}) => str`must NOT be present in ${params.context || "this"} context`,
params: ({params}) => _`{context: ${params.context}}`,
}

const def: CodeKeywordDefinition = {
Expand All @@ -18,13 +15,10 @@ const def: CodeKeywordDefinition = {
code(cxt: KeywordCxt) {
if (cxt.schema !== true) return

const mode = _`(${N.this} && ${N.this}.oas ? ${N.this}.oas.mode : undefined)`
const location = _`(${N.this} && ${N.this}.oas ? ${N.this}.oas.location : undefined)`
cxt.setParams({mode, location})
const apiContext = _`(${N.this} && ${N.this}.apiContext)`

cxt.fail(
_`typeof ${N.this} == "object" && ${N.this} && ${N.this}.oas && ${N.this}.oas.mode === "request"`
)
cxt.setParams({context: _`${apiContext}`})
cxt.fail(_`${apiContext} === "request"`)
},
}

Expand Down
17 changes: 6 additions & 11 deletions lib/vocabularies/validation/writeOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import {_, str} from "../../compile/codegen"
import N from "../../compile/names"

const error: KeywordErrorDefinition = {
message: ({params}) =>
str`must NOT be present in ${params.mode || "this context"}${
params.location ? str` (${params.location})` : ""
}`,
params: ({params}) => _`{mode: ${params.mode}, location: ${params.location}}`,
message: ({params}) => str`must NOT be present in ${params.context || "this"} context`,
params: ({params}) => _`{context: ${params.context}}`,
}

const def: CodeKeywordDefinition = {
Expand All @@ -17,12 +14,10 @@ const def: CodeKeywordDefinition = {
error,
code(cxt: KeywordCxt) {
if (cxt.schema !== true) return
const mode = _`(${N.this} && ${N.this}.oas ? ${N.this}.oas.mode : undefined)`
const location = _`(${N.this} && ${N.this}.oas ? ${N.this}.oas.location : undefined)`
cxt.setParams({mode, location})
cxt.fail(
_`typeof ${N.this} == "object" && ${N.this} && ${N.this}.oas && ${N.this}.oas.mode === "response"`
)
const apiContext = _`(${N.this} && ${N.this}.apiContext)`

cxt.setParams({context: apiContext})
cxt.fail(_`${apiContext} === "response"`)
},
}

Expand Down
24 changes: 7 additions & 17 deletions spec/validation/read-write.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type {DataValidationCxt} from "../../lib/types"
import type {DataValidationCxt, Context} from "../../lib/types"
import Ajv from "../ajv"
import chai from "../chai"

chai.should()

type OasMode = "request" | "response"

function buildContext(data: unknown): DataValidationCxt {
const dynamicAnchors: DataValidationCxt["dynamicAnchors"] = {}
return {
Expand All @@ -21,17 +19,9 @@ function getAjv() {
return new Ajv({passContext: true})
}

function getOasContext(mode: OasMode, location?: "requestBody" | "responseBody") {
return {oas: {mode, location}}
}

type Validate = ReturnType<InstanceType<typeof Ajv>["compile"]>

function expectValid(
validate: Validate,
data: unknown,
ctx?: {oas: {mode: OasMode; location?: "requestBody" | "responseBody"}}
) {
function expectValid(validate: Validate, data: unknown, ctx?: Context) {
const valid = ctx ? validate.call(ctx, data, buildContext(data)) : validate(data)
valid.should.equal(true)
}
Expand All @@ -40,7 +30,7 @@ function expectInvalid(
validate: Validate,
data: unknown,
keyword: "readOnly" | "writeOnly" | "required",
ctx?: {oas: {mode: OasMode; location?: "requestBody" | "responseBody"}}
ctx?: Context
) {
const valid = ctx ? validate.call(ctx, data, buildContext(data)) : validate(data)
valid.should.equal(false)
Expand Down Expand Up @@ -78,7 +68,7 @@ describe("readOnly/writeOnly", () => {

it("should skip readOnly in request context and still require writeOnly", () => {
const validate = getAjv().compile(schemaWithRequired)
const requestCtx = getOasContext("request", "requestBody")
const requestCtx: Context = {apiContext: "request"}

expectValid(validate, {password: "secret"}, requestCtx)
expectInvalid(validate, {id: "1", password: "secret"}, "readOnly", requestCtx)
Expand All @@ -87,7 +77,7 @@ describe("readOnly/writeOnly", () => {

it("should skip writeOnly in response context and still require readOnly", () => {
const validate = getAjv().compile(schemaWithRequired)
const responseCtx = getOasContext("response", "responseBody")
const responseCtx: Context = {apiContext: "response"}

expectValid(validate, {id: "1"}, responseCtx)
expectInvalid(validate, {id: "1", password: "secret"}, "writeOnly", responseCtx)
Expand All @@ -98,14 +88,14 @@ describe("readOnly/writeOnly", () => {
describe("presence validation with context", () => {
it("should reject readOnly in request context", () => {
const validate = getAjv().compile(baseSchema)
const requestCtx = getOasContext("request", "requestBody")
const requestCtx: Context = {apiContext: "request"}

expectInvalid(validate, {id: "1"}, "readOnly", requestCtx)
})

it("should reject writeOnly in response context", () => {
const validate = getAjv().compile(baseSchema)
const responseCtx = getOasContext("response", "responseBody")
const responseCtx: Context = {apiContext: "response"}

expectInvalid(validate, {password: "secret"}, "writeOnly", responseCtx)
})
Expand Down
Loading